大佬们, 请教一个 udp 建立 p2p 连接的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
ptg2008
V2EX    程序员

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

  •  
  •   ptg2008 82 天前 2089 次点击
    这是一个创建于 82 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前置科普

    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 就难以自圆其说

    14 条回复    2025-09-16 22:01:12 +08:00
    ysc3839
        1
    ysc3839  
       82 天前 via Android
    NAT 类型检测错了吧,阿里云服务器怎么可能是 Port Restricted ?没有公网 IP 吗?
    ysc3839
        2
    ysc3839  
       82 天前 via Android
    还有一种可能是 Windows 机子用端口扫描的方式,往 Mac 机子 IP 的不同端口都发送数据包,遇到有回应的端口那就是 NAT 分配到的端口
    ptg2008
        3
    ptg2008  
    OP
       82 天前
    @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
      &nsp;82 天前
    @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  
       82 天前
    估计类似于 生日悖论暴力扫描 https://arthurchiao.art/blog/how-nat-traversal-works-zh/
    edcopclub
        6
    edcopclub  
       82 天前 via Android
    在一定范围内扫描端口连接就行了,有概率能正好能连上。
    psllll
        7
    psllll  
       82 天前 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  
       82 天前
    @ptg2008 你把安全组所有端口都放通再试一下,我总觉得你这是被安全组拦了。
    Domado
        9
    Domado  
       81 天前
    我倾向于认为是 hard NAT ,因为过了 5 分钟才成功
    Domado
        10
    Domado  
       81 天前
    通过暴力扫描实现
    le4tim
        11
    le4tim  
       71 天前 via Android
    div class="reply_content">我认为应该是 k1 + k2 < 8 时, A 和 B 能容易成功建立 p2p 连接,因为 k3+k4 可以使用生日悖论碰撞端口。
    你可以试试 http://gonc.cc/ 这个工具在两端测试建立点对点连接,它会检测 nat 类型,打洞过程都有详细输出信息的,必要时就使用生日悖论碰撞端口,没有使用 relay 的。
    ptg2008
        12
    ptg2008  
    OP
       71 天前
    @le4tim 大佬 感谢你的回答 这两天我看了 easytier 的代码实现 我已经大概明白是 nat3 对 nat4 是怎么打洞的了
    20starter
        13
    20starter  
       70 天前
    打洞成功的流程应该是三步:
    1.nat3 机器通过 stun 建立公网 ip 端口映射
    2.nat4 机器尝试访问 nat3 机器(会失败),为了得到对 nat3 机器的公网 ip 端口映射
    3.nat3 机器再次访问 nat4 机器,这时两边应该就能通了
    楼主你看的 easytier 代码实现原理是咋样的,我最近做了个在线 nat 类型检测网站 https://natchecker.com ,想看看自己真学会了没。
    ptg2008
        14
    ptg2008  
    OP
       70 天前
    @20starter 大佬 谢谢的你回答 easytier 的代码我用 ai 辅助我理解的, 你也可以尝试用 AI 了解实现原理, 我也只是懂个大概, 不敢细说原理免得误人子弟了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1531 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 16:32 PVG 00:32 LAX 08:32 JFK 11:32
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86