用户态 tcpdump 如何实现抓到内核网络包的?

Linux阅码场 2021-09-20 11:50

大家好,我是飞哥!

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

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

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

  • tcpdump 是如何工作的?
  • netfilter 过滤的包 tcpdump 是否可以抓的到?
  • 让你自己写一个抓包程序的话该如何下手?

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

一、网络包接收过程

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

找到 tcpdump 抓包点

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

//file: net/core/dev.c
static 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.c
static 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.c
int ip_rcv(...)
{
 ......
 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
         ip_rcv_finish);
}

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

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

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

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

二、网络包发送过程

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

找到 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.c
int 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 挂在上面的虚拟协议。

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

如果 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 指向该协议族的对应创建函数。

根据上图,我们看到对于 packet 类型的 socket,pf->create 实际调用到的是 packet_create 函数。我们进入到这个函数中来一探究竟,这是理解 tcpdump 工作原理的关键!
//file: packet/af_packet.c
static 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 链表中添加了一个虚拟的协议进来。

我们再来看下 dev_add_pack 是如何注册协议到 ptype_all 中的。回顾我们开头看到的 socket 函数调用,第三个参数 proto 传入的是 ETH_P_ALL。那 dev_add_pack 其实最后是把 hook 函数添加到了 ptype_all 里了,代码如下。
//file: net/core/dev.c
void 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 过滤规则的包。

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

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 

运行结果预览如下。


