mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
5540 字
15 分钟
以太网与交换:数据包离开网卡
2022-02-15

你在终端敲下 ping 192.168.1.1,几毫秒后收到回复。看起来简单,但这个 ICMP 包从内核协议栈到达网卡、变成电信号跑上网线、穿过交换机到达目标——中间的每一步都值得追问:IP 包怎么被塞进以太网帧?MAC 地址是怎么填上去的?网卡怎么把内存里的数据变成物理信号?交换机怎么知道该往哪个端口转发?

本章聚焦数据链路层,追踪一个帧从内核到交换机的完整旅程。理解了这一层,你就掌握了局域网通信的底层逻辑。

一、以太网帧:数据包的”信封”#

网络层交付下来的是一个 IP 包,但 IP 包本身没法直接在物理介质上传输——它需要一个”信封”来承载,这个信封就是以太网帧。IEEE 802.3 标准定义了以太网帧的精确格式,每个字段都有固定的大小和作用。

1.1 802.3 帧结构详解#

graph LR subgraph 以太网帧结构 PREAMBLE["前导码<br/>7B"] --> SFD["SFD<br/>1B"] SFD --> DMAC["目的MAC<br/>6B"] DMAC --> SMAC["源MAC<br/>6B"] SMAC --> TYPE["类型/长度<br/>2B"] TYPE --> DATA["数据<br/>46~1500B"] DATA --> FCS["FCS<br/>4B"] end style PREAMBLE fill:#e8eaf6,stroke:#283593 style SFD fill:#e8eaf6,stroke:#283593 style DMAC fill:#e3f2fd,stroke:#1565c0 style SMAC fill:#e3f2fd,stroke:#1565c0 style TYPE fill:#fff3e0,stroke:#e65100 style DATA fill:#e8f5e9,stroke:#2e7d32 style FCS fill:#fce4ec,stroke:#c62828

逐字段拆解:

前导码(Preamble),7 字节:连续 7 个 0xAA(二进制 10101010),作用是让接收端时钟同步。以太网是异步通信,接收方需要通过前导码的曼彻斯特编码信号锁定发送方的时钟频率。这 7 个字节不会计入帧长度。

帧起始定界符(SFD),1 字节:固定值 0xAB(二进制 10101011),最后两位从 10 变成 11,告诉接收方”前导码结束,真正的帧数据从下一个字节开始”。

目的 MAC 地址,6 字节:帧的接收方地址。网卡收到帧后首先检查这个字段——如果与自己的 MAC 地址匹配、或者是自己加入的组播地址、或者是广播地址 ff:ff:ff:ff:ff:ff,就接收;否则丢弃。

源 MAC 地址,6 字节:帧的发送方地址。交换机用这个字段学习 MAC 地址与端口的映射关系。

类型/长度,2 字节:这个字段有双重含义。如果值 ≥ 1536(0x0600),它表示上层协议类型;如果值 ≤ 1500,它表示数据字段的长度。常见的类型值:

类型值上层协议说明
0x0800IPv4最常见的类型值
0x0806ARP地址解析协议
0x86DDIPv6IPv6 数据包
0x8100802.1QVLAN 标签(见第五节)
0x8864PPPoE宽带拨号
0x88CCLLDP链路层发现协议

数据(Payload),46~1500 字节:承载上层协议数据。最小 46 字节是历史原因——以太网规定最小帧长 64 字节(不含前导码和 SFD),减去目的 MAC(6)+ 源 MAC(6)+ 类型(2)+ FCS(4)= 18 字节的开销,剩余 46 字节。如果 IP 包不足 46 字节,需要填充零。1500 字节的上限就是著名的 MTU(Maximum Transmission Unit)。

帧校验序列(FCS),4 字节:对目的 MAC 到数据字段全部内容做 CRC-32 校验,接收方用它检测传输中的比特错误。如果校验失败,帧被静默丢弃——以太网不负责重传,那是上层协议(如 TCP)的事。

