详细讲解Linux内核角度分析tcpdump原理(1)
一、tcpdump的用途
tcpdump是Linux系统抓包工具,tcpdump基于libpcap库,根据使用者的定义对网络上的数据包进行截获,tcpdump可以将网络中传送的数据包中的"头"完全截获下来提供分析,支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助去掉无用的信息。通过tcpdump可以分析很多网络行为,比如丢包重传、详细报文、tcp分组等,总之通过tcpdunp可以为各种网络问题进行排查,可以在服务器上将捕获的数据包信息以pcap文件保存下来,通过wireshark打开,更直观地分析。
tcpdump是基于libpcap库的,数据包的过滤是基于BPF(tcpdump依附标准的bpf机器来运行,tcpdump过滤规则会被转化为一段bpf指令并加载到内核中的bpf虚拟机器上执行),使用bpf虚拟机的tcpdump完美解决了包过滤问题。总之tcpdump使用libpcap这种链路层旁路处理的形式进行包捕获,使用bpf机制实现对包的完美过滤。
二、libpcap简单介绍
libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库,独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。
利用libpcap函数库开发应用程序的基本步骤:
捕获各种数据包,例如:网络流量统计。
过滤网络数据包,例如:过滤掉本地上的一些数据,类似防火墙。
分析网络数据包,例如:分析网络协议,数据的采集。
存储网络数据包,例如:保存捕获的数据以为将来进行分析。
libpcap库在linux上的安装过程
测试:
编译:
报错提醒:
解决:
执行 locate libpcap.so.1 , 查看libpcap.so.1在系统中的路径 , 显示为 : /usr/local/lib/libpcap.so.1.2.1 以管理员权限打开编辑 /etc/ld.so.conf 文件, 末尾新一行追加 /usr/local/lib , /usr/local/lib 为 libpcap.so.1.7.4 所在目录, 保存退出 以管理员权限执行 ldconfig(如果不支持改命令用whereis ldconfig查看并设置环境变量)命令, 成功

【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


几个重要的API
pcap_lookupdev():函数用于查找网络设备,返回可被 pcap_open_live() 函数调用的网络设备名指针。
pcap_lookupnet():函数获得指定网络设备的网络号和掩码。
pcap_open_live(): 函数用于打开网络设备,并且返回用于捕获网络数据包的数据包捕获描述字。对于此网络设备的操作都要基于此网络设备描述字。
pcap_compile(): 函数用于将用户制定的过滤策略编译到过滤程序中。
pcap_setfilter():函数用于设置过滤器。
pcap_loop():函数 pcap_dispatch() 函数用于捕获数据包,捕获后还可以进行处理,此外 pcap_next() 和 pcap_next_ex() 两个函数也可以用来捕获数据包。
pcap_close():函数用于关闭网络设备,释放资源。
三、tcpdump实现抓包原理剖析
使用strace追踪

