3. 网络层核心协议:IP协议首部、IPv4与IPv6、子网划分、路由原理
各位同学,咱们今天聊聊网络层。说实话,这是整个网络协议栈里最核心的一层。你想想看,数据链路层只管同一根网线上的通信,但互联网之所以叫“互联”网,靠的就是网络层把全世界各种不同的网络串起来。我做了这么多年嵌入式开发,调试过无数网络问题,十有八九最后都追到了IP层。所以这一章,咱们得把它吃透。
3.1 IP协议首部:数据包的“身份证”
每个IP数据包,都带一个首部。这个首部就像快递单,写明了从哪来、到哪去、怎么处理。我习惯把IP首部拆成两部分看:固定部分20字节,加上可选的选项部分。
固定部分里,有几个字段我特别想强调:
- 版本号:4位,IPv4就是4,IPv6就是6。这个字段决定了后面怎么解析。
- 首部长度:4位,单位是4字节。最小值是5(20字节),最大值是15(60字节)。
- 总长度:16位,整个数据包的长度,包括首部和数据。单位是字节。最大值65535,但实际中很少有超过1500的,因为以太网帧有MTU限制。
- 标识、标志、片偏移:这三个字段是给分片用的。我在项目中遇到过一个问题,某个传感器设备发的数据包太大,路由器分片后,接收端重组失败。后来发现是标志位没设对,DF位(Don't Fragment)被置1了,导致大包直接被丢弃。嗯,这里要注意,很多嵌入式设备默认DF位是0,但有些安全策略会强制分片,你得搞清楚。
- 生存时间(TTL):8位,每经过一个路由器减1,减到0就丢弃。这玩意是为了防止数据包在网络里死循环。我记得有一次调试一个路由环路问题,抓包看到TTL从64一路减到1,最后包丢了。顺着这个线索,才找到是某台路由器的路由表配错了。
- 协议号:8位,标识上层协议。6是TCP,17是UDP,1是ICMP。这个字段在解包时至关重要,你写协议栈的时候,要根据它把数据交给不同的处理函数。
- 首部校验和:16位,只校验首部,不校验数据。为什么?因为数据部分有上层协议(TCP/UDP)自己校验,没必要重复。我刚开始写代码时,总想偷懒不校验这个字段,结果被一个网络丢包问题折磨了两天——原来是某个路由器把首部改坏了,我没发现。
核心要点:IP首部是网络层的“门面”。你写协议栈时,解析和构造IP首部是基本功。我建议你手写一遍IP首部的结构体,把每个字段的偏移和长度记清楚。
3.2 IPv4与IPv6:两代协议的恩怨
IPv4,32位地址,理论上43亿个。听起来很多,但互联网发展太快,早就分完了。IPv6,128位地址,号称能给地球上每一粒沙子分配一个IP。我个人的看法是,IPv6是未来,但短期内还得跟IPv4共存。
咱们对比一下:
| 特性 | IPv4 | IPv6 |
|---|---|---|
| 地址长度 | 32位 | 128位 |
| 地址表示 | 点分十进制,如192.168.1.1 | 冒号十六进制,如2001:db8::1 |
| 首部长度 | 20-60字节(可变) | 40字节(固定) |
| 分片处理 | 由路由器和发送端处理 | 只由发送端处理 |
| 校验和 | 有 | 无(依赖上层) |
| 自动配置 | DHCP | 无状态地址自动配置(SLAAC) |
为什么IPv6首部固定40字节?说白了,就是把那些不常用的字段(比如选项)挪到了扩展首部里。这样路由器处理起来更快,不用每次都得算首部长度。我在一个物联网项目里用过IPv6,设备数量上万,如果用IPv4,NAT(网络地址转换)配置能让人崩溃。IPv6每个设备一个公网IP,省事多了。
避坑指南:我曾经在一个嵌入式设备上同时支持IPv4和IPv6,结果发现协议栈的缓冲区大小没配好。IPv6首部虽然固定40字节,但加上扩展首部可能更大。建议你给缓冲区留够余量,至少1500字节起步。
3.3 子网划分:把大网络切成小段
子网划分,说白了就是把一个大的IP地址块,切成几个小的。为什么要切?因为网络太大了,广播风暴受不了。你想想看,一个公司几千台电脑都在同一个子网里,谁发个广播包,所有人都得处理,那网络就废了。
子网划分靠的是子网掩码。子网掩码是一个32位的数,跟IP地址做“与”运算,得到网络地址。比如IP是192.168.1.100,子网掩码是255.255.255.0,那网络地址就是192.168.1.0。主机部分就是最后8位,可以分配254个地址(全0和全1不能用)。
我举个例子:假设你有一个C类地址192.168.1.0/24,想分成4个子网。那你就得从主机位借2位作为子网位。子网掩码变成255.255.255.192(/26)。每个子网有62个可用地址。具体划分如下:
- 子网1:192.168.1.0/26,可用地址192.168.1.1 - 192.168.1.62
- 子网2:192.168.1.64/26,可用地址192.168.1.65 - 192.168.1.126
- 子网3:192.168.1.128/26,可用地址192.168.1.129 - 192.168.1.190
- 子网4:192.168.1.192/26,可用地址192.168.1.193 - 192.168.1.254
我在项目中遇到过一个问题:某个设备配置了静态IP,但子网掩码写错了,导致它以为目标地址在同一个子网里,直接发ARP请求,结果对方在另一个子网,根本收不到。嗯,这种问题排查起来很隐蔽,你得先确认子网掩码对不对。
注意:子网划分时,每个子网的第一个地址是网络地址,最后一个地址是广播地址,都不能分配给主机。这是新手最容易犯的错误。
3.4 路由原理:数据包怎么找到路
路由,就是数据包从源到目的地的路径选择。每个路由器都维护一张路由表,里面记录着:目标网络、下一跳地址、出接口。数据包到了路由器,路由器查路由表,找到匹配的条目,然后从对应的接口转发出去。
路由表匹配的原则是“最长前缀匹配”。什么意思?就是如果有多个条目都匹配目标地址,那就选子网掩码最长的那个。比如目标地址是192.168.1.100,路由表里有两条:
- 192.168.1.0/24,下一跳192.168.1.1
- 192.168.1.0/26,下一跳192.168.1.65
那就会选第二条,因为/26比/24更精确。我刚开始学的时候,总以为匹配第一条就行了,结果发现数据包走错了路。后来才明白,最长前缀匹配是为了保证最精确的路由优先。
路由协议分两大类:
- 静态路由:管理员手动配置。适合小网络,简单可靠。我习惯在嵌入式设备上用静态路由,因为资源有限,跑动态路由协议太费CPU。
- 动态路由:路由器之间自动交换路由信息。常见的有RIP(基于跳数)、OSPF(基于链路状态)、BGP(基于路径属性)。大网络必须用动态路由,否则管理员得累死。
我记得有一次调试一个多跳网络,数据包总是丢。抓包发现,中间某个路由器把TTL减到0后丢弃了,但没发ICMP超时消息。后来查配置,发现那个路由器上把ICMP给禁了。嗯,这种问题很坑,建议你在调试时先确认ICMP是开启的。
实战建议:写嵌入式路由代码时,路由表可以用哈希表或者Trie树实现。我推荐Trie树,因为最长前缀匹配在Trie树上实现起来很自然,查找效率也高。但要注意内存占用,嵌入式设备内存有限,别把路由表搞太大。
好了,这一章的内容就到这。IP协议是网络层的基石,你把它搞懂了,后面学TCP、UDP、ICMP都会轻松很多。下一章咱们聊聊ARP协议,就是那个把IP地址翻译成MAC地址的“翻译官”。