数据链路层访问

在Linux下数据链路层对的访问通常是通过编写内核驱动程序来实现的,在应用层使用SOCKE_PACKET类型的协议族可以实现部分功能。

SOCK_PACKET类型建立套接字的时候选择SOCK_PACKET类型,内核将不对网络数据进行处理而直接交给用户,数据直接从网卡的协议栈交给用户。建立一个SOCK_PACKET类型的套接字使用方式如下:

 socket(AF_INET, SOCK_PACKET, htons(0x0003));

其中AF_INET表示因特网协议族,SOCK_PACKET表示截取数据帧的层次在物理层,网络协议栈对数据不做处理。值0x0003表示截取的数据帧的类型为不确定,处理所有的包。

使用SOCK_PACKET进行程序设计的时候,需要注意的主要方面包括协议族选择、获取原始包、定位IP包、定位TCP包、定位UDP包、定位应用层数据几个部分,下面进行详细介绍。

设置套接口以捕获链路帧的编程方法

在Linux 下编写网络监听程序,比较简单的方法是在超级用户模式下,利用类型为SOCK_PACKET的套接口(用socket()函数创建)来捕获链路帧数据。Linux 程序中需引用如下头文件件:

#include <sys/socket.h>

#include <sys/ioctl.h>                        /*ioctl 命令*/

#include <netinet/if_ether.h>             /*ethhdr 结构*/

#include <net/if.h>                             /*ifreq 结构*/

#include <netinet/in.h>                      /*in_addr 结构*/

#include <netinet/ip.h>                      /*iphdr 结构*/

#include <netinet/udp.h>                  /*udphdr 结构*/

#include <netinet/tcp.h>                   /*tcphdr 结构*/

建立SOCK_PACKET 类型套接字,监听所有类型的包,采用如下代码:

int fd;

fd = socket(AF_INET, SOCK_PACKET, htons(0x0003));

侦听其他主机网络的数据在局域网诊断中经常使用。如果监听其他网卡的数据,需要将本地的网卡设置为“混杂”模式,还需要一个都连接于同一HUB 的局域网或者具有“镜像”功能的交换机才可以,否则,只能接收到其他主机的广播包。

char *ethname = “eth0”;                         /*对网卡eth0进行混杂设置*/

struct  ifreq  ifr;                                       /*网络接口结构*/

strcpy(ifr.ifr_name, ethname);               /*eth0 写入ifr结构的一个字段中*/

i = ioctl(fd, SIOCGIFFLAGS, &ifr);  

if(i<0){

    close(fd);

    perror(“can’t get flags \n”);

   return -1;

}

ifr.ifr_flags |= IFF_PROMISC;              /*保留原来的设置的情况下,在标志位中加入“混杂”方式*/

i = ioctl(fd, SIOCSIFFLAGS, &ifr);

if(i<0){

     perror(“promiscuous set error\n”);

     return -2;

}

        上面的代码使用了ioctl()的SIOCGIFFLAGS 和 SIOCSIFFLAGS命令,用来取出和写入网络接口的标志设置。注意,在修改网络接口标志的时候,务必要先将之前的标志取出,与想要设置的位进行“位或”计算后再写入,不要直接将设置的位值写入,因为直接写入覆盖之前的设置,造成网络接口混乱。

从套接口读取链路帧的编程方法

        以太网的数据结构如下图,总长度为1518字节,最小为64字节,其中目标地址的MAC为6字节,源地址的MAC为6字节,协议类型为2字节,含有46-1500字节的数据,尾部为4字节的CRC校验和,以太网的CRC校验和一般由硬件自动设置或剥离,应用层不用考虑。

   目标地址

     6字节

      源地址

       6字节

    类型

   2字节

           帧数据

           46-1500 

   校验和

    4字节

在头文件<netinet/if_ether.h>中定义了如下常量:

#define    ETH_ALEN    6

#define    ETH_HLEN    14

#define    ETH_ZLEN     60

#define    ETH_DATA_LEN    1500

#define    ETH_PARME_LEN   1514

以太网头部结构定义如下:

struct ethhdr{ 

    unsigned char  h_dest[ETH_ALEN];                        /*目的以太网地址*/

    unsigned char  h_source[ETH_ALEN];                    /*源以太网地址*/

   __be16    h_proto;                                                     /*包类型*/

};

        套接字文件描述符建立后,就可以从描述符中读取数据,数据的格式为上述的以太网数据,即以太网帧。套接口建立以后,就可以从中循环捕获的链路层以太网帧。要建立一个大小为ETH_FRAME_LEN 的缓冲区,并将以太网的头部指向此缓冲区,例如:

      char ef[ETH_FRAME_LEN];                  /*以太网缓存区*/

      struct ethhdr *p_ethhdr;                         /*以太网头部*/

      int n;                                     

      p_ethhdr = (struct ethhdr*)ef;                 /*使p_ethhdr指向以太网的帧头*/

      n = read(fd, ef, ETH_FRAME_LEN);      /*读取以太网数据,n为返回的实际捕获的以太网帧长*

        接收到数据以后,缓冲区ef和以太网头部的对应关系如下:

   h_desth_sourceh_proto 
6字节6字节2字节
ef

 因此,获得以太网的目的MAC地址,源MAC地址和协议的类型,可通过p_ethhdr->h_dest, p_ethhdr->h_source, p_ethhdr->h_proto 得到。

       定位IP包头的编程方法

获得以太网帧后,当协议为0x0800,其负载部分为IP协议。IP协议的数据结构如下:

struct iphdr{

#if defined(__LITTLE_ENDIAN_BITFIELD)             /*小端*/

     _u8    ihl:4,

     version:4;

#endif defined(__BIG_ENDIAN_BITFIELD)            /*大端*/

#else 

#error "please fix <asm/byteorder.h>"

#endif 

     __u8  tos;                       /*服务类型*/

     __be16 tot_len;              /*总长度*/

     __be16 id;                      /*标志*/

     __be16 frag_off;             /*片偏移*/

     __u8 ttl;                          /*生存时间*/

    __u8 protocol;                 /*协议类型*/

     __u16 check;                  /头部校验和*/

     __be32 saddr;                /*源IP*/

    __be32 daddr;                 /*目的IP*/

};

若捕获的以太网帧中h_proto的值为0x0800,将类型为iphdr的结构指针指向帧头后面载荷数据的起始位置,则可以得到IP数据包的报头部分,通过saddr 和 daddr可以得到IP报文的源IP地址和目的IP地址。下面的代码打印IP报文的源IP地址和目的IP地址。

   if(ntohs(p_ethhdr->h_proto) == 0x0800){

        struct iphdr *p_iphdr = (struct iphdr*)(ef + ETH_HLEN);

        printf("src ip:%s\n",inet_ntoa(p_iphdr->sadrr));

        printf("dest ip:%s\n",inet_ntoa(p_iphdr->daddr));

  }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值