UDP 打孔在非对称 NAT 上失败
Posted
技术标签:
【中文标题】UDP 打孔在非对称 NAT 上失败【英文标题】:UDP Hole Punching fails on non-symmetric NATs 【发布时间】:2017-08-27 16:09:26 【问题描述】:现在,我的 UDP 打孔系统遇到了一个严重的问题,可能涉及我现在无法解决的问题。我会尽量准确地解释,因为这真的让我很困扰:
UDP 打洞方法涉及第三个主机 S,它基本上是公共的(不在任何类型的 NAT 或持久防火墙后面),两个客户端 A 和 B 将使用它来相互了解。客户端 A 向 S 发送一个 udp 数据包,这样 S 就可以知道它的公共 IP 和端口(由 A 的 NAT 映射)。客户 B 也是如此。然后S通过发送到A B的公共地址来匹配A和B,并将A的公共地址发送给B。因此,他们都知道彼此的地址,可以进行通信。
到目前为止一切都很好,但这里有一个问题:它只适用于特殊情况。
我知道由对称 NAT 引起的问题(NAT 将您的私有 IP 和端口映射到您想要到达的每个目标地址的特定不同地址,所以假设我向 S1 和 S2 发送数据包,S1 将查看带有端口 50263 的公共地址,而 S2 将看到 50264)。我还知道,一些非对称 NAT 要求您先将 UDP 数据包发送到特定地址,然后才能从同一地址接收数据包。
所以到目前为止我所理解的是,只要您的 NAT 不是对称的,当打开一个新套接字并发送任何字节数组时,您的 NAT 就会为其分配一个公共 IP 和端口。公共服务器告诉您的对等方这个公共地址,以便它可以与您通信。然后,您必须向该对等方发送一个数据包才能让他回复。
但实际上并非每次都有效。我在我的大学公寓里试过,并设法使用我的公共 IP 地址连接到自己,即使是在 NAT 后面。我还设法向朋友发送了一个 UDP 数据包,但在我进行的数百次测试中它只工作了一次。我现在正在家里尝试,它根本不起作用。
我在 Internet 上设置了 2 个公共 VPS,并向它们每个发送一个数据包,以查看我的 NAT 是否是对称的,但不是,它们显示的端口是相同的。我只能从那些公共服务器上联系到我自己。使用 Wireshark,我观察到数据包离开我的计算机,但似乎在返回之前它们被丢弃了。
我编写了简单的 python 脚本来尝试简单地发送给自己:
服务器
import socket
import sys
from helper import *
# This script is used to allow a client to know which external address it has.
def main():
# Port used to receive client requests
port = 6666
# We create a UDP socket on this port
sockfd = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
sockfd.bind (("", port))
print ("Address discoverer started on port %d" % port)
# Server loop
while True:
# We wait for any client to request for his address.
data, addr = sockfd.recvfrom(1)
# Once a client has sent a request, we send him back his external address, which we obtained using the packet he sent us.
sockfd.sendto (addr2bytes(addr), addr)
print ("Client %s:%d requested his address" % addr)
if __name__ == "__main__":
main()
客户
import sys
import socket
from helper import *
def main():
master = ("xxx.xxx.xxx.xxx", 6666)
me = ("yyy.yyy.yyy.yyy", 6666)
sockfd = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
sockfd.bind(('',6666))
sockfd.sendto (bytearray([0]), master)
data, addr = sockfd.recvfrom (6)
a = bytes2addr(data)
print ('My address is %s:%d' % a)
for i in range (0, 10):
sockfd.sendto ('hi myself'.encode('utf-8'), me)
data, addr = sockfd.recvfrom (1024)
print ("Received : " + data.decode('utf-8'))
if __name__ == "__main__":
main()
在我的一个公共 VPS 上执行,它可以工作。在家执行,我没有收到任何东西。似乎从公共主机发送会授予所有访问权限,但在 NAT 后面则完全不同。
我错过了一些基本的东西吗?我想我几乎做了所有可能的测试,但没有成功。
【问题讨论】:
您的 ISP 使用 CGN 吗?如果是这样,您有一个双重 NAT,这将是一个问题。 【参考方案1】:根据您想要实现的目标,您可能希望使用IGD (spec) 来检索您的公共 IP 和/或自动配置端口转发。据我所知,大多数家用路由器都支持 UPnP IGD。
这是一个使用miniupnpc、最小UPnP IGD 客户端以及python 标准库中的ipaddress 和socket 模块的示例脚本:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
IGDTestScript.py
================
Description: UPnP IGD example script
Author: shaggyrogers
Creation Date: 2018-03-28
License: None / Public domain
"""
import ipaddress
import socket
import sys
import miniupnpc
def main(args):
# Get IP address for default interface
allhosts = ipaddress.ip_address(socket.INADDR_ALLHOSTS_GROUP)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect((allhosts.compressed, 0))
localIP = ipaddress.ip_address(sock.getsockname()[0])
if localIP.is_global:
print(f'Public IP: localIP (no NAT)')
return 0
print(f'Local IP: localIP')
# Discover and connect to IGD device
igd = miniupnpc.UPnP()
assert igd.discover() > 0
assert igd.selectigd() != ''
# Get public IP
publicIP = ipaddress.ip_address(igd.externalipaddress())
assert publicIP.is_global
print(f'Public IP: publicIP')
# Dump port mappings
i, mapping = 0, igd.getgenericportmapping(0)
while mapping:
print(f'Port mapping i: mapping')
i += 1
mapping = igd.getgenericportmapping(i)
# Create/update port mapping
externalPort, internalPort, protocol = 1234, 4321, 'TCP'
assert igd.addportmapping(externalPort, protocol, localIP.compressed,
internalPort, 'test mapping', '')
print((f'Added port mapping: protocol publicIP:externalPort'
f' --> localIP.compressed:internalPort'))
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
样本输出:
Local IP: 192.168.1.123
Public IP: 203.0.113.1
Port mapping 0: (8908, 'TCP', ('192.168.1.123', 9100), 'tcp_map', '1', '', 0)
Port mapping 1: (22222, 'UDP', ('192.168.1.123', 22222), 'udp_map', '1', '', 0)
Added port mapping: TCP 203.0.113.1:1234 -> 192.168.1.123:4321
【讨论】:
以上是关于UDP 打孔在非对称 NAT 上失败的主要内容,如果未能解决你的问题,请参考以下文章
HTTP与HTTPS的区别对称加密和非对称加密什么是数字证书DNS 解析过程TCP和UDP的区别HTTP的特点TCP 和 UDP 对应的应用场景