[关闭]
@pnck 2023-06-26T19:09:19.000000Z 字数 12315 阅读 145

PT瞎折腾

未分类


最近回家的时候发现家里的 IPv6 已经基本上 full-featured 了,于是想着在自己电脑上也尝试一点 IPv6 的特性。比如 —— PT

Concepts

众所周知,PT 是一种非常依赖公网 IP 的业务,BT 等众多 P2P 协议设计的早期 IP 地址耗尽情况还没那么严重,那时的理想是网络上各计算机都可以相互连通来传输数据,每个计算机都能获得一对多的网络资源,从而放大共享效率。但这就要求每台计算机都能监听来自任意远程的主动连接,在现在普遍大内网的时代是不现实的。

不过好在 IPv6 的全面铺开逐渐解决了这一点,v6 地址至少杜绝了无法主动接受连接的问题,P2P 的原初理想得以重新获得发挥机会。所以本文虽然标题是 PT 但 一开始就是为了IPv6 实验 去的,并不是一个 PT 教程或推荐方案,请读者先明确这一点。

PoC - Network @ WSL

所用的环境是一台 Windows 台式机,但 Windows 本身并没有什么可捣鼓的。我在家用的电脑是一台 macbook, 下载上传全都能用 aria2 来完成。出于 code hobbyist 的执念,我产生了一个想法:能不能用 WSL 来复刻?

桥接 WSL

很快我遇到了第一个问题:我发现 WSL 并不能正常获得 IPv6. 我尝试了升级(wsl --update),但最终 在这个讨论串发现微软不出意外地又一次展示了他的小小震撼 终结了「是我的问题」的错觉。

不过这个讨论里给出了一个方向: 把 WSL 的虚拟交换机改成桥接模式

天下苦 WSL2 久矣。除了被人诟病的「虚拟机重型实现」,还有乱飞的私有地址和难以逾越的 NAT 天堑。桥接不就不需要脑控 IP 了,这种好事?!

是的,现在有了。但 必须在 win11 上才可用。我赶紧先把 win10 升到了 win11.

创建虚拟交换机(Virtual Switch, vSwitch, VMSwitch, 后文用 VS 指代)

WSL2 启用时会默认创建一个用于 NAT 的虚拟交换机(vEthernet (WSL)),同时如果你用 Hyper-V 运行虚拟机,还会为其创建默认的 NAT VS. 如果你还自定义了一下 Hyper-V 虚拟机的网络,那么你的系统里很可能有四五个 vEthernet(xxx) ,这种命名方式简直是噩梦,想要记住它们的 IP 更是反人类般地不可能,更何况还是动态的。因此如无必要,我们可以完全用同一个 VS 互联所有的虚拟机和 WSL.

  1. PS C:\> New-VMSwitch -Name 'vmbr' -AllowManagementOS $True -NetAdapterName 'BoardIF' #"board interface"
  2. PS C:\> Get-NetAdapter
  3. Name InterfaceDescription ...
  4. ---- -------------------- ...
  5. ...
  6. vEthernet (vmbr) Hyper-V Virtual Ethernet Adapter #2 ...

这个步骤基本等同于在「Hyper-V 管理器」中使用「虚拟交换机管理器」 创建一个外部交换机。 如果需要更改虚拟机的网络模式,现在可以在虚拟机属性里指定名为 vmbrVS了。

  1. PS C:\> Get-HnsNetwork | ? type -Like "internal" #参考 help ?
  2. ActivityId : 9AF55F4A-873C-4EC1-9E07-C3FD12B55449
  3. AdditionalParams :
  4. CurrentEndpointCount : 0
  5. ...
  6. PS C:\> Get-HnsNetwork | ? ActivityId -Like "9AF55F4A-873C-4EC1-9E07-C3FD12B55449" | Remove-HnsNetwork
  7. PS C:\> Get-VMSwitch SOME_NAME | Remove-VMSwitch

