自写网卡抓包程序

来源:互联网 发布:床和床头柜尺寸 知乎 编辑:程序博客网 时间:2024/06/05 22:48

网上网卡抓包程序已经较多,我也就在这里写下近段时间我的感悟,一作自己整理思路,二为需要的人取用。本文所做的网卡抓包程序类似于简化版wireshark功能。废话不多说,我们开始。

我们的目标是获取所有经过本地网卡的报头信息,包括链路层的MAC头,网络层的IP头,传输层的tcp或udp头,以及应用层的http头,并将其完整打印出来。

整个思路其实很简单,无非就是获取经过网卡的数据帧,然后将其按照格式打印出来即可。

想要获取数据帧,就不得不借用原始套接字(raw socket),raw socket的原理按我理解相当于在数据帧进入网卡时直接通过调用raw socket将数据帧备份一遍,直接将备份的数据帧交由你写的抓包程序处理,而原本的数据帧仍然一步步通过协议栈解析。

一般情况下我们使用的raw socket有两类:1、socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送和接收ip数据包。2、socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))发送接收以太网数据帧。这里我们选用第二种。此函数的第三个变量是socket的protocol,定义于<netinet/in.h>。常用的协议有ETH_P_IP  , ETH_P_ARP,ETH_P_RARP或ETH_P_ALL,从字面上我们就可以很好理解。同时此函数返回值与一般的socket一样,就是个套接字。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nrecv_buff, sizeof(int));  
  2. if (ret < 0)  
  3. {  
  4.     perror("[Error]Set socket option");  
  5.     close(fd);  
  6.     exit(0);  
  7. }  

由于主机上的网卡可能不止一块,我们需要bind到某一块网卡上,这里就需要了解一个重要的结构类型struct ifreq。ifreq结构定义在/usr/include/net/if.h中,用来配置ip地址,激活接口,配置MTU等接口信息的。其中包含了一个接口名称和union(有可能是IP地址,广播地址,子网掩码,MAC号,MTU等)。ifreq包含在ifconf结构中。而ifconf结构通常是用来保存所有接口的信息的。利用ioctl与ifreq结构体我们可以获得某个网卡(特定接口名称ethx)的ip地址。

关于ioctl的解释可以参考my.oschina.net/dream0303/blog/179893。这里我们定义一个名为ifr的ifreq结构体,先strcpy(ifr.ifr_name, g_ifname)g_ifname可为(ethx自设),利用ioctl(fd, SIOCGIFINDEX, &ifr)即可得到此网卡的接口地址。(当有多块网卡时,每个网卡都会有一个索引值,值会随着正在被使用的网卡个数变化,这个索引值就是网卡的接口地址)。因此,通过索引值,我们就可以定位到本机上指定的一块网卡。需要注意的是,在我的测试机上有两块网卡,一块网卡仅服务于内网,因此我之前bind时接收到的全是arp数据包。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. strcpy(ifr.ifr_name, g_ifname);  
  2. printf("Get networkcard port!\n");  
  3. ret = ioctl(fd, SIOCGIFINDEX, &ifr);  
  4. if (ret < 0)  
  5. {  
  6.     perror("[Error]Get networkcard ip");  
  7.     close(fd);  
  8.     exit(0);  
  9. }  

然后通过网卡接口地址,将原始套接字绑定到网卡上,ret = bind(fd, (struct sockaddr *)&sock, sizeof(sock))这里的sock便不再是之前TCP连接时的 struct sockaddr_in 了,而是struct  sockaddr_ll,具体定义可以参考hi.baidu.com/sjb811023/item/e0463c16ee4f5ba7feded5ff。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. sock.sll_family = AF_PACKET;  
  2. sock.sll_ifindex = local_ifr.ifr_ifindex;  
  3. sock.sll_protocol = htons(ETH_P_ALL);  
  4. ret = bind(fd, (struct sockaddr *)&sock, sizeof(sock));  
  5. if (ret < 0)  
  6. {  
  7.     perror("[Error]Bind interface");  
  8.     close(fd);  
  9.     exit(0);  
  10. }  
  11. printf("Bind Success!\n");  
bind之后,前期的准备就完成了,可以开始抓包并分析了。

这里的原理也很简单,先用recvfrom把完整的数据帧接收进来,再用各种结构头去读取就好了。

mac头:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //获取链路层mac信息,以ether_header结构体抽出mac头  
  2. pst_eth_head = (struct ethhdr*)nrecv_buff;  
  3. //打印出以太帧报头的详细信息,并读出帧类型  
  4. printf("Source MAC address      :   %02x:%02x:%02x:%02x:%02x:%02x\n", pst_eth_head->h_source[0], pst_eth_head->h_source[1], pst_eth_head->h_source[2], pst_eth_head->h_source[3], pst_eth_head->h_source[4], pst_eth_head->h_source[5]);  
  5. printf("Destination MAC address :   %02x:%02x:%02x:%02x:%02x:%02x\n", pst_eth_head->h_dest[0], pst_eth_head->h_dest[1], pst_eth_head->h_dest[2],  pst_eth_head->h_dest[3], pst_eth_head->h_dest[4], pst_eth_head->h_dest[5]);  
  6. us_eth_type = ntohs(pst_eth_head->h_proto);  
  7. printf("%x\n", us_eth_type);  
