尝试探索基于Linux C的网卡抓包过程

来源:互联网 发布:淘宝店铺微淘怎么开通 编辑:程序博客网 时间:2024/06/01 07:26

来源: http://blog.csdn.net/sim_szm/article/details/8787759

 其实想探究网卡抓包问题已经有好久了。前几天找了时间算是基本上了解了那部分的一些基本东西,在这里只是赘述罢了。

抓包首先便要知道经过网卡的数据其实都是通过底层的链路层(MAC),在Linux系统中我们获取网卡的数据流量其实是直接从链路层收发数据帧。至于如何进行TCP/UDP连接本文就不再赘述(之前的一段关于web server的程序已经大概说明),直接从最关键的原始套接字( raw socket)开始。

通常情况下程序设计人员接触的网络知识限于如下两类:

(1)流式套接字(SOCK_STREAM),它是一种面向连接的套接字,对应于TCP应用程序。

(2)数据报套接字(SOCK_DGRAM),它是一种无连接的套接字,对应于的UDP应用程序。

 除了以上两种基本的套接字外还有一类原始套接字,它是一种对原始网络报文进行处理的套接字。

流式套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)涵盖了一般应用层次的TCP/IP应用。

原始套接字的创建使用与通用的套接字创建的方法是一致的,只是在套接字类型的选项上使用的是另一个SOCK_RAW。在使用socket函数进行函数创建完毕的时候,还要进行套接字数据中格式类型的指定,设置从套接字中可以接收到的网络数据格式

[cpp] view plaincopy
  1. fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));  

fd为返回的套接字描述符SOCK_RAW即创建原始套接字,这里的htons(ETH_P_ALL),是socket()的第三个参数 protocol ,即协议类型。是一个定义在<netinet/in.h>中的常量,基本上我们常用的是,ETH_P_IP  , ETH_P_ARP,ETH_P_RARP或ETH_P_ALL, 当然链路层协议很多,肯定不止我们说的这几个,但我们一般只关心这几个就够我们用了。这里简单 提一下网络数据收发的一点基础。协议栈在组织数据收发流程时需要处理好两个方面的问题:“从上倒下”,即数据发送的任务;“从下到上”,即数据接收的任 务。数据发送相对接收来说要容易些,因为对于数据接收而言,网卡驱动还要明确什么样的数据该接收、什么样的不该接收等问题。

他们都有特定的含义:

protocol

作用

ETH_P_IP

0X0800

只接收发往目的MAC是本机的IP类型的数据帧

ETH_P_ARP

0X0806

只接收发往目的MAC是本机的ARP类型的数据帧

ETH_P_RARP

0X8035

只接受发往目的MAC是本机的RARP类型的数据帧

ETH_P_ALL

0X0003

接收发往目的MAC是本机的所有类型(ip,arp,rarp)的数据帧,同时还可以接收从本机发出去的所有数据帧。在混杂模式打开的情况下,还会接收到发往目的MAC为非本地硬件地址的数据帧。

根据自己的抓包的对象我们选取相应的参数值,就可以获取不同的数据包。

这里我选择的是ETH_P_ALL,可以获取所有经过本机的数据包,但有一个前提便是需要设置网卡为混杂模式(Promiscuous Mode),这里我们就必须去了解一个重要的数据类型struct ifreq ;关于他的定义直接百度如下:

