V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
delpo
V2EX  ›  宽带症候群

分享一个在 NAT1 下将端口打开到公网上的方法

  •  2
     
  •   delpo · 2022-08-09 20:38:13 +08:00 · 5037 次点击
    这是一个创建于 870 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    最近在机缘巧合之下入了几个 pt 大站。大站下载人数多,总的来说还是比较好混的。但是为了永久保留账号,需要刷一定的上传流量。在苦恼于硬盘空间不足无法大量保种的同时,还有一个很重要的问题就是没有公网 ip 。家里的宽带是电信内网 v4+公网 v6 ,虽然有 ipv6 地址,并且大部分 pt 都支持 v6 tracker ,但是总的来看 ipv6 无论是做种还是下载人数都是远远少于 ipv4 的。所以一直想要一个公网 ip 。给运营商要公网 ip 好几次,都被打太极给拒绝了。因为之前研究过 nat 以及打洞相关的知识,就想研究一下没有公网 ip 是否也可以把某个端口暴露在公网上。恰好家里宽带是 nat1 ,经过一番折腾,发现了一种可以在 nat1 下将某个端口暴露在公网的办法,在这里分享给大家。

    准备工具

    • OS:Debian
    • BT 客户端: 这里以 qbittorrent-nox 为例,其他客户端可以自行参考
    • https mitm 、rewrite 工具: mitmproxy,选择它是因为可以兼顾中间人以及修改 http 请求
    • 网络包构造工具: sendip
    • 抓包工具:tcpdump
    • 另一台具有公网 ip 的 vps

    预备知识

    • nat1: 即完全锥形 nat ,意味着某一个 local port 对应的 external port 可以被任意地址的 peer 访问。其中 local port 和 external port 一般是不一样的。
    • tracker: 一般 pt 站使用的均为 http(s) tracker ,根据bep_0003,对 tracker 的 http(s)请求参数中包含客户端 ip 和 port 信息,其中 ip 信息是可选的。通过对 qbittorrent 的 http 请求抓包可以证实上述过程。
    • nexusphp:大部分国内 pt 站都用的这个框架。通过大致研究源码,可以发现客户端的 ip 地址实际是通过 tracker 请求对应的 ip 得到的,而 port 则是直接取 tracker 请求中的 port 参数。这就导致了服务器获取到的端口实际上是客户端的 local port 。

    具体步骤

    1 第一步当然是打开 BT 客户端。这里以 qbittorrent 为例,假设 qbittorrent 的 BT 监听端口,即 local port 为 12345

    2 将 local port 和 nat 防火墙对应的 external port 之间的映射打开。由于 qbittorrent 已经监听了 12345 端口。所以无法通过创建一个新 socket 进行发包。当然,你可以重新编译 qbittorrent ,加上 SO_REUSEPORT 标志,但是这里为了快速实验,使用基于 raw socket 的 sendip 工具进行网络包创建,命令如下:

    watch -n 1 sendip -p ipv4 -is <LOCAL_ADDR> -id <YOUR_VPS_ADDR> -p tcp -ts 12345 -td <ARBITRARILY_PORT> <YOUR_VPS_ADDR>
    

    这里使用 watch 命令每秒执行一次 sendip 命令,是因为我这里的 CGN nat 在未建立连接的情况下超时时间非常短,只有 5 秒。作为对比,linux 下 iptables 的 conntrack 模块在 NEW 状态下会持续 180 秒。所以需要每秒发一次包保持连接。注意,这个命令在之后需要一直运行以保持 nat 映射,即使已经端口映射成功。

    命令中-td 表示的目标端口可以随意指定

    3 在 vps 上抓包获取 external port 。使用 tcpdump 抓取上一步中发送的网络包,从而获取到 local port 12345 对应的 external port ,这里假设为 54321 。

    4 本地部署 mitmproxy ,抓取 qbittorrent 的 tracker 请求。这里 qbittorrent 需要修改三个设置:

    1. Connection -> Peer connection protocol 改为 TCP 。因为 tcp 和 udp 在 nat 防火墙上映射得到的端口是不一样的,所以想同时映射 udp 的话需要再执行一次本教程。
    2. Connection -> Proxy Server 设置 http 代理为 mitmproxy 的监听端口。注意这里不要勾选 Use proxy for peer connections ,因为我们只需要中间人 tracker 请求。
    3. Advanced -> Validate HTTPS tracker certificate 取消勾选。因为解密 https 需要中间人证书,这里直接忽略校验证书,省去了配置 mitmproxy 自签名证书的步骤。这样就可以在 mitmproxy 中抓取到 tracker 请求了。

    5 通过 mitmproxy 自定义脚本修改 tracker 请求中的 port 参数。官网有一个示例,将参数换为 port ,参数的值替换为第三步中得到的 external port 54321 即可。

    6 大功告成

    结果

    我从以下三方面验证了内网的 12345 端口已经打开在了公网上:

    1. 通过另外设备(手机)或者站长工具里的tcp ping,可以 ping 通 54321 号端口。
    2. pt 站的个人信息页面里会显示客户端的 ip 以及端口,可以确认端口变为了 external port 54321 ,同时连接性为绿色。
    3. qbittorrent 中,在种子的 peers 选项卡里,可以看到很多 peers 的 Flags 一栏中有 I 标志,意味着是由其他 peers 主动连接的。

    问题

    1. ipv6 。将 ipv4 的 external port 修改以后,在 pt 站的个人信息里,ipv6 的 external port 也改变了,导致 ipv6 无法接受入站连接。经过初步调研发现,似乎是 bt 标准对 ipv4&v6 双栈情况下的支持问题,即使 tracker 请求是 ipv4 发出的,qbittorrent 也会在参数上附加 ipv6=xxx 。尝试过将 v6 请求不经过 mitm 修改,也无法解决问题。其实个人也不推荐这么做,如果你的 v4 和 v6 端口不一样,可能会被管理员怀疑使用魔改客户端导致封号。一个变通的方法是,在本地使用 ip6tables 转发 ipv6 的 54321 端口到 12345 。
    2. nat 映射断开,事实上,虽然使用 watch 命令每秒发送一次数据包维持映射,但是在我实际测试中,还是有两次映射改变的情况,其中一次是因为宽带重拨,另一次我没有捕捉到原因。每次映射改变,都需要在 mitmproxy 脚本里更改 external port ,可以说是很不自动化。
    3. 这种方法只适用于通过 tracker 连接 peers 的情况,DHT 下似乎用不了?不过问题不大,毕竟是为了 pt 折腾的。

    总结

    可以看出,通过(很麻烦的)折腾,是可以达到将 BT 客户端的 nat1 端口映射到公网的目的的。这主要归功于 nat1 的特性,所以显然 nat234 都不能使用。这个方法应该只在 PT 的情况下比较实用,毕竟如果是打洞的话,有一台有公网 ip 的 vps 就已经可以完成打洞了。这次试验结果来看还是很成功的,发现了一种将 nat1 主机的某个端口打开到公网的方法,但是使用上述方法显然不能完成自动化,大部分操作都需要手动配置。所以如果可以开发一款 C/S 软件,自动化的打开 nat 映射,服务端抓包,获取外部端口,结果发送到客户端,可以极大地简化上述操作,并且在 nat 映射发生改变的情况下自动的更改外部端口。

    第 1 条附言  ·  2022-08-10 19:11:15 +08:00

    更新

    为了简化流程,方便自动化,我改进了之前的流程

    1. 客户端/服务端不再使用sendip/tcpdump进行抓包,而是直接在服务端架设一个简单的http服务器,客户端每秒发出一次http请求,服务端返回值为客户端的external port。http客户端使用一个额外的固定的本地端口发出请求,记为extra port。
    2. 客户端得到external port后保存为文件。即使nat映射改变,客户端也可以及时更新保存端口的文件。mitmproxy读取该文件中的端口进行tracker请求修改。
    3. 经过上述步骤,extra port已经暴露在公网上。为了使local port也暴露在公网上,使用iptables的DNAT模块进行本地端口转发,从而将extra port的入站流量全部转发到local port上。

    相比原流程,新流程使用的工具更简单,更方便自动化布署。如果之后有时间我会写成脚本发布。

    第 2 条附言  ·  2022-08-10 19:22:11 +08:00
    话说,要是有什么在线的服务 /工具,可以给客户端返回 external port ,那好像连 vps 都可以不用了🤣
    第 3 条附言  ·  2022-08-10 19:47:13 +08:00

    刚看了一下STUN协议标准,发现STUN竟然支持使用TCP连接,而公共的STUN服务器可是有很多的。感觉此贴可以终结了。

    第 4 条附言  ·  2022-08-13 14:35:12 +08:00

    用go写了一个小工具,可以保持nat映射,以及将外部端口输出到文件,包装成docker镜像了,欢迎大家使用

    https://hub.docker.com/r/alozxy/trav

    31 条回复    2024-06-10 21:29:12 +08:00
    br2049
        1
    br2049  
       2022-08-09 21:05:59 +08:00
    感谢 思路很清晰
    terrancesiu
        2
    terrancesiu  
       2022-08-09 22:13:33 +08:00 via iPhone
    很厉害,学到了
    LnTrx
        3
    LnTrx  
       2022-08-09 22:32:11 +08:00
    很好的想法。过程中涉及很多方面技能的综合运用,或有用于教学的潜力。
    ScotGu
        4
    ScotGu  
       2022-08-09 22:52:40 +08:00
    谢谢,然后拿起电话继续拨打一万号要公网 IP 。
    v2tudnew
        5
    v2tudnew  
       2022-08-09 22:53:48 +08:00
    如果有软件自动化还是不错的。
    isad
        6
    isad  
       2022-08-09 23:36:56 +08:00   ❤️ 1
    我也这样设想过,但没有实施,因为我发现 DHT 直接就会用你的公网端口,只需要下载个热门种子“启动”一下就好了
    hello365
        7
    hello365  
       2022-08-10 09:10:40 +08:00
    请教一下,如果 qb 勾选了 Use proxy for peer connections 是不是 pt 站显示的就是 vps 的 ip 了?如果本地 debian 和 vps 通过 tinc 、wireguard 这类的组网软件组成一个局域网,然后 vps iptables 转发到 qb 是不是也可行?只是所有流量都是通过 vps 进来的链路更长了。
    FrankAdler
        8
    FrankAdler  
       2022-08-10 10:15:49 +08:00 via iPhone
    牛逼啊,移动 nat1 有救了,虽然我有公网用不上
    delpo
        9
    delpo  
    OP
       2022-08-10 10:21:12 +08:00
    @isad DHT 我测试了一下,qbittorrent 会使用设置中的监听端口,通过 udp 发送 dht 查询消息. 但是具体到连接 peers 进行下载的时候,除非你把 peers 传输协议设置为仅μtp, 否则还是会优先使用 tcp 进行连接. 由于 tcp 和 udp 的 nat 映射不一样, 所以连接性依然不是很理想. 即使设置为仅μtp, 也同样只能在 nat1 下接受入站连接.
    不过话说回来,既然使用 DHT,就意味着在下载公网的种子,其实只要有速度,连接性好不好也不是很重要了,毕竟公网种子不强制要求上传.
    delpo
        10
    delpo  
    OP
       2022-08-10 10:25:58 +08:00   ❤️ 1
    @hello365 我这个实践里,vps 仅用于获取本地客户端在 nat 防火墙外的 external port, 而不是用来架代理的. mitmproxy 是和客户端一样搭建在本地的,所以源 ip 依然是你本地的 ip.
    至于你说的,相当于通过 vps 代理下载. 相关教程很多就不赘述了
    FrankAdler
        11
    FrankAdler  
       2022-08-10 10:30:43 +08:00 via iPhone
    自动化的话,这套 cs 软件就是在提供打洞的基础上代理并修改 tracker 的流量,看起来是可行的🤔,可能 mitmproxy 需要在 c 端再实现一个才能自动化
    delpo
        12
    delpo  
    OP
       2022-08-10 10:41:10 +08:00   ❤️ 1
    @hello365 你勾选 Use proxy for peer connections 相当于是把 BT 协议和 μtp 协议代理到 mitmproxy 里, 大概 mitmproxy 处理不了这两个协议汇报错吧.
    isad
        13
    isad  
       2022-08-10 10:47:46 +08:00
    @delpo 你搞错了吧,DHT 是别的主机交换信息后来找你连接,至于为什么都是 μtp ,是因为 qb 就只支持 udp 打洞。
    我没有用过公网 ip ,分享率依然有一点几
    delpo
        14
    delpo  
    OP
       2022-08-10 10:54:20 +08:00
    @isad 不好意思,我写错了,应该是: 除非"你"把 peers 传输协议设置为仅μtp --> 除非"别人"把 peers 传输协议设置为仅μtp
    因为大部份 BT 客户端默认情况都是基于 TCP 的 BT 协议优先,这个是没有问题的
    ChangeTheWorld
        15
    ChangeTheWorld  
       2022-08-10 13:58:53 +08:00
    这 NAT 都把人逼成啥样了,各种骚操作都出来了,IPv6 任重而道远啊
    lanlandezei
        16
    lanlandezei  
       2022-08-10 14:27:54 +08:00
    收藏了 过程太复杂了操作不来,期待大佬做个自动化软件
    mxuan
        17
    mxuan  
       2022-08-11 01:40:21 +08:00
    mark 一下,很有价值,很想尝试一下。
    hello365
        18
    hello365  
       2022-08-11 08:52:45 +08:00
    嗯,感谢解答,另外还有个疑问,sendip 里面指定-td 我理解的是将在 cgn nat 上申请的从 local 公网端口到 vps 的链接,是这样吗?如果是这样怎么保证这个-td 参数值是否可用呢?会不会已经在 cgn nat 上被占用了。
    delpo
        19
    delpo  
    OP
       2022-08-11 08:59:26 +08:00   ❤️ 1
    @hello365 -td 是你 tcp 包的目的端口,也就是你 vps 上的端口,你说的那个是 nat 防火墙上的 external port,这个是 nat 防火墙自动分配的
    mxuan
        20
    mxuan  
       2022-08-11 09:25:23 +08:00
    cgnat 是 nat3 ,用 stun 试了下,没法提供对应端口。心酸。。
    q197
        21
    q197  
       2022-08-11 20:55:31 +08:00 via Android
    @ChangeTheWorld 应该是 tracker 程序的问题,正常 nat1 无公网 ip 完全不影响 bt 互联
    delpo
        22
    delpo  
    OP
       2022-08-11 22:02:04 +08:00 via Android
    @q197 其实即便 tacker 记录的是 external port ,两个 nat1 的 peers 也很难连上,主要就是因为 nat 的映射时间太短,而 tracker 汇报的间隔很长,所以只有两个 peers 同时在 nat 映射打开的时候连接对方才行,这个概率真的很随缘。不过如果真是这种情况,只需要一个 peer 主动一直打开映射就一定能被其他 peer 连上,可以说能简化很多。
    delpo
        23
    delpo  
    OP
       2022-08-12 09:51:58 +08:00
    @q197 更进一步研究抓包发现,在 TCP 协议下,无论是连接 peers 还是连接 tracker ,使用的本地端口都是 socket 随机指定的,而不是设置里的监听端口,在这种情况下 nat 外的端口也是不固定的,所以两个 peers 自动打洞成功的概率可以忽略不计。
    但是作为对比,UDP 协议下,无论是μtp 还是 udp tracker ,使用的本地端口都是设置里的监听端口,这种情况下才有可能实现上一楼中说的情况,但是这要求必须是μtp + udp tracker ,实际使用中 udp tracker 其实是很少的。
    但是如果下载的不是私有种子,那么就和之前说的一样,可以使用 DHT 或者 PEX 协议接受μtp 入站连接,[BEP_0055]( https://www.bittorrent.org/beps/bep_0055.html)也规定了一部分打洞扩展。
    以上实验皆基于 qbittorrent
    lanlandezei
        24
    lanlandezei  
       2022-08-13 21:08:25 +08:00
    大佬有没有能直接运行的二进制执行文件,DOCKR 感觉比较麻烦
    delpo
        25
    delpo  
    OP
       2022-08-13 22:47:03 +08:00
    @lanlandezei 有需要可以自行编译

    https://github.com/Alozxy/trav
    lanlandezei
        26
    lanlandezei  
       2022-08-15 08:27:13 +08:00
    我应该已经执行到最后一步了,PT 站个人信息那里,BT 客户端有显示 trav 执行的端口了。现在就是差最后一步 DNAT 不知道怎么写。(用 iptables 的 DNAT 模块进行本地端口转发,从而将 extra port 的入站流量全部转发到 local port 上)。
    我的运行环境
    移动宽带 NAT1
    debian11
    apt 直接安装的 qbittorrent
    mitmdump -p 8888 -s http-modify-query-string.py
    trav -i 8 -l 12345
    qb 连接 mitmdump 的代理。
    delpo
        27
    delpo  
    OP
       2022-08-15 10:37:34 +08:00
    @lanlandezei 程序里有自动创建 iptables 规则的,你可以在 nat 表里找到
    还有,interval 不用设置太高,半个小时更新一次都没问题,毕竟用的公共服务器
    subing
        28
    subing  
       2022-08-15 18:09:51 +08:00
    老哥要是长期玩 pt 还是建议有公网方便,电信 /联通总有一家会给的
    james19820515
        30
    james19820515  
       201 天前
    nat3 才是全锥吧?
    delpo
        31
    delpo  
    OP
       199 天前
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4241 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 05:32 · PVG 13:32 · LAX 21:32 · JFK 00:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.