1.2 用 hex dump 看一个真实的帧#

用 tcpdump 抓一个 ICMP 包,逐字节解读:

# 抓取一个 ICMP 帧,显示完整内容
sudo tcpdump -i eth0 -XX -c 1 icmp

输出类似:

0x0000: ffff ffff ffff aabb ccdd ee01 0800 4500 0054
────────────── ────────────── ──── ──────────
目的MAC(广播) 源MAC 类型 IP头开始...
0x0010: 0001 4000 4001 ...(IP 头和数据)

拆解第一行:

  • ff ff ff ff ff ff:目的 MAC,全 1 即广播地址
  • aa bb cc dd ee 01:源 MAC,发送方网卡地址
  • 08 00:类型字段,0x0800 = IPv4
  • 45 00 00 54...:IP 头部开始(版本 4,头长 20 字节,总长 84 字节)
Note

前导码和 SFD 在网卡硬件层面处理,不会传递给操作系统,所以 tcpdump 抓到的帧从目的 MAC 开始。同样,FCS 由网卡硬件校验后剥离,也不会出现在抓包数据中。

二、MAC 地址:网卡的身份证#

2.1 MAC 地址的结构#

MAC 地址长 48 位(6 字节),通常写成 AA:BB:CC:DD:EE:FF 的形式。它的结构分两部分:

graph LR MAC["MAC 地址 (48 bit)"] --> OUI["OUI (24 bit)<br/>组织唯一标识符<br/>IEEE 分配给厂商"] MAC --> NIC["NIC (24 bit)<br/>网卡唯一标识<br/>厂商自行分配"] style OUI fill:#e3f2fd,stroke:#1565c0 style NIC fill:#fff3e0,stroke:#e65100
  • OUI(Organizationally Unique Identifier):前 24 位,由 IEEE 分配给设备厂商。比如 00:1A:2B 是某家网卡厂商的 OUI,你看到 MAC 地址以这个前缀开头,就知道是这家厂商的设备。
  • NIC(Network Interface Controller):后 24 位,由厂商自行分配,保证每块网卡的 MAC 地址全局唯一。

OUI 的第一个字节还有两个特殊位:

  • I/G 位(Bit 0):0 表示单播地址,1 表示组播地址
  • U/L 位(Bit 1):0 表示全球唯一(烧录在网卡上),1 表示本地管理(软件修改)

2.2 单播、组播与广播#

地址类型MAC 地址格式I/G 位用途
单播AA:BB:CC:DD:EE:FF0点对点通信,只有目标网卡接收
组播01:00:5E:xx:xx:xx1一组主机同时接收,如视频流、ARP
广播FF:FF:FF:FF:FF:FF1局域网内所有设备都接收

广播是组播的特例——所有位都为 1。ARP 请求就用广播地址,因为发送方还不知道目标的 MAC 地址,只能向全网喊话。

2.3 MAC 地址的分配与管理#

MAC 地址的分配是分层的:

  1. IEEE 拥有全部地址空间,将大块 OUI 分配给厂商
  2. 厂商拿到 OUI 后,自行分配后 24 位,确保每块网卡唯一
  3. MAC 地址烧录在网卡的 EEPROM 或 Flash 中,操作系统启动时读取

但 MAC 地址并非不可更改。Linux 下可以随意修改:

# 查看当前 MAC 地址
ip link show eth0
# 临时修改 MAC 地址(重启失效)
sudo ip link set dev eth0 down
sudo ip link set dev eth0 address 00:11:22:33:44:55
sudo ip link set dev eth0 up

这就是 U/L 位的意义——软件修改的 MAC 地址,U/L 位会被设为 1,表示这是本地管理的地址而非出厂烧录的全球唯一地址。

2.4 为什么 MAC 地址不能跨网段路由#

MAC 地址是扁平地址空间——它没有层次结构。00:1A:2B:3C:4D:5E00:1A:2B:3C:4D:5F 可能分属地球两端的设备,仅凭 MAC 地址无法判断它们是否在同一局域网。