[cpp] view plaincopy
  1. struct ifreq  
  2.   {  
  3. # define IFHWADDRLEN 6  
  4. # define IFNAMSIZ IF_NAMESIZE  
  5.     union  
  6.       {  
  7.         char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */  
  8.       } ifr_ifrn;  
  9.   
  10.     union  
  11.       {  
  12.         struct sockaddr ifru_addr;  
  13.         struct sockaddr ifru_dstaddr;  
  14.         struct sockaddr ifru_broadaddr;  
  15.         struct sockaddr ifru_netmask;  
  16.         struct sockaddr ifru_hwaddr;  
  17.         short int ifru_flags;  
  18.         int ifru_ivalue;  
  19.         int ifru_mtu;  
  20.         struct ifmap ifru_map;  
  21.         char ifru_slave[IFNAMSIZ]; /* Just fits the size */  
  22.         char ifru_newname[IFNAMSIZ];  
  23.         __caddr_t ifru_data;  
  24.       } ifr_ifru;  
  25.   };  
  26. # define ifr_name ifr_ifrn.ifrn_name /* interface name*/   在这里就是网卡eth0或eth1  
  27. # define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ mac地址  
  28. # define ifr_addr ifr_ifru.ifru_addr /* address */   source地址  
  29. # define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */   目的ip地址  
  30. # define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ 广播地址  
  31. # define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ 子网掩码  
  32. # define ifr_flags ifr_ifru.ifru_flags /* flags */   模式标志 设置混杂模式  
  33. # define ifr_metric ifr_ifru.ifru_ivalue /* metric */  
  34. # define ifr_mtu ifr_ifru.ifru_mtu /* mtu */  
  35. # define ifr_map ifr_ifru.ifru_map /* device map */  
  36. # define ifr_slave ifr_ifru.ifru_slave /* slave device */  
  37. # define ifr_data ifr_ifru.ifru_data /* for use by interface */  
  38. # define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */  
  39. # define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */  
  40. # define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */  
  41. # define ifr_newname ifr_ifru.ifru_newname /* New name */  
  42. # define _IOT_ifreq _IOT(_IOTS(char),IFNAMSIZ,_IOTS(char),16,0,0)  
  43. # define _IOT_ifreq_short _IOT(_IOTS(char),IFNAMSIZ,_IOTS(short),1,0,0)  
  44. # define _IOT_ifreq_int _IOT(_IOTS(char),IFNAMSIZ,_IOTS(int),1,0,0)  

上面好多的定义,但用到的只是几个,我做了相应的中文标注,设置混杂模式需要的就是将ifr_flags 设置为 IFF_PROMISC就可以了。

在这之后需要设置socket选项如下

ret= int setsockopt( intsocket, int level, int option_name, const void *option_value, size_toption_len);

第一个参数socket是套接字描述符。第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。option_name指定准备设置的选项,option_name可以有哪些取值,这取决于level。

完成这步后就可以通过

[cpp] view plaincopy
  1. {    
  2.     i struct ifreq ifr;  
  3.     strcpy(ifr.ifr_name, “eth0”);  
  4.     ioctl(fd, SIOCGIFHWADDR, &ifr);  
  5. }  

获取网卡的索引接口了,接着我们要做的就是将创建的原始套接字绑定在指定的网卡上了

ret= bind(fd, (struct sockaddr *)& sock, sizeof(sock));这里的sock便不再是之前TCP连接时的 struct sockaddr_in 了,而是struct  sockaddr_ll,定义如下:

[cpp] view plaincopy
  1. struct sockaddr_ll{   
  2.     unsigned short sll_family; /* 总是 AF_PACKET */   
  3.     unsigned short sll_protocol; /* 物理层的协议 */   
  4.     int sll_ifindex; /* 接口号 */   
  5.     unsigned short sll_hatype; /* 报头类型 */   
  6.     unsigned char sll_pkttype; /* 分组类型 */   
  7.     unsigned char sll_halen; /* 地址长度 */   
  8.     unsigned char sll_addr[8]; /* 物理层地址 */   
  9. };  

对应设置如下

[cpp] view plaincopy
  1. struct sockaddr_ll   sock;  
  2. sock.sll_family = AF_PACKET;  
  3. sock.sll_ifindex = stIf.ifr_ifindex;(struct   ifreq)  
  4. sock.sll_protocol = htons(ETH_P_ALL);  
  5. ret = bind(fd, (struct sockaddr *)&, sizeof(sock));  

这样我们就可以开始通过recvfrom()等函数获取对应套接字上的数据了。

说到这里我们已经获得了网卡上的一段数据包,可是数据包是什么样子的?这里你就必须了解什么是Ethnet,即以太数据帧




