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

561a67429e8a72751fd25e1e254ae79a.gif

作者 | 张彦飞allen

来源 | 开发内功修炼

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

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

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

  • tcpdump 是如何工作的?

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

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

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

一、网络包接收过程

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

90fe5ab60c698ac0633064e3c83f31f7.png

找到 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 协议层的。

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

6c81b9233c0683bb86665779bfbdcf9e.png

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

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

二、网络包发送过程

我们接着再来看网络包发送过程。

e9320e7771c035694b535a627b02b695.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.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 挂在上面的虚拟协议。

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

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

657b4ea1b5dc27e3d4a7e9c2ef0fc162.png

根据上图,我们看到对于 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 链表中添加了一个虚拟的协议进来。

8f6a232fc5f1c9b3ac84339996459adb.png

我们再来看下 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;elsereturn &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 过滤规则的包。

3638a1db649a81e54dc15d7d75f0cdd7.png

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

59ed9a4b0825b186242e0d8ad8095afc.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

运行结果预览如下:

6831ffaea8899a35346c01c2e2232b3c.png

9529b2b2ab6ef52f8bb5b9ad8983f6d7.gif

e85556f857885599d44ddd464a9e7493.png

往期推荐

协程到底有什么用?6种I/O模式告诉你!

在 Kubernetes 上部署 Secret 加密系统 Vault

Redis 内存满了怎么办?这样置才正确!

超值!Docker 常用命令汇总

82a7e0891a22e7af81ed03524726cc33.gif

点分享

8a7c91f63a953dc27a9c2759932d94a8.gif

点收藏

adbd6bb0048c48a80096bfccc44b6311.gif

点点赞

8a434bfe8859951f2e6b75a1f32c284e.gif

点在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/511713.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

技术实践第三期|HashTag在Redis集群环境下的使用

简介:欢迎了解友盟技术干货第三期内容:Redis集群环境如何按照前缀批量删除缓存。希望能对开发者们在实际应用中有所帮助。 一、背景 数据源列表添加缓存支持,types字段可传多值,如app, mini, web等,会构建如下缓存ke…

android java加密_Android Java字符串加密

Java字符串加密学习笔记Java字节码字节码生成Java2bitcode由Java源代码生成字节码的过程如下图(图片来源于网络)JVM执行不是热代码直接走字节码解释器热代码:多次调用,多次执行的循环体,会被JIT优化成机器码字节码执行方法调用在JVM中即是字节…

数据质量漫谈

简介: 数据质量问题无处不在,本文尝试找到一种方法,能够尽可能的发现数据质量问题并解决之。 作者 | 茂才 来源 | 阿里技术公众号 一 概述 1 数据质量问题无处不在 基本上每个用数据的同学,都遇到过以下类似的问题。 表没有按…

7招,实现安全高效的流水线管理

简介:云效团队多年来为阿里巴巴内部(Aone)和云上企业用户(云效)分别提供研发运维工具,并致力于打造企业级一站式的 DevOps 平台,更多关注不同类型的企业用户在使用过程中的管理与协作场景&#…

字节跳动最新音乐检索系统ByteCover2,检索速度提高八倍

翻唱识别(CSI)是音乐信息检索(MIR)领域的一项重要任务,在歌曲搜索,音乐分发,曲库整理,智能推荐等场景下有着重要作用,被誉为下一代音乐识别技术。 近期,字节…

Serverless 场景排查问题利器 : 函数实例命令行操作

简介:实例命令行功能的推出希望能消除用户使用 Serverless 的“最后一公里”,直接将真实的函数运行环境展现给用户。 背景介绍 全托管的 Serverless 计算平台能给用户带来更少的运维代价、更强的稳定性和更快的弹性能力,在 Serverless 落地…

从运维域看 Serverless 真的就是万能银弹吗?

简介:极客时间《Serverless 入门课》作者秦粤最新文章: 再次讨论正当时的 Serverless。文章分为三个部分,分别是 复杂化for 云开发商; 简化 for 开发者,以及团队使用 Serverless 的最佳场景。 作者说 在开始本篇内容前我想与各位开发者达成几…

多任务学习模型之ESMM介绍与实现

简介:本文介绍的是阿里巴巴团队发表在 SIGIR’2018 的论文《Entire Space Multi-Task Model: An Effective Approach for Estimating Post-Click Conversion Rate》。文章基于 Multi-Task Learning (MTL) 的思路,提出一种名为ESMM的CVR预估模型&#xff…

