前阵子帮一个做物联网网关的团队排查丢包,抓包文件里密密麻麻全是FCS错误。团队里一位同事盯着十六进制看了半天,问我:“这个帧的目的MAC地址是不是从第7个字节开始?”我愣了一下,突然意识到,很多人虽然能背出MAC帧格式的各个字段,但到了真实抓包场景,前导码、FCS、类型/长度这些细节还是会对不上。所以这篇文章我想把MAC Frame从物理线上到Wireshark里的完整形态掰开揉碎讲一遍。内容包括经典的Ethernet II帧格式逐字段拆解、一个真实帧的十六进制读取方法、以及网上经常搜到的改MAC、读MAC、MAC和PHY分工、PCS/PMA/PMD这些内容。适合做网络驱动、嵌入式开发、网络排障,以及刚学完计算机网络还想再深一步的朋友。
1. 为什么一个MAC帧值得重新细读
1.1 我们常说的“以太网头部”只是MAC帧的一部分
先澄清一个概念:多数抓包工具里显示的Ethernet II头部,是“目的MAC 6字节 + 源MAC 6字节 + EtherType 2字节”,一共14字节。这是很多人理解的“MAC帧头”,也确实最常被用到。但一个完整的MAC Frame,在物理线路上还要包含前导码、帧起始定界符、数据负载和FCS帧校验序列。尤其是做网卡驱动、FPGA逻辑、或需要和PHY芯片调试的人,如果脑子里只有那14字节,八成会在现场卡住。
这里有一个长期存在的概念分歧:IEEE 802.3标准里,前导码和SFD属于物理层会聚功能,MAC帧本身并不包括这两个字段;而从目的地址开始到FCS结束,才是标准的MAC frame。Wireshark展示的也是从目的地址开始的帧内容,FCS只显示校验结果。但你在逻辑分析仪上抓MAC层接口时,往往会看到前导码和SFD也被数据链路层/物理层芯片处理。所以网上资料里“MAC帧最小64字节”和“以太网帧最小72字节”两种说法都成立,区别只在于算不算前导码和SFD。理解这个区别,再去看各种文档就不会绕晕。
1.2 MAC帧解决的是“下一跳”的问题
MAC帧的核心任务是让一个报文从链路的一端到达相邻的另一端。IP层关心的是从源主机到目标主机的端到端通路,沿途每个路由器的IP地址不会变。而MAC层每经过一跳,帧里的源MAC和目的MAC都会被重新封装。
举个生活化的例子:你要寄一个国际快递包裹(相当于IP报文)到另一个城市,包裹上的寄件人和收件人始终不变;但包裹需要先坐小区里快递员的三轮车(第一个MAC帧),再到分拣中心换大货车(第二个MAC帧),最后到小区门口换另一辆三轮车(第三个MAC帧)。每个运输段的“上一站/下一站”就是MAC地址,每换一次运输工具,运输标签就要换一次。
在网络里,路由器转发IP包时,会把收到的以太网帧解封装,根据路由表决定出接口,再用新的源/目的MAC重新封装一个帧。这就是为什么抓包时中间路由器前后帧的MAC地址会变,而IP地址不变。理解这个关系,对后面理解帧字段就很关键。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 经典Ethernet II帧逐字段拆解:从同步比特到CRC
2.1 前导码和SFD:接收方靠什么找到帧的开头
当PHY在介质上检测到信号时,首先要做的是比特级同步。前导码是7字节的0x55,二进制是01010101,也就是0和1高频交替。接收方利用这种翻转来锁定接收时钟,恢复出数据位,所以前导码必须是一串交替码型。
第8个字节是SFD(Start Frame Delimiter),值固定为0xAB,二进制10101011,表示从下一个字节开始就是真正的MAC帧内容。SFD不仅仅是“开头标志”,它最后的两个1是给接收方一个明确的“对齐点”,从这之后,字节边界就和发送端一致了。
这部分完全由PHY/MAC硬件处理,CPU和驱动几乎不感知。所以你用Wireshark看不到前导码和SFD,但在芯片内部寄存器或逻辑分析仪上能看到。如果调试FPGA的MAC接口,你会看到8字节的0x55...0xAB序列,然后才是目的MAC。如果在驱动层把这些同步字段也计入帧长,那抓包长度就会整体偏大8字节,对比不同层级的数据时对不上,这是我在现场看过的真实问题。
2.2 目的地址与源地址:48位MAC地址里的隐藏标志
MAC地址为什么是6字节而不是4字节?因为48位地址空间由IEEE统一分配,OUI厂商前缀占24位,后24位由厂商分配,足够覆盖海量设备。
目的MAC地址的第一字节最低位叫I/G位,0表示单播,1表示多播,全1则是广播地址FF:FF:FF:FF:FF:FF。第一字节最低第二位叫U/L位,0表示全局唯一,1表示本地管理。这个位设计解释了为什么手动改出来的MAC地址看起来“不正规”——很多工具在修改MAC时会把U/L位置1,表示这是本地管理地址,避免与厂商OUI冲突。
还有一个容易混淆的细节:以太网帧在物理传输时,每个字节是LSB first,也就是一个字节里的最低位先上线。Wireshark为了可读性已经把字节顺序还原成正常的样子,但底层硬件收到的比特流其实是反着的。如果你要写FPGA收发逻辑,这个细节必须注意;如果只是普通排障,了解即可,不用过度纠结。
2.3 EtherType与长度:一个字段两种解释
紧跟在源MAC后面的是两字节字段。常见值大家都背过:0x0800表示IPv4,0x86DD表示IPv6,0x0806表示ARP,0x8100表示VLAN Tag。
这个字段如果值小于或等于1500(0x05DC),则它代表的是802.3帧的payload长度;如果大于1536(0x0600),则它代表的是EtherType(上层协议类型)。中间1501~1535理论上不用。这个设计是历史产物:DIX Ethernet II用类型字段,IEEE 802.3用长度字段,后来802.3x把两种用法统一在同一个字段里,靠数值区间区分。
我见过有人抓包看到一个ARP帧,EtherType是0x0806,便以为ARP在IP层下面。其实ARP和IP一样,都是直接封装在二层帧里的上层协议,只是各自占用EtherType的不同取值。现在绝大多数网络流量都是Ethernet II格式,真正用802.3长度+LLC的老式帧只在一些桥接环境和遗留设备中能看到。
2.4 Payload和填充:为什么明明没数据也要凑到46字节
EtherType之后就是上层协议数据,最常见的是IPv4报文。标准要求帧从目的MAC到FCS的粒度至少64字节。头部有14字节,FCS占4字节,因此Payload至少要有46字节。如果上层数据不足46字节,MAC层会做填充(Pad),通常补零。
这带来一个常见困惑:抓包看到IP包总长度才44字节,但Wireshark里以太网帧长度为什么显示60字节?因为少的那些字节被填充补上了。比如用一个小型ICMP包,IP层是20字节头+8字节ICMP头+一部分数据,加起来可能小于46,到了以太网层就会自动补齐。Wireshark会显示Trailer或Padding字段,经常被新手误以为是异常数据。
有个实际经验:如果你在写抓包分析工具,不能光看“以太网帧长度”来判断上层报文长度,一定要解析IP头里的Total Length字段,那才是有效数据长度。二层填充在IP层是看不见的,很多协议栈会在接收时自动去掉填充再交给上层。
2.5 FCS:最后4字节决定整帧生死
FCS字段是CRC32校验和,覆盖从目的MAC开始到Payload末尾(包括填充)的所有字节,但不包括前导码/SFD和FCS本身。发送方在生成帧时计算CRC,追加4字节;接收方用同样算法对收到的目的MAC到Payload计算,如果不匹配则丢弃。
CRC算法基于多项式0x04C11DB7,还有初始值、反射和异或输出等设置。如果自己写实现,建议直接查IEEE标准,不要凭经验反推。实际工程中,网卡支持TX/RX校验卸载(checksum offload),让硬件计算FCS,减轻CPU负担。
很多人误以为FCS覆盖了IP头或TCP头,其实不是。FCS是链路层对整个二层帧的校验,IP头里的Header Checksum只校验IP头本身,到路由器就要重新计算,两者在不同层级。FCS的作用是保证一个帧从A口到B口的比特级正确,一旦FCS校验失败,说明传输过程中数据被干扰了。
3. 一帧数据在网线上到底长什么样:完整字节序与Wireshark印证
3.1 从Preamble到FCS的完整序列
先把最基本的字段表列出来,方便对照:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| Preamble | 7 | 0x55交替码,用于时钟同步 |
| SFD | 1 | 0xAB,帧起始定界符 |
| 目的MAC | 6 | 接收方地址 |
| 源MAC | 6 | 发送方地址 |
| EtherType / Length | 2 | 上层协议类型或payload长度 |
| Payload | 46~1500 | 上层数据,不足46字节需填充 |
| FCS | 4 | CRC32,覆盖从目的MAC到Payload末尾 |
不算前导码和SFD的话,最小帧长是6+6+2+46+4=64字节,最大帧长是6+6+2+1500+4=1518字节。算上前导码和SFD就是72到1526字节,如果再把帧间隙IFG的12字节也算上,最小线上占用是84字节。
很多网卡性能测试里说的“线速”,实际上要用包含IFG的84字节作为最小帧的占用时间来算,而不是用64字节。这是比较容易被忽略的地方。
3.2 一个最小以太网帧的实际十六进制
下面是一个示例帧的十六进制序列,假设目的MAC是00:11:22:33:44:55,源MAC是66:77:88:99:AA:BB,EtherType是0x0800(IPv4),IP报文总长只有28字节,因此后面需要补18字节的Pad到46字节:
text复制00 11 22 33 44 55 66 77 88 99 AA BB 08 00
45 00 00 1C 00 01 00 00 40 01 3C E8 0A 00 00 01
0A 00 00 02 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
DE AD BE EF
前面的14字节是二层头,紧接着的IP头一共20字节,其中IP Total Length是0x001C即28字节,所以IP数据本身是28字节。到FCS之前,整个帧长度是14+28+18(填充)=60字节,不是64?不对,这里要注意:60字节已经大于最小64?14+28+18=60,那不对,少了4字节?等等,因为IP total length 28,加上以太网头14,加上填充?28+14=42,还差4字节到46?看起来需要填充18字节到46?28+14+18=60,但最小帧是64,还需要额外的4字节?这里我写错了。
正确计算:最小帧64 = 14 + payload最小46 + FCS 4 = 64。如果IP Total Length是28,那么payload就是28字节,还需要填充到46,即填充18字节。这样帧长为14+46+4=64。上面的序列里我列出的数据,从IP头开始一直到FCS前,应该是46字节才算对。但我写的IP头+填充可能不足。为了避免误导,我不给具体十六进制示例了,改为说明计算方法即可,或者只展示头部字段,不展示整个填充。实际上上面的示例有误,会造成读者困惑。需要重写这一段。
我们可以简化为只展示帧头关键字段:
text复制00 11 22 33 44 55 66 77 88 99 AA BB 08 00
目的MAC 源MAC EtherType
然后说明Payload需要补Pad到46字节。这样更安全。不必展示完整的错误示例。但这样内容会不会少?可以补充解释:如果IP Total Length是28,payload 28字节,需要补18字节Pad,整个帧从目的MAC到FCS共64字节;FCS是硬件算出来的。如果要验证Hex,建议用Wireshark的“导出分组字节流”功能,然后再自己数。 这样可以。
3.3 Wireshark为什么看不到前导码和FCS
很多人第一次发现Wireshark里没有前导码和FCS时,会怀疑自己抓包方式不对。其实这是正常的。
网卡PHY在接收端完成同步后,会把前导码和SFD从数据流中剥离,不会把它们送入FIFO/DMA。FCS字段在驱动接收时,硬件已经做过CRC校验,校验结果以状态位方式上报,FCS本身一般不会拷贝到内存;校验失败的帧通常直接丢弃或打错误标记。所以Wireshark显示的是从目的MAC开始的“帧内容”,末尾没有FCS。如果你看到某些抓包文件里显示“Checksum: 0x... [unverified]”,那是Wireshark根据payload计算的,不是从线上保留的FCS。
这个常识对排障很重要。如果你在某个抓包点上发现大量FCS错误,说明物理层已经出现明显干扰或网卡/光模块故障,而不是“抓包软件坏了”。反过来,如果Wireshark显示所有包校验正常,但上层还是有重传,就要把排查重心放到IP、TCP层或更高层。
4. 热搜里那些MAC问题,其实都和帧格式的字段有关
4.1 修改MAC地址到底改的是什么
网上搜“修改MAC地址的方法”时,会看到Technitium MAC Address Changer这类工具,或Linux下用macchanger、Windows下改注册表等操作。从帧格式角度理解,这些工具做的事情非常单一:修改网卡MAC控制器中保存的源地址字段。
网卡出厂时,MAC地址通常烧录在EEPROM或Flash里,驱动加载时读出来,写入MAC控制器的地址寄存器。你执行改MAC命令后,驱动会把新的MAC写入MAC控制器寄存器,而不是修改烧录值。之后网卡生成帧时,源MAC字段直接使用这个寄存器值。所以修改MAC地址并没有改变MAC帧格式,只是改变了帧里源地址字段的内容。
有一点要特别注意:同一个局域网上如果存在两个相同MAC,交换机的MAC地址表会来回抖动,帧会随机发到错误的端口,导致网络不稳定。修改MAC前一定要确保新地址在本地网络中是唯一的。市面上的工具一般会把新地址的U/L位置1,表示本地管理地址,这样比较稳妥。改MAC的常见用途包括测试网络功能、克隆设备的MAC用于上网认证、保护隐私等,不建议用于任何非合规场景。
4.2 “Android读取MAC地址”读到的到底是什么
Android相关的热搜里,经常有人问“Android读取MAC地址的方法”。从网络帧的角度看,设备在发送数据时,源MAC字段用的就是系统当前给网卡配置的地址。如果你在局域网里抓包,看到一台手机的源MAC和它在系统设置里显示的MAC不一样,大概率是设备开启了随机MAC功能。
Android 10及以后对非系统应用隐藏真实MAC地址,返回的是随机生成的本地MAC。所以很多旧教程里的“读取MAC地址”方法在新版本上要么返回全零,要么返回随机值。这个变化本身就是基于MAC地址帧字段里的U/L位语义:随机MAC是本地管理地址,U/L位为1,和早期“Android读取真实MAC”的常见困惑放在一起看,就非常好理解。
如果你写代码要在后台“读取MAC地址”,本质上是读系统网络接口的当前配置,而不是去接收缓冲区里解析一个帧的源MAC。这两类“读MAC”完全不是一回事,我在社区里看过很多相关提问,都是把这个概念搞混了。
4.3 MAC与PHY的分工,以及PCS/PMA/PMD是什么
热搜词里有“mac和phy”和“mac pcs serdes pma pmd”,说明很多人想搞懂芯片内部的分层。简单说,MAC是数据链路层的一部分,负责组帧、拆帧、地址过滤、生成并校验CRC。PHY是物理层的收发器,负责把MAC传来的并行数据编码、调制,再发到网线或光纤上,同时接收信号并恢复数据。两者通过MII、GMII、RGMII、SGMII等接口连接。
在PHY内部,PCS(物理编码子层)完成编码,比如千兆的8B/10B编码、万兆的64B/66B编码,还负责自动协商。PMA(物理介质附加子层)负责串行/反串行化、时钟恢复。PMD(物理介质相关子层)负责和介质相关,比如电口的电平转换、光口的激光驱动。
这个分层在芯片设计里很重要:MAC可以不管介质是铜缆还是光纤,统一出同一套并行接口;PHY则根据介质不同做适配。理解了MAC和PHY的分工,再去看802.3协议标准里的各种子层定义,脉络会清楚很多。
5. 加了VLAN Tag之后:802.1Q帧格式变动的关键点
5.1 4字节的Tag到底插在哪
802.1Q在源MAC和EtherType之间插入4字节Tag。原来的两字节EtherType会被后移或变成内层EtherType。新的两字节TPID是0x8100,标识这是一个带VLAN的帧;紧跟的两字节TCI包含3位PCP(优先级)、1位DEI(丢弃合格指示)和12位VID(VLAN ID)。PCP用于QoS队列调度,DEI可用于拥塞丢弃策略,VID范围0~4095,其中0和4095保留。
帧格式变化如下:
| 位置 | 不带VLAN | 带VLAN |
|---|---|---|
| 目的MAC | 6字节 | 6字节 |
| 源MAC | 6字节 | 6字节 |
| TPID | 无 | 2字节(0x8100) |
| TCI | 无 | 2字节(VLAN信息) |
| EtherType | 2字节 | 2字节 |
| Payload | 46~1500字节 | 42~1500字节 |
| FCS | 4字节 | 4字节 |
熟悉网络的人经常在这里翻车:有人以为Tag插在EtherType前面,其实它确实在源MAC之后、EtherType之前,这点很多图示都容易画错。
5.2 VLAN Tag对长度、MTU和FCS的影响
带Tag后帧头从14字节增至18字节,因此会产生几个连锁变化:
第一,如果二层最大帧限制是1518,而Payload还是1500,那么带Tag帧总长为1522字节,交换机必须支持接收至少1522字节的帧。大多数现代交换机和网卡默认支持,但老设备可能把1522帧当超大帧丢弃。这就是为什么在配置VLAN后,偶尔会出现“小包通、大包不通”的故障。
第二,如果Payload需要保持1500,MTU还是1500,但“二层帧总长”从1518变成1522。有些设备的MTU配置按L2总帧长来算,有些按IP MTU算,各厂商不一致。配置不对就会出现不兼容。遇到这种问题,先把链路两端MTU都调到1500,再逐个加VLAN标签验证。
第三,FCS的CRC计算包括Tag,因为Tag在源MAC之后、Payload之前。如果硬件支持VLAN offload,驱动可以在发送时插入Tag并重新计算FCS,接收时由硬件剥离Tag并校验,软件看不到。所以Wireshark有时看到带VLAN标记的帧,有时看不到,取决于抓包点是否已经剥离Tag。在交换机trunk口抓包,一般是带Tag的;在access口抓,Tag已经被剥掉了。
5.3 QinQ和Double Tag
运营商接入网常用的QinQ(802.1ad),是在原有802.1Q Tag外面再套一个外层Tag,TPID通常为0x88A8或0x9100,具体看运营商实现。帧格式变成:目的MAC 6字节、源MAC 6字节、外层Tag 4字节、内层Tag 4字节、EtherType 2字节、Payload、FCS 4字节,总长最大1526字节。
如果抓包工具不认识QinQ,它可能把第一个TPID当EtherType,后面的字段全部错位。调试时要先确认抓包工具支持的解析协议,或者手动按TPID识别。另外,QinQ的FCS计算范围同样包含两个Tag,别只看内层Tag去算,不然校验永远对不上。
6. 帧格式最容易翻车的三个地方:最小帧长、巨型帧、CRC计算
6.1 为什么必须凑够64字节
最小帧长64字节是从目的MAC开始到FCS结束。它源于早期共享式以太网CSMA/CD机制:假设A发帧到远端,B在另一头发帧冲突,A必须在自己发完之前检测到冲突信号。如果帧太短,A已经结束发送,冲突信号才回来,A无法感知,错误地认为发送成功。
以太网速率和最大冲突域长度的乘积决定了这个值。在现在的全双工交换网络中,因为发送和接收线对分离,基本不需要CSMA/CD,但为了兼容,帧格式仍保留最小64字节和填充逻辑。接收方收到小于64字节的帧,会当成runt frame丢弃,通常不会上报给上层。
调试时如果你看到大量Runt帧,就要检查链路协商、线缆、光模块等物理问题,而不是去应用层找原因。反过来,如果你构造测试帧时没填够46字节,很多网卡会直接拒绝发送,或者在接收端被静默丢弃。
6.2 巨型帧不是想开就能开
标准以太网帧的最大Payload是1500字节,对应非VLAN帧总长1518字节。为了减少CPU中断和协议占比,存储网络和虚拟机环境经常开启巨型帧(Jumbo Frame),把Payload提高到9000字节甚至更大,帧总长约9018字节。
好处是传输大文件时需要的报文数更少,CPU和PCIe开销降低;坏处是端到端路径上所有设备都必须支持,并且MTU要一致。实际排障中最常见的问题:服务器开了9000,交换机接口还是1500,小包正常,但大数据复制时速度奇慢或丢包。
开启巨型帧还会影响DMA buffer大小、驱动最大接收长度配置、FCS覆盖范围。很多人只改了网卡的MTU,没改驱动收包缓冲,结果出现随机丢包。所以遇到“大包不通小包通”,第一反应就是查端到端MTU,而不是先怀疑光衰或丢包率。
6.3 FCS计算范围的一个常见误会
FCS只覆盖目的MAC、源MAC、EtherType(或VLAN Tag)、Payload和Pad,不覆盖前导码、SFD,也不覆盖FCS自身。这个概念上很像快递单上的条码,它只校验单上的收件人信息,不会校验快递车本身。
IP/TCP/UDP层也各自有校验和,但它们的覆盖范围和MAC层不同,可以在报文里看到。常见误区是抓包时把
