Linux抓包技术简介

Linux抓包技术简介

Author: zausiu@gmail.com

1. 简介

大多数应用常景下,我们在IP层上用AF_INET或AF_INET6作为socket的第一个参数,SOCK_STREAM或SOCK_DGRAM作为socket的第二个参数创建最为常见的IPv4或IPv6的TCP或UDP套接字。这样的套接字满足了绝大多数网络应用常景。

 

本文简单介绍三种Linux平台下抓包,截获包的技术。它们提供一些更酷的功能。每种技术的水都很深,作者个人水平有限,按照自己的理解做一般的介绍。仅仅旨在起到科谱的作用。

 

2. RAW SOCKET

参数SOCK_RAW可以作为socket函数的第二个参数传入,这样创建的套接字称为原始套接字。设置Socket的第一个参数可以让指定原始套接字开在数据链路层上或者网络层。

 

下面的语句创建在数据链路层上的原始socket。

Socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));  

在负载不大的情况下,读这个socket可以拿到本机收到的所有数据包。写入这个socket需要程序员负责填充各种包头的数据。

 

Socket(AF_INET, SOCK_RAW, xxxxx协议号);

在IP层上创建IPv4的RAW socket,可以拿到各种经由IPv4的数据包,比如ICMP包,IGMP包等。Steven的<Unix网络编程>第三版第28.4节,第二段:“系统收到的UDP包和TCP包不会传给原始套接字”。但读tcpcopy的源代码时,确实用了原始套接字在网络层读UDP和TCP包。难道书上写的过时了。见代码,来自tcpcopy:

#if (TCPCOPY_UDP)

        fd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);

#else

        fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);

#endif

另外,在IP层上创建的原始套接字有一个选项:IP_HDRINCL,它被开启时,则内核帮助填充IP包头。Linux默认没有开启这个选项。

 

3. NF_Queue

NF_Queue是用户空间的一个库,需要内核模块NETFILTER_NETLINK_QUEUE的支持。官方网址:http://www.netfilter.org。通过这个库可以在数据包被发往用户空间处理前截获数据包,或者在数据包被发往网卡前截获数据库。截获后,可以修改数据包的内容,并决定是否丢充这个数据库。

NF_Queue可以用来编写防火墙等安全软件。

Tcpcopy使用NF_Queue丢弃测试中的程序回复给真实客户程序的数据包,使测试中程序回复的报文不影响真实客户程序。

另一个提供类似功能的库是IP_Queue,它已经被废弃。NF_Queue可以完全取代IP_Queue。我们用的tlinux,内核中built-in了NF_Queue模块。

 

4. libpcap

大名鼎鼎的Libpcap是一个古老的可以工作在所有Unix-like平台上的抓包库,比linux年纪更大。Tcpdump的广泛使用让libpcap成为事实上的行业标准。它可以很方便的编写抓取数据链路层上的数据包,并可以设置过滤条件在内核级别只复制关心的数据包,而数据链路层上的原始套接字会尽最大努力复制所有的包,所以libpcap的工作效率高。而且,pcap使用方便。下面是一个很简单的使用pcap抓到发往80端口的tcp包并打印源IP,目的IP,和目的端口号的程序。非常短,很容易看,起到介绍libpcap的作用。

 int main(int argc, char *argv[])

 {

pcap_t *handle; // pcap用的句柄,和其它各种句柄一样。

char *dev;         // 网络接口名,如eth0, eth1, tunl0 之类

char errbuf[PCAP_ERRBUF_SIZE]; // 存描述出错原因的缓冲

struct bpf_program fp;         // pcap用来过滤数据包用

char filter_exp[] = "tcp and dst port 80"; // 过滤字符串,和tcpdump的写法一样

struct pcap_pkthdr header;      // pcap自己用的包头

const u_char *packet;         // pcap包头后的数据,即抓到的数据

 

int len_of_iphdr;         // ip头的长度

struct in_addr src_ip;    // 源IP地址

struct in_addr dst_ip;    // 目的IP

const u_char *ip_hdr_addr; // IP头的首地址

const u_char *tcp_hdr_addr;     // tcp头的首地址

struct tcphdr *tcp_hdr;         // tcp头结构体

u_short dst_port;             // 目的端口

 

// 如果命令行指定网卡名

if (argv[1])

dev = argv[1]; //就用指定的网卡

else

dev = "eth0"; //否则用eth0

 

}

        

        // 用pcap打开这个网卡

handle = pcap_open_live(dev, BUFSIZ, 0, 1000, errbuf);

if (handle == NULL) {  // 打开网卡出错

fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);

return(2);

}

      

        // 编译过滤文件

if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {

fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));

return(2);

}

        // 设置过滤条件

if (pcap_setfilter(handle, &fp) == -1) {

fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));

return(2);

}

 

        // 抓包

packet = pcap_next(handle, &header);

 

// 至此packet指向抓到的一个目标端口号是80的数据链路层的包

// 它的包结构是 14字节以太网包头 + 可变IPv4包头 + TCP包头

ip_hdr_addr = (u_char*)(packet + sizeof(struct ethernet_hdr));

src_ip.s_addr = ((struct iphdr*)ip_hdr_addr)->saddr;

dst_ip.s_addr = ((struct iphdr*)ip_hdr_addr)->daddr;

len_of_iphdr = ((struct iphdr*)ip_hdr_addr)->ihl * 4;

tcp_hdr_addr = ip_hdr_addr + len_of_iphdr;

tcp_hdr = (struct tcphdr*)tcp_hdr_addr;

dst_port = tcp_hdr->dest;

 

// 经过上面的各种指针偏移,下面打印源IP和目标IP和目标端口.

printf("src addr %s\n", inet_ntoa(src_ip));

printf("dst addr %s\n", inet_ntoa(dst_ip));

printf("dst_port %d\n", ntohs(dst_port));

 

/* And close the session */

pcap_close(handle);

 

return(0);

 }

 

5. 总结

本文只是一个非常简单的介绍。如需要进阶学习,Stevens的网络编译有更详细地关于Raw Socket的内容,研究NF_Queue技术则需要读源代码和收集散在社区各个角落的资料了,而讲libpcap有两篇很好的文章:

"Programming with Libpcap – Sniffing the network from our own application "用Libpcap编程,在我们的程序中嗅探网络。非常漂亮的pdf文档。

http://recursos.aldabaknocking.com/libpcapHakin9LuisMartinGarcia.pdf

The Sniffer’s Guide to Raw Traffic 嗅探器教程。斯坦福大学的Geek于大概2001年写的。

http://yuba.stanford.edu/~casado/pcap/section1.html

copyright ykyi.net