TCP两端同时尝试建立连接

15
考虑TCP的三次握手。可以在此处找到解释。
上面的文章提到,两端可能同时尝试连接,而在这种情况下三次握手仍然有效。
我们能否使用套接字API模拟这种情况呢?通常使用套接字编写的是被动打开(服务器)和主动打开(客户端)吗?

3
不完全是。这是在上课期间突然冒出的一个想法。不需要提交,只是为了学习。 - Rohit Banga
就我所知,这与TCP打洞非常相似。 - andrew cooke
3个回答

20

使用套接字API可以导致同时进行TCP打开。正如尼古拉所提到的那样,这是执行以下序列并使初始SYN交叉的时间问题。

bind addr1, port1
connect addr2, port2
bind addr2, port2
connect addr1, port1

以下是我如何使用单个Linux主机实现同时打开的步骤:

  1. 使用netem减慢环回接口的速度。

tc qdisc add dev lo root handle 1:0 netem delay 5sec
  • 运行两次netcat

  • netcat -p 3000 127.0.0.1 2000
    netcat -p 2000 127.0.0.1 3000
    
  • 这两个netcat进程互相连接,形成一个TCP连接。
  • $ lsof -nP -c netcat -a -i # some columns removed 
    COMMAND   PID NAME
    netcat  27911 127.0.0.1:2000->127.0.0.1:3000 (ESTABLISHED)
    netcat  27912 127.0.0.1:3000->127.0.0.1:2000 (ESTABLISHED)
    

    下面是tcpdump展示给我的内容(为了清晰起见进行了编辑)

    127.0.0.1.2000 > 127.0.0.1.3000: Flags [S], seq 1139279069
    127.0.0.1.3000 > 127.0.0.1.2000: Flags [S], seq 1170088782
    127.0.0.1.3000 > 127.0.0.1.2000: Flags [S.], seq 1170088782, ack 1139279070
    127.0.0.1.2000 > 127.0.0.1.3000: Flags [S.], seq 1139279069, ack 1170088783
    

    那是一个非常全面的答案。我尝试了一下并学到了新的东西。谢谢。 - Rohit Banga
    小修正。在我的电脑上,使用 tc qdisc replace 会出现错误 RTNETLINK: file exists。此外,我使用了 ls -la /dev/loop0 中的主要和次要编号。 - Rohit Banga
    我看到连续交换了多个SYN,所有的序列号都相同。有什么原因吗? - Rohit Banga
    你可以尝试回答这个问题: https://dev59.com/qEvSa4cB1Zd3GeqPivqH - Rohit Banga
    1
    @iamrohitbanga SYN会被重传直到收到ACK,这就是为什么你会看到有多个具有相同序列号的SYN。我也看到了它们,但选择不在此处包含它们。 - sigjuice

    3

    为方便参考,除了sigjuice和Nikolai提供的优秀答案,我们可以用Python轻松实现同时打开多个程序。在两个不同的Python解释器中输入以下代码:

    >>> import socket
    >>> s1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    >>> s1.bind(('localhost', 1111))
    >>> s1.connect(('localhost', 2222))
    >>> s1.send('hello')
    5
    >>> s1.recv(5)
    'world'
    

    并且:

    >>> import socket
    >>> s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    >>> s2.bind(('localhost', 2222))
    >>> s2.connect(('localhost', 1111))
    >>> s2.recv(5)
    'hello'
    >>> s2.send('world')
    5
    

    (连接调用必须在两个绑定调用都返回后进行)

    2
    我们采用被动服务器和主动客户端的方式,因为这种方式易于理解、相对容易实现和编码。可以将其类比为商店和顾客之间的关系,我们可能会处于以下情况之一:
    • 顾客去商店(主动客户端),商店开门迎接(被动服务器)——双方都很高兴。
    • 顾客去商店,商店关闭了(没有服务器在监听)——顾客没那么幸运。
    • 商店开门了,但没有顾客来——商店不太幸运。
    • 商店关闭了,也没有顾客来——无人问津 :)
    由于服务器被动等待客户端连接,因此很容易预测何时可以建立连接。除了服务器地址和端口号外,不需要任何预先协议。
    另一方面,“同时打开”则需要双方连接超时,即必须仔细协调才能建立连接,以便 SYN 在“飞行”中交叉。这是 TCP 协议的一个有趣特性,但我不认为在实践中有任何用途。
    您可以尝试通过打开套接字、绑定到端口(以便另一端知道要连接到哪里)并尝试连接来模拟此过程。两端是对称的。可以使用 netcat 的 -p 选项尝试。不过您必须非常快 :)

    1
    不,你很可能无法在本地主机上完成这个任务 - 数据包传输速度太快了 :) - Nikolai Fetissov
    在两个主机上执行对称的connect(),如果SYN交叉,将建立工作中的TCP连接?在这种情况下,是否根本不需要使用listen()? - sigjuice
    是的,不需要使用 **listen()**,但需要使用 **bind()**。 - Nikolai Fetissov
    2
    实际上有一个使用案例。穿透两个NAT打通TCP连接。 - Wu Yongzheng
    是的,一种人工解决方案来应对有限地址空间上笨拙的黑客攻击...非常实用 :) - Nikolai Fetissov

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接