使用 WSL 配置文件指定 WSL 使用刚创建的 VS

  1. [wsl2] # 如果不存在则把该行加到开头
  2. networkingMode = bridged
  3. vmSwitch = vmbr # 刚创建的 VS
  4. macAddress = 00:15:xx:xx:xx:xx # 为了让路由器能固定 IP,需要指定固定的 MAC 地址
  5. IPv6 = true
  6. dhcp = false # 防止 WSL 自动管理 IP 擅自修改 Linux 内的网卡配置

为 WSL 启用 systemd 以让 systemd 自动管理网络

systemd 支持也是需要升级到 win11 后才可用。在此之前 WSL 的 systemd 方案是用 genie 之类的模拟环境来运行 systemd 的一些部件。不过现在我们有微软官方实现的 systemd 了。

  1. [boot]
  2. systemd = true
  3. [network]
  4. generateResolvConf = false # 将使用 systemd 内建的来代替
  5. hostname = WSL # 方便在路由器上区分与host主机的名字
  1. $ sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
  2. # 使用resolv.conf 还是 stub-resolv.conf 取决于需求

添加 systemd 使用的网络接口定义

我使用的 WSL 发行版是 Ubuntu 22.04, 不同系统使用的 systemd 版本会有区别,请注意参考自己系统环境。

  1. [Match]
  2. Name=eth0
  3. [Network]
  4. Description=Virtual Switch(vmbr)
  5. DHCP=true # 重要
  6. MulticastDNS=true
  7. LLDP=true
  8. EmitLLDP=true
  9. IPv6AcceptRA=true # 重要,用于 IPv6 SLAAC
  10. IPv6SendRA=true
  11. IPv6PrefixDelegation=static
  12. [DHCPv4]
  13. CriticalConnection=true

测试 IPv6 是否互通(重要)

2023 年还在使用的大部分光猫和家用路由器应该都能正确处理 IPv6 的下发,但运营商提供的光猫用作路由器很可能会有其它问题,比如 阻止 IPv6 入站。这种问题甚至不是管理员配置的主动措施而是「特性」,所以必须先测试一下互通性来保证 IPv6 是双向完全可用的。

最简单的测试方法是用手机连上移动数据,然后在手机上通过 termux ping 或者开启一个 web server 让手机来访问。或者连上自己的 vps 远程访问回来也行(需要 vps 有 IPv6 地址)。

修改光猫的上网模式

如果你在上一步发现外部的 ICMP 无法发到 WSL 机子上,甚至到不了宿主 Windows 上,那么就需要使用其它设备(如自己的路由器)来拨号了。

我这里光猫的型号是 tewa-800g,网上有大量破解并修改存量各种光猫网络模式的各种教程,可以自行寻找。这里放个捷径:

  1. 在登录页面打开浏览器控制台,查看 sessionKey 变量,例如得到 123
  2. 访问 http://192.168.1.1/loginUser.htm?username=CUAdmin&password=CUAdmin&sessionKey=123 登录
  3. 基本配置 - 上行线路配置 选择 INTERNET_ 连接,修改连接模式为 桥接
  4. 如不知道 PPPoE 拨号密码,可以在路由模式页面中的密码输入框获得(将 input 元素的 type 属性改为 text)

经过以上所有步骤,我们终于完成了第一个目标:现在 WSL 环境获得了固定的内网 IP,也不会再有恼人的端口转发烦恼。你的 Windows 此时真正意义上同时运行着两个系统,它们彼此独立但又很方便地共享你的硬盘和显示器。

最重要的是,现在 「WSL 这台电脑」是有公网 IPv6 的,终于可以开始 PT 的部分了!


POC - Play PT @ WSL

我一开始尝试了各种客户端。
得益于 win11 的 WSLg (g for GUI),其实可以直接在 WSL 中安装 Llinux 版的 GUI download manager, 如 qbittorrent, transmission-gui.
不过这些图形界面版的 BT 工具通常都比 Windows 版简陋,而且无系统托盘、无后台机制,使得他们都不是很好用于挂机。而且说到底想使用 WSL 来挂机就奔着无感的目标去的,用 GUI 版本岂不是南辕北辙?所以最终在观察了各种 peers 的客户端后,我选择了通用方案: transmission-daemon

