网上网卡抓包程序已经较多,我也就在这里写下近段时间我的感悟,一作自己整理思路,二为需要的人取用。本文所做的网卡抓包程序类似于简化版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一样,就是个套接字。
- ret = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &nrecv_buff, sizeof(int));
- if (ret < 0)
- {
- perror("[Error]Set socket option");
- close(fd);
- exit(0);
- }
由于主机上的网卡可能不止一块,我们需要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数据包。
- strcpy(ifr.ifr_name, g_ifname);
- printf("Get networkcard port!\n");
- ret = ioctl(fd, SIOCGIFINDEX, &ifr);
- if (ret < 0)
- {
- perror("[Error]Get networkcard ip");
- close(fd);
- exit(0);
- }
然后通过网卡接口地址,将原始套接字绑定到网卡上,ret = bind(fd, (struct sockaddr *)&sock, sizeof(sock))这里的sock便不再是之前TCP连接时的 struct sockaddr_in 了,而是struct sockaddr_ll,具体定义可以参考hi.baidu.com/sjb811023/item/e0463c16ee4f5ba7feded5ff。