0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

tcpdump如何实现抓内核态的包

Linux爱好者 来源:开发内功修炼 作者:张彦飞allen 2021-10-08 10:34 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

今天聊聊大家工作中经常用到的 tcpdump。

在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包的呢?有的同学知道 tcpdump 是基于 libpcap 的,那么 libpcap 的工作原理又是啥样的呢。如果让你裸写一个抓包程序,你有没有思路?

按照飞哥的风格,不搞到最底层的原理咱是不会罢休的。所以我对相关的源码进行了深入分析。通过本文,你将彻底搞清楚了以下这几个问题。

tcpdump 是如何工作的?

netfilter 过滤的包 tcpdump 是否可以抓的到?

让你自己写一个抓包程序的话该如何下手?

借助这几个问题,我们来展开今天的探索之旅!

一、网络包接收过程

在图解Linux网络包接收过程一文中我们详细介绍了网络包是如何从网卡到达用户进程中的。这个过程我们可以简单用如下这个图来表示。

772732fa-239b-11ec-82a8-dac502259ad0.png

找到 tcpdump 抓包点

我们在网络设备层的代码里找到了 tcpdump 的抓包入口。在 __netif_receive_skb_core 这个函数里会遍历 ptype_all 上的协议。还记得上文中我们提到 tcpdump 在 ptype_all 上注册了虚拟协议。这时就能执行的到了。来看函数:

//file: net/core/dev.cstatic int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)

{

。..。..

//遍历 ptype_all (tcpdump 在这里挂了虚拟协议)

list_for_each_entry_rcu(ptype, &ptype_all, list) {

if (!ptype-》dev || ptype-》dev == skb-》dev) {

if (pt_prev)

ret = deliver_skb(skb, pt_prev, orig_dev);

pt_prev = ptype;

}

}

}

在上面函数中遍历 ptype_all,并使用 deliver_skb 来调用协议中的回调函数。

//file: net/core/dev.c static inline int deliver_skb(。..)

{

return pt_prev-》func(skb, skb-》dev, pt_prev, orig_dev);

}

对于 tcpdump 来说,就会进入 packet_rcv 了(后面我们再说为啥是进入这个函数)。这个函数在 net/packet/af_packet.c 文件中。

//file: net/packet/af_packet.cstatic int packet_rcv(struct sk_buff *skb, 。..)

{

__skb_queue_tail(&sk-》sk_receive_queue, skb);

。..。..

}

可见 packet_rcv 把收到的 skb 放到了当前 packet socket 的接收队列里了。这样后面调用 recvfrom 的时候就可以获取到所抓到的包!!

再找 netfilter 过滤点

为了解释我们开篇中提到的问题,这里我们再稍微到协议层中多看一些。在 ip_rcv 中我们找到了一个 netfilter 相关的执行逻辑。

//file: net/ipv4/ip_input.cint ip_rcv(。..)

{

。..。..

return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,

ip_rcv_finish);

}

如果你用 NF_HOOK 作为关键词来搜索,还能搜到不少 netfilter 的过滤点。不过所有的过滤点都是位于 IP 协议层的。

在接收包的过程中,数据包是先经过网络设备层然后才到协议层的。

77693a4c-239b-11ec-82a8-dac502259ad0.png

那么我们开篇中的一个问题就有了答案了。假如我们设置了 netfilter 规则,在接收包的过程中,工作在网络设备层的 tcpdump 先开始工作。还没等 netfilter 过滤,tcpdump 就抓到包了!

所以,在接收包的过程中,netfilter 过滤并不会影响 tcpdump 的抓包!

二、网络包发送过程

我们接着再来看网络包发送过程。在25 张图,一万字,拆解 Linux 网络包发送过程一文中,我们详细描述过网络包的发送过程。发送过程可以汇总成简单的一张图。

77a4a190-239b-11ec-82a8-dac502259ad0.png

找到 netfilter 过滤点

在发送的过程中,同样是在 IP 层进入各种 netfilter 规则的过滤。

//file: net/ipv4/ip_output.c int ip_local_out(struct sk_buff *skb)

{

//执行 netfilter 过滤

err = __ip_local_out(skb);

}