Transmission

transmission 本身就是为了使用 BT 协议 分享 文件设计的,GUI 版本的功能也都很精简,没有复杂的信息显示和标签化管理,本质上讲只有开始和暂停两个功能,几乎等同于一个 server 软件。

在 Linux 上它还有 headless 的版本 transmission-daemon,这就是个实打实的 server 程序了,所有操作必须通过 web 界面和 RPC 来完成。

宿主文件系统互通

在 web 界面上传已经下好的种子是个很冗余的操作,可以通过调整文件系统的共享方式来解决它。

挂载用来存文件的大容量硬盘

  1. PS C:\> GET-CimInstance -query "SELECT * from Win32_DiskDrive"
  2. PS C:\> wsl --mount \\.\PHYSICALDRIVE2
  3. $ sudo mount /dev/sdc3 /mnt/x # WSL 现在可以使用自己内核中的驱动访问物理硬盘

当然如果你的硬盘文件系统是 NTFS, 可以直接在 磁盘管理 中为其分配盘符,WSL 会自动将其映射到内部。

配置 transmission-daemon 文件监视

  1. "watch-dir": "/where/to/save/torrents",
  2. "watch-dir-enabled": true

新放入 watch-dir 的种子文件就会自动创建任务了。 不过要注意的是 transmission-daemon 使用 inotify 来监视文件系统,这意味着它只能监听 Linux 文件系统,尝试监视挂载的 NTFS 卷是不会起作用的。

不过问题不大,下载文件或任何 Windows 自带的文件选择对话框中,在地址栏输入 \\wsl.localhost\<发行版> 跳转,都能直接在 Windows 中访问 WSL 的文件系统。可以直接把种子文件下载到监视目录内。

防火墙

由于现在 WSL 环境相当于暴露在公网中,并且使用弱密码还与 Windows 的文件系统互通,所以有必要为其启用防火墙来阻止潜在的攻击。快速简便的方法是直接使用 UFW

使用 UFW

WSL 的 Ubuntu 发行版已默认安装了 UFW,其它发行版也仅需用自带包管理搜索即可。

  1. $ sudo ufw enable
  2. $ sudo ufw logging low # 启用日志
  3. $ sudo ufw default reject incoming # 将默认入站策略设为 REJECT

通常来讲默认入站策略会设为 DROP ,收到 SYN 时直接丢弃,连接的客户端会等到超时。而 REJECT 策略会返回一个 ICMP 消息告知客户端服务端主动拒绝连接。由于我的电脑 IPv4 环境处于大局域网内,被扫描器光顾的可能性较低,所以我选择 REJECT 策略 ,这样调试本机连接时能方便知道是否被防火墙拦截。

  1. # 放行高端口段,这部分用于 BT 等可能需要随机分配监听端口的程序
  2. $ sudo ufw allow in on eth0 from any to any port 50000:59999 proto tcp
  3. $ sudo ufw allow in on eth0 from any to any port 50000:59999 proto udp
  4. # 放行来自局域网的连接(比如宿主Windows)
  5. $ sudo ufw allow from 192.168.3.0/24
  6. # web 端口
  7. $ sudo ufw allow 80,443,8000,8001,8080,9000,9001/tcp
  8. # ssh (example, 实际应避免使用默认端口)
  9. $ sudo ufw allow ssh

