重学TCP协议(5) 自连接 | 小册免费学

119 阅读2分钟

1.自连接是什么

在发起连接时,TCP/IP的协议栈会先选择source IP和source port,在没有显示调用bind()的情况下,source IP由路由表确定,source port由TCP/IP协议栈从local port range中选取尚未使用的port。

如果destination IP正好是本机,而destination port位于local port range,且没有服务器监听(listen)的话,source port可能正好选中了destination port这就出现了(source IP,source port)=(destination IP,destination port)的情况,即发生了自连接。

2.自连接是怎么发生的

1. 两个tcp连接同时打开

image.png

2.本来正常运行的客户端和服务端,服务端偶然挂掉之后,客户端再去连接它,就有可能出现自连接现象。

例如:同一台机器上的客户端和服务端(端口:50000)建立了一个长连接,服务端的进程挂掉了,端口50000释放,客户端去connect这个服务端的目的端口的时候正好选择了50000作为源端口,此时该端口没人用(因为服务端挂断了),使用是合法的,于是自连接形成了。

3.如何防止自连接

1. 让服务监听的端口与客户端随机分配的端口不可能相同即可

因为客户端随机分配的范围由 /proc/sys/net/ipv4/ip_local_port_range 文件决定,,所以只要让服务监听的端口小于 客户端分配的端口范围,就不会出现客户端与服务端口相同的情况。

2. 出现自连接的时候,主动关掉连接

例如在Go语言的tcp库中,加入的自连接判断

func selfConnect(fd *netFD, err error) bool {
	// If the connect failed, we clearly didn't connect to ourselves.
	if err != nil {
		return false
	}

	// The socket constructor can return an fd with raddr nil under certain
	// unknown conditions. The errors in the calls there to Getpeername
	// are discarded, but we can't catch the problem there because those
	// calls are sometimes legally erroneous with a "socket not connected".
	// Since this code (selfConnect) is already trying to work around
	// a problem, we make sure if this happens we recognize trouble and
	// ask the DialTCP routine to try again.
	// TODO: try to understand what's really going on.
	if fd.laddr == nil || fd.raddr == nil {
		return true
	}
	l := fd.laddr.(*TCPAddr)
	r := fd.raddr.(*TCPAddr)
	return l.Port == r.Port && l.IP.Equal(r.IP)
}