网络通不通是个大问题–半夜鸡叫

网络通不通是个大问题–半夜鸡叫

半夜鸡叫

凌晨啊,还有同学在为网络为什么不通的问题搏斗着:

undefined

问题描述大概如下:

slb后面配了一台realserver(就叫172吧), 在172上通过curl http://127.0.0.1:80/ 是正常的(说明服务自身是正常的)
如果从开发同学的笔记本直接curl slb-ip 就卡住了,进一步发现如果从北京的办公网curl slb-ip就行,但是从杭州的curl slb-ip就不行。

从杭州curl的时候在172上抓包如下:
undefined

明显可以看到tcp握手包正确到达了172,但是172一直没有回复。也就是如果是杭州访问服务的话,服务端收到握手请求后直接扔掉没有任何回复(回想下哪些场景会扔包)

问题排查

先排除了iptables的问题

略过

route 的嫌疑

因为北京可以杭州不行,明显是某些IP可以,于是检查route 表,解决问题的必杀技(基础知识)都在这里

发现杭州的ip和北京的ip确实命中了不同的路由规则,简单说就是172绑在eth0上,机器还有另外一块网卡eth1. 而回复杭州ip的时候要走eth1. 至于为什么没有从eth1回复等会再说

知道原因就好说了,修改一下route,让eth0成为默认路由,这样北京、杭州都能走eth0进出了

所以到这里,问题描述如下:
undefined

机器有两块网卡,请求走eth0 进来(绿线),然后走 eth1回复(路由决定的,红线),但是实际没走eth1回复,像是丢包了。

解决办法

修改一下route,让eth0成为默认路由,这样北京、杭州都能走eth0进出了

为什么5U的机器可以

开发同学接着反馈,出问题的172是7U2的系统,但是还有一些5U7的机器完全正常,5U7的机器上也是两块网卡,route规则也是一样的。

这确实诡异,看着像是7U的内核行为跟5U不一致,咨询了内核同学,让检查一下 rp_filter 参数。果然看到7U2的系统默认 rp_filter 开着,而5U7是关着的,于是反复开关这个参数稳定重现了问题

1
sysctl -w net.ipv4.conf.eth0.rp_filter=0

rp_filter 原理和监控

rp_filter参数用于控制系统是否开启对数据包源地址的校验, 收到包后根据source ip到route表中检查是否否和最佳路由,否的话扔掉这个包【可以防止DDoS,攻击等】

​ 0:不开启源地址校验。
​ 1:开启严格的反向路径校验。对每个进来的数据包,校验其反向路径是否是最佳路径。如果反向路径不是最佳路径,则直接丢弃该数据包。
​ 2:开启松散的反向路径校验。对每个进来的数据包,校验其源地址是否可达,即反向路径是否能通(通过任意网口),如果反向路径不通,则直接丢弃该数据包。

那么对于这种丢包,可以打开日志:/proc/sys/net/ipv4/conf/eth0/log_martians 来监控到:

undefined

rp_filter: IP Reverse Path Filter, 在ip层收包的时候检查一下反向路径是不是最优路由,代码位置:

ip_rcv->NF_HOOK->ip_rcv_finish->ip_rcv_finish_core

也就是rp_filter在收包的流程中检查每个进来的包,是不是符合rp_filter规则,而不是回复的时候来做判断,这也就是为什么抓包只能看到进来的syn就没有然后了

开启rp_filter参数的作用

  • 减少DDoS攻击: 校验数据包的反向路径,如果反向路径不合适,则直接丢弃数据包,避免过多的无效连接消耗系统资源。
  • 防止IP Spoofing: 校验数据包的反向路径,如果客户端伪造的源IP地址对应的反向路径不在路由表中,或者反向路径不是最佳路径,则直接丢弃数据包,不会向伪造IP的客户端回复响应。

通过netstat -s来观察IPReversePathFilter

$netstat -s | grep -i filter
    IPReversePathFilter: 35428
$netstat -s | grep -i filter
    IPReversePathFilter: 35435

能明显看到这个数字在增加,如果没开rp_filter 就看不到这个指标或者数值不变

undefined

问题出现的时候,尝试过用 watch -d -n 3 ‘netstat -s’ 来观察过哪些指标在变化,只是变化的指标太多,留意不过来,或者只是想着跟drop、route有关的参数

