首页 文章

在非对称NAT上,UDP Hole Punching失败

提问于
浏览
2

一段时间以来,我的UDP打孔系统出现了严重问题,这可能涉及到我现在无法触及的事情 . 我会尽可能准确地解释,因为这真的困扰着我:

UDP Hole打孔方法涉及第三个主机S,它基本上是公共的(不在任何类型的NAT或持久防火墙之后),将由两个客户端A和B使用以相互了解 . 客户端A向S发送udp数据包,以便S可以知道其公共IP和端口(由A的NAT映射) . 客户B也这样做 . 然后S通过发送到A B的公共地址和A的公共地址来匹配A和B.因此,他们每个人都知道彼此的地址并且可以进行通信 .

到目前为止一切都很好,但这里有一个问题:它仅适用于特殊情况 .

我知道对称NAT(NAT将您的私有IP和端口映射到您想要到达的每个目标地址的特定不同地址的问题),所以假设我发送一个数据包到S1和S2,S1会看到一个公共地址为50263,而S2为50264 . 我也知道一些非对称NAT要求您在允许从同一地址接收UDP数据包之前将UDP数据包发送到特定地址 .

所以我到目前为止所理解的是,只要你的NAT不是Symmetric,当打开一个新套接字,并发送任何字节数组时,你的NAT将为它分配一个公共IP和端口 . 公共服务器告诉您的对等方这个公共地址,以便它可以与您通信 . 然后,您必须向该对等方发送数据包以允许他回复 .

但它实际上并不是每次都有效 . 我已经在我的大学公寓里尝试过,并设法使用我的公共IP地址连接到自己,即使是在NAT后面 . 我还设法向朋友发送UDP数据包,但它在我做的一百次测试中只工作了一次 . 我现在正在家里尝试它根本不起作用 .

我在互联网上设置了2个公共VPS,并向每个人发送了一个数据包,看看我的NAT是否是Symmetric,但不是,两个端口显示的端口是相同的 . 我只能从那些公共服务器上找到自己 . 使用Wireshark,我观察到数据包离开了我的计算机,但似乎它们被丢弃了 .

我编写了简单的python脚本来尝试简单地发送给自己:

Server

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()

Client

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背后的完全不同 .

我错过了什么基本的东西?我想我做了很多可能的测试,但没有成功 .

1 回答

  • 0

    根据您要实现的目标,您可能希望使用IGDspec)来检索您的公共IP和/或自动配置端口转发 . 据我所知,大多数家用路由器都支持UPnP IGD .

    这是一个示例脚本,它使用miniupnpc,一个最小的UPnP IGD客户端,以及来自python标准库的ipaddresssocket模块:

    #!/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
    

相关问题