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

    文章

    1479

    浏览量

    43129
  • 网络
    +关注

    关注

    14

    文章

    8372

    浏览量

    95683

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

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

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    基于MoonBit的高效符号计算内核Symbit实现方案

    本文介绍一个以 MoonBit 实现的符号计算内核 Symbit,目标是通过AI辅助,在保留 sympy 风格符号表达与精确计算能力的同时,将大部分算法移植到 MoonBit,利用 native 与 WebAssembly 后端提升执行效率并降低用户访问门槛,理想情况下用
    的头像 发表于 04-24 09:30 403次阅读
    基于MoonBit的高效符号计算<b class='flag-5'>内核</b>Symbit<b class='flag-5'>实现</b>方案

    睿擎派 3562 整固件制作指南

    在嵌入式开发中,经常遇到需要定制固件的场景:更新RT-Thread应用程序、替换Linux内核或设备树、修改根文件系统配置等。这些需求都离不开对Rockchipupdate.img整固件的解包
    的头像 发表于 04-01 18:08 1196次阅读
    睿擎派 3562 整<b class='flag-5'>包</b>固件制作指南

    Linux内核驱动开发的技术核心精要

    开发中必须理解的技术要点,供从业者参考。 一、并发与同步:多核系统的根基 现代内核默认支持SMP(对称多处理),驱动代码可能同时运行在多个CPU核、中断、软中断、抢占路径上,竞风险无处不在。内核提供
    发表于 03-10 13:56

    内核配置项引发网络性能下降的深度剖析

    在嵌入式系统开发中,内核配置对系统性能起着关键作用。近期在对基于 Rockchip 平台的 Linux 内核配置调试时,发现三个内核跟踪器配置项(CONFIG_IRQSOFF_TRACER
    的头像 发表于 02-01 16:48 1874次阅读
    <b class='flag-5'>内核</b>配置项引发网络性能下降的深度剖析

    如何使用wireshark进行远程

    一、概述 通常我们使用wireshark进行分析的时候,在远端设备抓取一部分数据后,再回传到本地,然后使用wireshark进行分析。这种操作主要是抓取的数据不是实时的,不能做
    的头像 发表于 01-16 16:05 1230次阅读
    如何使用wireshark进行远程<b class='flag-5'>抓</b><b class='flag-5'>包</b>

    深入Linux内核:进程调度的核心逻辑与实现细节

    ,背后都离不开内核调度算法的精准操控。今天,我们就从优先级、调度算法、时间片分配到底层实现,全方位拆解Linux内核进程调度的核心逻辑。 一、进程调度的“身份标识”:优先级与分类 要理解调度逻辑,首先得搞懂:进程凭什么“插队”?
    的头像 发表于 12-24 07:05 4700次阅读
    深入Linux<b class='flag-5'>内核</b>:进程调度的核心逻辑与<b class='flag-5'>实现</b>细节

    Linux内核日志玩明白了吗?printk调试神器全解析

    的日志等级机制,从参数配置到实战用法一次讲透~一、printk与printf的差异用户的printf大家都熟,直接打印内容,简单粗暴。但内核场景更复杂,系统崩溃或是
    的头像 发表于 12-19 08:32 1159次阅读
    Linux<b class='flag-5'>内核</b>日志玩明白了吗?printk调试神器全解析

    fpga嵌入e203内核搭建soc如何实现通信功能?

    在fpga嵌入e203内核实现以太网,开发板有PHY芯片LAN8720A,怎么搭建soc,如何使用总线,实现通信功能?
    发表于 11-10 06:54

    探索操作系统底层的关键接口

      在linux中,将程序的运行空间分为内核空间与用户空间(内核和用户),在逻辑上它们之间是相互隔离的,因此用户程序不能访问内核数据,也
    的头像 发表于 11-08 12:42 924次阅读

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

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

    TCPDump分析实战

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

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

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

    强实时运动控制内核MotionRT750(一):驱动安装、内核配置与使用

    强实时运动控制内核MotionRT750的驱动安装与内核配置
    的头像 发表于 07-03 15:48 3962次阅读
    强实时运动控制<b class='flag-5'>内核</b>MotionRT750(一):驱动安装、<b class='flag-5'>内核</b>配置与使用

    鸿蒙5开发宝藏案例分享---折叠屏悬停开发实践

    ?【鸿蒙折叠屏开发宝藏指南】原来官方藏了这么多好东西!手把手教你玩转悬停开发**?** Hey小伙伴们!我是你们的老朋友XX,最近在肝鸿蒙折叠屏项目时,意外挖到了官方文档里的隐藏宝藏!原来
    发表于 06-03 12:04

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

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