TCP的自连接是网络编程中的又一个“坑”。
先写一个简单的脚本来展示这个问题:
-
# cat self_connect.sh
-
#!/bin/bash
-
while true; do
-
telnet 127.0.0.1 44444
-
done
脚本很简单,不停地去连接本机的端口号44444。
看一下本机的44444端口状态:
-
# netstat -anp | grep 444444
-
没有任何程序在监听该端口,所以连接不可能被建立。
运行程序,输出如下:
-
./self_connect.sh
-
telnet: Unable to connect to remote host: Connection refused
-
Trying 127.0.0.1...
-
telnet: Unable to connect to remote host: Connection refused
-
Trying 127.0.0.1...
-
telnet: Unable to connect to remote host: Connection refused
-
Trying 127.0.0.1...
-
# 中间省略
-
telnet: Unable to connect to remote host: Connection refused
-
Trying 127.0.0.1...
-
telnet: Unable to connect to remote host: Connection refused
-
Trying 127.0.0.1...
-
Connected to 127.0.0.1.
我们发现,脚本在多次连接失败后,竟然连接上了。再来看下端口状态:
-
# netstat -anp | grep 44444
-
tcp 0 0 127.0.0.1:44444 127.0.0.1:44444 ESTABLISHED 8668/telnet
状态确实是ESTABLISHED,更奇怪的是连接的源和目标端口都是44444。
为了更清晰地了解情况,我们使用tcpdump工具来抓包分析:
-
# tcpdump -i lo
-
前面省略
-
19:22:02.416705 IP localhost.44441 > localhost.44444: Flags [S], seq 3362395666, win 43690, options [mss 65495,sackOK,TS val 2589409478 ecr 0,nop,wscale 7], length 0
-
19:22:02.416715 IP localhost.44444 > localhost.44441: Flags [R.], seq 0, ack 3362395667, win 0, length 0
-
19:22:02.418525 IP localhost.44442 > localhost.44444: Flags [S], seq 2268272069, win 43690, options [mss 65495,sackOK,TS val 2589409479 ecr 0,nop,wscale 7], length 0
-
19:22:02.418536 IP localhost.44444 > localhost.44442: Flags [R.], seq 0, ack 2268272070, win 0, length 0
-
19:22:02.420240 IP localhost.44443 > localhost.44444: Flags [S], seq 3381102759, win 43690, options [mss 65495,sackOK,TS val 2589409479 ecr 0,nop,wscale 7], length 0
-
19:22:02.420251 IP localhost.44444 > localhost.44443: Flags [R.], seq 0, ack 3381102760, win 0, length 0
-
19:22:02.421920 IP localhost.44444 > localhost.44444: Flags [S], seq 4242302188, win 43690, options [mss 65495,sackOK,TS val 2589409479 ecr 0,nop,wscale 7], length 0
-
19:22:02.421932 IP localhost.44444 > localhost.44444: Flags [S.], seq 4242302188, ack 4242302189, win 43690, options [mss 65495,sackOK,TS val 2589409479 ecr 2589409479,nop,wscale 7], length 0
-
19:22:02.421938 IP localhost.44444 > localhost.44444: Flags [.], ack 1, win 342, options [nop,nop,TS val 2589409479 ecr 2589409479], length 0
从tcpdump的输出,我们可以发现几点:
1. 每次连接失败后,下一次进程连接使用的端口号都是前一次加1
2. 连接失败的情况,进程向44444端口发送SYN后,由于44444端口没有被监听,进程收到RST分节后退出。
3. 当进程使用44444端口发起连接时,三次握手正常完成了。
这里可以提出两点疑问:1. 发起连接时系统是如何分配端口号的? 2. 如果源端口号和目标端口号相同,会发生什么?我们分别来解答。
发起连接时系统是如何分配端口号的?
因为我们的程序没有使用bind绑定端口号,内核会自动为我们分配一个端口,可以分配的端口称作,使用如下命令查看ephemeral port范围:
-
$ cat /proc/sys/net/ipv4/ip_local_port_range
-
32768 61000
在本台机子上,内核可以自动分配的端口范围为32768 - 61000。于是,我们的测试脚本执行时就会出现源端口号和目标端口号相同的情况。
如果源端口号和目标端口号相同,会发生什么?
在前面用tcpdump抓包时,我们看到内核是正常完成了三次握手,那么具体是怎么做的呢?
对于这个问题,linux邮件列表也进行过讨论(详见:),内核维护人员认为:
It is not a bug, it is caused by the "simultaneous connect" feature of TCP.
simultaneous connect又是什么呢?这是指相互独立的两台主机的两个tcp套接字, 可能会在同一时刻向对方发起连接。
该过程如下:
socket A和socket B同时向对方发送SYN分节,分别接收到SYN又分别发送了SYN和ACK,两者接收到后又分别发送了ACK,进入ESTABLISHED状态。
linux内核对这种情况进行了处理。在文件net/ipv4/tcp_input.c函数tcp_rcv_synsent_state_process()中,有一段代码:
-
if (th->syn) {
-
/* We see SYN without ACK. It is attempt of
-
* simultaneous connect with crossed SYNs.
-
* Particularly, it can be connect to self.
-
*/
-
tcp_set_state(sk, TCP_SYN_RECV);
-
...
-
}
这段代码是在三次握手时发送第一个SYN后执行的,if条件指的是发送完SYN后又接收到了SYN,那么我们就认为这种情况simultaneous connect了,并且注释中还写了这很可能是自连接。代码就不看下去了,这里只需要知道内核确实做了处理,并且这个处理导致自己连接自己是可行的。
小结一下,自连接指的是当源ip和目标ip、源端口和目标端口都相同时,出现的自己连上自己的情况。为了避免这种情况,我们需要使目标端口不在ephemeral port范围内。可以修改目标端口,也可以修改/proc/sys/net/ipv4/ip_local_port_range文件。另外,本文浅析了出现自连接的原因,内核人员认为这个问题不是一个bug,这属于simultaneous connect,因此,需要我们自己在实际应用中避免这个情况。
阅读(4400) | 评论(0) | 转发(0) |