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的第三版第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