V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
ptg2008
V2EX  ›  程序员

大佬们, 请教一个 udp 建立 p2p 连接的问题

  •  
  •   ptg2008 · 6 小时 52 分钟前 · 462 次点击

    前置科普

    nat 类型分类

    • NAT1:Full Cone (全锥形 NAT )
    • NAT2:Restricted Cone ( IP 受限锥形 NAT )
    • NAT3:Port Restricted Cone (端口受限锥形 NAT )
    • NAT4:Symmetric (对称型 NAT )

    结论: 若 Full Cone = 1, Restricted Cone = 2, Port Restricted Cone = 3, Symmetric = 4, 假设 A 端的 nat 类型代表的数值为 k1 (1 <= k1 <= 4), B 端的 nat 类型代表的数值为 k2 (1 <= k2 <= 4), 根据我掌握的到的计算机网络知识, 只有当 k1 + k2 <= 6 时, A 和 B 才能成功建立 p2p 连接

    发现问题

    今天我试了用 easytier 组网, 具体来说, 就是我有台的 MacBook Pro, 一台 Windows, 还有一个阿里云小水管机器, 组网成功后, 我执行了命令查看组网情况, 如下图所示 relay 没错, 符合我预期, MacBook Pro 是 Symmetric = 4, Windows 是 Port Restricted Cone = 3, 4 + 3 = 7 > 6, 只能走阿里云小水管中继

    但是 5 分钟后我又执行了命令查看组网情况, 如下图所示 relay 发现了 MacBook Pro 和 Windows 之间是 变成了 P2P 连接

    分析一波

    网络拓扑大概这样

    MacBook (priv_ip1, priv_port1)
        |
        |
    公网出口 1 (pub_ip1, pub_port1)  
        |
       ...(中间很多路由)
        |
    阿里云小水管 (aliyun_ip, aliyun_port)
        |
       ...(中间很多路由)
        |
    公网出口 2 (pub_ip2, pub_port2) 
        |
        |
    Windows (priv_ip2, priv_port2)    
    
    

    分析如下

    • 步骤 1:双方先向中继 S (代指阿里云小水管) “报备” 自己的临时端口
      • Windows ( Port Restricted = 3 )向 S 发请求:分配到了一个出口路由器(pub_ip2)的临时端口 pub_port2 ,并记录 (priv_ip2, priv_port2) <-> (pub_ip2, pub_port2) 的映射; 同时,S 会知道 Windows 的 (pub_ip2, pub_port2) 。
      • MacBook ( Symmetric = 4 )向 S 发请求: 分配到了一个出口路由器(pub_ip1)的临时端口 pub_port1 ,并记录 (priv_ip1, priv_port1) <-> (pub_ip1, pub_port1) 的映射; 同时,S 会知道 MacBook 的 (pub_ip1, pub_port1) 。
    • 步骤 2:中继 S 向双方 “转发对方的端口信息”
      1. S 把 Windows 的 (pub_ip2, pub_port2) 告诉 MacBook ;
      2. S 把 MacBook 的 (pub_ip1, pub_port1) 告诉 Windows 。
    * case1: 
     1. Windows 发起请求访问 MacBook 的(pub_ip1, pub_port1) , 但是被拒绝, 这个请求对于 nat4 而言是"陌生请求"(因为 Windows 和 MacBook 之前没有建立连接)
     2. 但是这时候能从(pub_ip1, pub_port1)访问 Windows 了 (因为是 nat3, 端口和 IP 都受限, 只能从(pub_ip1, pub_port1)访问, 其它的网段访问 Windows 还是会被拒绝)
     3. 这时候 MacBook 再次向(pub_ip2, pub_port2)发起请求, 但是因为 MacBook 是 nat4, 导致分配到的出口路由器的端口不再是 pub_port1, 而是 another_pub_port1 (假设出口路由器不变)
     4.  这样访问 Windows 就会被拒绝, 因为对于 Windows 而言, 只允许从(pub_ip1, pub_port1)过来的请求访问, 而从(pub_ip1, another_pub_port1)过来的是没法访问的
    
    * case2: 
     1. MacBook 发起请求访问 Windows 的(pub_ip2, pub_port2) , 但是被拒绝, 因为是 nat4, 导致分配到的出口路由器的端口不再是 pub_port1, 而是 another_pub_port1 (假设出口路由器不变)
    
    

    所以这样就没法打洞成功

    疑问

    • 在上面案例中, Windows 和 MacBook 怎么建立 P2P 连接的
    • MacBook 在和 S 建立连接断开后, 然后再和 Windows 建立连接时, 能复用出口端口 pub_port1 吗? 如果是这样, 应该就可以 P2P 成功, 实际上不同路由器的对于 nat 分配实现是怎么样的呢?

    AI 怎么说

    所有 AI 不能很清楚的回答我的问题, 在我的追问后这些 AI 就难以自圆其说

    8 条回复    2025-09-04 23:57:54 +08:00
    ysc3839
        1
    ysc3839  
       6 小时 44 分钟前 via Android
    NAT 类型检测错了吧,阿里云服务器怎么可能是 Port Restricted ?没有公网 IP 吗?
    ysc3839
        2
    ysc3839  
       6 小时 39 分钟前 via Android
    还有一种可能是 Windows 机子用端口扫描的方式,往 Mac 机子 IP 的不同端口都发送数据包,遇到有回应的端口那就是 NAT 分配到的端口
    ptg2008
        3
    ptg2008  
    OP
       6 小时 30 分钟前
    @ysc3839 这个我考虑过在 case1 的第 4 步后 Mac 从(pub_ip1, another_pub_port1)访问 windows 的(pub_ip2, pub_port2) 会被拒绝, 但是对于(pub_ip1, another_pub_port1)而言, 可以从 windows 的(pub_ip2, pub_port2) 访问, 因为由 Mac 主动发起了, 不过这个 another_pub_port1 只能靠对端的 windows 去猜 another_pub_port1 的分配规律, 猜中的话就能建立接连, 但是我 rust 不熟练, 等我有空了我看看 easytier 的实现
    ptg2008
        4
    ptg2008  
    OP
       6 小时 24 分钟前
    @ysc3839 检测了 他确实是 Port Restricted, 有公网 IP 的

    Welcome to Alibaba Cloud Elastic Compute Service !

    Last login: Thu Sep 4 15:06:12 2025 from 58.251.160.132
    ➜ ~ sudo /opt/easytier/easytier-cli stun
    stun info: StunInfo {
    udp_nat_type: PortRestricted,
    tcp_nat_type: Unknown,
    last_update_time: 1756978903,
    public_ip: [
    xxxxxx,
    ],
    min_port: 58939,
    max_port: 58939,
    }
    xdeng
        5
    xdeng  
       6 小时 20 分钟前
    估计类似于 生日悖论暴力扫描 https://arthurchiao.art/blog/how-nat-traversal-works-zh/
    edcopclub
        6
    edcopclub  
       4 小时 59 分钟前 via Android
    在一定范围内扫描端口连接就行了,有概率能正好能连上。
    psllll
        7
    psllll  
       4 小时 52 分钟前 via Android
    试试用 frp 的 xtcp 模式
    可以一直重试直到打洞成功,不会回落走中转
    像这样
    serverAddr = ""
    serverPort =
    natHoleStunServer = "stun.miwifi.com:3478"


    [[visitors]]
    name = ""
    type = "xtcp"
    keepTunnelOpen = true
    maxRetriesAnHour = 3600
    minRetryInterval = 1
    serverName = ""
    bindAddr = "127.0.0.1"
    bindPort = 10088
    arrow629
        8
    arrow629  
       10 分钟前
    @ptg2008 你把安全组所有端口都放通再试一下,我总觉得你这是被安全组拦了。
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2340 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 16:08 · PVG 00:08 · LAX 09:08 · JFK 12:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.