1
2
3
4
$netstat -s |egrep -i "drop|route"
12 dropped because of missing route
30 SYNs to LISTEN sockets dropped
InNoRoutes: 31

当时观察到这几个指标,都没有变化,实际他们看着像但是都跟rp_filter没关系,最后我打算收藏如下命令保平安:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$netstat -s |egrep -i "drop|route|overflow|filter|retran|fails|listen"
12 dropped because of missing route
30 times the listen queue of a socket overflowed
30 SYNs to LISTEN sockets dropped
IPReversePathFilter: 35435
InNoRoutes: 31
$nstat -z -t 1 | egrep -i "drop|route|overflow|filter|retran|fails|listen"
IpOutNoRoutes 0 0.0
TcpRetransSegs 20 0.0
Ip6InNoRoutes 0 0.0
Ip6OutNoRoutes 0 0.0
Icmp6InRouterSolicits 0 0.0
Icmp6InRouterAdvertisements 0 0.0
Icmp6OutRouterSolicits 0 0.0
Icmp6OutRouterAdvertisements 0 0.0
TcpExtLockDroppedIcmps 0 0.0
TcpExtArpFilter 0 0.0
TcpExtListenOverflows 0 0.0
TcpExtListenDrops 0 0.0
TcpExtTCPPrequeueDropped 0 0.0
TcpExtTCPLostRetransmit 0 0.0
TcpExtTCPFastRetrans 0 0.0
TcpExtTCPForwardRetrans 0 0.0
TcpExtTCPSlowStartRetrans 0 0.0
TcpExtTCPBacklogDrop 0 0.0
TcpExtTCPMinTTLDrop 0 0.0
TcpExtTCPDeferAcceptDrop 0 0.0
TcpExtIPReversePathFilter 2 0.0
TcpExtTCPTimeWaitOverflow 0 0.0
TcpExtTCPReqQFullDrop 0 0.0
TcpExtTCPRetransFail 0 0.0
TcpExtTCPOFODrop 0 0.0
TcpExtTCPFastOpenListenOverflow 0 0.0
TcpExtTCPSynRetrans 10 0.0
IpExtInNoRoutes 0 0.0

如果客户端建立连接的时候抛异常,可能的原因(握手失败,建不上连接):

  • 网络不通,诊断:ping ip
  • 端口不通, 诊断:telnet ip port
  • rp_filter 命中(rp_filter=1, 多网卡环境), 诊断: netstat -s | grep -i filter ;
  • snat/dnat的时候宿主机port冲突,内核会扔掉 syn包。 troubleshooting: sudo conntrack -S | grep insert_failed //有不为0的
  • 全连接队列满的情况,诊断: netstat -s | egrep “listen|LISTEN”
  • syn flood攻击, 诊断:同上
  • 若远端服务器的内核参数 net.ipv4.tcp_tw_recycle 和 net.ipv4.tcp_timestamps 的值都为 1,则远端服务器会检查每一个报文中的时间戳(Timestamp),若 Timestamp 不是递增的关系,不会响应这个报文。配置 NAT 后,远端服务器看到来自不同的客户端的源 IP 相同,但 NAT 前每一台客户端的时间可能会有偏差,报文中的 Timestamp 就不是递增的情况。nat后的连接,开启timestamp。因为快速回收time_wait的需要,会校验时间该ip上次tcp通讯的timestamp大于本次tcp(nat后的不同机器经过nat后ip一样,保证不了timestamp递增),诊断:是否有nat和是否开启了timestamps
  • NAT 哈希表满导致 ECS 实例丢包 nf_conntrack full

总结

本质原因就是服务器开启了 rp_filter 为1,严格校验进出包是否走同一块网卡,而如果请求从杭州机房发过来的话,回复包的路由走的是另外一块网卡,触发了内核的rp_filter扔包逻辑。

改server的路由可以让杭州的包也走同一块网卡,就不扔掉了。当然将 rp_filter 改成0 关掉这个校验逻辑也可以完全避免这个扔包。

从问题的解决思路来说,基本都可以认定是握手的时候服务器扔包了。只有知道 rp_filter 参数的内核老司机可以直接得出是这里的原因。如果对于一个新手的话还是得掌握如何推理分析得到原因。