内网能通、外网不通,新机器插上网线半天拿不到地址,视频会议服务器明明在线客户端就是连不上,抓包看到一堆Destination Unreachable却不知道谁在发——这些年我处理过的网络报障里,十有七八最后都会绕回到同一组协议上。DHCP、NAT、ICMP、IGMP,这四个在教科书里被分在不同章节、看起来没什么关系的东西,实际上是一条完整的链条:DHCP 负责给一台设备一个合法的身份,NAT 负责让它带着这个身份走出去,ICMP 负责在出问题时告诉你它死在了哪一段,IGMP 负责在"一对多"的场景里别把带宽白白烧掉。你单看任何一个协议都能背出报文格式,但真正让人头大的是它们之间的交接处——地址租约到期那一刻 NAT 会话表怎么变、ICMP 被安全策略掐掉之后大包为什么卡住、组播流量为什么在交换机上变成全端口泛洪。这篇东西就是从工程现场的角度,把四大支柱的职责、参数、配置和踩坑点拆开讲清楚。
内容适合谁看:刚接手企业网运维、手里有一堆"能 ping 通但业务不通"的工单要处理的同学;做嵌入式或物联网设备、需要自己实现一套最简协议栈的开发者;以及准备网络方向面试、想把零散知识点串成一条线的人。我会尽量少讲纯理论,多讲"这个参数为什么是这个值""这条命令为什么这么敲""这个现象大概率是什么原因"。所有命令基于通用 Linux 工具链和通用网络设备概念,不绑定具体厂商。
1. 四个协议到底各自管哪一段
1.1 从一个典型的选址故事讲起
一台新设备接入网络,它面对的第一个问题是"我叫什么"。物理层给不了答案,链路层只能给一个出厂烧死的 MAC 地址。这时候 DHCP 登场,用四步交互把一个 IP、一个掩码、一个网关、一个 DNS 递到设备手上。到这里设备有了内网身份,能在二层和三层本地通信了。
接着它要访问外部资源,问题变成"我这个私有地址怎么出去"。私有地址段在公网上不可路由,必须有人做地址翻译,这就是 NAT 的活。NAT 在出口设备上维护一张映射表,把内网源地址+源端口换成公网地址+另一个端口,回程流量再按表翻回来。
再往下,如果中间某一跳出问题了、某个包太大过不去、某个端口没人监听,反馈信息靠谁传?靠 ICMP。它是 IP 层的"系统消息",不承载业务数据,只承载状态:我到不了、包太大、超时了、重定向走那边。
最后,如果业务本身是"一份数据发给很多人"——比如内网的视频直播、监控分发、行情推送——点对点复制会把出口带宽吃干净,这时候用组播,而组播能不能高效工作,取决于 IGMP 有没有把"谁要这份数据"这件事告诉上游路由器。
把这四件事串起来看,你会发现它们不是并列关系,而是接力关系:地址分配 → 地址转换 → 状态反馈 → 高效分发。排障的时候按这个顺序走,基本不会迷路。
1.2 职责边界与分层定位对照
很多人把 NAT 和 DHCP 混在一起说,因为家用路由器上它们都跑在同一个盒子里;也有人把 ICMP 当成"ping 协议",觉得禁掉就没风险。先把边界划清楚,后面所有讨论才有基础。
| 协议 | 工作层次 | 核心职责 | 典型触发时机 | 传输方式 |
|---|---|---|---|---|
| DHCP | 应用层(承载于 UDP 67/68) | 动态分配 IP、掩码、网关、DNS 等参数 | 设备上线、租约到期、链路状态变化 | 初期全广播,可借中继单播 |
| NAT | 网络层/传输层之间 | 私有地址与公网地址的映射与端口复用 | 数据包跨越内外网边界 | 透明,对端点不可见 |
| ICMP | 网络层 | 差错报告与连通性探测 | 转发失败、TTL 耗尽、需要分片、主动探测 | 封装在 IP 包内,协议号 1 |
| IGMP | 网络层(承载于 IP 包内) | 主机向本地路由器声明组播组成员关系 | 加入/离开组播组、成员查询 | 本地链路内传输,不跨路由器转发 |
这张表最值得记的一点:IGMP 的作用域只在本链路。它不负责把组播数据送到远端,只负责告诉直连的路由器"我这台机器想收这个组"。跨网段的组播分发要靠组播路由协议(PIM 之类),那是另一个话题。
注意:IGMP 报文本身 TTL 固定为 1,路由器收到不会转发。所以你在核心交换机上抓不到终端发出的 IGMP 报文,除非那台终端就接在这台交换机上。
2. DHCP:地址从哪来,租约怎么算
2.1 DORA 四步交互里每一步的真实意图
DHCP 的经典四步缩写是 DORA:Discover、Offer、Request、Ack。背下来很容易,但每一步"为什么这么设计"决定了你排障时的判断方向。
第一步Discover,客户端发广播。为什么是广播?因为它此刻还没有 IP,源地址只能填 0.0.0.0,目的地址只能填 255.255.255.255,目标端口 67。这个包的意思是"附近有没有 DHCP 服务器,我想要个地址"。
第二步Offer,服务器回应,里面带上准备分配的地址、掩码、租约时长、各种 Option。注意此时客户端还没确认要不要,服务器只是"报个价"。如果网段里有多台 DHCP 服务器(比如有人私接了路由器),客户端可能收到多个 Offer。
第三步Request,客户端选定一个 Offer 后,还是用广播发出请求。这里的关键设计意图是:广播是为了让所有发过 Offer 的服务器都听到,没被选中的服务器就知道"这个客户端不要我的地址",从而把自己预留的地址回收。很多资料忽略这一点,导致有人以为 Request 是单播,其实规范要求广播(除非客户端已经拿到过地址并处于续租状态)。
第四步Ack,服务器确认,客户端正式启用地址。同时服务器会做一次 ARP 探测或 ICMP 探测,确认这个地址在网段内没被占用,如果发现冲突就回 NAK 或者把地址标记为冲突。
除了这四个,还有几个报文必须知道:
- NAK:服务器拒绝了客户端的请求,比如客户端请求的地址不在本网段、或者租约已经失效。客户端收到 NAK 必须立刻放弃当前地址,重新走 Discover。
- Decline:客户端通过 ARP 发现拿到的地址已经被别人用了,主动告诉服务器"这地址有冲突,别给我了"。看到大量 Decline 说明网段里有手工配的静态地址冲突。
- Release:客户端主动释放地址,关机时常见。
- Inform:客户端已经有地址了,只想问一些额外参数(比如 DNS),不需要分配。
2.2 租约时长怎么定,两个续租时间点怎么算
租约时长是 DHCP 里最容易被拍脑袋决定的参数。设太短,广播风暴、服务器压力大、地址频繁漂移;设太长,设备搬走后地址长期占着不回收。
工程上的经验值大概是这样的:
| 场景 | 建议租约 | 理由 |
|---|---|---|
| 办公网有线终端 | 8 小时到 1 天 | 覆盖一个工作日,设备下班关机后地址能回收 |
| 员工无线网 | 2 到 8 小时 | 移动设备多,地址周转快,用户带设备离开后地址需要较快释放 |
| 访客网络 | 1 到 4 小时 | 池子小、人员流动大 |
| 服务器/打印机等固定设备 | 静态绑定或超长租约 | 避免地址变化导致访问异常 |
| 物联网/传感器网段 | 1 到 7 天 | 设备数量大但位置固定,减少续租开销 |
租约里有两个隐藏的时间点,由 Option 58(T1,续租时间)和 Option 59(T2,重绑定时间)控制。如果不显式指定,默认 T1 = 租约的 50%,T2 = 租约的 87.5%。
举例:租约 8 小时 = 28800 秒,则 T1 = 14400 秒(4 小时),T2 = 25200 秒(7 小时)。
到了 T1,客户端进入RENEWING状态,直接向当初给它地址的那台服务器发单播 Request。如果服务器还活着,直接续上,一切静默完成。到了 T2 还没续上,客户端进入REBINDING状态,改为广播 Request,任何一台 DHCP 服务器都可以接手。如果租约彻底到期还没续上,客户端必须停止使用该地址,退回 0.0.0.0 重新走 Discover。
这个机制的意义在于容错:T1 到 T2 之间给了客户端 37.5% 的租约时间去尝试单播续租,失败了还能靠广播找别的服务器兜底。所以如果你在抓包里看到大量广播 Request,说明当初那台服务器在 T1 阶段没响应——这时候去查服务器进程和 DHCP 中继,方向就对了。
2.3 实操:抓包、配置与三个高频坑
先看抓包。定位 DHCP 问题,最直接的一条命令:
tcpdump -i eth0 -n -vv 'port 67 or port 68'正常情况下应该能看到完整的 DORA 四包,时间间隔在毫秒级。如果只看到 Discover 没有 Offer,问题在服务器侧或者中间设备没放行;如果看到 Offer 但客户端不发 Request,通常是客户端侧选路逻辑出错或者收到了更好的 Offer。
在 Linux 上手工触发一次完整租约流程,比等系统自动跑更快:
# 释放当前租约 dhclient -r eth0 # 前台详细模式重新获取,能看到每个阶段 dhclient -v -1 eth0-1表示只尝试一次,失败就退出,避免脚本卡住。
服务器侧如果用 dnsmasq 做轻量级部署,一个能用的最小配置大概是这样:
# /etc/dnsmasq.d/lan.conf interface=eth1 bind-interfaces dhcp-range=192.168.50.100,192.168.50.200,255.255.255.0,8h dhcp-option=3,192.168.50.1 # 网关 dhcp-option=6,192.168.50.1,192.168.50.2 # 两个 DNS dhcp-option=28,192.168.50.255 # 广播地址,一般可省 dhcp-leasefile=/var/lib/misc/dnsmasq.leases dhcp-authoritative # 本网段只有这一台,直接权威应答8h就是租约时长,dhcp-authoritative这个开关值得强调:在只有一个 DHCP 服务器的网段里打开它,服务器收到未知客户端的 Request 时会直接 ACK 而不是忽略,能让续租和换网后的重新获取快很多。
第一个坑,地址池耗尽。现象是终端反复拿不到地址,抓包只有 Discover。查服务器日志能确认:
journalctl -u dnsmasq -n 100 --no-pager | grep -i "no address" # 或者看租约文件里已用数量 wc -l /var/lib/misc/dnsmasq.leases如果池子确实满了,别急着扩池子,先看看是不是有一大批本该是静态地址的设备在动态池里漂着,或者某台设备因为网卡故障反复申请导致地址被占。
第二个坑,私接路由器抢答。这是办公网最经典的事故。有人把自己家里的无线路由器 LAN 口插到办公网口上,那台路由器的 DHCP 服务开始给周围设备发 Offer,网关指向他自己,于是整个片区的设备上不了网。排查手法是查交换机 MAC 地址表,找出 DHCP Offer 的源 MAC 出现在哪个端口。
tcpdump -i eth0 -n -e 'udp port 67' # -e 打印 MAC 头,看 Offer 是从哪个 MAC 发出来的防御层面,接入交换机上开DHCP Snooping,把上联口设为信任口,其余口设为非信任口,非信任口只允许 Discover/Request 通过,不允许 Offer/Ack 通过。同时绑定 IP-MAC-Port 三元组,防止中间人。这是接入层必备的一道防线。
第三个坑,中继(Relay)配置漏了 Option 82 或源接口。跨网段分配地址时,DHCP 服务器收到的 Discover 源地址是路由器的中继接口地址,需要靠 Option 82(或 giaddr 字段)判断这个客户端属于哪个地址池。如果中继设备的 giaddr 填错了,服务器会给客户端分一个别的网段的地址,客户端一启用就各种不通。判断方法很简单:拿到地址后看掩码和网关是否匹配它实际所在的网段,不匹配就是分错池了。
实操心得:把 DHCP 服务器日志的级别调到能看到每个事务的 MAC 和分配结果,出问题时用 MAC 反查日志,比在几百台终端里瞎找快得多。日志能保留 7 天以上,这是排障时最值钱的证据链。
3. NAT:地址不够用之后的翻译术
3.1 三种形态:静态、动态、端口复用
NAT 不是单一技术,而是一族技术的统称,实现方式差别很大。
静态 NAT,一对一固定映射,内网 192.168.1.10 永远对应公网 203.0.113.10。适合需要对外提供服务、但又不希望暴露真实内网拓扑的场景。配置简单,双向主动发起都行。
动态 NAT,从一个公网地址池里动态取一个映射给内网主机,用完归还。比静态灵活,但一个内网主机独占一个公网地址,地址利用率还是 1:1。
NAPT(网络地址端口转换),也就是大多数人说的"端口映射"或 PAT。多个内网主机共享一个公网地址,靠源端口号区分。这是家用路由器和绝大多数企业出口的实际做法。原理不复杂:维护一张(内网IP, 内网端口, 目标IP, 目标端口, 协议) → (公网IP, 公网端口)的表,出方向做替换,回方向查表还原。
真正的价值在于端口号是 16 位,也就是理论上一个公网 IP 能承载 65535 个并发映射。扣掉 1-1023 的知名端口区(实际大部分 NAT 实现会避开这一段),可用大概 6 万个。这个数字决定了一个公网 IP 能带多少内网设备,是很现实的规划依据。
3.2 连接跟踪表和会话数的估算方法
Linux 上的 NAT 建立在 conntrack 之上,这张表才是 NAT 的心脏。
# 看当前会话数 cat /proc/sys/net/netfilter/nf_conntrack_count # 看上限 cat /proc/sys/net/netfilter/nf_conntrack_max # 查某个内网IP的所有会话 conntrack -L -s 192.168.50.100 # 实时观察新建速率,排查是否有异常高频连接 conntrack -E -e NEW | head -50会话数怎么估?关键要理解一个事实:同一个 (源IP, 源端口) 对不同目标可以复用。所以限制不在"内网设备数",而在"单台设备对单一目标地址+端口能开多少会话"。
举个具体例子。假设出口只有一个公网 IP,可用端口按 60000 算,那么对同一个目标203.0.113.80:443,全内网最多同时只能建立 60000 条连接——因为回程要能区分,只能靠公网端口。但对不同目标,端口可以重复使用,所以总量上限变成 60000 乘以目标数量。
这意味着:如果你的业务是"大量设备同时访问同一个云端服务",那 60000 就是硬顶;如果业务分散在几千个不同目标上,那基本碰不到上限。实际排障中遇到"连接莫名建立失败、日志里 time wait 一堆",十有八九是这种"单目标热点"造成的端口耗尽。
会话超时也很好查和调整:
sysctl net.netfilter.nf_conntrack_tcp_timeout_established # 默认 432000 秒,5 天 sysctl net.netfilter.nf_conntrack_udp_timeout # 默认 30 秒 sysctl net.netfilter.nf_conntrack_udp_timeout_stream # 默认 120 秒长连接业务(比如某些即时通讯、数据库连接池、游戏房间)经常需要把established调大,否则中间设备提前老化会话,表现为"连接还在但数据发不出去",非常难查。有个经验数据:会话数超过nf_conntrack_max的 70% 就该扩容或者查异常了,接近 100% 会出现丢包且日志里刷nf_conntrack: table full, dropping packet。
3.3 实操:iptables 配置与两个隐蔽的天坑
最常用的两种规则,出方向做源地址伪装,入方向做端口映射:
# 开启转发 sysctl -w net.ipv4.ip_forward=1 # 出方向:内网段伪装成 eth0 的地址出去 iptables -t nat -A POSTROUTING -s 192.168.50.0/24 -o eth0 -j MASQUERADE # 入方向:把公网 8080 映射到内网 192.168.50.10:80 iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 \ -j DNAT --to-destination 192.168.50.10:80 # 别忘了放行转发链 iptables -A FORWARD -i eth0 -o eth1 -p tcp --dport 80 -j ACCEPT iptables -A FORWARD -i eth1 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPTMASQUERADE和SNAT --to-source的区别值得说一下:前者会自动跟随出口接口的地址变化(适合动态获取公网地址的线路),后者写死地址性能稍好但地址一变规则就废。出口是拨号或者地址会变的场景,一律用MASQUERADE。
第一个天坑,ALG(应用层网关)。有些协议会把 IP 和端口写在应用层载荷里,比如 FTP 的 PORT 命令、SIP 的 SDP 描述。NAT 只改 IP 头不改载荷,对方拿到的还是内网地址,连接自然失败。传统做法是开启对应协议的 ALG 模块,但那玩意儿经常带来新问题(比如 SIP 单向语音、FTP 大文件传输卡死)。更稳的方案是换成被动模式 FTP、或者把服务端放到有公网地址的 DMZ 区,绕开 ALG。
第二个天坑,双 NAT。光猫做一层 NAT,后面接的路由器再做一层,端口映射要在两层都配,而且很容易出现"内网能访问、外网访问不了"。判断方法是从外网侧看出口公网地址,和路由器 WAN 口拿到的地址是不是同一个;不一致就说明存在上游 NAT。要么把光猫改成桥接,要么在内层路由器上再补一层映射。这个问题处理起来很费时间,但一旦确认,解决起来很干脆。
还有一点值得单独提:NAT 对端到端通信的破坏是根本性的。P2P 打洞、语音视频的直连、某些游戏的对战,都依赖双方能互相主动发起连接。有 NAT 就必须有 STUN/TURN 这类辅助机制,或者干脆走中继服务器。做这类业务时,把"没有公网地址时如何降级"当成设计的一部分,而不是后期打补丁。
实操心得:手工测端口映射别只在服务器本机
curl 127.0.0.1自己骗自己。正确的验证路径是找一台完全不同网络的机器,或者用手机流量去访问公网地址,这样才排除了"回环访问"造成的假象。很多人的"映射配好了"其实是内网回环生效,外网根本没通。
4. ICMP:被误解最深的辅助协议
4.1 报文类型速查与真实用途
ICMP 常被简化成"ping 用的",实际上它有几十种类型,跟日常排障相关的集中在下面这几类:
| 类型 | 名称 | 常见代码含义 | 排障价值 |
|---|---|---|---|
| 0 / 8 | Echo Reply / Echo Request | 无 | 连通性、RTT 测量 |
| 3 | Destination Unreachable | 0 网络不可达、1 主机不可达、3 端口不可达、4 需要分片但设了 DF | 判断是路由问题、主机问题还是服务未监听 |
| 5 | Redirect | 无 | 路由次优,通常应该关闭 |
| 11 | Time Exceeded | 0 TTL 耗尽、1 分片重组超时 | traceroute 的核心,也提示存在环路 |
| 13 / 14 | Timestamp Request / Reply | 无 | 极少用,一般直接关掉 |
代码 3(端口不可达)特别值得记:如果 ping 能通但服务连不上,且抓包看到ICMP port unreachable,说明包已经到达目标主机,只是那个端口没有进程监听——问题在服务本身,不在网络。这类判断能省掉大量"是不是防火墙挡了"的无效排查。
4.2 ping 和 traceroute 背后的机制
ping的机制简单:发一个 Echo Request,带序号和时间戳,收到 Echo Reply 就计算 RTT。看结果时除了丢包率,RTT 的抖动更值得关注。稳定在 0.2ms 突然跳到 200ms,即使不丢包,也说明链路上某一段存在拥塞或队列积压。
# 指定次数和间隔,长间隔更容易暴露抖动 ping -c 20 -i 0.5 10.0.0.1 # 记录每跳的 RTT 分布,比单看平均更有信息量 ping -c 100 -q -i 0.2 10.0.0.1 | tail -3traceroute的机制是依赖 TTL:发一个 TTL=1 的包,第一跳路由器转发时把 TTL 减到 0,回一个 Time Exceeded 告诉我们"这个包的坟在我这儿";再发 TTL=2,如此递增,就一节一节把路径画出来了。
Linux 上的traceroute默认用 UDP 高端口,对防火墙比较友好(有些设备对 UDP 高端口的限制比 ICMP 松),也支持换成 ICMP 或 TCP SYN:
traceroute -n 203.0.113.80 # 默认 UDP traceroute -n -I 203.0.113.80 # 用 ICMP Echo traceroute -n -T -p 443 203.0.113.80 # 用 TCP SYN,穿透性最好-T加 TCP 是实战利器。很多环境禁 ICMP、也限制 UDP,但不会禁 443 出站,这时候只有 TCP 探测能出结果。
4.3 PMTUD 黑洞:最难缠的一类"大包不通"
这是 ICMP 被禁造成的、最典型也最难查的问题,值得单独开一节。
机制是这样:链路 MTU 通常是 1500,但经过隧道(IPsec、GRE、VXLAN 之外的封装)时有效 MTU 会变小,比如 1400。发送端默认会设置 DF(Don't Fragment)位,中间某跳发现包超长时会回一个ICMP Type 3 Code 4(Fragmentation Needed),告警"下次发小点",发送端据此调整 MSS。这套机制叫 PMTUD(路径 MTU 发现)。
问题是:如果中间某台设备的策略把 ICMP 全丢了,这条告警就到不了发送端。发送端继续发 1500 的包,一路被丢,表现为小包通、大包不通——ping 得过、TCP 握手能成(握手包小),一到传数据就卡死或者传到某个大小就断。
验证方法非常直接,用-M do强制不分片(Linux 上是-M do,等价于设置 DF 位),逐级试大小:
# 1472 + 8(ICMP头) + 20(IP头) = 1500 ping -c 3 -M do -s 1472 203.0.113.80 # 如果失败,往下试 ping -c 3 -M do -s 1400 203.0.113.80 ping -c 3 -M do -s 1372 203.0.113.80 # 对应 MTU 1400找到能通的那个值,就得到了实际路径 MTU。-s的数值是 ICMP 载荷大小,加上 28 字节的 IP+ICMP 头就是线上包大小。
解决路径有两条:一是在出口或隧道两端把 TCP MSS 钳到合适值,让双方在握手时就协商小一点的分段,绕开 PMTUD 依赖:
# 把经过 eth0 出去的 SYN 包 MSS 限制为 1360(对应 MTU 1400) iptables -t mangle -A FORWARD -o eth0 -p tcp --tcp-flags SYN,RST SYN \ -j TCPMSS --set-mss 1360 # 或者钳收到 MSS 的值 iptables -t mangle -A FORWARD -i eth0 -p tcp --tcp-flags SYN,RST SYN \ -j TCPMSS --clamp-mss-to-pmtu第二条是放开必要的 ICMP 类型。安全策略里"禁 ping"和"禁 ICMP"是两回事,正确的做法是只放行排障和机制必需的类型:
# 放行 Echo Request/Reply iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT # 放行分片需要(PMTUD 必需,别禁) iptables -A INPUT -p icmp --icmp-type fragmentation-needed -j ACCEPT # 放行 TTL 超时(traceroute 需要,可选) iptables -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT # 限速,防止被当成探测目标刷 iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 20/second -j ACCEPT另外还有两个 ICMP 相关配置值得检查:net.ipv4.icmp_ratelimit控制内核回 ICMP 的最小间隔(默认 1000 毫秒,即每秒最多 1 个目标),net.ipv4.icmp_ratemask决定哪些类型受限速。在高并发探测场景下,这个限速会让排障结果失真——明明通,却因为限速丢了一部分 reply,被误判为丢包。一般不建议改,但要知道它的存在。
最后,ICMP 重定向(Type 5)建议关掉。它的原意是"同网段有更好的网关,下次直接发它",但攻击者可以伪造重定向把流量引到错误路径。Linux 上关掉:
sysctl -w net.ipv4.conf.all.accept_redirects=0 sysctl -w net.ipv4.conf.all.send_redirects=0实操心得:遇到"ping 通但服务不通",先抓包确认有没有 ICMP 差错报文回来。有
port unreachable就查服务监听(ss -lntp);一个包都不回就是中间被丢弃,逐段用traceroute -T定位。花十分钟做这个区分,比反复重启服务有用得多。
5. IGMP:组播的入群申请
5.1 版本演进与地址映射规则
组播的核心概念是"一个发送者,多个接收者,网络上只传一份"。要做到这点,需要两层配合:三层用组播路由把流量引到有接收者的分支,二层用 IGMP Snooping 决定从哪个端口转发。
IGMP 承担的是主机与直连路由器之间的成员管理,不涉及跨网段分发。它的版本演进带来了明显的功能增量:
| 版本 | 关键能力 | 局限 |
|---|---|---|
| v1 | 主机加入组,路由器周期性查询(默认 60 秒),无离开报文 | 离开延迟长,最多要等 2-3 分钟才停止转发 |
| v2 | 增加 Leave 报文和特定组查询,离开延迟缩短到秒级 | 只能表达"我要这个组",不能表达"我要来自哪个源" |
| v3 | 支持源过滤(SSM),可以声明"只收来自某几个源的该组流量" | 配置复杂,需要路由器和主机都支持 |
日常内网视频分发场景,v2 基本够用。需要精确控制来源(比如只允许某个推流服务器往这个组发)才需要 v3。
组播地址的映射有个细节经常被忽略:组播 IPv4 地址范围是 224.0.0.0 到 239.255.255.255(224.0.0.0/4)。映射到以太网时,取 IPv4 低 23 位加上固定的01:00:5E前缀,组成 48 位 MAC。这意味着高 5 位被丢弃,导致地址折叠——比如239.1.1.1和239.129.1.1会映射到同一个 MAC 地址。设计组播地址规划时必须知道这一点,否则两个不同业务组会在二层互相干扰,查起来极其费解。
几个保留地址记一下:224.0.0.1本链路全部主机,224.0.0.2本链路全部路由器,224.0.0.22是 IGMPv3 报告地址。实际业务分配组播地址时,避开224.0.0.0/24,用239.0.0.0/8这个管理范围段最省事,因为它不会和公网组播地址冲突。
5.2 IGMP Snooping:二层不泛洪的关键
这是组播部署里最容易出问题的地方。
交换机默认行为是"未知目的 MAC 就泛洪"。组播 MAC 是以01:00:5E开头,交换机不认识,于是把组播数据从所有端口发出去。100 台机器里只有 5 台想收,其余 95 台白白被灌流量,遇到高码率视频直接把接入带宽打满,终端侧因为无关流量抖动得厉害。
解决办法是开启IGMP Snooping:交换机监听主机发出的 IGMP 报告,维护一张"哪个端口要哪个组"的表,组播流量只往有需求的端口送。
配套还需要一个Querier(查询器)。整个二层域里必须有一台设备周期性发 IGMP 通用查询,主机收到才回报告,Snooping 才能建立表项。如果所有设备都没开 Querier,主机会静默,表项建不起来,Snooping 开了也没效果——表现是组播时通时不通,或者几分钟后自动断掉。
典型配置思路(通用概念,不是具体厂商命令):接入交换机开 Snooping 和全局 Querier,上联口设为路由器端口(接收上游组播流量),终端口设为主机端口。如果三层网关设备本身就发查询,那二层只开 Snooping 就够;如果网关不在二层域里,就得在交换机上单独开 Querier。
5.3 实操:手工验证组播链路是否打通
先用最简单的工具验证"组播到底通不通",别一上来就部署业务。
接收侧:用socat加入组播组并监听,收到什么打印什么。
# eth0 上加入 239.1.1.1:5004 接收 socat -u UDP4-RECV:5004,ip-add-membership=239.1.1.1:eth0 -发送侧:用ffmpeg推一段测试流,或者用socat直接发文本。
# 推一个测试视频流到组播组 ffmpeg -re -i test.mp4 -c copy -f mpegts udp://239.1.1.1:5004?ttl=16 # 或者更轻量,直接发字符串 echo "multicast-test" | socat - UDP4-DATAGRAM:239.1.1.1:5004,ip-multicast-ttl=16接收侧能打出内容,说明三层组播路径通了。这时候再去验证 Snooping:在多台终端同时接收的情况下,在交换机端口统计里看非接收端口的组播流量是否归零。如果还是泛洪,检查 Snooping 是不是只开了全局没开端口、Querier 是不是没人发查询。
再看本机的组播加入状态,确认组有没有加成功(很多时候是代码里setsockopt调用失败但没检查返回值):
ip maddr show dev eth0输出里应该能看到239.1.1.1对应的条目。如果没有,问题在应用层,网络侧还不用查。
排查顺序我一般按这个走:先确认接收端加了组(ip maddr show)→ 再确认同网段直连能通(发送接收在同一台交换机下)→ 再确认跨网段路由器的组播路由表里有这个组(ip mroute show)→ 最后才查 Snooping 和 Querier。反着查很容易在二层设备上耗掉几个小时,结果发现是应用没调用 join。
注意:TTL 是组播能不能跨路由的开关。组播包默认 TTL 常常是 1,只在本地链路传播。跨网段必须把 TTL 调到足够大(比如 16),否则上游路由器根本收不到。这个参数经常藏在应用的配置项里,很容易漏。
6. 四个协议联动的排障路径
6.1 一条从终端到服务的完整排查链
把这四个协议按数据流向串起来,就是一条现成的排障路径。我处理复杂工单时基本按这个顺序走,很少绕冤枉路。
第一步,看地址是否正常。终端ip -4 addr show,如果是169.254.x.x,说明 DHCP 失败了,直接跳到 DHCP 排查,后面都不用看。如果是正确网段的地址,但用ip route看发现的网关不对、或者 DNS 是空的,也说明 DHCP 的 Option 下发有问题。
第二步,看本地网关通不通。ping网关。不通的话,可能是网段配置错误、VLAN 划分错误、或者 ARP 层面有问题(IP 冲突)。这一步能排除掉大量"地址对但根本没接入正确网络"的情况。
第三步,看出口转换是否正常。网关通了但外网不通,在出口设备上看 conntrack 表里有没有对应的会话,看 NAT 规则的计数器有没有命中。如果规则一次都没匹配,说明流量根本没走到这台设备;如果匹配了但没有回程,多半是上游过滤或者端口映射没配对。
第四步,看差错反馈。用traceroute -T走一遍,看断在第几跳;或者抓包看有没有 ICMP 差错回来。有Type 3 Code 13(管理禁止)说明被策略挡了,有Code 4说明是 MTU 问题,有Code 3说明目标端口没服务。
第五步,如果是组播业务,单独查组。前面的单播通了不代表组播通,组播有自己的路径和成员管理,必须单独验证ip maddr show和 Snooping 状态。
这套顺序的价值在于每一层都有明确的判断依据,不用靠猜。我见过太多人一上来就怀疑防火墙,抓包抓了半天,最后发现是 DHCP 把网关下发错了。
6.2 常见问题速查表
把高频故障和对应的判断依据整理成一张表,出问题时可以先对照,节省时间:
| 现象 | 最可能原因 | 快速验证 | 处理方向 |
|---|---|---|---|
| 终端拿到 169.254.x.x | DHCP 未响应 | 抓包看有无 Offer | 查服务器进程、中继、地址池 |
| 地址网段正确但网关不通 | 分错地址池或 VLAN 错 | 对比掩码与网关是否同网段 | 查中继 giaddr、VLAN 配置 |
| 内网通、外网不通 | NAT 规则未命中或上游过滤 | conntrack -L看有无会话 | 补 POSTROUTING 规则、查策略 |
| 映射配好但外网访问不了 | 双 NAT 或入方向规则缺失 | 从外部网络访问验证 | 光猫桥接或补内层映射 |
| 同时访问同一服务大量失败 | 端口耗尽或 conntrack 满 | nf_conntrack_count接近上限 | 扩公网地址或加目标分散 |
| ping 通但端口连不上 | 服务未监听或被中间拦截 | 抓包看有无 port unreachable | 查ss -lntp或逐跳排查 |
| 小包通、大包不通 | PMTUD 黑洞 | ping -M do -s 1472逐级试 | 钳 MSS 或放行 frag-needed |
| 长时间空闲的连接突然不可用 | NAT 会话老化 | 看 established 超时值 | 调大超时或加心跳保活 |
| 组播几台机器能收、多数收不到 | Snooping 未开或无人发查询 | 看交换机端口组播流量 | 开 Snooping 并配置 Querier |
| 组播几分钟后自动断 | 无 Querier,表项老化 | 抓 IGMP 查询报文 | 启用查询器 |
| 抓包看到大量 ICMP 重定向 | 路由次优或被伪造 | 看重定向源地址 | 关闭 accept_redirects |
6.3 几条花了时间才换来的经验
第一,抓包位置比抓包内容更重要。同一个故障,在终端上抓和在出口抓,看到的完全是两个故事。我的习惯是至少抓两个点:客户端侧和出口侧。两边的包对得上,说明中间透明;对不上,差异点就是问题所在。这个思路比盯着某一个包反复研究有效得多。
第二,DHCP 和 NAT 共用一台设备时,改配置的顺序很重要。先改地址池再改 NAT 规则,中间会有一段时间新地址没有对应映射,业务直接中断。正确顺序是先加映射再切地址池,或者用维护窗口。这种"顺序性"的坑,文档里不会写,只有踩过才知道。
第三,别迷信"禁 ICMP 更安全"。禁掉 Echo Request 确实能减少扫描噪音,但把 Type 3 Code 4 一起禁掉,等于把 PMTUD 废了,后续所有隧道和跨运营商的连接都可能间歇性出问题,而这种问题极难定位。安全策略要精确到类型和代码,不要一刀切。
第四,组播地址规划要提前做。因为 MAC 映射只取低 23 位,不同组播组可能撞到同一个 MAC。业务少的时候感觉不到,等到组播组多了、不同部门各自申请地址,就会出现"我明明只订阅了 A 组,却收到 B 组的内容"。规划时按业务分大段,比如视频用239.1.x.x、行情用239.2.x.x,每段内部再细分,能有效避免折叠冲突。
第五,给 conntrack 留余量。很多设备默认的nf_conntrack_max是 65536,看着不小,但现代办公网几百台设备加上各种云同步、即时通讯、浏览器预连接,很容易打满。打满之后不是"慢",而是直接丢新建连接,表现为"时好时坏"。建议按终端数乘以 500 到 1000 来估算上限,同时监控使用率,超过 70% 就处理。
这套东西说到底,是四个协议各自把一件事做扎实,然后在交接处互相配合。分开看每个协议都不复杂,难的是故障发生时快速判断"现在该看哪一层"。把上面这条排查链走熟,多数网络问题能在十几分钟内收敛到具体环节,剩下的事情就是改配置或者换设备了。