int __ip_local_out(struct sk_buff *skb)

{

。..。..

return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,

skb_dst(skb)-》dev, dst_output);

}

在这个文件中,还能看到若干处 netfilter 过滤逻辑。

找到 tcpdump 抓包点

发送过程在协议层处理完毕到达网络设备层的时候,也有 tcpdump 的抓包点。

//file: net/core/dev.cint dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,

struct netdev_queue *txq)

{

。..

if (!list_empty(&ptype_all))

dev_queue_xmit_nit(skb, dev);

}

static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)

{

list_for_each_entry_rcu(ptype, &ptype_all, list) {

if ((ptype-》dev == dev || !ptype-》dev) &&

(!skb_loop_sk(ptype, skb))) {

if (pt_prev) {

deliver_skb(skb2, pt_prev, skb-》dev);

pt_prev = ptype;

continue;

}

。..。..

}

}

}

在上述代码中我们看到,在 dev_queue_xmit_nit 中遍历 ptype_all 中的协议,并依次调用 deliver_skb。这就会执行到 tcpdump 挂在上面的虚拟协议。

在网络包的发送过程中,和接收过程恰好相反,是协议层先处理、网络设备层后处理。

77da513c-239b-11ec-82a8-dac502259ad0.png

如果 netfilter 设置了过滤规则,那么在协议层就直接过滤掉了。在下层网络设备层工作的 tcpdump 将无法再捕获到该网络包。

三、TCPDUMP 启动

前面两小节我们说到了内核收发包都通过遍历 ptype_all 来执行抓包的。那么我们现在来看看用户态的 tcpdump 是如何挂载协议到内 ptype_all 上的。

我们通过 strace 命令我们抓一下 tcpdump 命令的系统调用,显示结果中有一行 socket 系统调用。Tcpdump 秘密的源头就藏在这行对 socket 函数的调用里。

# strace tcpdump -i eth0

socket(AF_PACKET, SOCK_RAW, 768)

。..。..

socket 系统调用的第一个参数表示创建的 socket 所属的地址簇或者协议簇,取值以 AF 或者 PF 开头。在 Linux 里,支持很多种协议族,在 include/linux/socket.h 中可以找到所有的定义。这里创建的是 packet 类型的 socket。

协议族和地址族:每一种协议族都有其对应的地址族。比如 IPV4 的协议族定义叫 PF_INET,其地址族的定义是 AF_INET。它们是一一对应的,而且值也完全一样,所以经常混用。

//file: include/linux/socket.h#define AF_UNSPEC 0#define AF_UNIX 1 /* Unix domain sockets */#define AF_LOCAL 1 /* POSIX name for AF_UNIX */#define AF_INET 2 /* Internet IP Protocol */#define AF_INET6 10 /* IP version 6 */#define AF_PACKET 17 /* Packet family */

。..。..

另外上面第三个参数 768 代表的是 ETH_P_ALL,socket.htons(ETH_P_ALL) = 768。

我们来展开看这个 packet 类型的 socket 创建的过程中都干了啥,找到 socket 创建源码。

//file: net/socket.c

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

{

。..。..

retval = sock_create(family, type, protocol, &sock);

}

int __sock_create(struct net *net, int family, int type, 。..)

{

。..。..

pf = rcu_dereference(net_families[family]);

err = pf-》create(net, sock, protocol, kern);

}

在 __sock_create 中,从 net_families 中获取了指定协议。并调用了它的 create 方法来完成创建。

net_families 是一个数组,除了我们常用的 PF_INET( ipv4 ) 外,还支持很多种协议族。比如 PF_UNIX、PF_INET6(ipv6)、PF_PACKET等等。每一种协议族在 net_families 数组的特定位置都可以找到其 family 类型。在这个 family 类型里,成员函数 create 指向该协议族的对应创建函数。

780b9012-239b-11ec-82a8-dac502259ad0.png

根据上图,我们看到对于 packet 类型的 socket,pf-》create 实际调用到的是 packet_create 函数。我们进入到这个函数中来一探究竟,这是理解 tcpdump 工作原理的关键!

//file: packet/af_packet.cstatic int packet_create(struct net *net, struct socket *sock, int protocol,

