8-TCP自连接

如果本地tcp程序本地通信,且客户端先于服务端启动,那么有极大可能会产生一种自连接现象。

tcp自连接:tcp连接两段使用了同一端口进行连接,而tcp并没有报错并且连接成功。即localhost:x --> localhost:x 。

 

代码

下面的代码是python2代码,所以要用python2执行。

代码逻辑:输入一个端口号port,然后尝试连接65536次,连接成功,打印连接信息:本段和对端的sockname。

 #!/usr/bin/python
 
 import errno
 import socket
 import sys
 import time
 
 if len(sys.argv) < 2:
     print "Usage: %s port" % sys.argv[0]
     print "port should in net.ipv4.ip_local_port_range"
 else:
     port = int(sys.argv[1]) # 命令行输入端口号
     for i in range(65536):
         try:
             sock = socket.create_connection(('localhost', port)) # 尝试对端口号发起连接
             print "connected", sock.getsockname(), sock.getpeername() # 连接成功,打印本机地址和对端地址
             time.sleep(60*60) # 休眠一小时
         except socket.error, e:
             if e.errno != errno.ECONNREFUSED:
                 break
 

 

自连接复现步骤

命令 netstat -ltnp 可以列出所有监听tcp端口,-n或—numeric:直接使用ip地址,而不通过域名服务器,-p或—programs:显示正在使用Socket的程序识别码和程序名称;

使用自连接程序,连接 22 号端口(在 netstat -ltnp 命令中显示,该22端口是正在监听的,所以可以连接成功)。接下来连接了一个没有在监听的端口 31000, 却也可以连接成功,并且程序打印的连接提示是 127.0.0.1::31000 连接到 127.0.0.1::31000。 这就是程序的自连接了。

image-20230211191105401

 

代码自己执行时并不会出现自连接,why???

 

下面是查看本地端口范围

 [root@k8s-master ~]# sysctl -A | grep range
 net.ipv4.ip_local_port_range = 32768 60999
 net.ipv4.ping_group_range = 1 0
 sysctl: reading key "net.ipv6.conf.all.stable_secret"
 sysctl: reading key "net.ipv6.conf.br-4986ea332974.stable_secret"
 sysctl: reading key "net.ipv6.conf.cni0.stable_secret"
 sysctl: reading key "net.ipv6.conf.default.stable_secret"
 sysctl: reading key "net.ipv6.conf.docker0.stable_secret"
 sysctl: reading key "net.ipv6.conf.eth0.stable_secret"
 sysctl: reading key "net.ipv6.conf.flannel/1.stable_secret"
 sysctl: reading key "net.ipv6.conf.lo.stable_secret"
 sysctl: reading key "net.ipv6.conf.veth19b3a315.stable_secret"
 sysctl: reading key "net.ipv6.conf.veth28409c52.stable_secret"
 sysctl: reading key "net.ipv6.conf.veth2c14f7af.stable_secret"
 sysctl: reading key "net.ipv6.conf.veth49730377.stable_secret"
 sysctl: reading key "net.ipv6.conf.veth4f1e29a0.stable_secret"
 sysctl: reading key "net.ipv6.conf.vetha1560f13.stable_secret"

 

原来是本地端口范围是从32768到60999,超出这个范围是不会出现自连接的。

 

将端口换成32768,果然会出现自连接

 [root@k8s-master ~]# python self-connection.py 32768
 connected ('127.0.0.1', 32768) ('127.0.0.1', 32768)
 ^CTraceback (most recent call last):
  File "self-connection.py", line 17, in <module>
    time.sleep(60*60)
 KeyboardInterrupt
 

 

netstat查看端口32768

 [root@k8s-master ~]# netstat -ntp | grep 32768
 tcp6       0      0 ::1:32768               ::1:32768               TIME_WAIT   -

 

原因

Tcp在发起连接时,会从 ip_local_port_range 中选取一个临时端口号,选定端口号后再向服务器的端口发送一个syn的请求。如果指定端口在侦听,那么这个随机端口就不会选取到这个端口(连接到22端口,不会发生自连接),如果指定端口没有在侦听,那么就有可能发生自连接。

究其原因为,客户端不断地尝试连接 32768 端口,每次都会随机分配一个临时端口(临时端口分配规则是,给上一次分配的端口号+1)。在多次尝试后,会出现分配的临时端口和目的端口一样,此时就会出现自连接情况了。 详细步骤大致为:

  • 客户端在选取了一个临时端口 x 后,该端口就被加入内核中,客户端在发送syn报文到31000端口(x to 32768 ),但因为 32768 没有在监听,因此连接失败

  • 在此尝试连接,重新选取临时端口号 x + 1 ,再次尝试连接 … 失败。

  • 第n次尝试时,选取的临时端口号刚好是 31000 端口,然后向 31000 端口发起syn报文,此时在内核中检测到了刚加入的端口,然后连接成功。

 

解决方法

每次TCP连接成功后检测一下是否为自连接(检测TCP连接的本段地址和对端地址是否相同),如果是自连接断开即可。

 

 // 检测是否为自连接:判断本机地址是否等于对端地址
 bool isSelfConnection(const Socket& sock)
 {
   return sock.getLocalAddr() == sock.getPeerAddr();
 }
 
 // 连接的处理:是自连接则断开连接
 TcpStreamPtr TcpStream::connectInternal(const InetAddress& serverAddr, const InetAddress* localAddr)
 {
   TcpStreamPtr stream;
   Socket sock(Socket::createTCP());
   if (localAddr)
  {
     sock.bindOrDie(*localAddr);
  }
   if (sock.connect(serverAddr) == 0 && !isSelfConnection(sock)) // 连接成功 且 不是自连接
  {
     // FIXME: do poll(POLLOUT) to check errors
     stream.reset(new TcpStream(std::move(sock))); // 设置stream,将连接移动到strean中
  }
   return stream;  // 如果不设置stream等于放弃sock的连接(sock出作用域后会自动释放,连接自动断开)
 }
 
 
posted @ 2023-04-29 15:11  DavidJIAN  阅读(297)  评论(0)    收藏  举报