Tcpdump实现机制

本篇是以前研究Tcpdump抓报时,发现网上资料比较琐碎,所以做了个整理,希望对其他刚研究的朋友有所帮助,有所不对的地方,请指正。

一、基本介绍

Linux作为网络服务器,特别是作为路由器和网关时,数据的采集和分析是不可少的。TcpDump是Linux中强大的网络数据采集分析工具之一。Tcpdump由两部分组成,主要分为应用层交互的tcpdump与内核交互的libpcap两部分,tcpdump主要用于实现基于五元素的过滤报文,分析报文类型做对应输出格式转变与将报文存储到相应存储文件的功能,libpcap主要用于获取网络设备,创建套接字使用Netlink通信机制与内核通信获取报文。

二、基本框架

在这里插入图片描述

1.协议栈接口收发包流程

2.BPF(berkeley packet filter伯克利包过滤)过滤器.

BPF根据用户设置的过滤规则计算应该接收的数据包长度值,如果该值比数据包的长度小,那么数据包将会被截短.特别地,如果该值为0,数据包会被PACKET套接字丢弃而直接返回协议栈进行网络层的处理.BPF在Linux中,BPF被用于内核进行数据包过滤,以减小提交给应用程序的数据包的包数和字节数,提高系统性能.

3.缓存队列(BufferQ).

用于缓存供应用程序读取的数据包,如果队列长度超过了预设缓存区的长度,那么数据包将会被丢弃.共享内存队列,共享内存被划分为固定大小的帧,数据包被按顺序拷贝到帧中,然后内核设置数据有效位,表示该帧存放了一个有效的数据包.从PACKET 套接字的缓存队列中获取数据包.在共享内存队列中,libpcap在打开NIC设备时,使用mmap 函数将共享内存映射到应用程序的虚拟空间中.libpcap根据帧大小按顺序读取缓存区.如果当前帧的数据有效位被设置,则将该数据包提交给应用程序处理,在处理完毕以后,libpcap清除当前帧的有效位,允许内核再次使用该帧;否则,libpcap使用poll()函数等待数据包的到达.

4.应用层

通过netlink获取报文

三、协议栈接口收发包流程

在这里插入图片描述
IP报文的处理过程如下:

1.驱动处理函数接口流程

do_IRQ-->irq_exit-->do_softirq-->call_softirq-->__do_softirq-->net_rx_action->e1000e_poll

-->e1000_receive_skb->napi_gro_receive-->netif_receive_skb-->__netif_receive_skb-->

__netif_receive_skb_core-->deliver_skb

2.协议栈处理函数接口流程

在这里插入图片描述
INPUT链处理

ip_rcv-->NF_HOOK(NF_INET_PRE_ROUTING)(NF_开头的函数为iptables接口规
则)-->ip_rcv_finish-->dst_input-->ip_local_deliverN-->F_HOOK(NF_INET_LOCAL_IN)-->ip_loc
al_deliver_finish-->ipprot->handler()-->

FORWARD链处理

ip_forward-->NF_HOOK(NF_INET_FORWARD)-->ip_forward_finish-->

OUTPUT链处理

dst_output-->dst->output-->ip_output-->NF_HOOK_COND(NF_INET_POST_ROUTING)-->ip_f
inish_output-->ip_finish_output2-->

邻居子系统 (MAC头封装)

__ipv4_neigh_lookup_noref(邻居项的查找函数)-->dst_neigh_output(没用邻居信息调用
其发送)-->neigh_hh_output(有邻居信息将其mac填充报文)

netdevice子系统

dev_queue_xmit-->dev_hard_start_xmit(首先是拷贝一份skb给“packet taps”,tcpdump
就是从这里得到数据 钩子函数 实际函数在驱动中)-->ndo_start_xmit

3.sk_buff数据结果

1)内核中sk_buff结构体在各层协议之间传输不是用拷贝sk_buff结构体,而是通过增加协议头和移动指针来操作的。如果是从L4传输到L2,则是通过往sk_buff结构体中增加该层协议头来操作;如果是从L4到L2,则是通过移动sk_buff结构体中的data指针来实现,不会删除各层协议头。这样做是为了提高CPU的工作效率。

2)因为sk_buff结构体是linux网络代码中最重要的数据结构,是整个网络传输载体。所以sk_buff结构体里面有很多关于其他功能的成员字段,比如:防火墙,子路由系统,多播等。这些字段并不是一定有的,只有在满足特点条件才有的。所以可以在需要的时候再去关心这些成员字段,现在我们只来讲解下一般的成员字段。

3)sk_buff结构体关联多个其他结构体,
第一是数据区:由sk_buff中head和end指向的数据块,用来存储sk_buff结构的数据也即是存储数据包的内容和各层协议头。
第二是分片结构:用来表示IP分片的一个结构体,实则上是和sk_buff结构的数据区相连的,即是end指针的下一个字节开始就是分片结构。也正是此原因,所以分片结构和sk_buff数据区内存分配及销毁时都是一起的。
第三个是分片结构指向的数据区,即是IP分片内容。