int kern)

{

。..

po = pkt_sk(sk);

po-》prot_hook.func = packet_rcv;

//注册钩子

if (proto) {

po-》prot_hook.type = proto;

register_prot_hook(sk);

}

}

static void register_prot_hook(struct sock *sk)

{

struct packet_sock *po = pkt_sk(sk);

dev_add_pack(&po-》prot_hook);

}

在 packet_create 中设置回调函数为 packet_rcv,再通过 register_prot_hook =》 dev_add_pack 完成注册。注册完后,是在全局协议 ptype_all 链表中添加了一个虚拟的协议进来。

78583390-239b-11ec-82a8-dac502259ad0.png

我们再来看下 dev_add_pack 是如何注册协议到 ptype_all 中的。回顾我们开头看到的 socket 函数调用,第三个参数 proto 传入的是 ETH_P_ALL。那 dev_add_pack 其实最后是把 hook 函数添加到了 ptype_all 里了,代码如下。

//file: net/core/dev.cvoid dev_add_pack(struct packet_type *pt)

{

struct list_head *head = ptype_head(pt);

list_add_rcu(&pt-》list, head);

}

static inline struct list_head *ptype_head(const struct packet_type *pt)

{

if (pt-》type == htons(ETH_P_ALL))

return &ptype_all;

else

return &ptype_base[ntohs(pt-》type) & PTYPE_HASH_MASK];

}

我们整篇文章都以 ETH_P_ALL 为例,但其实有的时候也会有其它情况。在别的情况下可能会注册协议到 ptype_base 里了,而不是 ptype_all。同样, ptype_base 中的协议也会在发送和接收的过程中被执行到。

总结:tcpdump 启动的时候内部逻辑其实很简单,就是在 ptype_all 中注册了一个虚拟协议而已。

四、总结

现在我们再回头看开篇提到的几个问题。

1. tcpdump是如何工作的

用户态 tcpdump 命令是通过 socket 系统调用,在内核源码中用到的 ptype_all 中挂载了函数钩子上去。无论是在网络包接收过程中,还是在发送过程中,都会在网络设备层遍历 ptype_all 中的协议,并执行其中的回调。tcpdump 命令就是基于这个底层原理来工作的。

2. netfilter 过滤的包 tcpdump是否可以抓的到

关于这个问题,得分接收和发送过程分别来看。在网络包接收的过程中,由于 tcpdump 近水楼台先得月,所以完全可以捕获到命中 netfilter 过滤规则的包。

77693a4c-239b-11ec-82a8-dac502259ad0.png

但是在发送的过程中,恰恰相反。网络包先经过协议层,这时候被 netfilter 过滤掉的话,底层工作的 tcpdump 还没等看见就啥也没了。

77da513c-239b-11ec-82a8-dac502259ad0.png

3. 让你自己写一个抓包程序的话该如何下手

如果你想自己写一段类似 tcpdump 的抓包程序的话,使用 packet socket 就可以了。我用 c 写了一段抓包,并且解析源 IP 和目的 IP 的简单 demo。

源码地址:https://github.com/yanfeizhang/coder-kung-fu/blob/main/tests/network/test04/main.c

编译一下,注意运行需要 root 权限。

# gcc -o main main.c# 。/main

运行结果预览如下。

78f00468-239b-11ec-82a8-dac502259ad0.png

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    4

    文章

    1436

    浏览量

    42481
  • 网络
    +关注

    关注

    14

    文章

    8129

    浏览量

    93054

