TCP 自连接

  • tcp连接两段使用了同一端口进行连接,而tcp并没有报错并且连接成功。即localhost:x --> localhost:x
  • 只存在于 tcp 本地通信,而且客户端先于服务端启动

代码:

https://github.com/Anita-Mul/recipes/blob/master/python/self-connect.py

#!/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
    
  • 选择一个正在监听的端口,启动程序
    python self-connect.py 22
    
  • 查看本机端口号的范围【ip_local_port_range
    sysctl -A |grep range
    
  • 选择一个不在监听的端口,启动程序
    发现也会启动成功
    python self-connect.py 31000
    # 查看 31000 端口占用情况,发现只有自连接的情况出现
    netstat -tpn|grep 31000
    

原因

  • tcp在发起连接时,会从 ip_local_port_range 中选取一个临时端口号,选定端口号后再向服务器的端口发送请求。如果指定端口在侦听,那么这个随机端口就不会选取到这个端口(连接到22端口,不会发生自连接),如果指定端口没有在侦听,那么就有可能发生自连接。
  • 客户端不断地尝试连接 31000 端口,每次都会随机分配一个临时端口(临时端口分配规则是,给上一次分配的端口号+1)。在多次尝试后,会出现分配的临时端口和目的端口一样,此时就会出现自连接情况了
    • 客户端在选取了一个临时端口 x 后,该端口就被加入内核中,客户端在发送syn报文到31000端口(x to 31000),但因为 31000 没有在监听,因此 连接失败 。

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

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

解决方法

  • 在连接成功后检测一下是否为自连接,如果是自连接断开即可
    // 检测是否为自连接:判断本机地址是否等于对端地址
    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出作用域后会自动释放,连接自动断开)
    }
    
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