以太帧首部中2字节的帧类型字段指定了其上层所承载的具体协议,常见的有0x0800表示是IP报文、0x0806表示RARP协议、0x0806即为我们将要讨论的ARP协议。硬件类型: 1表示以太网。

网卡从线路上收到信号流,网卡的驱动程序会去检查数据帧开始的前6个字节,即目的主机的MAC地址,如果和自己的网卡地址一致它才会接收这个帧,不符合的一般都是直接无视。然后该数据帧会被网络驱动程序分解,IP报文将通过网络协议栈,最后传送到应用程序那里。

从这里我们可以看出以太数据帧头包含了MAC地址与帧类型,这正是我们需要的,所以我们便要开始对以太帧头进行解析,这就涉及另一个重要的数据类型struct ether_header;

其中重要的数据信息有

u_charether_dhost[ETHER_ADDR_LEN];//dest 的MAC地址信息
u_char ether_shost[ETHER_ADDR_LEN];// source的MAC地址信息
u_short ether_type; // ip 协议类型 :ipv4……….等

[cpp] view plaincopy
  1. static int ethdump_parseEthHead(const struct ether_header *pstEthHead)  
  2. {  
  3.     unsigned short usEthPktType;  
  4.     /* 协议类型、源MAC、目的MAC */  
  5.     usEthPktType=ntohs(pstEthHead->ether_type);  printf("EthType:0x%04x(%s)\n",usEthPktType,ethdump_getProName(usEthPktType));  
  6.     ethdump_showMac( pstEthHead->ether_shost);  
  7.     ethdump_showMac(pstEthHead->ether_dhost);  
  8.       
  9.     return 0;      
  10. }  
  11. ntohs(ether_type)返回一个以主机字节顺序表达的数  
  12. static void ethdump_showMac(const char acHWAddr[])   
  13. {  
  14.     for(i = 0; i < ETHER_ADDR_LEN - 1; i++)  
  15.     {  
  16.         printf("%02x:", *((unsigned char *)&(acHWAddr[i])));  
  17.     }  
  18.     printf("%02x] \n", *((unsigned char *)&(acHWAddr[i])));  
  19. }  

接着我们就需要对下一段内容,即IP数据包进行解析,这里对应的数据结构为struct ip,对于他的定义有些复杂,我也不是很懂,但是我们只需要获取

u_int8_tip_p; /* protocol */协议类型

structin_addr ip_src, ip_dst; /* source and dest address */源IP与目的IP地址

通过getprotobynumber( ); 返回对应于给定协议号的相关协议信息,输出对应的p_name。

以及通过inet_nota(ip_src)打印出对应的source IP 与 dest IP。就可以获取目前我们需要的内容。

[cpp] view plaincopy
  1. static int ethdump_parseIpHead(const struct ip *pstIpHead)  
  2. {  
  3.     struct protoent *pstIpProto = NULL;  
  4.     /* 协议类型、源IP地址、目的IP地址 */  
  5.     pstIpProto = getprotobynumber(pstIpHead->ip_p);  
  6.     if(NULL != pstIpProto)  
  7.     {  
  8.         printf("IP-Type: %d (%s) \n", pstIpHead->ip_p, pstIpProto->p_name);  
  9.     }  
  10.     else  
  11.     {  
  12.         printf("IP-Type: %d (%s)\n ", pstIpHead->ip_p, "None");  
  13.     }  
  14.     printf("SAddr=[%s] \n", inet_ntoa(pstIpHead->ip_src));  
  15.     printf("DAddr=[%s] ", inet_ntoa(pstIpHead->ip_dst));  
  16.     printf("\n========================================\n");  
  17.     return 0;  
  18. }  

到这里基本上我们就完成了对数据包的抓取,以及获取 source mac ,dest mac ,source ip ,des ip ,以及对应协议类型的分析。当然还可以进行进一步的数据分析,以及IP段、协议栈等的分析。越往后走涉及的东西就越复杂。

 


原创粉丝点击