可以看到tcpdump抓包创建的的套接字类型AF_PACKET
在libpcap库源码中也可以看到有调用socket系统调用:
AF_PACKET和socket应用结合一般都是用于抓包分析,packet套接字提供的是L2的抓包能力。
socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
系统调用:
socket创建函数:
调用:__sock_create
sock_create 函数主要就是创建了socket . 同时根据之前PF_PACKET 模块注册到全局变量net_families 。 找到af_packet.c 中初始化的 static const struct net_proto_family packet_family_ops。而sock_create 函数中 err = pf->create(net, sock, protocol, kern); 最终就会调用 packet_family_ops 里的packet_create
Linux内核中定义了net_proto_family结构体,用来指明不同的协议族对应的socket创建函数,family字段是协议族的类型,create是创建socket的函数,如下是PF_PACKET对应结构体。
找到AF_PACKET协议族对应的create函数:可以看到po->prot_hook.func = packet_rcv;po->prot_hook其实packet_type,packet_type结构体: packet_type 结构体第一个type 很重要,对应链路层中2个字节的以太网类型。而dev.c 链路层抓取的包上报给对应模块,就是根据抓取的链路层类型,然后给对应的模块处理,例如socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_ALL)); ETH_P_ALL表示所有的底层包都会给到PF_PACKET 模块的处理函数,这里处理函数就是packet_rcv 函数。
设置了回调函数:packet_rcv,并通过register_prot_hook(sk)完成了注册,其中注册过程将再下面分析
展开注册函数register_prot_hook(sk)
展开dev_add_pack
综上:tcpdump在刚开始工作时创建了PF_PACKET套接字,并在全局的ptype_all中挂载了该套接字的pt(packet_type *pt),其中pt的字段func设置了相应的回调函数packet_rcv(后面将分析该函数),到此tcpdump抓包的socket(AF_PACKET)创建完成,相应的准备工作完成。
网络收包时tcpdump进行抓包
函数调用关系
调用关系:netif_receive_skb-->netif_receive_skb-->netif_receive_skb_internal->__netif_receive_skb-->__netif_receive_skb_core
核心函数__netif_receive_skb_core
__netif_receive_skb_core函数在遍历ptype_all时,同时也执行了deliver_skb(skb, pt_prev, orig_dev);deliver函数调用了paket_type.func(),也就是packet_rcv ,如下源码所示:
下面将展开packet_rcv函数进行分析;函数接收到链路层网口的数据包后,会根据应用层设置的bpf过滤数据包,符合要求的最终会加到struct sock sk 的接收缓存中。使用BPF过滤过程将在后面进行分析。
综上一旦关联上链路层抓到的包就会copy一份给上层接口(即PF_PACKET 注册的回调函数packet_rev). 而回调函数会根据应用层设置的bpf过滤数据包,最终放入接收缓存的数据包肯定是符合应用层想截取的数据。因此最后一步recvfrom 也就是从接收缓存的数据包copy给应用层,如下源码:
到这,网络接收数据包时的抓包过程就结束了
网络发包时tcpdump进行抓包
Linux协议栈中提供的报文发送函数有两个,一个是链路层提供给网络层的发包函数dev_queue_xmit(),另一个就说软中断发吧包函数之间调用的sch_direct_xmit(),这两个函数最终都会调用dev_hard_start_xmit()
xmit_one():发送一个到多个数据包
dev_queue_xmit_nit():将数据包发送给driver
在遍历ptype_all时,同时也执行了deliver_skb(skb, pt_prev, orig_dev);deliver函数调用了paket_type.func(),也就是packet_rcv ,如下源码所示:
下面的流程就和网络收包时tcpdump进行抓包一样了(packet_rcv函数中会将用户设置的过滤条件,通过BPF进行过滤,并将过滤的数据包添加到接收队列中,应用层在libpcap库中调用recvfrom 。 PF_PACKET 协议簇模块调用packet_recvmsg 将接收队列中的数据copy应用层)
tcpdump进行抓包的内核流程梳理
应用层通过libpcap库:调用系统调用创建socket,sock_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));tcpdump在socket创建过程中创建packet_type(struct packet_type),并挂载到全局的ptype_all链表上。(同时在packet_type设置回调函数packet_rcv
网络收包/发包时,会在各自的处理函数(收包时:__netif_receive_skb_core,发包时:dev_queue_xmit_nit)中遍历ptype_all链表,并同时执行其回调函数,这里tcpdump的注册的回调函数就是packet_rcv
packet_rcv函数中会将用户设置的过滤条件,通过BPF进行过滤,并将过滤的数据包添加到接收队列中
应用层调用recvfrom 。 PF_PACKET 协议簇模块调用packet_recvmsg 将接收队列中的数据copy应用层,到此将数据包捕获到。
总结
本文主要从tcpdump抓包时调用的libpcap库开始梳理,从socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))进入系统调用,再从内核角度对Tcpdump在收包和发包的流程分析了一遍,其实还有一个重点:BPF的过滤过程,如下源码所示:run_filter(skb, sk, snaplen),下次文章将对BPF的过滤过程进行一些分析。