4)重要数据结果

sk_buff_data_t		transport_header;      // 指向四层帧头结构体指针
sk_buff_data_t		network_header;	       // 指向三层IP头结构体指针
sk_buff_data_t		mac_header;	       // 指向二层mac头的头
sk_buff_data_t		tail;			  // 指向数据区中实际数据结束的位置
sk_buff_data_t		end;			  // 指向数据区中结束的位置(非实际数据区域结束位置)
unsigned char		*head,			  // 指向数据区中开始的位置(非实际数据区域开始位置)
unsigned char		*data;			  // 指向数据区中实际数据开始的位置

5)Ip头数据结构

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,
     version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,
     ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
    __u8    tos;
    __be16 -tot_len;
    __be16 -id;
    __be16 -frag_off;
    __u8    ttl;
    __u8    protocol;
    __be16 -check;
    __be32 -saddr;
    __be32 -daddr;
};

在这里插入图片描述

6)Eth头数据结构

struct ethhdr
{	
	unsigned char h_dest[ETH_ALEN]; //目的MAC地址
	unsigned char h_source[ETH_ALEN]; //源MAC地址
	__u16 h_proto ; //网络层所使用的协议类型
}__attribute__((packed))  //用于告诉编译器不要对这个结构体中的缝隙部分进行填充操作;

四、Libpcap内核实现

在libpcap使用 socket(AF_PACKET,SOCK_DGRAM/SOCK_RAW,hton(ETH_P_ALL))创建一个与netlink通信的AF_PACKET套接字,通过recefrom获取报文
在内核中处理流程
net/socket.c

__sock_create(创建了socket 同时根据之前PF_PACKET 模块注册到全局变量net_families)-->

net/packet/af_packet.c

pf->create--> packet_create--> __register_prot_hook(最终挂载到dev.c 中的ptype_all链表上)-> 

net/core/dev.c

dev_add_pack-->ptype_head-->

net/packet/af_packet.c

packet_rcv--> run_filter(BPF过滤)--> skb_clone--> __skb_queue_tail(将报文添加到
BufferQ是缓存)--> packet_recvmsg--> skb_recv_datagram(从接收缓存中获取数据
包)--> skb_copy_datagram_msg(将最终的数据copy到用户空间) 

packet_create作用
1创建了struct sock * sk ,同时初始化struct socket *sock 中结构体的操作集。struct sock 和 struct socket 这两个结构体是内核底层网络通信重要的结构体。这里不做过多阐述。
2 初始化了packet_type 结构体,然后注册到了dev.c 中的ptype_all链表中。上面代码没看到packet_type结构体, 其实po->prot_hook 这个就是packet_type 结构体

五、libpcap应用层实现

1 准备pcap_if结构体

pcap_lookupdev()-->add_addr_to_iflist()add_or_find_if()get_instance()-->get_instanced()-->

libpcap提供了系列函数用于tcpdump抓包,第一步通常是在系统中找到合适的网络接口设备,调用 pcap_lookupdev() 函数获得可用网络接口的设备名,再利用函数 getifaddrs() 获得所有网络接口的地址,以及对应的网络掩码、广播地址、目标地址等相关信息,再利用 add_addr_to_iflist()、add_or_find_if()、get_instance() 把网络接口的信息增加到结构链表 pcap_if,最后从链表中提取第一个接口作为捕获设备。其中 get_instanced() 的功能是从设备名开始,找第一个是数字的字符,做为接口的实例号。网络接口的设备号越小,则排在链表的越前面,因此,通常函数最后返回的设备名为 eth0。

2 准备相应sockst套接字

pcap_open_live() -->live_open_new-->socket(PF_PACKET, SOCK_RAW, 
htons(ETH_P_ALL))-->handle->setfilter_op = pcap_setfilter_linux-->handle->read_op = 
pcap_read_linux

使用pcap_open_live() 尝试创建 PF_PACKET 方式或 SOCK_PACKET 方式的 socket,使用pcap_setfilter_linux实现过滤,使用pcap_read_linux获取报文

3 读取报文

pcap_read_linux-->pcap_read_packet-->recvfrom(获取报文)

六、Tcpdump获取报文流程

pcap_open_live-->pcap_compile-->pcap_setfilter-->pcap_loop(读取多个报
文)-->callback(报文回调函数)-->dump_packet_and_trunc/dump_packet/print_packet

pcap_compile–>pcap_setfilter–>pcap_loop对应libpcapsetfilter_op = pcap_setfilter_linux操作 pcap_loop对应handle->read_op = pcap_read_linux操作 callback对应pcap_read_linux中的报文回调处理函数

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值