IP 地址则不同——它有网络号和主机号的层次结构,路由器可以根据网络号做前缀匹配转发。如果用 MAC 地址做路由,每台路由器需要维护 48 位精确匹配的转发表,规模完全不可行。

所以 MAC 地址只在局域网内有效,跨网段通信必须依靠 IP 地址逐跳路由,每一跳重新封装源和目的 MAC 地址。

三、网卡发送流程:从内核到物理#

当一个 IP 包准备好要发送时,它还在内核内存里。从内核到电信号跑上网线,中间经历了什么?

3.1 完整路径概览#

flowchart TB SKB["sk_buff<br/>内核数据包"] --> DMA["DMA 映射<br/>物理地址"] DMA --> RING["Ring Buffer<br/>发送描述符"] RING --> DRV["网卡驱动<br/>填充描述符"] DRV --> NIC["网卡 DMA 读取<br/>内存→网卡缓冲区"] NIC --> MAC["MAC 层<br/>添加帧头/帧尾"] MAC --> PHY["PHY 芯片<br/>编码/调制"] PHY --> CABLE["物理介质<br/>电信号/光信号"] style SKB fill:#e3f2fd,stroke:#1565c0 style DMA fill:#e8eaf6,stroke:#283593 style RING fill:#e0f2f1,stroke:#00695c style DRV fill:#fff3e0,stroke:#e65100 style NIC fill:#fce4ec,stroke:#c62828 style MAC fill:#f3e5f5,stroke:#6a1b9a style PHY fill:#e8f5e9,stroke:#2e7d32 style CABLE fill:#fff9c4,stroke:#f9a825

3.2 sk_buff:内核的数据包载体#

Linux 内核用 sk_buff(简称 skb)结构体表示网络数据包。skb 不仅仅是数据缓冲区——它还包含各层协议的头部指针、路由信息、时间戳等元数据。

// sk_buff 的简化结构(内核 5.x)
struct sk_buff {
struct net_device *dev; // 关联的网卡设备
unsigned int len; // 数据总长度
__u8 pkt_type; // 包类型(单播/组播/广播)
__be16 protocol; // 上层协议(ETH_P_IP 等)
unsigned char *head; // 缓冲区起始
unsigned char *data; // 数据起始
unsigned char *tail; // 数据结束
unsigned char *end; // 缓冲区结束
// ... 还有几十个字段
};

当 IP 层把数据包交给网络设备子系统时,skb 的 data 指针已经指向 IP 头部。网卡驱动需要在 data 前面预留空间来填充以太网帧头(14 字节),在尾部追加 FCS(4 字节,通常由硬件自动计算)。

3.3 Ring Buffer 与 DMA#

网卡和内核之间通过 Ring Buffer 传递数据。Ring Buffer 是一个环形队列,由一组 DMA 描述符组成,每个描述符指向一块物理内存。

发送方向的工作流程:

  1. 内核将 skb 数据映射到 DMA 可访问的物理地址
  2. 驱动将物理地址填入 Ring Buffer 的下一个可用发送描述符
  3. 驱动写网卡寄存器,通知网卡”有新数据待发送”
  4. 网卡通过 DMA 直接读取内存中的数据到网卡内部缓冲区
  5. 网卡 MAC 层添加前导码、SFD、帧头、FCS
  6. PHY 芯片将数据编码为物理信号发送出去
  7. 发送完成后,网卡触发中断,驱动回收描述符
# 查看 Ring Buffer 大小
ethtool -g eth0
# 输出示例:
# Ring parameters for eth0:
# Pre-set maximums:
# RX: 4096
# RX Mini: 0
# RX Jumbo: 0
# TX: 4096
# Current hardware settings:
# RX: 512
# RX Mini: 0
# RX Jumbo: 0
# TX: 512

Ring Buffer 太小会导致丢包——网卡处理不过来时,新到的帧无处安放。生产环境常把 RX/TX 调大到 1024 或 2048。