解决 IPv6 大量包被 BLOCK 的问题

  1. Feb 08 04:04:36 WSL kernel: [UFW BLOCK] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=2408:8207:183d:0120:0211:32ff:fe12:3456 DST=2408:8207:6c62:56c0:0215:5dff:fea7:b951 LEN=140 TC=40 HOPLIMIT=59 FLOWLBL=954762 PROTO=TCP SPT=60954 DPT=50001 WINDOW=234 RES=0x00 ACK PSH FIN URGP=0
  2. Feb 08 04:06:27 WSL kernel: [UFW BLOCK] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=2408:8207:183d:0120:0211:32ff:fe12:3456 DST=2408:8207:6c62:56c0:0215:5dff:fea7:b951 LEN=140 TC=40 HOPLIMIT=59 FLOWLBL=553381 PROTO=TCP SPT=37397 DPT=50001 WINDOW=235 RES=0x00 ACK PSH FIN URGP=0
  3. Feb 08 04:06:54 WSL kernel: [UFW BLOCK] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=2408:8207:183d:0120:0211:32ff:fe12:3456 DST=2408:8207:6c62:56c0:0215:5dff:fea7:b951 LEN=140 TC=40 HOPLIMIT=59 FLOWLBL=327198 PROTO=TCP SPT=37397 DPT=50001 WINDOW=235 RES=0x00 ACK PSH FIN URGP=0

这些包的目标端口都是 transmission 的监听端口,看起来完全是正常的,也不在规则里,为什么会 BLOCK 呢?

  1. Feb 08 05:33:34 WSL kernel: [UFW AUDIT INVALID] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=240e:0370:051f:b102:0211:32ff:fe22:244d DST=2408:8207:6c62:56c0:0001:0000:0000:0000 LEN=140 TC=0 HOPLIMIT=52 FLOWLBL=915931 PROTO=TCP SPT=55398 DPT=50001 WINDOW=231 RES=0x00 ACK PSH FIN URGP=0
  2. Feb 08 05:33:34 WSL kernel: [UFW BLOCK] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=240e:0370:051f:b102:0211:32ff:fe22:244d DST=2408:8207:6c62:56c0:0001:0000:0000:0000 LEN=140 TC=0 HOPLIMIT=52 FLOWLBL=915931 PROTO=TCP SPT=55398 DPT=50001 WINDOW=231 RES=0x00 ACK PSH FIN URGP=0
  1. -A ufw6-logging-deny -m conntrack --ctstate INVALID -j LOG --log-prefix "[UFW AUDIT INVALID] "
  2. -A ufw6-logging-deny -j LOG --log-prefix "[UFW BLOCK] "

这两条 log 规则会在 ufw logging level > low 的时候被添加,看起来产生 BLOCK 记录的原因是包被 conntrack 标为无效了。

  1. # drop INVALID packets (logs these in loglevel medium and higher)
  2. -A ufw6-before-input -m conntrack --ctstate INVALID -j ufw6-logging-deny
  3. -A ufw6-before-input -m conntrack --ctstate INVALID -j DROP

被标为 INVALID 的包会转到到 ufw6-logging-deny 链上记录输出,然后回到 ufw6-before-input 链上被 DROP 规则丢弃。
由于 ufw logging 设为 high 之后会有大量 ALLOWAUDIT 记录影响日志追踪,所以我们可以新建一条链来自定义输出,观察情况。

/etc/ufw/user[6].rules 文件会被 ufw 自动管理并修改,但 before[6].rulesafter[6].rules 是可以持久化用户自定义的规则的。

  1. # custom logging
  2. :ufw6-custom-logging - [0:0]
  3. -A ufw6-custom-logging -j LOG --log-prefix "[UFW MARKED INVALID] "
  1. # drop INVALID packets (logs these in loglevel medium and higher)
  2. -A ufw6-before-input -m conntrack --ctstate INVALID -j ufw6-custom-logging
  3. # -A ufw6-before-input -m conntrack --ctstate INVALID -j ufw6-logging-deny
  4. # -A ufw6-before-input -m conntrack --ctstate INVALID -j DROP

