近日调试Linux主机切换网络时IP地址不发生变化,总结了一下排查问题的方法。
问题的闭环:
1- 核心认知:现象不是问题本身
最开始的现象是:
1- 路由器A切到路由器B网络不通
2- 有线网络A切到有线网络B网络不通
3- 旧IP未刷新
4- DHCP没有Request/Discover
要素:
谁:eth0 / NM / DHCP client
时间:carrier down/up
预期:重新DHCP,刷新IP
实际:没有DHCP,IP不变
影响:切换新网络后仍使用旧地址
证据:dmesg 有NIC down / up 事件,tcpdump eth0 没有DHCP discovery/ request, ip addr 不变
2- 定义问题的方法
模板1:现象定义模板
在 【触发条件】下,【组件A】已经发生了【事实1】,但【组件B】没有发生【预期动作】,导致【结果】
情况:
在切换eth0网络时,
dmesg 已经发生了 NIC down/up
但 NM 没有发生 DHCP request
导致 eth0 无法正常工作
模板2: 预期 VS 实际
预期:
eth link down
NM deactive connection
ip route cleanup
eth0 link up
NM activate connection
DHCP Discover/Request
New IP Addr
实际
eth0 link down
NM 未 deactive connection
No DHCP Request
ip addr 不变
模板3:边界定义
一定要问:
这个问题发生在哪一层?
这个问题没有发生在哪一层?
边界非常重要,防止在错误方向钻太久
不是PHY 完全没事件,Dmesg能看到 Down/up
不是DHCP Server不可用,因为改配置后能发DHCP
不是有线有异常,因为 Carrier变化事件能拿到
问题在NetworkManager 对 carrier 事件的策略处理
3- 方法论一:分层排查法
物理层
内核层
管理层
地址层
路由层
应用层
核心认知:每一层都只回答自己的问题,不要用一层的结果替代下一层的结论。
4- 方法论二:状态机排查法
NetworkManager不是简单执行命令,内部状态有:
disconnected
preparing
configuring
ip-config
activated
deactivating
failed
理想状态:
activated -- carrier lost -> deactivating --> disconnected - carrier on -> preparing/configuring -> ip-config -> activated
异常状态
activated - carrier down /up -> activated -> nothing
遇到系统问题时先问,
这个组件有状态机吗?
当前卡在哪个状态?
预期应该跳到哪个状态?
是什么事件触发跳转?
这个事件到了吗?
事件到了以后为什么没有跳转?
5- 方法论三:事件链排查法
将问题抽象成一条事件链:
网线变化:
-> NIC carrier down /up
-> kernel netlink event
-> NM device event
-> NM connection state change
-> DHCP client action
-> IP address update
-> route update
-> app traffic recovery
只需要找到问题:“链条断在哪一环?”
Kernel netlink evnet 到了
但NM conn/DHCP 无后续操作
查看相关的 NetworkManager.conf 配置文档
6- 方法论四:控制面和数据面分详离
控制面
控制面负责“决定网络应该怎么配置”
包括:
NetworkManager
DHCP
route table
DNS
connection state
primary connection
数据面
数据面负责“真实流量怎么走”
包括:
实际 TCP/UDP 包
SSH 流量
PING 包
ARP/ND
网卡收发包
问题
控制面:
NM没有重新DHCP Request, IP addr 不变
数据面:
无DHCP包
7- 方法论五:反证和排除法
排查不要只寻找支持自己猜想的证据,也要主动找反证。
什么证据能证明我这个猜想是错的?
8- 方法论六: 从“谁拥有状态”入手
这个状态谁拥有?
谁负责更新它?
谁可能阻止他更新?
示例:
dmesg -> NIC down /up
但是DHCP由 NM 的策略控制
总结
第一步定义触发条件
什么操作触发
拔线?
插线?
切交换机?
切DHCP网段?
重启NM?
开机阶段?
拒绝写“网络不通”这种模糊定义。
第二步:定义期望链路
比如:
拔旧网络
-> eth0 carrier down
-> NM deactivate wired conn
-> 删除旧 IP /route
-> 插新网线
-> eth0 carrier up
-> NM activate wired conn
-> DHCP Request / Discover
-> DHCP IP Offer
-> default route 更新
必须知道“正确的行为链路是什么”,否则就不知道哪儿错了。
第三步:实际打点
ip monitor link addr route
nmcli monitor
journalctl -u NetworkManager -f
tcpdump -i eth0 -n -vvv 'port 67 or 68'
watch -n 0.5 ip addr show eth0
第四步:找到第一处偏差
不要看最后哪里坏,要找第一处和预期不一样的地方。
预期:NM connection 应该deactivate
实际:一直 activated
偏差:
carrier down/up 之后,NM没有进入重新配置流程
第五步:做最小修改验证
修改NetworkManager
[device-eth0-carrier]
match-device=interface-name:eth0
ignore-carrier=no
carrier-wait-timeout=0
然后观察是否能正常ping 通8.8.8.8
认知:
1- 不要把看到事件等同于处理完成
2- 先缩小问题空间,再谈解决方案
Tips:
1- 当前现象是什么?
2- 触发条件是什么?
3- 正常情况下状态链画出来
4- 现在实际走在层一步,哪一层
5- 第一处偏差在哪里?
6- 哪些证据证明了这点?
7- 哪些假设已经被排除?
8- 下一步最小验证是什么?
9- 临时 workaround 是什么?
10- 长期修复方案和副作用是什么?
方法
1- 这条从外部事件到最终结果的状态链,断在哪一环?
2- 这一环是没有收到事件,还是收到了但策略不处理?
3- 是控制面没更新,还是数据面问题?
4- 是组件状态没跳转,还是跳转后执行失败?