searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

一次Nginx no route to host 原因分析

2023-03-20 02:17:15
619
0

问题现象:

某资源池 nginx 偶尔出现如下错误日志,频率约为每天1-2条。

connect() failed(113: No route to host) while connecting to upstream, client: *** server: ***r, request: "**  HTTP/1.1",upstream: "**", host: "**"

 

分析

在此期间,其余请求均能正常处理,upstream上仍能收到该nginx发送的其他请求。日志中失败的请求在Nginx尝试其他upstream后请求成功。

 

通过Nginx日志,可以找到Nginx开源代码ngx_http_upstream.c,其中 ngx_http_upstream_send_request会调用ngx_http_upstream_test_connect函数, 使用getsockopt, 在复用tcp连接再次请求upstream前先检查SO_ERROR, 此时发现有错误113,因此记录了日志,并尝试下一个upstream。

由于问题复现概率较低,因此在所有的upstream上抓一台LB相关的报文,由于流量较大,分析问题只需要报文头即可,因此采用TCP dump -s (snap)参数并对输出文件进行切片,便于后续根据时间进行报文查找,命令如下

nohup timeout 86400 tcpdump -ibond1.301 -s64 -w /var/tcpdump/%Y_%m%d_%H%M_%S.pcap '(ip or icmp or tcp) and host $LB_IP' -nn -vv -G 3600 &

 

等待一天后,根据nginx记录No route to host的时间,定位到抓包结果中有如下内容。

从结果中分析,可见upstream发送了icmp 拒绝报文,Type 3(Destination unreachable), 这应该是Nginx 检查socket错误时得到No route to Host的原因, ICMP Code 10,表示Host administratively prohibited,说明时Upstream主动拒绝了报文,根据ICMP type3的格式,被拒绝的报文的header放在ICMP报文中回送,上图图中可见。

抓取该tcp流,发现文件传输正常,ICMP拒绝的仅是其中一个ACK报文,没有影响传输。这也印证了Nginx的代码中,复用上一个请求的TCP连接时才报错的场景。

 

那么拒绝该报文的原因是什么呢?查看upstream的iptales,可见如下内容,应该是该ACK报文命中了REJECT的规则,因此回复了ICMP type 3 CODE 10 的报文。

分析规则列表,可见其使用了状态跟踪规则,允许一部分端口上NEW状态的报文进入建立连接,对于ESTABLISHED状态的报文都允许通过;正常情况下,我们Upstream监听的端口时允许NEW状态的报文进入的,因此可以正常建链并进行后续数据的传输。至此推测被拒绝的报文时因为状态不是ESTABLISHED和NEW,因而命中了REJECT规则。

那么状态不正确的原因是什么呢,通过对内核netfilter conntrack部分源码的阅读,在nf_conntrack_proto_tcp.c 的 static bool tcp_in_window() 找到 ACK序号判断逻辑, 可见存在ACK的有效窗口, ACK的序号下限为发送方发出的最大发送序号-MAXACKWINDOW,超过此范围的ACK报文即被标为INVALID。

针对这个问题,可以通过完善iptables来解决,例如可以在icmp-host-prohibited之前,插入一条iptables规则,匹配INVALID的 ACK报文 将其DROP,从而避免发送icmp。

 

0条评论
0 / 1000
刘****润
2文章数
1粉丝数
刘****润
2 文章 | 1 粉丝
刘****润
2文章数
1粉丝数
刘****润
2 文章 | 1 粉丝
原创

一次Nginx no route to host 原因分析

2023-03-20 02:17:15
619
0

问题现象:

某资源池 nginx 偶尔出现如下错误日志,频率约为每天1-2条。

connect() failed(113: No route to host) while connecting to upstream, client: *** server: ***r, request: "**  HTTP/1.1",upstream: "**", host: "**"

 

分析

在此期间,其余请求均能正常处理,upstream上仍能收到该nginx发送的其他请求。日志中失败的请求在Nginx尝试其他upstream后请求成功。

 

通过Nginx日志,可以找到Nginx开源代码ngx_http_upstream.c,其中 ngx_http_upstream_send_request会调用ngx_http_upstream_test_connect函数, 使用getsockopt, 在复用tcp连接再次请求upstream前先检查SO_ERROR, 此时发现有错误113,因此记录了日志,并尝试下一个upstream。

由于问题复现概率较低,因此在所有的upstream上抓一台LB相关的报文,由于流量较大,分析问题只需要报文头即可,因此采用TCP dump -s (snap)参数并对输出文件进行切片,便于后续根据时间进行报文查找,命令如下

nohup timeout 86400 tcpdump -ibond1.301 -s64 -w /var/tcpdump/%Y_%m%d_%H%M_%S.pcap '(ip or icmp or tcp) and host $LB_IP' -nn -vv -G 3600 &

 

等待一天后,根据nginx记录No route to host的时间,定位到抓包结果中有如下内容。

从结果中分析,可见upstream发送了icmp 拒绝报文,Type 3(Destination unreachable), 这应该是Nginx 检查socket错误时得到No route to Host的原因, ICMP Code 10,表示Host administratively prohibited,说明时Upstream主动拒绝了报文,根据ICMP type3的格式,被拒绝的报文的header放在ICMP报文中回送,上图图中可见。

抓取该tcp流,发现文件传输正常,ICMP拒绝的仅是其中一个ACK报文,没有影响传输。这也印证了Nginx的代码中,复用上一个请求的TCP连接时才报错的场景。

 

那么拒绝该报文的原因是什么呢?查看upstream的iptales,可见如下内容,应该是该ACK报文命中了REJECT的规则,因此回复了ICMP type 3 CODE 10 的报文。

分析规则列表,可见其使用了状态跟踪规则,允许一部分端口上NEW状态的报文进入建立连接,对于ESTABLISHED状态的报文都允许通过;正常情况下,我们Upstream监听的端口时允许NEW状态的报文进入的,因此可以正常建链并进行后续数据的传输。至此推测被拒绝的报文时因为状态不是ESTABLISHED和NEW,因而命中了REJECT规则。

那么状态不正确的原因是什么呢,通过对内核netfilter conntrack部分源码的阅读,在nf_conntrack_proto_tcp.c 的 static bool tcp_in_window() 找到 ACK序号判断逻辑, 可见存在ACK的有效窗口, ACK的序号下限为发送方发出的最大发送序号-MAXACKWINDOW,超过此范围的ACK报文即被标为INVALID。

针对这个问题,可以通过完善iptables来解决,例如可以在icmp-host-prohibited之前,插入一条iptables规则,匹配INVALID的 ACK报文 将其DROP,从而避免发送icmp。

 

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
1
0