3.4 TSO/GSO:卸载与分段#

如果应用层发送大量数据(比如 HTTP 响应),TCP 层可能产生一个远超 MTU 的 skb。传统做法是 TCP 层在内核中按 MSS 分段,每段单独封装成以太网帧。但这样 CPU 开销大——每个分段都要走一遍协议栈。

TSO(TCP Segmentation Offload) 把分段工作交给网卡硬件:内核把大 skb 直接交给网卡,网卡硬件自动按 MSS 切分、计算校验和、封装帧头。

# 查看网卡卸载能力
ethtool -k eth0 | grep -E "tcp-segmentation|generic-segmentation"
# 输出示例:
# tcp-segmentation-offload: on ← TSO 已开启
# generic-segmentation-offload: on ← GSO 已开启

GSO(Generic Segmentation Offload)是 TSO 的软件泛化版本——即使网卡不支持 TSO,GSO 也能延迟分段到尽可能晚的时机,减少协议栈遍历次数。

3.5 中断与 NAPI#

早期网卡每收到一个帧就触发一次中断。千兆网卡每秒可能收到上百万个帧,中断风暴会压垮 CPU。

NAPI(New API) 采用混合策略:

  1. 第一个帧到达时触发硬中断
  2. 中断处理函数禁用中断,调度软中断轮询(poll)
  3. 轮询批量处理 Ring Buffer 中的帧(一次 budget 通常 64 个)
  4. 处理完或 budget 用尽后,重新启用中断

这样在流量大时用轮询避免中断开销,流量小时用中断保证低延迟。

# 查看网卡中断绑定
cat /proc/interrupts | grep eth0
# 查看网卡驱动信息
ethtool -i eth0
# 输出示例:
# driver: igb
# version: 5.15.0-91-generic
# firmware-version: 1.5, 0x800006fa
# expansion-rom-version:
# bus-info: 0000:01:00.0

3.6 一帧数据的完整旅程#

把上面的流程串起来,追踪一个 ICMP echo request 从内核到网线:

  1. 用户执行 ping 192.168.1.1
  2. 内核构造 ICMP echo request,封装在 IP 包中
  3. IP 层查询路由表,确定出接口 eth0,下一跳 MAC 地址通过 ARP 获取
  4. skb 的 protocol 设为 ETH_P_IP,dev 设为 eth0
  5. dev_queue_xmit() 将 skb 加入 eth0 的发送队列
  6. 驱动 igb_xmit_frame() 将 skb 数据 DMA 映射,填入 Ring Buffer 描述符
  7. 驱动写 TDT(Transmit Descriptor Tail)寄存器通知网卡
  8. 网卡 DMA 读取内存数据,MAC 层添加帧头(目的 MAC + 源 MAC + 0x0800),计算 FCS
  9. PHY 芯片将帧编码为 1000BASE-T 信号,通过网线发送
  10. 发送完成,网卡触发 MSI-X 中断,驱动清理已发送描述符

四、交换机:局域网的交通枢纽#

帧离开网卡、跑上网线后,下一个遇到的就是交换机。交换机是数据链路层的核心设备——它根据 MAC 地址转发帧,让局域网内的设备互相通信。

4.1 交换机 vs 集线器 vs 路由器#

特性集线器(Hub)交换机(Switch)路由器(Router)
工作层次物理层数据链路层网络层
转发依据无(广播)MAC 地址IP 地址
冲突域整个网络共享每个端口独立每个端口独立
广播域整个网络整个 VLAN每个接口独立
带宽共享每端口独享每端口独享
地址学习不支持MAC 地址表路由表

集线器是纯物理层设备——收到信号就向所有端口转发,所有设备共享带宽和冲突域。交换机通过 MAC 地址表实现精确转发,每个端口独享带宽。路由器工作在网络层,连接不同网段。

4.2 MAC 地址学习#