然后把 logging level 改成 low,这样就只能看到自定义的记录了。

  1. Feb 08 06:41:09 WSL kernel: [UFW MARKED INVALID] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=2408:8256:0280:f558:147c:1911:1664:cd28 DST=2408:8207:6c62:56c0:0001:0000:0000:0000 LEN=5580 TC=0 HOPLIMIT=56 FLOWLBL=544976 PROTO=TCP SPT=3197 DPT=50001 WINDOW=1023 RES=0x00 ACK URGP=0
  2. Feb 08 06:41:09 WSL kernel: [UFW MARKED INVALID] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=2408:8256:0280:f558:147c:1911:1664:cd28 DST=2408:8207:6c62:56c0:0001:0000:0000:0000 LEN=1440 TC=0 HOPLIMIT=56 FLOWLBL=544976 PROTO=TCP SPT=3197 DPT=50001 WINDOW=1023 RES=0x00 ACK URGP=0
  3. Feb 08 06:41:09 WSL kernel: [UFW MARKED INVALID] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=2408:8256:0280:f558:147c:1911:1664:cd28 DST=2408:8207:6c62:56c0:0001:0000:0000:0000 LEN=2820 TC=0 HOPLIMIT=56 FLOWLBL=544976 PROTO=TCP SPT=3197 DPT=50001 WINDOW=1023 RES=0x00 ACK URGP=0
  4. Feb 08 06:41:09 WSL kernel: [UFW MARKED INVALID] IN=eth0 OUT= MAC=00:15:5d:a7:b9:51:68:a0:3e:50:4a:2b:86:dd SRC=2408:8256:0280:f558:147c:1911:1664:cd28 DST=2408:8207:6c62:56c0:0001:0000:0000:0000 LEN=1440 TC=0 HOPLIMIT=56 FLOWLBL=544976 PROTO=TCP SPT=3197 DPT=50001 WINDOW=1023 RES=0x00 ACK URGP=0

由于 TIME_WAIT 等待时间较长且不能积极结束,这是一个通常容易造成资源(即追踪数)耗尽的状态。不过查看之后显示 flow entries 数量保持在 100 以下,完全正常。

那么只能猜测双方 IPv6 的协议栈实现略有区别或非标准行为。由于我用着一个比较旧的路由器,而且是在 WSL 这样一个复杂的网络协议栈环境,所以也难以继续深究,为了防止防火墙阻断本来可以建立的连接,我把 DROP 规则和日志记录都禁用了。

终极之路: 公网 IPv4映射+虚拟文件系统实现多点上传

有了公网 IPv6 之后,理论上 PT 的上传就已经可以保证了。但 IPv4 毕竟仍是当前的主流 IP 协议栈,有许多「落后」的用户还不能正确使用 IPv6 的链路相互传输数据,因此挖一个公网 IPv4 的隧道还是有必要的。

传统方法有很多,比如 ngrok frp 之类的映射工具,或使用 zerotieropenvpn 等建立虚拟局域网,再通过网关上的路由或反代来连接到我们本地的机器。

但既然我们现在已经有 IPv6 公网 IP 了,为什么还需要我安装额外的客户端再主动反弹一个连接出去呢? 让提供 IPv4 公网的机器主动连接我的 IPv6 地址不就行了吗?

我的预设方案是一种网络文件系统比如 NFS ,IPv4 服务器把我的 IPv6 地址挂载成它的虚拟文件系统,它自己再启动 transmission 做种,这样既能获得 IPv4 的上传机会,也能避免在服务器上落盘文件带来的巨额存储开销

IPv6 隧道

不过上述方案有一个小缺陷,就是 IPv4 服务器本身必须是双栈的,连接我的 IPv6 地址要求它本身也有 IPv6,并不是所有的 vps 服务商都能提供。

好在它自己有固定的公网 IPv4,可以通过 免费的 IPv6 隧道服务商 给它创建一条 SIT 隧道,这样它就能与 IPv6 地址互通了。

