1. 从"寄快递"理解网络层到底在忙什么
很多人学《计算机网络自顶向下方法》到网络层这一章时,会有一种突然从"看得见摸得着"掉进"全是抽象概念"的感觉。前面讲应用层,你能对着浏览器抓包看到HTTP报文;讲传输层,你能用netstat看到TCP连接状态;可一到网络层,满屏都是转发、路由、子网、掩码、AS、BGP,好像离日常开发一下子远了很多。
但网络层恰恰是整本书里最"硬核"也最值钱的一章。你平时遇到的大部分网络疑难杂症——跨机房延迟高、容器网络不通、双线机房回包走错、K8s Pod跨节点通信失败——根子几乎都在网络层。传输层只负责"端到端",而网络层负责的是"主机到主机"这一跳一跳的接力,它决定了你的数据包到底走哪条路、能不能到达、绕了多远。
我习惯用一个寄快递的类比来给新人讲网络层:传输层像是你把一堆文件装进一个信封,保证信封完整、按顺序送达;而网络层是快递公司的分拣和运输系统,它不关心信封里装的是什么,只关心"这个包裹从A城市发往B城市,中间要经过哪些中转站、走哪条干线、遇到堵车怎么绕"。IP协议就是那个"面单格式",路由协议就是"分拣中心的调度规则",而路由器就是一个个中转站。
这一章我前后读了三遍,第一遍是学生时代应付考试,第二遍是工作后排查线上问题回头补课,第三遍是带新人时逐节拆解。三次读下来的体会完全不同,这篇笔记我想把书里的骨架和我自己踩过的坑揉在一起讲,重点放在"这些机制为什么这么设计"以及"实际排障时怎么用"。如果你正在啃这一章,或者工作里被路由、子网、NAT这些问题反复折磨,这篇应该能帮你把零散的知识点串成一条线。
2. 转发与路由:网络层里最容易被混为一谈的两件事
2.1 转发是"当下这一跳",路由是"整条路径的规划"
书里开篇就把网络层的两个核心功能分得很清楚:转发(forwarding)和路由(routing)。这两个词中文看着像,英文也像,但它们是两个完全不同时间尺度上的动作,混在一起理解后面全乱。
转发是路由器收到一个包之后,查自己的转发表(forwarding table),决定从哪个接口把它扔出去。这是一个极快的、每个包都要做一次的动作,通常由硬件(ASIC)在纳秒到微秒级完成。你可以把它理解成快递分拣员扫一眼面单上的城市,直接扔到对应格口,他不需要知道整个运输网络长什么样。
路由则是运行路由协议,在路由器之间交换信息,计算出转发表里那些条目到底该填什么。这是一个慢得多的、秒级甚至分钟级的后台过程。它回答的是"去往某个网段,下一跳应该是谁"这种全局性问题。
提示:很多人第一次读会把"路由表"和"转发表"当成一个东西。在传统单机路由器上它们确实常常是同一张表,但在现代高性能路由器里,控制平面(跑路由协议、算路由)和转发平面(查表转发)是物理分离的,前者用通用CPU,后者用专用芯片。理解这个分离,你才能明白为什么路由协议收敛慢(几秒到几十秒)但转发从不受影响。
2.2 为什么要把控制平面和数据平面拆开
书里专门用一节讲SDN(软件定义网络),核心思想就是把控制平面从每台路由器里抽出来,集中到一个远程控制器上。这个设计不是学术玩具,它解决的是传统分布式路由的一个真实痛点:每台路由器各自为政,策略很难统一。
我举个实际场景。一个机房里有上百台交换机,你想实现"来自A网段的流量必须经过防火墙,来自B网段的直接放行"。在传统分布式方案里,你得在每一台设备上配ACL,改一次策略要登录上百台机器,还容易漏配。而在SDN架构下,控制器算好全局路径,直接下发流表,改策略只改一处。
这里的关键概念是流表(flow table)。传统转发表只匹配目的IP前缀,而OpenFlow这类流表可以匹配源IP、目的IP、源端口、目的端口、协议号等一堆字段的组合,动作也不只是"转发到某接口",还可以是丢弃、修改、上报控制器。匹配字段越丰富,能表达的策略就越细。
| 对比维度 | 传统分布式路由 | SDN集中控制 |
|---|---|---|
| 决策位置 | 每台路由器本地 | 远程控制器统一 |
| 匹配粒度 | 主要看目的前缀 | 多维字段组合 |
| 策略变更 | 逐台配置,慢且易错 | 控制器下发,快且一致 |
| 典型协议 | OSPF、BGP | OpenFlow、P4 |
| 适用场景 | 通用互联网 | 数据中心、园区网 |
2.3 一个容易忽略的细节:最长前缀匹配
转发表里存的不是单个IP,而是CIDR前缀,比如192.168.1.0/24。当一个包的目的地址是192.168.1.5时,可能同时匹配192.168.1.0/24、192.168.0.0/16、0.0.0.0/0三条表项。路由器选哪条?答案是最长前缀匹配(longest prefix match),也就是掩码最长、最具体的那条,这里就是/24。
这个规则看着简单,但它是很多"诡异路由"的根源。我遇到过一个问题:某台服务器访问内网某个服务总是超时,traceroute显示包被扔到了一个奇怪的网关。排查半天发现,运维为了做灰度,加了一条10.1.2.0/24的明细路由,但目标服务实际在10.1.0.0/16里,结果明细路由把流量引到了一个没配好的下一跳。删掉那条明细路由,问题立刻消失。
注意:排查路由问题时,永远先看"有没有更具体的明细路由覆盖了你的预期路径"。
ip route get <目标IP>这条命令能直接告诉你内核最终选了哪条路由,比对着路由表一行行看快得多。
3. IPv4数据报格式:每个字段背后都是一段历史
3.1 头部字段逐个拆解
IPv4头部固定20字节(不含选项),书里那张字段图值得反复看。我把几个关键字段和它们的"为什么"列一下:
- 版本号(4位):4表示IPv4,6表示IPv6。路由器靠它决定用哪套解析逻辑。
- 首部长度(4位):单位是4字节,所以最大值15×4=60字节。这就是为什么选项字段最多40字节。
- 服务类型(TOS,8位):早期用来标记优先级,后来被DiffServ复用做QoS标记。你配的DSCP值就填在这里。
- 总长度(16位):头部+数据的总字节数,最大65535。这也是为什么单个IP包理论上限就是64KB。
- 标识、标志、片偏移:这三个字段专为分片服务,后面单独讲。
- 生存时间(TTL,8位):每经过一个路由器减1,减到0就丢弃并回ICMP超时。它的本意是防止包在网络里无限循环,现在被traceroute玩出了花。
- 协议(8位):6是TCP,17是UDP,1是ICMP。它告诉接收方"数据部分该交给谁"。
- 首部校验和(16位):只校验头部,不校验数据。每跳都要重算,因为TTL变了。
3.2 TTL:一个被玩坏的字段
TTL的设计初衷很朴素:网络里如果有环路,包会永远转下去,加个计数器让它自然死亡。但traceroute把它变成了探测路径的神器——发送TTL=1的包,第一个路由器减到0回ICMP超时,于是你就知道了第一跳是谁;再发TTL=2,就知道第二跳。如此递增,整条路径就画出来了。
实际排障时,TTL还能帮你判断一些奇怪现象。比如你ping某台机器,返回的TTL是64,说明中间没经过路由器(或者经过的路由器不改TTL,极少见);返回TTL是63,说明经过了一跳;返回TTL是250左右,说明跨了很多跳。不同操作系统的初始TTL不同:Linux通常是64,Windows历史上是128,某些网络设备是255。通过返回TTL反推初始值,能大致猜出对端是什么系统。
提示:有些网络会故意把TTL改成一个固定值来隐藏拓扑,这时候靠TTL判断跳数就不准了。别把它当铁律,当参考。
3.3 分片:一个正在被淘汰但必须懂的机制
MTU(最大传输单元)是链路层能承载的最大帧,以太网默认1500字节。当一个IP包比出口链路的MTU大时,路由器要么分片,要么丢弃并回ICMP"需要分片但DF置位"。
分片机制本身不难:把数据切成若干块,每块加上IP头,用标识字段标记"我们是一家人",用片偏移标记"我在原包里的位置",用MF(更多分片)标志标记"后面还有没有"。接收方靠这些字段重组。
但分片是个麻烦制造者。首先,只要有一个分片丢了,整个包就得重传;其次,分片重组要消耗接收方内存,容易被攻击者利用(经典的Teardrop攻击就是构造畸形分片);再次,NAT和防火墙对分片的处理经常出问题。
所以现代实践里,大家更倾向于路径MTU发现(PMTUD):发送方先把DF(不分片)置位,如果中间某跳MTU不够,路由器回一个ICMP"需要分片"消息并带上自己的MTU,发送方据此调小包大小。问题是很多防火墙把这类ICMP给拦了,导致PMTUD失效,出现"小包能通、大包卡死"的经典故障。
我踩过这个坑:某次跨机房调用接口,GET请求正常,POST大一点的数据就超时。抓包发现TCP握手正常,但一发大数据就卡住。最后定位到中间一条隧道MTU只有1400,而PMTUD的ICMP被拦了。解决办法是在服务器上把MSS钳到1360(iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu),问题解决。
4. IPv4地址与CIDR:子网划分是基本功不是选修课
4.1 从分类地址到CIDR的演进
早期IPv4用分类地址:A类以0开头(/8),B类以10开头(/16),C类以110开头(/24)。这个设计的问题是按类分配太粗,一个B类地址段有6万多个地址,很多组织根本用不完,造成巨大浪费。
CIDR(无类别域间路由)把"类别"这个概念彻底扔掉,改成用前缀/长度表示,比如192.168.1.0/24。前缀长度可以是0到32之间的任意值,分配粒度一下子细了。同时CIDR支持路由聚合:把192.168.0.0/24到192.168.3.0/24四条聚合成192.168.0.0/22一条,大幅缩小全球路由表规模。
4.2 子网划分的实操方法
给一个网段让你划分子网,是面试和实际工作里的高频题。方法其实很机械:
假设给你192.168.10.0/24,要划分成4个子网。你需要借2位主机位当子网位(2²=4),新前缀就是/26,每个子网64个地址(含网络号和广播号,可用62个)。
| 子网 | 网络地址 | 可用范围 | 广播地址 |
|---|---|---|---|
| 1 | 192.168.10.0/26 | .1 - .62 | .63 |
| 2 | 192.168.10.64/26 | .65 - .126 | .127 |
| 3 | 192.168.10.128/26 | .129 - .190 | .191 |
| 4 | 192.168.10.192/26 | .193 - .254 | .255 |
记住一个速算技巧:块大小 = 256 - 掩码最后一段的值。比如/26的掩码是255.255.255.192,块大小=256-192=64,子网就按64递增。
4.3 私有地址与NAT:为什么你家路由器能带一堆设备
私有地址(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)是RFC 1918保留的,不能在公网路由。你家里所有设备用的都是192.168.x.x,但访问外网时看起来都来自同一个公网IP,这就是NAT(网络地址转换)的功劳。
NAT的基本原理:出口路由器维护一张转换表,把内网(私有IP, 端口)映射成(公网IP, 新端口)。回包到达时,根据目的端口反查表,改回私有IP转发给内网设备。
NAT解决了IPv4地址枯竭的燃眉之急,但也带来一堆问题:
- 破坏了端到端原则:外网无法主动连接内网设备,除非配端口映射。
- P2P应用难做:两个都在NAT后的设备想直连,需要打洞(STUN/TURN那一套)。
- 协议兼容性差:像FTP这种在载荷里带IP地址的协议,需要ALG(应用层网关)特殊处理,而ALG经常出bug。
注意:排查"内网服务外网访问不了"时,先确认NAT映射配了没、端口有没有被占用、防火墙放行了没。这三步能解决八成问题。
5. 路由算法与协议:从理想算法到真实互联网
5.1 链路状态 vs 距离向量:两种世界观的碰撞
路由算法分两大类,理解它们的差异比背具体协议重要得多。
链路状态(LS)算法,代表是OSPF。每台路由器把自己直连链路的状态(和谁相连、开销多少)泛洪给全网,最终每台路由器都拿到一张完整的网络拓扑图,然后用Dijkstra算法算出到所有目的地的最短路径。它的特点是"全局视野、各自计算"。
距离向量(DV)算法,代表是RIP。每台路由器只知道自己到邻居的开销,以及邻居告诉它的"我到各个目的地有多远"。它靠邻居之间反复交换向量来逐步收敛。特点是"局部信息、迭代传播"。
DV算法有个著名的问题叫计数到无穷(count-to-infinity)。假设A-B-C三台路由器串联,A到某网络的路径断了,B和C会互相以为对方还有路,开销一点点往上加,直到加到"无穷"(实际用一个有限大值表示)才停。解决办法有水平分割、毒性逆转、触发更新等,但都只是缓解。
| 对比项 | 链路状态(OSPF) | 距离向量(RIP) |
|---|---|---|
| 信息范围 | 全网拓扑 | 仅邻居向量 |
| 收敛速度 | 快 | 慢,易环路 |
| 资源消耗 | CPU/内存高 | 低 |
| 适用规模 | 大型企业网 | 小型网络 |
| 算法 | Dijkstra | Bellman-Ford |
5.2 OSPF为什么能成为企业网主力
OSPF(开放最短路径优先)能统治企业网,靠的是几个实用设计:
分层区域(Area)。OSPF把网络划成多个区域,区域0是骨干,其他区域必须和骨干相连。区域内的路由器只维护本区域的详细拓扑,跨区域只传汇总路由。这样一台路由器不需要知道全网每一根线,规模就上去了。
多种链路开销度量。开销可以基于带宽、延迟等,比RIP单纯数跳数合理得多。RIP里一条1Gbps链路和一条10Mbps链路都算"1跳",显然荒谬。
等价多路径(ECMP)。如果到同一目的地有多条等开销路径,OSPF可以同时用,做负载均衡。这在数据中心很常用。
快速收敛机制。通过Hello包检测邻居、LSA泛洪、SPF重算,配合BFD(双向转发检测)能做到亚秒级故障切换。
5.3 BGP:互联网的"外交协议"
如果说OSPF是企业内部的"内政",那BGP就是连接各个自治系统(AS)的"外交"。全球互联网就是几万个AS通过BGP互相宣告"我这边有哪些网段可达"拼起来的。
BGP是路径向量协议,它宣告的不是距离,而是"到达某网段要经过哪些AS"(AS-PATH)。这个设计能天然防环:如果收到的路径里已经有自己的AS号,就丢弃。
BGP选路不是单纯选最短,而是按一堆属性依次比较:权重、本地优先级、本地起源、AS路径长度、起源类型、MED、eBGP优先于iBGP、IGP开销、router ID……这套规则复杂到被称为"BGP选路的九条军规"。实际排障时,show ip bgp <前缀>能看到每条路径的属性,逐条对比就能知道为什么选了某条。
提示:BGP收敛慢是出了名的,全球路由震荡可能要几十秒到几分钟。所以大型网络会用BGP多路径、路由反射器、前缀聚合等手段来提升稳定性。别指望BGP像OSPF那样秒级收敛。
6. 排障实战:把网络层知识用起来
6.1 一套可复用的排查链路
遇到"网络不通"类问题,我习惯按这个顺序走,从下往上、从近到远:
- 确认本机网络层配置:
ip addr看IP和掩码对不对,ip route看默认网关在不在。 - 确认同网段可达:ping网关。不通说明二层或本机配置有问题。
- 确认跨网段路由:
ip route get <目标>看内核选了哪条路,traceroute看在哪一跳断。 - 确认目标端口:
telnet <IP> <端口>或nc -zv,区分是网络不通还是服务没起。 - 抓包定位:
tcpdump -i any host <目标> -nn,看包发出去了没、有没有回、回的什么。
这套流程能覆盖绝大多数问题。关键是每一步都要有明确的预期结果,不符合预期就停在那一步深挖,别跳。
6.2 三个真实踩坑案例
案例一:容器跨节点不通。某次K8s集群里,同节点Pod互通,跨节点不通。排查发现节点间路由没配——容器网段的路由没有通过CNI插件下发到对端节点。手动加一条ip route add <对端Pod网段> via <对端节点IP>立刻恢复。根因是CNI插件配置里漏了一个网段声明。
案例二:双线机房回包走错。服务器有电信和联通两条线,用户从联通进来,回包却从电信出去,导致延迟高。这是典型的非对称路由。解决办法是用策略路由(policy routing),根据源地址选出口:ip rule add from <联通用户网段> table 100,再在table 100里配联通网关。
案例三:MTU导致的"大包卡死"。前面提过的PMTUD失效问题。定位方法是ping -M do -s 1472 <目标>(1472+28=1500),逐步减小size找到能通的最大值,就知道路径MTU了。
6.3 常用命令速查
| 目的 | 命令 |
|---|---|
| 看本机地址 | ip addr show |
| 看路由表 | ip route show |
| 查某目标走哪条路由 | ip route get 8.8.8.8 |
| 看ARP表 | ip neigh show |
| 路径追踪 | traceroute/mtr |
| 抓包 | tcpdump -i any -nn host x.x.x.x |
| 看监听端口 | ss -tulnp |
| 测路径MTU | ping -M do -s <size> <目标> |
7. 读完这一章之后我真正带走的东西
网络层这一章信息密度极大,第一遍读很容易陷进字段和协议的细节里出不来。我的建议是:先抓住"转发vs路由"这条主线,再理解IP地址和CIDR这套寻址体系,最后把OSPF和BGP当成两种不同规模的解决方案来记。字段细节可以查,但主线逻辑必须刻在脑子里。
另外,这一章的知识特别适合"边读边动手"。你完全可以在本地用Linux网络命名空间搭一个小型拓扑,跑一跑OSPF(用FRR或Quagga),抓抓包看看LSA长什么样。纸上读十遍不如动手配一遍,路由协议的行为只有跑起来才能真切感受到。
我个人最大的收获,是学会了用"分层+分平面"的视角看网络问题。控制平面和数据平面分离、转发和路由分离、AS内部和AS之间分离——这种"把复杂系统拆成职责清晰的层"的思路,其实不只适用于网络,做任何分布式系统设计都用得上。这大概就是经典教材的价值:它教的不只是知识点,更是一套看问题的方法。