ip头:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //获取网络层ip信息  
  2.         pst_ip_head = (struct iphdr*)(nrecv_buff + sizeof(struct ethhdr));  
  3.         memset(&source, '\0'sizeof(source));  
  4.         source.sin_addr.s_addr = pst_ip_head->saddr;  
  5.         memset(&dest, '\0'sizeof(dest));  
  6.         dest.sin_addr.s_addr = pst_ip_head->daddr;  
  7.   
  8.   
  9.         //由于大小端的问题,所以buf中多字节类型都需要转化,而8位的单字节类型不用转化0,ntohs函数处理16位,ntohl函数处理32位  
  10.         printf("\tVersion           :   %d\n", (unsigned int)pst_ip_head->version);  
  11.         printf("\tHead Lenfth       :   %d\n", (unsigned int)pst_ip_head->ihl);  
  12.         printf("\tType of service   :   %d\n", (unsigned int)pst_ip_head->tos);  
  13.         printf("\tTotal length      :   %d\n", ntohs(pst_ip_head->tot_len));  
  14.         printf("\tIdentifier        :   %d\n", ntohs(pst_ip_head->id));  
  15.         printf("\tFragmented offset :   %d\n", ntohs(pst_ip_head->frag_off));  
  16.         printf("\tTime to live      :   %d\n", (unsigned int)pst_ip_head->ttl);  
  17.         printf("\tProtocol          :   %d\n", (unsigned int)pst_ip_head->protocol);  
  18.         printf("\tHeader checksum   :   %d\n", ntohs(pst_ip_head->check));  
  19.         printf("\tSource IP         :   %s\n", inet_ntoa(source.sin_addr));  
  20.         printf("\tDestination IP    :   %s\n", inet_ntoa(dest.sin_addr));  
tcp头:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. pst_tcp_head = (struct tcphdr*)(nrecv_buff + sizeof(struct ethhdr) + iphdr_len);  
  2. //打印tcp信息  
  3. printf("\tSource port           :   %u\n",  ntohs(pst_tcp_head->source));  
  4. printf("\tDestination port      :   %u\n",  ntohs(pst_tcp_head->dest));  
  5. printf("\tSequence number       :   %u\n",  ntohl(pst_tcp_head->seq));  
  6. printf("\tAcknowledgement number    :   %u\n",  ntohl(pst_tcp_head->ack_seq));  
  7. printf("\tHead length           :   %d\n",  (unsigned int)pst_tcp_head->doff);  
  8. printf("\tUrgent flags          :   %d\n",  (unsigned int)pst_tcp_head->urg);  
  9. printf("\tAcknowledgement Flag      :   %d\n",  (unsigned int)pst_tcp_head->ack);  
  10. printf("\tPush Flag         :   %d\n",  (unsigned int)pst_tcp_head->psh);  
  11. printf("\tReset Flag            :   %d\n",  (unsigned int)pst_tcp_head->rst);  
  12. printf("\tSynchronise Flag      :   %d\n",  (unsigned int)pst_tcp_head->syn);  
  13. printf("\tFinish Flag           :   %d\n",  (unsigned int)pst_tcp_head->fin);  
  14. printf("\tWindow size value     :   %d\n",  ntohs(pst_tcp_head->window));  
  15. printf("\tChecksum          :   %d\n",  ntohs(pst_tcp_head->check));  
  16. printf("\tUrgent pointer        :   %d\n",  ntohs(pst_tcp_head->urg_ptr));  

udp头:


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <span style="white-space:pre">      </span>printf("\tSource port        :   %d\n",  ntohs(pst_udp_head->source));  
  2.         printf("\tDestination port  :   %d\n",  ntohs(pst_udp_head->dest));  
  3.         printf("\tLength        :   %d\n",  ntohs(pst_udp_head->len));  
  4.         printf("\tChecksum      :   %d\n",  ntohs(pst_udp_head->check));  
这里只要大家去查阅各结构体的定义即可,但是需要注意的是大小端的问题。如果结构体中的类型是多字节的,就需要考虑大小端问题。大小端所影响的只是变量的值在网络中以及主机中存储的形式不同而已,对于各变量的顺序没有影响。2字节用ntohs函数,4字节用ntohl函数,单字节的不用考虑。

打印http头,http使用tcp的80端口

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. void ethdump_print_req_http(){  
  2.     int len = strlen(data);  
  3.     char *buff;  
  4.     int off;  
  5.   
  6.     memset(buff, '\0', SIZE);  
  7.     if (strncmp(data, "GET", 3) && strncmp(data, "POST", 4) == 0)  
  8.     {  
  9.         buff = get_line(buff, data, len);  
  10.         printf("%s", buff);  
  11.         off = strlen(buff);   
  12.         len = len - off;  
  13.         while(strcmp(buff, "\r\n")){  
  14.             memset(buff, '\0', SIZE);  
  15.             buff = get_line(buff, data + off, len);  
  16.             printf("%s", buff);  
  17.             off = strlen(buff) + off;  
  18.             len = len - off;  
  19.         }     
  20.     }  
  21. }  


最后感谢firefoxbug的耐心指导。

0 0
原创粉丝点击