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

Linux是从什么时候开始支持线程的.

几天前和同事吃饭,谈论到一个问题:linux从什么时候开始支持线程。他说linux中的线程出现的很晚,直到2.2.x才有。

我觉得不对啊,怎么会那么晚。然后它用手机搜索了一下,linux对POSIX线程库的支持确实很晚,直到2.6.x才有。

但我觉得POSIX线程库只不过是用户空间用来创建线程的接口,并不能证明那么晚linux才支持线程。

后来用PC的时候搜索了一下,在The Linux Document Project的官方网站找到了答案:

http://www.tldp.org/FAQ/Threads-FAQ/Support.html

Does Linux support threads?

Yes. As of 1.3.56, Linux has supported kernel-space multithreading. There also have been user-space thread libraries around as early as 1.0.9. There is on-going effort to refine and make the kernel more reentrant. With the introduction of 2.1.x, the memory space is being revised so that the kernel can access the user memory more quickly.

问: Linux支持线程吗?

答: 是的,自从1.3.56开始,Linux就开始支持内核空间中的多线程了。早到1.0.9,就已经有了用户空间的线程库。

/////

这里有个比较混淆的概念:内核线程, 内核态支持多线程。

内核态支持多线持指的是在内核空间支持线程的调度,创建,销毁等操作。

内核线程则是另外一个概念,它和一般task的最大区别是,描述内核线程的task_struct结构体中的内存描述符mm_struct是NULL。具体参考经典书:linux kernel development. 我另一篇贴子也有写到: http://ykyi.net/2012/06/%E5%86%85%E6%A0%B8%E7%BA%BF%E7%A8%8B%E6%98%AF%E4%BB%80%E4%B9%88%E4%B8%9C%E4%B8%9C-what-is-kernel-threads/ 是从Understand the linux kernel抄下来的。

    用线上机测试tcpcopy基本通过。支持旧模式,Advanced模式失败

    测试结果介绍:

     

    tcpcopy在编译期配置成使用NFQueue模式,用Raw Socket抓包。线上机的流量被成功复制并导向测试机。经改动的tcpd初期工作正常。

    改动版的tcpd(实际上是一个echo服务器)。测试机上的tcpd把收到的报文被打印到日志文件。但日志文件到6M+大小的时候,就停止打印。目前还没有查到原因,可能是改动后的tcpdBug。此时,tcpdump仍然抓到大量从线上机复制来的真实流量到达测试机。tcpcopy应该是工作正常的。

     

    tcpcopy被编译成使用libpcap抓包时不能在线上机上正常工作。tcpdump没有抓到预期从线上机复制到测试机的报文。原因是:线上机10.130.68.100上的真实流量从网络接口tunl0:0进入,从网络接口eth1发出。分析tcpd的日志发现,tcpd使用libpcap只拿到tunl0IP,没有发现tunl0:0IP。需要了解IP隧道,和libpcap库的相关知识,有可能解决这个问题。

     

    编译成使用Raw Socket抓包,和Advanced模式时,tcpcopy启动失败,错误日志显示Advanced模式只能使用libpcap库抓包。因此,只有在解决libpcap正确识别tunl0:0网络接口后,才能在线上机上运行tcpcopy

     

    预备知识:

    Tcpcopy可以编译成用libpcap抓包或者使用Raw Socket抓包。Libpcap库是tcpdump使用的抓包库,Raw Socket是操作系统内建的功能。

    官方文档建议用libpcap抓包,以获得更高性能和较低丢包率。

     

    Tcpcopy在编译期指定工作模式。tcpcopy目前有两种工作模式,一种使用IPQueue技术或者NFQueue技术。NFQueue技术是用来替代IPQueue技术的。我们目前使用的tlinux,内建了NFQueue内核模块,没有开启IPQueue模块。因为NFQueue是内建的即CONF_XXXXXX = y,而不是编译成模块,即CONF_XXXXX = m, 所以我们在环境中不需要使用modprobe 加载IPQueue或者NFQueue模块。忽略官方文档中提到的modprobe ip_queue

     

    测试步骤:

     

    使用NFQueue模式:

    线上机:10.130.68.100

    编译tcpcopy时指定使用NFQueue不要指定 –enable-pcap,则默认使用raw socket抓包。用pcap抓包时识别不到网络接口tunl0:0,而tunl0:0是真实流量的入口。

    # ./configure –enable-nfqueue

    #./make

    生成的tcpcopysrc/tcpcopy 目录下。

    运行tcpcopy命令,把流量复制到测试机:

    # ./tcpcopy -x 80-10.213.243.156

    测试机:10.213.243.156

    编译tcpcopy时同样指定–enable-nfqueue,不要指定–enable-pcap。然后运行intercept

    # ./intercept

     

    使用Advanced模式:

    因为暂时未能解决用libpcap识别到网络设备tunel0:0的问题,实际上Advanced模式暂时不能工作。根据以前的使用经验,用Advanced模式时需要注意两点:

    1. ./configure时指定 –enable-advaned –enable-pcap

    2. Advanced模式需要两台测试机,需要设成一条路由把复制后的真实流量导向测试机。需要更改默认路由:删掉当前的默认路由,新的默认路由从正确的网卡指向测试机2IP

    # route del default

    # route add default gw 10.213.243.156 dev eth1

    copyright ykyi.net