Linux TCP keepalive 计时器是错误的?

ss(1) 加上 -o 参数可以打印 tcp 连接的 timer 信息,例如:

[root@VM-0-11-tlinux ~]# ss -ntpo 'sport = 80'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 172.16.0.11:80 183.14.31.93:25144 users:(("socat",pid=10624,fd=6)) timer:(keepalive,1min32sec,0)

这是一个用 socat 设定了 120s 空闲(keepalive 的 idle)时间的连接,可以看到它正确打印了 timer 信息,执行 ss(1) 时候 timer 超时时间只剩下 92s 了。

问题:我们在连接的一端发送数据后,预期是 timer 应该把超时时间重制为 120s 开始倒数计算,然而实际并不是这样。客户端发送数据后,我们看下 timer:

[root@VM-0-11-tlinux ~]# ss -ntpo 'sport = 80'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 172.16.0.11:80 183.14.31.93:25144 users:(("socat",pid=10624,fd=6)) timer:(keepalive,1min28sec,0)

ss(1) 仍然在继续倒数连接空闲超时时间。但是当我们实际去抓包的时候,可以发现确实是以接收到数据为起点 120s 之后才会发起探测的,所以 keepalive 这个机制并没有错,错的是这个 timer 展示。应该是为了减少计算量而采取这种“惰性”展示方式,具体可以参考:https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_timer.c#L662