使用也很简单,注册后左侧 create regular tunnel 按提示选择区域并填入连接隧道时使用的 IPv4 即可。隧道创建好后可在 Tunnel DetailsExample Configurations 选项卡复制配置方法。配置成功后 ip addr 能看到隧道 interface 上获得的公网 IPv6,并且可对本地机器的 IPv6 服务发起连接了。

NFS

但当在本地机器上编辑好 exports 后会发现并不能成功启用 NFS server:

  1. # cat /etc/exports
  2. ...
  3. /mnt/x/ 2001:x:x:x::2(ro,no_subtree_check,fsid=0)
  4. # exportfs -ra
  5. exportfs: /mnt/x does not support NFS export

原因是 WSL 挂载外部驱动器或目录时使用的 drvfs 文件系统(或 9p 协议)其本身就是一种类 NFS 的网络文件系统,而 NFS 并 不支持重新导出 挂载的其它 NFS 文件系统。因此需要寻找其它的基于网络的虚拟文件系统。

WebDav/davfs

没错其实这时候本来应该直接奔着 rclone 去就对了,但由于我习惯用 nginx 统一反代整合一下各种 web 服务,所以首先想到的其实是 nginx 的 webdav module. 这里arch wiki 都讲得很简单清晰,我的步骤也基本照着它们做的。

但我发现 nginx-dav-ext-module 有些 很恶性的 bug配合 davfs2 触发), 作者还摆烂了一直不修,(看issue列表和最后更新时间)最后 nginx 模块这个方案否决了。

调试这俩 bug 的过程中发现 golang 实现 webdav server 其实很简单,顺手也写了一小段

附 nginx 配置:

  1. upstream godav {
  2. # 使用 unix socket 节省资源也便于服务化 (然而最后并没有实现服务化)
  3. server unix:/tmp/dav.sock;
  4. server 127.0.0.1:10000;
  5. }
  6. # ...
  7. location /dav/ {
  8. set $dest $http_destination;
  9. if ($http_destination ~ "^https://(?<path1>(.+))") {
  10. set $dest http://$path1;
  11. }
  12. # rewrite ^\/dav/(.*)$ /$1 break;
  13. # 注意上面这个 rewrite 是不起作用的, webdav 的很多操作请求路径包含在 body xml 中,因此只能在 server 脚本中指定 prefix 来匹配这里的 location
  14. proxy_pass http://godav;
  15. proxy_set_header Connection "";
  16. proxy_set_header Destination $dest;
  17. proxy_set_header Host $http_host;
  18. proxy_set_header Remote-Host $remote_addr;
  19. proxy_pass_header Server;
  20. # ...

当然了再强调一次 rclone 其实就是最终的完美方案,但我最后并没有用上。原因是上述这套简易的 webdav/davfs 可以正常跑起来已经验证了设想的「终极之路」,但很可惜「终极之路」本身是个伪命题或伪需求,原因有几点:

结尾和其它

其实我在这台机子上这轮捣鼓的东西还不止上述这些。为了随时更方便地访问,这上面还有 ddns,还有 web 版的文件管理器,又为了双栈访问还给它配置了正常工作的 certbot (HTTPS证书)和 cloudflare 代理。

虽说初衷是入坑 PT,目的是实验 IPv6 相关的特性支持,但其实最后的惊喜和经验全在 WSL 上。

WSL 的进化之快是令人惊讶(amazing)的,虽然暂时还需要(只需要)一点点小补丁才能享用接近 native linux installation 的体验,但目前已经几乎感知不到蹩脚的完全不符合 linux 习惯的东西了。除开「网络资源和地域限制」类需求必须使用远程的 vps 外,几乎一切依赖 linux 服务器的非生产事务(尤其是原来选用国内服务商完成的那些)都已经可以用 WSL 完美取代。成本、效率、性能,甚至网络条件都要比国内服务商的 vps 更优。

早在 2019 年「最好的 linux 发行版」称号就已经归属 WSL (调侃)了。 2023 年,我愿将「最好的 vps 服务商」称号也(非官方地)授予 WSL.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注