交换机核心是一张 MAC 地址表(也叫 CAM 表、FDB 表),记录 MAC 地址与端口的映射关系。这张表是动态学习来的:

  1. 交换机收到帧时,记录源 MAC 和入端口的映射
  2. 如果表中已有该 MAC 但端口不同,更新为新端口(处理设备移动)
  3. 表项有老化时间(通常 300 秒),超时未刷新则删除
# Linux bridge 查看 MAC 地址表
bridge fdb show
# 输出示例:
# aa:bb:cc:dd:ee:01 dev eth0 vlan 1 master bridge
# aa:bb:cc:dd:ee:02 dev eth1 vlan 1 master bridge
# ff:ff:ff:ff:ff:ff dev eth0 vlan 1 master bridge permanent

4.3 转发、泛洪与过滤#

交换机对收到的帧有三种处理方式:

flowchart TB FRAME["收到帧"] --> CHECK{"目的 MAC<br/>在地址表中?"} CHECK -->|是| FWD["转发:从对应端口发出"] CHECK -->|否| FLOOD["泛洪:向所有端口(除入端口)转发"] CHECK -->|"是,但端口=入端口"| FILTER["过滤:丢弃(无需转发)"] FWD --> DONE["完成"] FLOOD --> DONE FILTER --> DONE style FRAME fill:#e3f2fd,stroke:#1565c0 style CHECK fill:#fff3e0,stroke:#e65100 style FWD fill:#e8f5e9,stroke:#2e7d32 style FLOOD fill:#fce4ec,stroke:#c62828 style FILTER fill:#e0e0e0,stroke:#616161
  • 转发(Forward):目的 MAC 在地址表中,从对应端口发出——这是最常见的情况
  • 泛洪(Flood):目的 MAC 不在地址表中,向所有端口广播——交换机还没学到的地址只能泛洪
  • 过滤(Filter):目的 MAC 对应的端口就是入端口——说明源和目的在同一网段,不需要交换机转发

广播帧(目的 MAC = ff:ff:ff:ff:ff:ff)永远泛洪,组播帧也泛洪(除非启用了 IGMP Snooping)。

4.4 生成树协议 STP#

交换机网络可能出现环路——比如两台交换机之间连了两根线做冗余。环路会导致广播帧无限循环(广播风暴),瞬间撑满链路带宽。

STP(Spanning Tree Protocol,IEEE 802.1D) 通过阻塞某些端口来消除环路:

  1. 选举根桥(Bridge ID 最小的交换机)
  2. 每台非根交换机选举到根桥路径开销最小的端口为根端口
  3. 每个网段选举一个指定端口
  4. 其余端口设为阻塞状态,不转发数据但监听 BPDU

STP 的收敛时间长达 30~50 秒,这在现代网络中不可接受。RSTP(Rapid STP,IEEE 802.1w) 将收敛时间缩短到几秒,核心改进是引入了替代端口(Alternate Port)和备份端口(Backup Port)的概念,拓扑变化时可以快速切换。

# Linux bridge 查看 STP 状态
brctl show
# 输出示例:
# bridge name bridge id STP enabled interfaces
# br0 8000.001a2b3c4d5e yes eth0
# eth1
# eth2
Warning

生产环境中交换机环路是常见故障源。一根误插的网线就能制造广播风暴,让整个局域网瘫痪。务必在所有交换机上启用 STP/RSTP,即使你确信当前拓扑没有环路——防的是未来的人为失误。

4.5 一个帧穿过交换机的完整流程#

