linux一次ip和默认网关丢失

最近排查一个和网络相关的问题,学习到了一些 dhcp 和 systemd-networkd 的一些知识,在这里记录分享一下

0x1 问题

某日我们对一个机器做流量压测,然后机器负载拉满,ip无法访问,然后出现了再也无法通过ip访问的情况,ping也是不通的

0x2 排查

机器ip不见了?

ping不通,说明机器没有和ip绑定,进入机器后台(我们的场景是虚拟机,所以有管理平台进入后台)

进入后台,执行 ip a 命令,大致如下

1
2
eth0: ....
link/ether ...

一个很关键的问题在于,没有ip地址,正常如下:

1
2
3
4
2: enp9s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether ....
inet 192.168.123.99/24 brd 192.168.123.255 scope global dynamic noprefixroute enp9s0
valid_lft 37780sec preferred_lft 37780sec

再通过ip r 查看网关,如下

1
172.17.0.0/16 ....

发现没有default网关的路由,可能有其他的一些虚拟网卡网桥的东西,比如docker创建的之类的

说明系统的网络服务出现了问题,没有自动去获取dhcp 的ip,或者静态ip服务也没有设置正常,那么就继续排查网络服务

systemd-networkd 服务

linux常见管理网络的方式有三种

  1. network服务

  2. systemd-networkd 服务

  3. NetworkManager 服务

这三者都可以通过systemd来管理

  1. 第一种是最初直接设置网卡的方式,它实际上就是个脚本,每次开机自动配置一下网卡,通过dhcp或者静态ip的方式,如果是dhcp后续就会自动续约之类的

  2. 第二种就是systemd自带的网路管理,也是通过一个配置文件来处理,它通常会有后台服务,定期获取dhcp的状态并处理,其实和network没有太大差别

  3. 第三种其实主要是附加了一些自动化的功能,常见于桌面端,因为提供了一些图形化界面,并且会自动发现网卡并配置,服务端用的比较少

这里的服务其实是采用了第一种的静态ip和第二种的dhcp混合的方式

首先查看 systemd-networkd 服务日志:

1
2
eth0: DHCP Addrees set failed: connect timeout
eth0: Failed

最主要的就是这两条,能的到的信息如下:

  1. systemd-networkd 在这个时候dhcp租约到期了,需要重新申请IP,但是访问 dhcp 服务器超时了

  2. systemd-networkd 不会再去处理关于 eth0 网卡的配置了

此外,我们在 /etc/sysconfig/network-scripts/ifcfg-eth0 能看到静态ip的配置,说明这个系统同时启用了 network 服务来配置静态ip,但是这个静态ip只会配置一次,即系统启动之时

由于内部特殊组件的原因,这里不方便展开说,最终我们定位这时候的ip和网关丢失与该服务无关

那么为什么systemd-networkd在超时之后为什么会清除网卡ip和默认网关呢?

查源码是一个很烦的事情,我们更希望从其他的特征中发现这个问题

ip会自动清除?

再仔细观察我们的 ip a 的输出,会发现它有一个很特殊的行

这是啥,好像超出理解范围了,通过文心一言的问答,如下

  1. valid_lft 代表这个 IP 的可使用时长

  2. preferred_lft 代表这个 IP 的优先使用时长

简单理解,就是第一个时间代表了这个ip可以用多久,第二个代表了这个ip在多久之内是优先使用的

这好像就对上了,它会自动清除

实验一下,将时间调整为 10s 和 5s

1
ip a change dev enp0s3 10.0.2.6/24 valid_lft 10 preferred_lft 5

得到如下结果:

时间流逝

ip 自动丢失,默认网关路由也消失,也就验证了我们的猜想

恢复ip

只需要重启 systemd-networkd 服务即可

0x3 收获

实际上整个问题非常简单,难点在于

  1. 对这几个组件一无所知,Linux下的网络管理方式和配置方式

  2. DHCP又是怎么实现的

  3. IP竟然还有有效期会自动消失

事实上DHCP这里我没有展开讲,因为中间我忽略了踩坑的过程,开始我试图复现错误日志中的超时问题,利用iptables对 dhcp 的 67 和 68 端口做丢包处理,结果我意外的发现,systemd-networkd这个服务竟然还能正确获取到ip地址,这一点真的很神奇,可能需要进一步了解其工作原理

DHCP

DHCP的服务过程大概如下:

  1. 客户端发起一个广播,找dhcp服务器

  2. dhcp服务器收到请求,知道了对方的mac地址,然后尝试提供一个ip地址和网关信息,并继续广播一个分配的请求

  3. 客户端收到广播后确认这个ip可以,就继续广播一个请求说自己收到了并且需要使用这个

  4. 服务端收到这个广播之后继续广播一个请求,ACK确认

然后有个特殊点在于,在租期的 0.5 和 0.875 节点时,客户端会尝试续约,但是这个时候是单播的,为什么前面全部用广播是因为客户端没有一个分配好的ip地址

大家其实会有一个讨论点在于dhcp服务端第一次分配ip的时候是广播还是单播,这里我实测抓包了一下,从目的ip的角度来说,确实全部为广播,因为在网络中可能有多个dhcp服务器回包,客户端会选择第一个接受到的,然后广播说自己选择了哪一个,从逻辑上讲,没有完成整个过程之前,这个客户端都是没有自己的ip的,所以就应该使用广播