tcp连接失败

协议栈

Posted by Secssion on December 31, 2023

拓扑图

recycle_tuo_pu.png

内核版本

v3.16

现象

内部环境集群拓扑下,本地终端浏览器访问web服务,偶尔浏览器报”网络访问拒绝”错误

分析

  1. 本地终端上抓包发现,tcp三次握手失败,终端的第一个syn包未能得到回复。
  2. 从主机进入ingress-nginx的网络命名空间,可发现nginx已经接受到syn。
  3. 分别对k8s_node1与k8s_node2的主机抓包,发现syn包是在node节点丢失。
  4. 由于tcpdump抓包的是在__netif_receive_skb函数,确定包未被netfilter处理,那么只可能是协议栈丢包了。包传递过程如下: recycle_devlive.png
  5. 通过下面命令,从主机进入容器网络命名空间使用tcpdump转包。
     docker inspect -f '' <container_name_or_id>
     sudo nsenter -t <container_pid> 
    

代码

tcp_conn_request用于接受方处理第一次syn包,该函数处理失败情况下,会写入tcp统计信息,将LINUX_MIB_LISTENDROPS值加1

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
...
	if (tmp_opt.saw_tstamp &&
		    tcp_death_row.sysctl_tw_recycle &&
		    (dst = inet_csk_route_req(sk, &fl4, req)) != NULL &&
		    fl4.daddr == saddr) {
			if (!tcp_peer_is_proven(req, dst, true)) {
			    //由于timestamp与sysctl_tw_recycle异常会执行下面函数
				NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
				goto drop_and_release;
			}
	}
...
	return 0;
drop_and_release:
	dst_release(dst);
drop_and_free:
	reqsk_free(req);
drop:
    
    //被丢包的时候,都会走到该函数,将`LINUX_MIB_LISTENDROPS`对应的值加1
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
	return 0;
...	    
}		    
  • INUX_MIB_LISTENDROPSLINUX_MIB_sysctl_tw_recycle的值都可以在/proc/net/netstat`中可以读取到。
  • 复重现现象,可以发现这个两个值都在增加,那基本确定问题出现在LINUX_MIB_PAWSPASSIVEREJECTED
  • tcp_peer_is_proven该函数判断同一终端的时间戳是否递增,不是递增就丢包。内网环境下,多个时间不完全同步的主机发tcp请求,包经过ingress-inginx会被做snat将包转发到node节点,所以node协议栈判断发包源时间戳不递增。

tcp包状态统计

cat /proc/net/netstat可获取tcp的状态统计数据,数据格式比较乱,使用脚本处理数据显示格式

cat /proc/net/netstat |  awk '(f==0) {name=$1; i=2; while ( i<=NF) {n[i] = $i; i++ }; f=1; next} (f==1){ i=2; while ( i<=NF){ printf "%s%s = %d\n", name, n[i], $i; i++}; f=0} '

PAWSPassiveRejected

tcp_tw_recycle启动时,内核会将在time_wait状态的连接重用。当有新连接过来且启动时间戳的时候,它检查传入连接的时间戳,并与之前的进行比较。如果传入连接的时间戳早于之前的时间戳,则被认为是一个无效的包。未等待2ML结束的连接被重用后,加入了一个时间判断机制,判断是否为旧包。

解决办法

关闭tw_recycle,执行命令sudo sysctl -w net.ipv4.tcp_tw_recycle=0

参考