Chancel's blog

703 字 3 分钟阅读

使用 natmap 为 qBittorrent 实现 NAT 穿透

  • Network

在 Debian 12 下,借助 natmap 可以为处于 CGN(运营商大内网)环境中的 qBittorrent 建立公网可达连接。整体方案需以下工具:

  • natmap:NAT 穿透工具,利用 TURN/STUN 协议在 Carrier-Grade NAT 环境下建立端口映射
  • qBittorrent No-WEB UI:BT 客户端
  • nftables:Linux 内核级防火墙,负责端口重定向

编译 natmap

cd /tmp && git clone --recursive https://github.com/heiher/natmap.git
cd natmap && make
mv bin/natmap /usr/local/bin

测试连通性

假设本机 8080 端口有一个 HTTP 服务,可通过以下命令测试 natmap 是否正常工作:

natmap -4 -s turn.cloudflare.com -h qq.com -b 0 -t 127.0.0.1 -p 8080

# 执行后会返回如下格式的输出({public-addr} {public-port} {ip4p} {private-port} {protocol} {private-addr})
1.1.1.1 10037 2001::2735:78e6:7bda 37423 tcp 172.16.75.115

输出字段含义:{public-addr} 为公共 IPv4 地址(CGN 出口 IP),{public-port} 为外网宣告端口,{ip4p} 为 IPv6 地址(备用),{private-port} 为运营商分配的内部端口(真实入站流量到此端口),{protocol} 为协议类型(tcp/udp),{private-addr} 为本机的私有 IP 地址。

qBittorrent 与其他 Peer 通信时,需要向对方广播自己监听的端口号。在 CGN 环境下,这个端口并不直接对外,需要经过 NAT 转换:qBittorrent 监听在内网网卡上的 {public-port},运营商的 CGN 将实际流量打到 {private-port},nftables 负责将 {private-port} 的入站流量转发到 {public-port}

外网 Peer --{target: public-port}--> CGN NAT --{映射到: private-port}--> ppp0接口 --{nftables重定向}--> 本机:public-port (qBittorrent)

示意图如下:

PlantUML diagram

这个示意图展示了连接从进入 CGN 网关,被映射到 ppp0接口,再到 nftables 重定向,最终到达 qB 监听端口的完整流程。

natmap 的 -e 参数可以在隧道成功建立后触发外部脚本,并按顺序传入以下参数:{public-addr} {public-port} {ip4p} {private-port} {protocol} {private-addr}。借此,我们可以编写一个脚本来实现两个功能:通知 qBittorrent 修改监听端口和配置 nftables 端口转发规则。脚本保存为 /usr/local/lib/natmap/notify-qbit.sh

#!/bin/bash

PUBLIC_PORT=$2       # 外网随机端口 {public-port} -> 10134 (让 qbit 绑定并上报这个)
PRIVATE_PORT=$4      # 运营商打到本机的临时端口 {private-port} -> 37235 (真实入站流量端口)
QB_WEB_URL="http://127.0.0.1:8080" # 已开启白名单免密登录

echo "=================================================="
echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') - natmap 隧道成功,触发入站重定向逻辑..."
echo "[INFO] 外网宣告端口: $PUBLIC_PORT | 本机实际进流量端口: $PRIVATE_PORT"

# 1. 安全边界校验
if ! [[ "$PUBLIC_PORT" =~ ^[0-9]+$ ]] || ! [[ "$PRIVATE_PORT" =~ ^[0-9]+$ ]]; then
    echo "[ERROR] 参数异常,退出执行!"
    echo "=================================================="
    exit 1
fi

# 2. 更新 qBittorrent 监听端口
echo "[INFO] 正在通知 qBittorrent 监听端口修改为: $PUBLIC_PORT ..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
    -d "json={\"listen_port\":$PUBLIC_PORT}" \
    "$QB_WEB_URL/api/v2/app/setPreferences")

if [ "$HTTP_CODE" -eq 200 ]; then
    echo "[SUCCESS] qBittorrent 端口已变更为白名单外网端口: $PUBLIC_PORT"
else
    echo "[WARNING] qBittorrent WebUI 响应状态码: $HTTP_CODE"
fi

# 3. 配置 nftables:入站流量重定向 (PREROUTING)
echo "[INFO] 正在配置 nftables 入站 (PREROUTING) 端口转发规则..."

# 创建并清理专门的 natmap_qb 表与 prerouting 链
nft add table ip natmap_qb 2>/dev/null
nft delete chain ip natmap_qb prerouting 2>/dev/null

# 重新注入针对入站的常规 dstnat 链
nft add chain ip natmap_qb prerouting { type nat hook prerouting priority dstnat ; }

# 把运营商真正打进来的 PRIVATE_PORT 流量,强行重定向到 qb 正在监听的 PUBLIC_PORT
nft add rule ip natmap_qb prerouting tcp dport $PRIVATE_PORT redirect to $PUBLIC_PORT
nft add rule ip natmap_qb prerouting udp dport $PRIVATE_PORT redirect to $PUBLIC_PORT

echo "[SUCCESS] 规则配置完毕:入站端口 $PRIVATE_PORT 的流量已被转发至 qb 监听的 $PUBLIC_PORT"
echo "=================================================="

脚本主要做了两件事:通过 Web API 更新 qBittorrent 的监听端口为 {public-port},让 qBittorrent 对外广播正确的端口号;用 nftables 创建独立表(natmap_qb),在 PREROUTING 链中将对 {private-port} 的 TCP/UDP 入站流量重定向到 {public-port}

将 natmap 注册为 systemd 服务,实现断线自动重连。创建文件 /etc/systemd/system/natmap.service

[Unit]
Description=natmap (qBittorrent forward mapping via CGN traversal)
Documentation=https://github.com/heiher/natmap
BindsTo=sys-subsystem-net-devices-ppp0.device
After=sys-subsystem-net-devices-ppp0.device network-online.target nss-lookup.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/natmap -i ppp0 -b 0 -h qq.com:80 \
          -s turn.cloudflare.com:3478 \
          -e /usr/local/lib/natmap/notify-qbit.sh -k 30
Restart=always
RestartSec=60
ExecStartPre=/bin/sh -c 'until ip -4 addr show dev ppp0 | grep -q "inet"; do sleep 2; done'
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW
NoNewPrivileges=false
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadOnlyPaths=/
ReadWritePaths=/var/log

[Install]
WantedBy=sys-subsystem-net-devices-ppp0.device multi-user.target

关键配置:BindsTo=...ppp0.device 会在 PPPoE 断线时自动停止 natmap 服务;ExecStartPre=... 会在开机时轮询等待 ppp0 获取到 IPv4 地址,避免启动过快失败;Restart=always, RestartSec=60 会在服务异常退出后,60 秒后自动重启;-k 30 参数会让 natmap 每 30 秒向 TURN 服务器保活,保持映射存活;CAP_NET_ADMIN, CAP_NET_RAW 允许 natmap 调用 nftables 和进行原始网络操作。

启动并验证

systemctl daemon-reload
systemctl enable --now natmap

启动后,进入 qBittorrent WebUI 检查连接状态,确认端口显示为对外宣告的端口号,且连接状态正常即表示穿透成功。

互动

留言

发表留言

暂无留言,来做第一个吧。