本文来自依云's Blog,转载请注明。
两台没有外网 IP、在 NAT 后边的主机如何直连?UDP打洞通常可行,但是需要第三方服务器。方法如下:
在服务器 S 上监听一个 UDP 端口,在收到 UDP 数据包后把源地址发回去。代码如下(github):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
sys
import
time
import
socket
def
main(port):
s
=
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('', port))
try
:
while
True
:
data, addr
=
s.recvfrom(
4096
)
back
=
'Your address is %r\n'
%
(addr,)
s.sendto(back.encode(), addr)
print
(time.strftime(
'%Y-%m-%d %H:%M:%S'
), addr,
'just sent us a message:'
, data.decode(
'utf-8'
,
'replace'
), end
=
'')
except
KeyboardInterrupt:
print
()
if
__name__
=
=
'__main__'
:
try
:
main(
int
(sys.argv[
1
]))
except
(ValueError, IndexError):
sys.exit(
'which port to listen?'
)
|
主机 A 发送数据包:
1
2
3
|
$ socat readline udp:xmpp.vim-cn.com:2727,sourceport=4567
my addr?
Your address is (
'a.b.c.d'
, 40060)
|
输入任意消息并回车,一个 UDP 就从本地的 4567 发送出去了。从上述示例我们可以看到,NAT 设备转发时是从 40060 端口发送出去的。为了让服务器返回的数据能够到达内网主机,在一段时间内,NAT 设备会记住外网来自 40060 端口的 UDP 数据包要发送给主机 a.b.c.d 的 4567 端口。完全圆锥型NAT不会在意外部数据包是从什么地方发回来的。受限圆锥型NAT会忽略掉其它主机的数据包,上例中只认可来自 xmpp.vim-cn.com 的数据包。端口受限圆锥型NAT更进一步地要求源端口(上例中是 2727)必须跟之前发出的数据包的目的端口一致。当然,「之前发出的数据包」不必是最后一个。所以,除了最后一种——对称NAT——之外,其它类型的NAT都是有可能成功穿透的。参见维基百科条目网络地址转换和STUN。
后来通过 pystun 程序,我得知我所处的 NAT 是完全圆锥型的。
在知道 A 的发送地址后,主机 B 就可以向这个地址发送数据了。接下来的操作使用 socat 命令就是:
1
2
3
4
|
# host A
$ socat readline udp-listen:4567
# host B
$ socat readline udp:A:4567
|
然后 B 先发送数据让 A 知道 B 的地址(socat 会 connect 到这个地址),双方就可以相互通信了。当然,因为是 UDP 协议,所以通信是不可靠的,丢包啊乱序啊都有可能。
2013年10月13日更新:想要连接到 NAT 后边的 mosh 请看这里~