把交换机的处理逻辑串起来:

  1. 帧从端口 1 进入,PHY 芯片将物理信号还原为比特流
  2. 交换机 ASIC 解析帧头,提取源 MAC(AA:BB:CC:DD:EE:01)和目的 MAC(11:22:33:44:55:66
  3. 将源 MAC + 端口 1 写入 MAC 地址表(如果不存在或端口变了)
  4. 查找目的 MAC 在地址表中的端口
  5. 找到 → 端口 3,从端口 3 转发出去
  6. 找不到 → 向端口 2、3、4 泛洪
  7. 如果目的 MAC 对应端口就是端口 1 → 丢弃
  8. 帧从出端口的 PHY 芯片编码为物理信号发出

整个查表和转发过程由 ASIC 硬件完成,延迟在微秒级别。

五、VLAN:虚拟局域网#

5.1 为什么需要 VLAN#

默认情况下,交换机的所有端口在同一个广播域——任何广播帧都会泛洪到所有端口。当网络规模增大,广播风暴的影响范围也跟着扩大,安全隔离也无从谈起。

VLAN(Virtual LAN,IEEE 802.1Q)把一个物理交换机划分为多个逻辑上的虚拟局域网,每个 VLAN 是独立的广播域。不同 VLAN 之间的流量互不可见,除非通过路由器转发。

5.2 802.1Q 标签结构#

VLAN 在以太网帧中插入一个 4 字节的标签,位于源 MAC 和类型字段之间:

graph LR subgraph 802.1Q帧结构 DMAC2["目的MAC<br/>6B"] --> SMAC2["源MAC<br/>6B"] SMAC2 --> TAG["802.1Q标签<br/>4B"] TAG --> TYPE2["类型<br/>2B"] TYPE2 --> DATA2["数据<br/>46~1500B"] DATA2 --> FCS2["FCS<br/>4B"] end subgraph 标签内部 TPID["TPID<br/>0x8100<br/>2B"] --> TCI["TCI<br/>2B"] TCI --> PCP["PCP<br/>3bit<br/>优先级"] PCP --> DEI["DEI<br/>1bit<br/>丢弃指示"] DEI --> VID["VID<br/>12bit<br/>VLAN ID"] end style TAG fill:#fff3e0,stroke:#e65100 style TPID fill:#e3f2fd,stroke:#1565c0 style VID fill:#fce4ec,stroke:#c62828
  • TPID(Tag Protocol ID):固定值 0x8100,标识这是一个 802.1Q 标签帧
  • PCP(Priority Code Point):3 位优先级,用于 QoS,值 0~7
  • DEI(Drop Eligible Indicator):1 位,标记该帧在拥塞时可优先丢弃
  • VID(VLAN Identifier):12 位,VLAN 编号,范围 0~4095,其中 0 和 4095 保留

插入 4 字节标签后,帧的最大长度从 1518 字节增加到 1522 字节。一些老旧设备可能无法处理这种超长帧。

5.3 Access 端口与 Trunk 端口#

特性Access 端口Trunk 端口
连接对象终端设备(PC、服务器)交换机之间、交换机与路由器
帧格式不带 802.1Q 标签带 802.1Q 标签
VLAN 归属只属于一个 VLAN可承载多个 VLAN 的流量
入方向处理给帧打上 PVID 标签检查标签中的 VID
出方向处理剥离标签后发送保留标签发送
默认 PVID通常为 VLAN 1通常为 VLAN 1

Access 端口连接终端设备,终端发出的帧不带 VLAN 标签。交换机在 Access 端口收到帧后,给它打上该端口的 PVID(Port VLAN ID)标签;发送时剥离标签,让终端无感知。

Trunk 端口连接交换机或路由器,帧带着 VLAN 标签传输,接收方根据标签中的 VID 区分不同 VLAN 的流量。

# Linux 下创建 VLAN 子接口
sudo ip link add link eth0 name eth0.100 type vlan id 100
sudo ip link set dev eth0.100 up
sudo ip addr add 192.168.100.1/24 dev eth0.100
# 旧版工具 vconfig(已废弃,推荐用 ip link)
# sudo vconfig add eth0 100

5.4 VLAN 间路由#

不同 VLAN 之间不能直接通信——它们是不同的广播域,交换机不会在 VLAN 间转发帧。要实现 VLAN 间通信,必须经过路由器。

常见方案:

  1. 单臂路由(Router-on-a-stick):路由器通过一根 Trunk 链路连接交换机,路由器子接口对应不同 VLAN 的网关
  2. 三层交换机:交换机内置路由功能,硬件转发 VLAN 间流量,性能远超单臂路由
# Linux bridge 模拟 VLAN 隔离
# 创建 bridge
sudo ip link add name br0 type bridge vlan_filtering 1
sudo ip link set dev br0 up
# 添加端口,配置 Access VLAN
sudo ip link set dev eth0 master br0
sudo bridge vlan add dev eth0 vid 10 pvid untagged
# 添加 Trunk 端口
sudo ip link set dev eth1 master br0
sudo bridge vlan add dev eth1 vid 10-20

5.5 VLAN 架构图#

graph TB subgraph VLAN10["VLAN 10 — 研发部"] PC1["PC1<br/>192.168.10.10"] PC2["PC2<br/>192.168.10.11"] end subgraph VLAN20["VLAN 20 — 市场部"] PC3["PC3<br/>192.168.20.10"] PC4["PC4<br/>192.168.20.11"] end subgraph SW["交换机"] P1["端口 1<br/>Access VLAN10"] P2["端口 2<br/>Access VLAN10"] P3["端口 3<br/>Access VLAN20"] P4["端口 4<br/>Access VLAN20"] P5["端口 5<br/>Trunk VLAN10,20"] end PC1 --- P1 PC2 --- P2 PC3 --- P3 PC4 --- P4 P5 --- ROUTER["路由器<br/>单臂路由"] style VLAN10 fill:#e3f2fd,stroke:#1565c0 style VLAN20 fill:#fff3e0,stroke:#e65100 style SW fill:#e8f5e9,stroke:#2e7d32 style ROUTER fill:#fce4ec,stroke:#c62828

VLAN 10 和 VLAN 20 的流量在交换机内部隔离。PC1 和 PC2 可以直接通信(同 VLAN),PC1 和 PC3 不能直接通信(跨 VLAN),必须通过路由器转发。

六、链路追踪:从主机到交换机#

纸上得来终觉浅。用工具亲手抓一个以太网帧,逐字节解读,把前面学到的知识全部串起来。

6.1 用 tcpdump 抓取以太网帧#

# -e 显示链路层头部(MAC 地址)
# -XX 以 hex+ASCII 显示完整帧内容
# -c 1 只抓一个包
sudo tcpdump -i eth0 -e -XX -c 1 icmp

输出示例:

10:30:45.123456 aa:bb:cc:dd:ee:01 > ff:ff:ff:ff:ff:ff,
ethertype IPv4 (0x0800), length 98:
192.168.1.100 > 192.168.1.1: ICMP echo request, id 1234, seq 1, length 64
0x0000: ffff ffff ffff aabb ccdd ee01 0800 4500 0054
0x0010: 0001 0000 4001 a5c3 c0a8 0164 c0a8 0101
0x0020: 0800 4d5a 0001 0001 6162 6364 6566 6768
0x0030: 696a 6b6c 6d6e 6f70 7172 7374 7576 7778
0x0040: 797a 6162 6364 6566 6768 696a 6b6c 6d6e
0x0050: 6f70 7172 7374 7576 7778 797a 6162 6364
0x0060: 6566 6768 696a 6b6c 6d6e

逐字节解读:

  • ff ff ff ff ff ff:目的 MAC,广播地址(ARP 请求场景)
  • aa bb cc dd ee 01:源 MAC,本机网卡地址
  • 08 00:类型字段,0x0800 = IPv4
  • 45:IP 版本 4,头长 5×4=20 字节
  • 00 54:IP 总长度 84 字节(20 字节 IP 头 + 64 字节 ICMP)
  • 40 01:TTL=64,协议=1(ICMP)
  • c0 a8 01 64:源 IP 192.168.1.100
  • c0 a8 01 01:目的 IP 192.168.1.1
  • 08 00:ICMP 类型 8(echo request),代码 0
  • 后续为 ICMP 数据

6.2 用 Wireshark 深入分析#

tcpdump 适合快速查看,Wireshark 提供更直观的图形化分析:

# 抓包保存为 pcap 文件
sudo tcpdump -i eth0 -w /tmp/capture.pcap -c 10
# 用 Wireshark 打开分析
wireshark /tmp/capture.pcap

在 Wireshark 中展开 Ethernet II 层,可以清晰看到每个字段的解析:目的 MAC、源 MAC、类型字段,以及各字段的含义解释。

6.3 用 scapy 构造以太网帧#

scapy 是 Python 的网络包操作库,可以构造、发送、接收任意网络帧:

from scapy.all import *
# 构造一个以太网帧
frame = Ether(
dst="ff:ff:ff:ff:ff:ff", # 广播目的 MAC
src="aa:bb:cc:dd:ee:01", # 源 MAC
type=0x0800 # IPv4
) / IP(
dst="192.168.1.1",
src="192.168.1.100"
) / ICMP()
# 查看帧结构
frame.show()
# 输出原始字节
bytes(frame).hex()
# 发送帧(需要 root 权限)
# sendp(frame, iface="eth0")

scapy 让你从”观察者”变成”构造者”——你可以手动指定每个字段的值,验证交换机的转发行为、测试 VLAN 标签、模拟 ARP 欺骗。

6.4 从 ping 到帧离开网卡的完整过程#

把整章内容串起来,追踪 ping 192.168.1.1 的完整链路:

  1. 应用层ping 命令构造 ICMP echo request
  2. 网络层:内核封装 IP 头部,查询路由表确定出接口 eth0
  3. ARP 解析:内核查 ARP 缓存获取 192.168.1.1 的 MAC 地址,缓存未命中则发送 ARP 请求(广播帧)
  4. 数据链路层:内核填充以太网帧头(目的 MAC + 源 MAC + 0x0800),构造完整的 sk_buff
  5. 驱动层dev_queue_xmit() 将 skb 加入发送队列,驱动将数据 DMA 映射到 Ring Buffer
  6. 网卡硬件:DMA 读取内存数据,MAC 层添加前导码/SFD,计算 FCS,PHY 编码发送
  7. 物理介质:电信号通过网线到达交换机端口
  8. 交换机:解析帧头,学习源 MAC,查表转发目的 MAC 对应的端口
  9. 目标主机:网卡接收帧,校验 FCS,DMA 写入 Ring Buffer,驱动构造 skb,中断通知内核
  10. 内核协议栈:从以太网帧中提取 IP 包,从 IP 包中提取 ICMP echo request,构造 ICMP echo reply,原路返回

七、本章小结#

概念要点
802.3 帧结构前导码(7B)+SFD(1B)+目的MAC(6B)+源MAC(6B)+类型(2B)+数据(46~1500B)+FCS(4B),最小帧 64 字节
MAC 地址OUI(24bit)+NIC(24bit),单播/组播/广播,扁平地址空间不可路由
网卡发送sk_buff→DMA映射→Ring Buffer→网卡DMA读取→MAC封装→PHY编码→物理信号
TSO/GSO将 TCP 分段卸载给网卡硬件,减少 CPU 开销
NAPI中断+轮询混合策略,高流量时批量处理,低流量时中断驱动
交换机转发MAC 地址学习→查表转发/泛洪/过滤,ASIC 硬件实现微秒级延迟
STP/RSTP阻塞冗余端口消除环路,RSTP 收敛时间从 30 秒缩短到几秒
VLAN802.1Q 标签(4B)插入帧中,Access 端口不打标签,Trunk 端口保留标签
VLAN 间路由不同 VLAN 是独立广播域,通信必须经过路由器或三层交换机

本章追踪了数据包从内核内存到交换机端口的完整路径。下一章深入网络层——ARP与首跳路由,看看数据包如何找到第一跳路由器、ARP 如何解析 MAC 地址。


参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

以太网与交换:数据包离开网卡
https://blog.souloss.com/posts/internet-architecture/ethernet-and-switching/
作者
Souloss
发布于
2022-02-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时