最后,还是求再看,求转发!


Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论 (0)
  • 文/Leon编辑/cc孙聪颖‍蛇年春晚最有意思的节目,一定非机器人跳舞莫属。就算是遥控或预编程,机器人能够做出如此复杂的动作,在五年前都是不敢想象的事情,其制造商宇树科技也因此火爆全网。就在春节过后不到一个月,会骑自行车的人形机器人诞生了。这背后,是近年来“具身智能”概念的迅猛发展。“我们造了一个跟人一样灵动的机器人!”3月11日,智元机器人联合创始人兼首席技术官彭志辉在微博上说道。在视频中,灵犀X2会骑自行车、能跳《科目三》,还可以与人促膝长谈,甚至拿起葡萄“穿针引线”。在全球人形机器人领域,
    华尔街科技眼 2025-03-17 12:38 112浏览
  • 在招聘合适的人才时,清晰度至关重要。想要找到合适的人选,并确保他们在岗位上取得成功,第一步就是明确职位的关键绩效指标(KPI)和预期成果。但光有这些还不够,如何判断候选人是否具备必要的特质?这时,KSA模型就派上用场了。它是一个简单但强大的方法,能帮助你聚焦于真正影响岗位表现的关键要素。今天,我想和你聊聊这个模型,帮你更轻松地为合适的候选人设定合适的KPI。了解KSA模型KSA代表知识(Knowledge)、技能(Skills)和态度(Attitude),是评估候选人是否适合岗位的三个关键维度。
    优思学院 2025-03-18 15:03 62浏览
  • 在工业4.0与智能制造深度融合的今天,设备实时性、稳定性和成本效益成为企业核心竞争力的关键。触觉智能将基于RK3506平台,分享工业应用方案,本期为大家带来DSMC串行接口在数控行业的应用。DSMC技术解析底层架构突破双倍数据速率:通过上升沿与下降沿双重触发机制,实现单周期内2倍数据吞吐量,较传统SPI接口效率提升300%。多通道并行:支持8线/16线位宽可配置模式,满足多轴协同场景下的同步通信需求。性能实测标杆超低延迟:FPGA互联场景下,写延时小于75ns,读延时小于260ns,相比PCIe
    Industio_触觉智能 2025-03-18 11:46 54浏览
  • 随着汽车行业逐步迈向电气化和电池动力,位置传感器以及其他长期在车辆中被忽视但却至关重要的小型元器件正逐渐成为关注的焦点。某些电子元器件常常吸引大量关注,例如如今用于训练AI模型的强大GPU几乎每天都出现在新闻中。而其他元器件则默默地执行着重要但鲜为人知的功能。艾迈斯欧司朗一些历史悠久的产品线便隶属于后者,其中包括磁性和电感式位置传感器、电容式传感器和电池监控芯片。工业泵和风扇等产品的制造商利用位置传感器实现电动机高效平稳运行。在车辆的方向盘中安装电容传感器可以保障安全,它可以用于在辅助驾驶模式下
    艾迈斯欧司朗 2025-03-17 22:22 73浏览
  • 失效模式与影响分析(FMEA)失效模式与影响分析(FMEA)是一种系统方法,用于识别和分析系统或过程中的潜在失效,广泛应用于工程和制造领域,以提高产品可靠性和安全性。最新标准由 2019 年发布的 AIAG-VDA FMEA 手册(第一版) 定义,该手册结合了美国和欧洲汽车行业的最佳实践,并引入了 七步法,确保分析全面且结构化。图:优思学院六西格玛新版 FMEA 失效分析的七个步骤1. 规划与准备确定 FMEA 研究的 范围、边界和目标。组建跨职能团队(设
    优思学院 2025-03-17 14:43 115浏览
  •        在工业视觉检测线上,一台搭载传统图像传感器的机器人因高温导致图像噪点激增,误将合格零件判定为瑕疵品,每小时损失超10万元;在深夜的安防监控画面中,模糊的噪点让犯罪分子身影难以识别,导致案件侦破延迟—— 噪声,已成为图像传感器行业的“无声杀手”。据Yole统计,全球约35%的工业检测误差源于传感器噪声干扰,而安防场景下60%的有效信息因低照度噪点丢失。传统方案试图通过单一优化像素或电路来降噪,却陷入“按下葫芦浮起瓢”的困境。  &nb
    中科领创 2025-03-18 10:24 51浏览
  • 新兴的个人健康监测技术为何在医疗场景和日常生活中越来越受到青睐?为了准确回答这个关键问题,我们首先需要理解三个全球性趋势:如今,几乎人手一部智能手机,这等于随身携带了一台高性能计算机、一个全天候运行的智能医疗传感器中心,还有一块显示屏。发达工业国家的人口正在迅速老龄化,而老年群体的疾病发病率较高。与此同时,年轻人也比过去更加关注如何延长健康寿命。这些人群以及服务他们的医务人员可以利用新技术来优化生活方式,合理调控运动、饮食、睡眠和压力等关键因素,帮助他们作出更健康的生活选择。如摩尔定律所预言,半
    艾迈斯欧司朗 2025-03-17 21:50 75浏览
  • 在制药行业中,生产工艺的精准控制与产品质量安全密切相关。随着制药工业4.0的发展,传感器作为生产流程的"感知器官",在确保合规性、提升效率、降低风险方面发挥着不可替代的作用。本文将以晨穹电子科技(以下简称"晨穹")的压力、温度、流量及液位传感器为例,解析制药厂关键工艺流程中的传感器应用场景及技术要求。一、制药核心工艺流程中的传感器需求1. 原料处理与配液系统液位监测:储罐内原料液位实时监控需使用卫生型液位计。晨穹磁翻板液位计采用316L不锈钢材质,具备CIP/SIP(在线清洗/灭菌)耐受性,符合
    传感器晨穹 2025-03-18 15:51 63浏览
  • 近期,据全球物联网市场调研机构IoT Analytics公布数据显示,2025年全球物联网设备连接数预计将突破200亿,同比增长约14%,物联网技术正以稳定上升态势向工业自动化、智慧城市、智慧农业与智慧家居等领域纵深推进。在多样化的应用场景和复杂环境需求的驱动下,物联网无线通信技术的运行功耗、传输距离和频段兼容性正受到前所未有的关注。为增加物联网通信模块的配置灵活度,消除物联网设备的“连接焦虑”,华普微重磅推出了一款自主研发的超低功耗、可兼容Sub-GHz与2.4GHz 双频段的高性能LoRa
    华普微HOPERF 2025-03-18 15:43 56浏览
  • 晨穹电子一家专业从事研发、生产、销售各类传感器为一体的高新科技企业。1 人赞同了该文章在工业4.0、智能家居、新能源汽车等场景中,传感器作为数据采集的核心器件,其抗电磁干扰(EMC)能力直接影响系统可靠性。尤其在5G通信、高功率电机、无线充电等复杂电磁环境下,传感器的信号失真问题愈发突出。本文结合MEMS传感器、物联网(IoT)设备、边缘计算等热度技术,解析提升传感器抗干扰能力的6大策略。 一、电磁干扰对传感器的威胁; 1、电磁干扰(EMI)会导致传感器出现 。2、信号跳变(
    传感器晨穹 2025-03-18 09:28 83浏览
我要评论
0
18
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