TCP Hole Punching(绕过监听套接字)

TCP Hole Punching (bypassed listening socket)

自从我玩打孔以获得某种可靠的行为以来已经有几天了,但我现在处于死胡同。

UDP 打洞效果很好只需先向远程发送一个数据包,然后让远程发送一个数据包,因为它会通过源 NAT。根据我的尝试,它相当可靠

但是现在出现了 TCP...我不明白。

现在,我可以通过 NAT 建立连接,但是 只能使用连接套接字:

A.connect(B) -> Crash agains't B's NAT, but open a hole in A's NAT.
B.connect(A) -> Get in A's NAT hole, reach A's connecting socket.

但是现在,发送SYN包进行连接的两个socket已经连接上了。

你会认为我会做到的,通过 2 个 NAT 建立连接,万岁。

但问题是这不是正常行为,并且给了本文:http://www.brynosaurus.com/pub/net/p2pnat/,我应该能够有一个与连接套接字并联的监听套接字。

所以我确实绑定了一个监听套接字,它将接受入站连接。

但是入站连接总是被连接套接字捕获,而不是被监听套接字捕获...

例如:

#!/usr/bin/env python3
from socket import *
from threading import Thread
Socket = socket

# The used endpoints:
LOCAL = '0.0.0.0', 7000
REMOTE = 'remote', 7000

# Create the listening socket, bind it and make it listen:
Listening = Socket(AF_INET, SOCK_STREAM)
Listening.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
Listening.bind(LOCAL)
Listening.listen(5)

# Just start in another thread some kind of debug:
# Print the addr of any connecting client:
def handle():
    while not Listening._closed:
        client, addr = Listening.accept()
        print('ACCEPTED', addr)
Thread(target=handle).start()

# Now creating the connecting socket:
Connecting = Socket(AF_INET, SOCK_STREAM)
Connecting.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
Connecting.bind(LOCAL)

# Now we can attempt a connection:
try:
    Connecting.connect(REMOTE)
    print('CONNECTED', Connecting.getpeername())
except Exception as e:
    print('TRIED', type(e), e)

现在有了这个脚本,只需与朋友或其他人商定一个端口,然后在一端执行它,Connecting.connect(...) 应该 运行 一点(等待超时,因为 SYN packet crashed into the distant NAT, 幸好在自己的NAT开了一个洞),同时在另一端执行脚本,现在Connecting.connect(...)会return 因为已经连接了

最奇怪的部分是:Listening 套接字 从未被触发

为什么?如何让侦听套接字通过连接套接字捕获入站连接?

注意:关闭连接套接字确实会在网络上发送一些东西,这会立即关闭漏洞,至少在我的网络上是这样。

第二注:我在windows。

Edit:主要问题是在任何情况下,此脚本输出 CONNECTED [...] 而不是 CLIENT [...],这在某些讲座中是不应该发生的。

为什么没有触发listen socket

我想答案就在这里。 TCP 连接由四个元素的元组定义:

  • 本地地址
  • 本地端口
  • 远程地址
  • 远程端口

当您建立 TCP 连接时,您会创建从该元组到本地主机上的连接套接字的绑定。

当通过 NAT 发送 SYN 时,它会创建绑定: - 本地 address/port -> Public address/port

当远程端将其 SYN 发送到 Public address/port 时,此地址将转换为本地 address/port 并传送到本地计算机。在这台机器上,此连接无法与初始连接区分开来并已成功建立(SYN/ACK)。

这意味着本地没有收到INITIAL SYN。

如何让侦听套接字通过连接套接字捕获入站连接?

源NAT是不可能的。要接受 NAT 后面的新连接,您需要将一些 public IP/Port 映射到您私有的目标 NAT IP/port

所以,经过更多的测试和阅读,这就是我的结论:

事实上,可以在同一地址(ip、端口)上绑定一个监听套接字和一个出站连接套接字。

但是套接字的行为在很大程度上取决于系统/TCP 堆栈的实现,如 http://www.brynosaurus.com/pub/net/p2pnat/§4.3:

中提到的

What the client applications observe to happen with their sockets during TCP hole punching depends on the timing and the TCP implementations involved. Suppose that A's first outbound SYN packet to B's public endpoint is dropped by NAT B, but B's first subsequent SYN packet to A's public endpoint gets through to A before A's TCP retransmits its SYN. Depending on the operating system involved, one of two things may happen:

  • A's TCP implementation notices that the session endpoints for the incoming SYN match those of an outbound session A was attempting to initiate. A's TCP stack therefore associates this new session with the socket that the local application on A was using to connect() to B's public endpoint. The application's asynchronous connect() call succeeds, and nothing happens with the application's listen socket.
    Since the received SYN packet did not include an ACK for A's previous outbound SYN, A's TCP replies to B's public endpoint with a SYN-ACK packet, the SYN part being merely a replay of A's original outbound SYN, using the same sequence number. Once B's TCP receives A's SYN-ACK, it responds with its own ACK for A's SYN, and the TCP session enters the connected state on both ends.

  • Alternatively, A's TCP implementation might instead notice that A has an active listen socket on that port waiting for incoming connection attempts. Since B's SYN looks like an incoming connection attempt, A's TCP creates a new stream socket with which to associate the new TCP session, and hands this new socket to the application via the application's next accept() call on its listen socket. A's TCP then responds to B with a SYN-ACK as above, and TCP connection setup proceeds as usual for client/server-style connections.
    Since A's prior outbound connect() attempt to B used a combination of source and destination endpoints that is now in use by another socket, namely the one just returned to the application via accept(), A's asynchronous connect() attempt must fail at some point, typically with an “address in use” error. The application nevertheless has the working peer-to-peer stream socket it needs to communicate with B, so it ignores this failure.

The first behavior above appears to be usual for BSD-based operating systems, whereas the second behavior appears more common under Linux and Windows.

其实我属于第一种情况。在我的 Windows 10.

这意味着为了使TCP Hole Punching成为一种可靠的方法,我需要在连接套接字的同时绑定一个监听套接字,但我稍后需要检测哪个触发了(监听或连接)并将其传递到应用程序的流程中。