原文标题:用户态 tcpdump 如何实现抓到内核网络包的?

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    求助,关于rt-smart用户线程实时性差的问题求解

    我在树莓派4B上使用v5.2.0 开启smart的rt-thread 并启用SMP多核(4核)内核时,在用户内核运行同样的代码测试: #include #include
    发表于 09-26 08:25

    TCPDump分析实战

    作为一名资深运维工程师,我在生产环境中遇到过各种奇葩的网络问题。今天分享10个真实案例,带你掌握TCPDump这把利器,让网络问题无处遁形!
    的头像 发表于 08-06 17:58 782次阅读

    详解Linux网络管理中的关键命令

    本文档概述了网络管理中的关键命令,如ifconfig配置网络接口,ip管理路由,ping测试连通性,以及nmap进行安全扫描。还介绍了nslookup和dig用于域名解析,tcpdump分析,以及arp操作和nmap的深入应
    的头像 发表于 07-04 11:37 617次阅读
    详解Linux网络管理中的关键命令

    单片机有没有串口包工具推荐的,纯小白,想像网络协议那样直接curl协议转化为代码

    能否做到像网络协议那样重放呢?刚刚涉及 esp32 单片机开发,不太懂这方面的知识点
    发表于 06-01 11:04

    TwinCAT3 EtherCAT | 技术集结

    在使用TwinCAT测试EtherCATEOE功能时,我们会发现正常是无法使用Wireshark去进行网络抓取EtherCAT报文的,今天这篇文章就带大家来上手EtherCAT
    的头像 发表于 05-15 18:04 5150次阅读
    TwinCAT3 EtherCAT<b class='flag-5'>抓</b><b class='flag-5'>包</b> | 技术集结

    关于晶振的三

    晶振的高阻在电路起什么作用,为什么有的晶振需要三脚有的不需要,晶振的三是靠什么去控制的?
    发表于 05-15 11:08

    Linux TCP内核的参数设置与调优

    把这个参数修改为2测试一下 接着我们来验证一下,我们去ssh一台不存在的主机,因为ssh也是基于tcp 通过wireshark可以看到,果然重传了2个数据 net.ipv4.tcp_wind
    的头像 发表于 01-22 10:36 1340次阅读
    Linux TCP<b class='flag-5'>内核</b>的参数设置与调优

    调试TCP协议连接的常用工具

    : 捕获实时数据 分析历史数据 过滤和搜索数据 统计和图形化展示数据信息 1.2 tcpdump
    的头像 发表于 01-22 09:59 5132次阅读

    AN4889-使用独立于内核的外设实现Peltier冷却金属板

    电子发烧友网站提供《AN4889-使用独立于内核的外设实现Peltier冷却金属板.pdf》资料免费下载
    发表于 01-21 14:35 0次下载
    AN4889-使用独立于<b class='flag-5'>内核</b>的外设<b class='flag-5'>实现</b>Peltier冷却金属板

    使用独立于内核的外设实现降压转换器设计和反馈控制器

    电子发烧友网站提供《使用独立于内核的外设实现降压转换器设计和反馈控制器.pdf》资料免费下载
    发表于 01-15 15:31 0次下载
    使用独立于<b class='flag-5'>内核</b>的外设<b class='flag-5'>实现</b>降压转换器设计和反馈控制器

    衍射级次偏振的研究

    分析提供了通用和方便的工具。为此,复杂的一维或二维周期结构可以使用界面和调制介质进行配置,这允许任何类型的光栅形貌进行自由的配置。在此用例中,详细讨论了衍射级次的偏振的研究。 任务说明 简要介绍
    发表于 01-11 08:55

    tcpdump使用示例

    tcpdump -i eth0 -nn -s0 -v port 80 -i :进行的接口,通常是以太网卡或无线适配器,
    的头像 发表于 01-06 09:33 1227次阅读

    Linux运维必备技能:手把手教你用tcpdump精准

    简介 网络数据截获分析工具。支持针对网络层、协议、主机、网络或端口的过滤。并提供and、or、not等逻辑语句帮助去除无用的信息。 tcpdump - dump traffic on a
    的头像 发表于 12-24 11:20 2159次阅读

    衍射级次偏振的研究

    分析提供了通用和方便的工具。为此,复杂的一维或二维周期结构可以使用界面和调制介质进行配置,这允许任何类型的光栅形貌进行自由的配置。在此用例中,详细讨论了衍射级次的偏振的研究。 任务说明 简要介绍
    发表于 12-18 13:45

    飞凌嵌入式ElfBoard ELF 1板卡-Linux内核移植之内核简介

    ,所以讲解均以4.1.15版本内核为例。(二)可以选择使用git克隆,也可以选择下载ZIP压缩。获取ELF 1开发板源码ELF 1的内核源码在资料当中的路径如下:ELF 1开发板资
    发表于 12-13 09:03