先写一个简单的脚本来展示这个问题:
- # cat self_connect.sh
- #!/bin/bash
- while true; do
- telnet 127.0.0.1 44444
- done
看一下本机的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
为了更清晰地了解情况,我们使用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
1. 每次连接失败后,下一次进程连接使用的端口号都是前一次加1
2. 连接失败的情况,进程向44444端口发送SYN后,由于44444端口没有被监听,进程收到RST分节后退出。
3. 当进程使用44444端口发起连接时,三次握手正常完成了。
这里可以提出两点疑问:1. 发起连接时系统是如何分配端口号的? 2. 如果源端口号和目标端口号相同,会发生什么?我们分别来解答。
发起连接时系统是如何分配端口号的?
因为我们的程序没有使用bind绑定端口号,内核会自动为我们分配一个端口,可以分配的端口称作ephemeral port,使用如下命令查看ephemeral port范围:
- $ cat /proc/sys/net/ipv4/ip_local_port_range
- 32768 61000
如果源端口号和目标端口号相同,会发生什么?
在前面用tcpdump抓包时,我们看到内核是正常完成了三次握手,那么具体是怎么做的呢?
对于这个问题,linux邮件列表也进行过讨论(详见:https://lkml.org/lkml/2007/10/21/228),内核维护人员认为:
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);
- ...
- }
小结一下,自连接指的是当源ip和目标ip、源端口和目标端口都相同时,出现的自己连上自己的情况。为了避免这种情况,我们需要使目标端口不在ephemeral port范围内。可以修改目标端口,也可以修改/proc/sys/net/ipv4/ip_local_port_range文件。另外,本文浅析了出现自连接的原因,内核人员认为这个问题不是一个bug,这属于simultaneous connect,因此,需要我们自己在实际应用中避免这个情况。