ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

内核子网与驱动:三层视角的抓包练习指南

内核子网与驱动:三层视角的抓包练习指南 最近调试一块嵌入式开发板现象很典型板子能 ping 通网关但业务流量一上来就开始丢包。当时我有三个怀疑对象——内核协议栈处理不过来、子网划分和路由配置有问题、网卡驱动收包效率太低。为了把这几个层面分开确认我做了一轮“内核子网和驱动——抓包练习”不是简单打开 Wireshark 看几眼而是分别从子网视角、内核视角、驱动视角去设计抓包实验用观测结果验证每一层的行为是否正常。这轮练习做下来收获比预期大很多有些丢包现象在应用层、链路层、驱动层看到的完全是不同的信号如果只停留在“会不会用 tcpdump”这个层面根本定位不了问题。这篇文章就把整套练习的思路、操作和踩坑整理出来适合想系统提升抓包能力、做嵌入式或网络开发、以及准备读内核源码的读者。1. 抓包练习的坐标系报文从驱动到内核再到应用的完整路径很多朋友把抓包理解成“装个 Wireshark 点开始”然后看到一堆包就能分析。真到了排查问题的时候这套思路很容易卡住——因为同一个报文在网络上、在驱动里、在内核协议栈里、在应用层代理里表现出来的“长相”完全不一样。我建议所有做抓包练习的人第一步先把报文流向的坐标系建立起来。1.1 一个 ping 包从网线到 ping 程序到底经历了什么先看一条最经典的数据路径网线上的电信号到达网卡 PHY 芯片经过 MAC 控制器解析出以太网帧通过 DMA 写进网卡驱动维护的环形缓冲区Ring Buffer硬件触发中断或者由 NAPI 机制轮询接收内核调用netif_receive_skb()把数据包送入协议栈。接下来依次经过链路层、IP 层、传输层最终把 ICMP 的数据部分放进 socket 接收队列ping 程序从这里读出来显示。这个流程看起来简单但它解释了很多奇怪现象。比如驱动层的环形缓冲区如果被占满新到的包会被直接丢弃而应用层 tcpdump 什么都看不到又比如网卡硬件默认会做 CRC 校验校验失败的帧根本不会进环形缓冲区驱动也不会报错表现就是“链路明明通了但跑满带宽时总有神秘丢包”。我习惯把这条路径用快递分拣来做类比网线上跑的是快递车DMA 相当于把整车的货卸到转运场环形缓冲区内核协议栈是分拣员根据收件地址IP 和端口把货分配到不同的格口socket 接收队列应用从格口取走包裹。抓包练习本质上就是在这些环节装摄像头问题是你得知道摄像头装在哪、能拍到什么、拍不到什么。1.2 常用抓包工具各自守在哪一层这一节是做练习前必须搞清楚的配置。我整理过一张表按“工具、观测位置、能看见什么、看不见什么”来分类工具/手段观测位置能看到的信息常见盲区tcpdump / Wireshark协议栈入口AF_PACKET / libpcap链路层及以上的完整报文驱动内部丢弃、网卡硬件过滤、DMA 前的错误帧Fiddler / Charles应用层 HTTP(S) 代理重组后的请求/响应、body、HeaderTCP 分片细节、非 HTTP 流量、其他进程的流量网卡计数ethtool -S网卡芯片/驱动收发包计数、FCS 错误、rx_missed报文内容本身内核 tracepointkfree_skb 等协议栈各层内核在哪个点丢弃报文被驱动丢弃的帧usbmonUSB 总线USB URB 请求/响应、方向、端点总线物理信号细节SocketCAN candumpCAN 驱动/协议层CAN 帧 ID、数据、错误帧总线物理层错误这张表花不了几分钟就能记住但价值极高。比如你用 tcpdump 抓不到某个包不代表网络上没有这个包有可能是网卡在 DMA 前就把它丢了或者驱动根本没有把混杂模式的包投递给协议栈。1.3 为什么“内核缓冲”和“驱动丢包”成了排查焦点热门搜索词里“内核缓冲”和“驱动安装”出现的频率很高说明有大量开发者在这两个词附近踩坑。我在实践中也确认过很多生产环境的丢包既不是网线问题也不是服务端响应慢而是环形缓冲、内核backlog队列或者 socket 接收缓冲满了。练习时可以这样设计一个实验在两台虚拟机上用 iperf3 打满网卡带宽同时用tcpdump -i eth0 -w /tmp/x.pcap抓包结束后对比三组数据——应用层实际吞吐、抓包文件的报文总量、网卡的rx_dropped计数。如果 tcpdump 抓到的包数和 iperf3 报告的速率对不上先不要怀疑 tcpdump 丢了包而是去查驱动和内核计数。这个实验能让你直观理解“抓包工具本身也会丢包”这件事同时把驱动、内核缓冲、应用层三个层面的概念第一次串起来。2. 子网视角的抓包练习地址规划错了路由表会替你说谎子网划分和抓包看起来是两件事但抓包是验证子网配置正确性的最好手段。很多类似“两台机器明明在同一个交换机下为什么 ping 不通”的问题根因都是掩码写错导致各自认为对方在别的网段。2.1 先掌握子网计算的实用技巧别依赖工具子网计算工具搜索词里也有“子网计算工具”和“网络地址划分子网计算”用起来确实快但你得自己心里有数否则抓包结果出来没法判断网络行为是否正常。我常用的快速算法很简单把子网掩码想成从高位向低位“借位”借一位可用地址数减半。举例192.168.10.0/26。/26说明最后一个字节有 6 位是主机位也就是 2 的 6 次方减 2共 62 个可用地址。网络地址怎么算256 - 192 64所以这个 /26 子网的块大小是 64网络地址是192.168.10.0广播地址是192.168.10.63可用范围是.1到.62。练习时我建议先手算三五个地址再用在线工具核对。手算过程中你会自然明白为什么广播地址是主机位全 1为什么网关一定要落在可用范围内。这些东西在做抓包实验时非常关键——你看到 ARP 广播的目的 MAC 是全 F就知道这是广播地址看到目标 IP 是子网广播地址就知道这是一次全网段探测。2.2 用 ARP 和 ICMP 抓包验证掩码是否一致我最喜欢的一个练习是准备两台同网段的机器故意把掩码配得不一致再抓包。具体步骤主机 A 配192.168.10.10/24网关192.168.10.1。主机 B 配192.168.10.20/25网关192.168.10.1。在 A 上tcpdump -i eth0 arp or icmp。A ping B观察抓包结果。你大概率会看到A 发出的 ARP 请求到达交换机但 B 不回应因为 B 自己算出 A 的地址不在“自己的”子网内于是走路由表试图通过网关通信而非直接在二层响应 ARP。最终现象是 A 一直发 ARP 重传ping 不通。这个实验的价值在于抓包能抓到“发送方已经尽力了”但看不到“接收方因为掩码分歧拒绝回应”的逻辑必须结合子网计算的知识才能解释现场。2.3 同网段、跨网段、广播报文的三张抓包图对比更完整的子网练习应该做三个场景对照同网段两台机器 ping你会看到 ARP 请求/响应然后才是 ICMP echo request/reply且源目 MAC 都是对方真实 MAC。跨网段 ping通过网关ARP 请求询问的是网关 MACICMP 包的目标 MAC 是网关 MAC源 IP 和目标 IP 始终不变但 MAC 一跳一跳地变。ping 子网广播地址如ping -b 192.168.10.63同一个广播域内所有主机会回 ICMP抓包台上会看到大量不同源 MAC 的 reply。这三个场景跑一遍你基本就能读懂以太网上任意一个报文的二层和三层的对应关系。做实验时建议用 Linux 的网络命名空间ip netns加 veth 对不用真交换机也能模拟还能随意改掩码和路由非常安全。命令参考如下ip netns add ns_a ip netns add ns_b ip link add veth_a type veth peer name veth_b ip link set veth_a netns ns_a ip link set veth_b netns ns_b ip netns exec ns_a ip addr add 192.168.10.10/24 dev veth_a ip netns exec ns_b ip addr add 192.168.10.20/25 dev veth_b ip netns exec ns_a tcpdump -i veth_a arp or icmp ip netns exec ns_a ping 192.168.10.20这个命令序列可以反复修改掩码和 IP快速复现各种子网配置错误比拔网线重插效率高得多。2.4 子网划分里最常见的三个“能通但很怪”的坑第一类是掩码写得太宽广播域变得很大ARP 广播几乎发到全网看起来能通但网络稍微大一点就处处是 ARP 风暴。抓包时你会在短时间内看到大量who has 192.168.x.x? Tell 192.168.y.y的请求。第二类是掩码写得太窄两台本该直连的机器被迫通过网关绕路通了但延迟高抓包会发现直连流量变成了两次 MAC 寻址。第三类是把网关配置在了可用地址范围之外抓包能看到本机发出的 ARP 请求根本没人回因为那个 IP 根本不属于该广播域。做子网练习的时候我强烈建议不要只看 pcap 文件里的会话列表而是逐包看 ARP 请求的目标 IP、ICMP 的源目 IP 和 MAC 对应关系。抓包练习的核心不是收集数据而是建立“看到一种包结构就能反推出网络配置”的能力。3. 内核视角的抓包练习协议栈丢包和缓冲溢出的真相应用层抓包抓不到的时候很多人就放弃了。实际上内核里有非常多的观测点能告诉你报文去哪了、被谁丢了。这一层的练习是把注意力从“报文内容”转向“报文生命周期”。3.1 内核在哪几个点“看见”报文的netfilter 钩子与协议栈路径Linux 内核的网络路径上有五个经典的 netfilter 钩子点PREROUTING、LOCAL_IN、FORWARD、LOCAL_OUT、POSTROUTING。报文从驱动进来先过PREROUTING目标是本机的进LOCAL_IN需要转发的进FORWARD本机发出的包在路由后先过LOCAL_OUT、再过POSTROUTING。练习时可以用iptables在某个钩子点打日志或直接抓 TRACE比如iptables -t raw -A PREROUTING -p tcp --dport 8080 -j TRACE iptables -t mangle -A POSTROUTING -p icmp -j LOG --log-prefix ICMP_OUT: 然后用dmesg看内核打出的日志或者在/proc/net/netfilter/nf_log里调整日志级别。这样你能区分一个包到底是进本机了、转发了、还是被防火墙规则丢弃了。为什么这个练习重要因为 tcpdump 挂在协议栈的底层它只负责“这个包被内核收到了没有”不负责“这个包最后去哪了”。一个被 FORWARD 链拦截的包tcpdump 能抓到它进来但看不到它被丢弃的过程只有配合 netfilter 日志或 conntrack 记录才能确认规则动作。3.2 内核缓冲区不足造成的丢包该看哪几个计数搜索词里“内核缓冲”这个词很显眼。很多人一看到“缓冲”就想到去调参数但我的建议是先分清是哪个缓冲在丢包。最典型的三个位置网卡驱动 RX Ring Buffer可以用ethtool -g eth0看当前大小和最大值用ethtool -S eth0看rx_missed、rx_dropped、rx_errors。如果rx_missed在持续增长多半是网卡 FIFO 或环形缓冲不够优先调大rx-ring。内核协议栈的backlog队列用sysctl net.core.netdev_max_backlog查看这个值决定 CPU 处理软中断时有多少包能在队列里排队。过小会触发dropwatch里看到的netif_receive_skb丢弃。Socket 接收缓冲区sysctl net.core.rmem_max、net.ipv4.tcp_rmem。缓冲区满时抓包能看到一种非常典型的信号——TCP 的接收窗口逐渐变成 0抓包面板上出现大量TCP Window Full和Zero Window说明应用消费太慢不是网络丢包。做练习时我建议打开两个终端一个跑watch -n 1 cat /proc/net/softnet_stat一个跑 iperf3 打流。softnet_stat里的第二列和第三列对应 backlog 丢弃和超时丢弃看到这两个数字不断上涨就能定位到内核协议栈入口已经在丢包进而针对性调netdev_max_backlog。3.3 用内核 tracepoint 和源码精确定位丢包点抓包抓不到、计数又不够细的时候就要上内核观测工具了。我最常用的丢包定位手段是 tracepoint尤其是skb:kfree_skb这个事件。它记录了内核释放一个 socket buffer 的位置配合trace-cmd可以还原每次丢包的调用栈。trace-cmd record -e skb:kfree_skb -e net:netif_receive_skb # 复现问题后 CtrlC然后 trace-cmd report报告里能看到location字段它告诉你是在哪个函数里把包丢掉的。拿到函数名之后再去读对应的内核源码效率比全文搜索高得多。搜索词里“嵌入式内核源码”“linux内核源码”被频繁搜索但很多人打开源码就迷路。我的经验是永远先用 tracepoint 或 ftrace 拿到具体的函数和调用栈再打开源码精读关键路径否则随便翻net/ipv4目录下的几十个文件很容易淹没。3.4 用 Python 做批量分析时pyshark 的兼容性坑做抓包练习到一定量手动筛选 pcap 不够用很多人会想起用 Python。但“python2.7pyshark无法抓包问题”这类搜索词说明这条路并不是一帆风顺。pyshark 本身是 tshark 的 Python 封装它自身不解析报文而是调 tshark 的 JSON 输出。一旦你的 pyshark 版本、tshark 版本和 Python 版本三者不匹配最常见的现象就是接口都能加载但capture.sniff()没有任何输出或者直接抛异常。我的建议很直接放弃 python2直接用 python3 最新版 pyshark。读取 pcap 文件的方式比实时抓包更稳定特别适合练习import pyshark caps pyshark.FileCapture(test.pcap, display_filtertcp.port8080) for pkt in caps: print(pkt.ip.src, pkt.ip.dst, pkt.tcp.flags)注意display_filter用的是 Wireshark 显示过滤语法而不是 BPF 语法写错不会报错但会返回空列表。我踩过好几次把tcp.port写成tcp.dstport的坑结果排查了半天以为网络没包其实是过滤表达式不匹配。4. 驱动视角的抓包练习网卡收包路径上的观测点驱动这层是很多抓包练习的盲区因为大多数资料默认“抓不到包就是网络不通”。实际上驱动和网卡内部的工作情况需要另一套观测方法。我把这一层的练习叫做“驱动收包观测游戏”目的是理解网卡硬件、DMA、环形缓冲和 NAPI 之间的关系。4.1 驱动收包的完整流程和关键参数一个现代网卡从物理信号到内核的路径可以简化成五步PHY 接收模拟信号并转成数字帧MAC 解析帧头和帧尾做 CRC 校验DMA 把帧写到驱动分配的 Ring Buffer网卡触发中断或者直接进入 NAPI 轮询模式poll回调里调用napi_gro_receive()把帧交给协议栈。对应的命令行体检项目有三项ethtool -g eth0 # 查看 RX/TX ring buffer 大小 ethtool -S eth0 | head -50 # 查看网卡计数重点看 rx_missed / rx_dropped cat /proc/interrupts # 查看中断分布确认是否集中在某个 CPU如果/proc/interrupts显示 RX 中断全部在 CPU0且ethtool -S计数正常但业务有延迟可以考虑确认网卡是否支持 RSS 多队列并开启。驱动层面的“抓包练习”很多时候是在修计数和中断这些问题而不是去改协议栈。4.2 驱动层抓包和 tcpdump“看不到包”的边界很多人问驱动如果收包了tcpdump 应该一定能抓到吧答案是否定的。至少有三种情况是 tcpdump 无能为力的网卡硬件在 DMA 前就按 MAC 地址过滤了报文。除非打开混杂模式否则目的 MAC 不是本机的帧直接被硬件丢弃。网卡开启了 flow control对端收到 Pause 帧后不再发送tcpdump 只能看到链路空闲。驱动把某些帧标记为 error 并直接释放比如 CRC 错误、过长帧、VLAN 错配。这些帧根本没进 AF_PACKET 套接字。练习时可以尝试用ethtool -K eth0 rx-checksumming off关闭网卡校验和计算或者用ethtool -K eth0 gro on/off切换 GRO 收包合并再用 tcpdump 观察报文数量和 TCP 序列号的变化。你会发现 GRO 打开时抓到的包数量会明显少于实际网卡收的帧数因为多个小包被合并成一个 skb 提交给协议栈。这不是丢包是报文合并理解这一点对分析“抓包流量小于实际带宽”特别关键。4.3 给驱动加“观察点”从网卡计数到内核模块打点如果你正好在搞某个网卡驱动可以在驱动代码的poll或收包中断函数里加计数器或者用#define DEBUG打开驱动的调试打印。但日常练习不建议直接改生产驱动更安全的方式是使用内核的rx_handler机制或者ifb虚拟设备来观察流量。ifb配合 tc 的mirred动作可以把入口流量镜像到一个虚拟设备上然后对这个虚拟设备抓包modprobe ifb ip link set ifb0 up tc qdisc add dev eth0 handle ffff: ingress tc filter add dev eth0 parent ffff: u32 match u32 0 0 action mirred egress redirect dev ifb0 tcpdump -i ifb0 -n这种方式不修改驱动源码却是理解“驱动收包入口到底能看到什么帧”的好练习——因为被镜像到 ifb0 的帧已经经过了网卡驱动处理但又还没经过完整的协议栈分析你抓到的是更接近“原始收包结果”的数据。4.4 USB、CAN、串口调试器这些“非网卡”设备的抓包思路驱动抓包练习不止网卡还有 USB 和 CAN 这类常见总线。搜索词里 “usb抓包”、“can抓包数据分析”、“cp2102驱动”、“jlink驱动安装” 高频出现说明很多人在调试 USB 转串口或者调试器时也需要抓包思路。USB 层最方便的手段是 usbmon。先加载模块再抓modprobe usbmon cat /sys/kernel/debug/usb/usbmon/0u这样能把 USB 总线上传输的 URB 请求打印出来能看到控制传输、批量传输、中断传输各自的端点号和长度。如果设备插上后系统根本没识别先别急着抓包先看dmesg | tail确认驱动是否枚举成功。CP2102、J-Link、ST-Link 这类设备能不能工作很多时候卡在 USB 描述符解析和驱动绑定上usbmon 抓到的 URB 能帮你判断是主机侧枚举失败还是设备侧无响应。CAN 总线使用 SocketCAN发数据和抓数据都可以用candumpip link set can0 type can bitrate 500000 ip link set can0 up candump can0 -L # -L 表示同时打印本地发送的帧抓到的标准帧包含 CAN ID、DLC、8 字节数据以及时间戳。分析时可以把输出重定向到文件再用脚本按 ID 统计频率。CAN 的报文量远小于以太网但每条数据的含义完全依赖 DBC 协议定义所以“抓包数据分析”的重点是把十六进制字节按报文矩阵解析成物理量而不是单纯数包。5. 抓包练习的常见坑从抓包失败到过滤表达式最后这部分是所有练习的“回收站”把我在实际练习和工作中最常遇到的问题集中列出来。大部分搜索词里的问题翻来覆去其实就是这几个原因。5.1 App 抓包失败和 HTTPS 解密问题“app抓包失败”、“fiddler抓包详细教程”、“charles抓包”这几个搜索词的背后九成是 HTTPS 证书问题。Android 7 以上默认不再信任用户安装的 CA 证书所以 Fiddler 或 Charles 生成的证书装到手机里也没用抓到的全是 TLS 加密数据。处理思路有三个方向把抓包代理的 CA 证书安装到系统证书目录需要 root 或者使用带系统证书的模拟器镜像。如果 App 做了证书固定SSL Pinning需要配合正向代理或者 hook 方式绕过但这已经属于高级调试手段只建议在调试自己拥有权限的应用时使用。最省事的是优先抓明文协议DNS、HTTP 内网服务、MQTT over TCP、WebSocket 等练通了再处理 TLS。无论哪种方向都要强调一个原则抓包调试只针对自己拥有或获得授权的系统。用抓包做任何未经授权的数据获取行为都涉及合规问题这条红线不能踩。5.2 Wireshark 显示过滤和抓包过滤千万不能混用很多人把捕获过滤和显示过滤当成一回事结果抓了一堆巨型文件再慢慢筛。两者的区别是捕获过滤用 BPF 语法在抓包那一刻就决定哪些包进入 pcap显示过滤是 Wireshark 的展示语法只影响显示不影响采集。我常用的几组如下需求显示过滤表达式捕获过滤 BPF只看某个 IP 的流量ip.addr 192.168.1.1host 192.168.1.1只看 HTTP 明文请求http.request or http.responsetcp port 80只看 TCP 重传tcp.analysis.retransmission无对应捕获语法只看 ARParparp只跟某条 TCP 流tcp.stream eq 0无对应捕获语法实践中我发现一个规律定位丢包、重传、窗口问题时优先用显示过滤因为tcp.analysis.*这类分析字段是 Wireshark 计算出来的捕获过滤做不到想控制 pcap 文件大小时才用捕获过滤。5.3 抓包点位不同同一报文的“长相”也不同有一个经典疑问同一个 HTTP 上传请求为什么在 tcpdump 里看到的是十几个 TCP 分段在 Fiddler 里看到的是一个完整 body这不是抓包抓错了而是点位不同。tcpdump 在协议栈底层看到的是正在传输的 TCP 段应用层代理在 socket 之上看到的是数据流重组后的 HTTP 消息。Wireshark 里如果看到一个 TCP 段标注着TCP segment of a reassembled PDU就说明应用层数据被分成多段传输。如果是大文件上传抓包台还会看到很多PSH, ACK标志位交替出现的报文。搞明白这个区别你就不会再因为“同一个接口的回包长度对不上”而怀疑工具了。5.4 把抓包练习变成日常技能的路线建议我给想认真练抓包的朋友一个可复制的路线按这个顺序做下来基本能覆盖“内核子网和驱动”三个层面先在 Linux 网络命名空间里练 ARP 和 ICMP验证子网划分和路由行为。用 veth 或者虚拟机练 tcpdump 抓包区分同网段/跨网段/广播报文。用 iperf3 打流同时观察softnet_stat、ethtool -S、tcpdump三种数据理解缓冲和丢包。用 tracepoint 定位一次真实的丢包点读对应内核源码完成一次“抓包-定位-源码闭环”。找一块带多队列的网卡配合 NAPI 和中断计数观察驱动收包行为。最后用 Wireshark 做一次完整 HTTP 请求分析把 TLS 握手、TCP 三次握手、HTTP 请求响应三段流程全部理清。我个人在实际操作中的体会是抓包练习的价值不在于能看懂多少个协议而在于遇到诡异现象时能立刻判断“该在哪一层去找证据”。驱动、子网、内核这三个层面各有各的“语言”驱动层看计数和中断子网层看 ARP 和路由内核层看缓冲和 tracepoint。把这三套语言都练熟了再回去读内核源码或者调驱动基本不会迷路。
返回列表