java pinyin4j 首字母_通讯录之按汉字首字母排序 --java--pinyin4J

最近开发手机端OA系统通讯录时遇到了用汉字首字母排序的问题,各种谷歌后发现了一个轮子pinyin4J,这个轮子是可以将汉字转换成字母拼音,个人觉得很好用,完美的解决了排序的问题,分享一下。一.工具介绍pinyin4j是一个支持…

助力开源生态繁荣,统信软件建设中国桌面操作系统根社区

继 React、SUSE、RedHat 宣布对俄罗斯停服后,近日 Ubuntu 开发商 Canonical 在俄乌冲突下也宣布对俄罗斯企业停止支持和专业服务。 这给我们敲醒了警钟:因为Ubuntu 事件瞄准桌面操作系统,桌面操作系统用户庞大,其安全性属于系统级…

一文详解 | 开放搜索兼容Elasticsearch做召回引擎

简介:开放搜索发布开源兼容版,支持阿里云Elasticsearch做搜索召回引擎,本文详细介绍阿里云ES用户如何通过接入开放搜索兼容版丰富行业分词库,提升查询语义理解能力,无需开发、算法投入,即可获得淘系同款搜索…

人人都是 Serverless 架构师 | 现代化 Web 应用开发实战

简介:本篇实战将介绍如何以超低成本构建动态的 Web 站点,并且实现灵活扩展,限流等效果,最后再跟大家聊一聊“现代应用”的相关概念。 相信很多同学都有过想要拥有自己的 Web 站点的想法,但是如果想要搭建动态的站点&a…

Gartner:如何在中国成功应用多云模式

作者 | Gartner研究总监 杜勇 供稿 | Gartner 当前,中国政府鼓励行业企业通过云计算技术来实施数字化转型,从而加速经济增长。许多企业机构已部署了私有云和单一供应商混合云,以实现这一目标。为了满足全球业务和本地业务需要分别部署在不同的…

java socket 线程池_程序员:java使用线程池和TCP实现简单多轮聊天系统

最近在做物联网项目,需要使用TCP和传感器进行双向交互,通过这种渠道,找到了下面的代码,写成博客主要也是为了记录一下,以后用到随时可以看。代码实现服务端package com.tcp;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.…

阿里云李飞飞:中国数据库的时与势

简介:数据库、操作系统和中间件并列为三大基础软件,无论是在银行存取款,还是进行健康码查询,我们的日常应用和企业业务背后都离不开数据库。可以说,没有数据库,就难以构建数字化底座。过去的40多年&#xf…

阿里巴巴超大规模 Kubernetes 基础设施运维体系介绍

简介:ASI 作为阿里集团、阿里云基础设施底座,为越来越多的云产品提供更多专业服务,托管底层 K8s 集群,屏蔽复杂的 K8s 门槛、透明几乎所有的基础设施复杂度,并用专业的产品技术能力兜底稳定性,让云产品只需…

数据库资深“学霸”再启程,专访数据库初创公司矩阵起源全球 CTO 田丰博士

师出名门,工业界履历从大厂首席工程师到创业公司 CTO,并能一直从事底层系统的核心研发工作,可能是很多优秀技术人向往的光鲜履历。不过抛弃大厂的光鲜稳定工作和成功的创业项目,再次加入初创公司,则需要比常人更大的魄…

Spring官方RSocket Broker 0.3.0发布: 快速构建你的RSocket架构

简介:Spring官方的RSocket Broker其实开发已经非常久了,我以为会伴随着Spring Cloud 2021.0发布的,但是没有发生。不过Spring RSocket Broker还是发布了最新的0.3版本,虽然还是预览版,但目前已经可用,考虑官…

Redis 6 中的多线程是如何实现的!?

作者 | 张彦飞allen来源 | 开发内功修炼Redis 是一个高性能服务端的典范。它通过多路复用 epoll 来管理海量的用户连接,只使用一个线程来通过事件循环来处理所有用户请求,就可以达到每秒数万 QPS 的处理能力。下图是单线程版本 Redis 工作的核心原理图单…

如何构建流量无损的在线应用架构 | 专题开篇

简介:本篇是整个《如何构建流量无损的在线应用架构》系列的第一篇,这一系列共三篇,旨在使用最为朴素的语言将影响在线应用流量稳定性的技术问题做一个归类,这些问题的解决方案有的只是一些代码层面的细节,有的需要工具…