在家用宽带"封端口"时代,我如何用 Tailscale 搞定跨网络远程开发

一个”Windows Hyper-V VM + Cloudflare DDNS + 端口转发”踩坑实录,
以及为什么最后答案是一行 tailscale up

(本文已对账号、IP、tailnet 名等敏感信息脱敏,仅保留技术结构。)

TL;DR

我有一台家用宽带(动态公网 IP)里的 Hyper-V Ubuntu 虚拟机,想从手机热点、
公司、任何网络
随时连进去开发。我花了半天搭 DDNS + 三层端口转发,
结果外网连不进来——运营商在封家用宽带的入站端口。最后用 Tailscale
一条命令搞定,彻底绕开 DDNS、端口转发、运营商封锁。

下面是完整踩坑和最终方案。

一、目标

  • 家里:Windows 宿主机跑 Hyper-V Ubuntu VM,静态内网 IP 172.x.x.x
  • 手机 / 任意网络:能随时 SSH 进 Ubuntu 开发
  • 要求:IP 怎么变都不用管,最好不用开公网端口

二、第一套方案:DDNS + 端口转发(为什么走不通)

2.1 架构

1
互联网 → 光猫/路由器(动态公网IP) → Windows(192.168.x.7) → portproxy 2222 → Ubuntu VM(172.x.x.x:22)

需要打通三层:

  1. Cloudflare DDNS:把 devbox.dingdao.me 自动指向当前公网出口 IP
  2. 路由器端口转发:外部 2222 → 192.168.x.7:22
  3. Windows portproxy2222 → 172.x.x.x:22 + 防火墙放行

2.2 我写的 DDNS 脚本(带容错)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env bash
# /usr/local/bin/cf-ddns.sh (v2)
set -u
CONF=/etc/cf-ddns.conf
LAST_IP=/var/lib/cf-ddns/last_ip
...
# 5 个 IP 源依次尝试,3 次重试;全部失败 → 保留 last_ip,绝不把 CF 记录写脏
for src in "https://api.ipify.org" "https://ifconfig.me" ...; do
ip=$(timeout 8 curl -4 -sS "$src" 2>/dev/null | tr -d '[:space:]')
[[ "$ip" =~ ^[0-9.]+$ ]] && break
done
[[ -z "$ip" ]] && ip=$(cat "$LAST_IP" 2>/dev/null) # 兜底
[[ -z "$ip" ]] && { echo "no ip, skip"; exit 0; }
curl -sS -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE/dns_records/$RECORD" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "{\"type\":\"A\",\"name\":\"$HOSTNAME\",\"content\":\"$ip\"}"
echo "$ip" > "$LAST_IP"

systemd timer:开机 + 每 15 分钟跑一次。

踩坑:我一开始把 Cloudflare 控制台里的 Account ID 当成 Zone ID 填进脚本,结果 API 404。后来用 API 自动解析出 devbox.dingdao.me 所属 zone 的真实 Zone ID,脚本里直接查 API,不再手填。

2.3 为什么外网连不进来

所有配置都对:

  • ✅ CF 记录 devbox.dingdao.me → xx.xx.xx.xx(当前公网出口 IP)
  • ✅ 服务器公网出口 IP 就是那个 IP
  • ✅ Windows portproxy 2222 → 172.x.x.x:22 正确,LAN 段 Test-NetConnection 192.168.x.7 -Port 2222
  • ✅ 手机有公网 IPv4(测试 curl -4 https://ifconfig.me 能拿到一个公网 v4,不是 IPv6-only)

外网 xx.xx.xx.xx:2222 始终 CLOSED

排查结论:运营商在封家用宽带入站。家用宽带(尤其 PPPoE 拨号那层)
很多地区默认拒绝非 80/443 的入站 TCP,甚至全端口拒绝。DDNS + 端口转发
这套在”运营商不封入站”的网络里是完美的,但在我家这堵墙前无能为力。

这是国内家宽的老问题,不怪配置,怪物理链路。

三、最终方案:Tailscale(出站打洞,零入站端口)

3.1 为什么 Tailscale 能绕过这堵墙

Tailscale 本质是 WireGuard + 出站打洞

  • 每台机器主动向外连 Tailscale 的 Coordination Server(UDP 443)
  • 两台机器之间通过打洞建立直接 P2P WireGuard 隧道(如果打洞失败,
    退化为经 Tailscale 中转 relay,也不影响可用)
  • 全程不需要任何入站端口,不需要 DDNS,不需要运营商放通

对你的网络意味着:哪怕家用宽带入站全封,只要能出站(上网),
Tailscale 就能通。

3.2 三端落地(同一 Google 账号 → 同一 tailnet)

1
2
3
4
5
6
7
8
Ubuntu VM (Hyper-V)  +  Windows 宿主机  +  手机(Android/Termux)
\ / /
\ / /
┌────────────────── 同一 tailnet (xxx) ─────────────────┐
│ 100.123.x.x 100.113.x.x 100.98.x.x │
│ Ubuntu ←——⇄——→ Windows ←——⇄——→ 手机 │
│ (full mesh,任意两两直通) │
└────────────────────────────────────────────────────────┘

Ubuntu(服务器侧)

1
2
3
4
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --ssh --hostname=devbox
# 浏览器打开控制台给的链接授权
# 得到固定域名 devbox.<tailnet>.ts.net,稳定不变
  • 开机自启(systemctl enable tailscaled
  • --ssh 开了 Tailscale 自带 SSH,免私钥直连(控制台自动分发公钥)

Windows(宿主机)winget install Tailscale.Tailscale 或官网安装包,
同账号登录。

手机:App Store / Play 装 Tailscale,同账号登录,自动进 tailnet。

3.3 连接(任意网络、任意时刻)

本地 ~/.ssh/config(和原 DDNS 并存,双保险):

1
2
3
4
5
6
7
8
9
10
Host devbox-ts
HostName devbox.<tailnet>.ts.net
User kali
Port 22
IdentityFile C:\Users\<你>\.ssh\id_ed25519

Host devbox # DDNS 备用(局域网/公网入站可用时)
HostName devbox.dingdao.me
Port 2222
User kali

手机(最省事,免私钥):

1
tailscale ssh [email protected]

3.4 验证

  • sudo tailscale status 三端全在线
  • 手机 tailscale ssh kali@... 直接进 Ubuntu shell ✅
  • tailscale ssh 不走 22 端口、不走公网,运营商封多少端口都不影响

四、安全性(针对”私人文件会不会暴露”的担忧)

Tailscale 在这套场景里不增加公网攻击面,原因:

  1. 默认对公网不可见100.x.y.z 是 RFC1918 保留段,公网路由送不到;
    攻击者没有你的账号 + tailnet 的 WireGuard 密钥,连”敲门”都找不到
  2. 双向 WireGuard 加密(AES-256-GCM):流量嗅探也解不开
  3. 默认零开放端口:不开 Funnel、不开 Subnet Router、不开 --ad-v4
    我确认过 Funnel 是关的
  4. 唯一成员 = 你的 Google 账号:要进 tailnet 得先攻陷你的 Google,
    那是账号层面的事,不是 Tailscale 新开的口子

真实剩余风险全在”账号/设备被攻破”层面,按性价比做这几项加固:

  • Google 开两步验证(最优先)
  • Tailscale 控制台开 2FA + 定期查 活跃会话/陌生设备(陌生即 revoke)
  • Per-Device ACL 收紧:手机/Windows 只允许连 Ubuntu 的 22,其余 deny
    (即使某台设备密钥泄露,横向也被限死在 ACL 内)

五、踩坑清单(可复用)

解法
Cloudflare 的 Account ID ≠ Zone ID,填错 404 用 API 查 zone 列表拿真实 Zone ID,脚本自动解析
DDNS 脏数据污染(IP 源全挂时把记录写空) 全失败时保留 last_ip,绝不覆盖已有正确记录
家宽外网入站被封(DDNS+转发全对但外网 CLOSED) 换 Tailscale 出站打洞,零入站
Hyper-V VM 重启 IP 变导致 portproxy 指错 VM 侧 netplan 配静态 IP,一劳永逸
Tailscale headless 拿不到认证 URL nohup sudo tailscale up ... & 后台跑,读 log 里的 URL

六、一句话总结

家用宽带入站不可控(运营商封端口、光猫路由模式二级 NAT、动态 IP)
这三个坑让”DDNS + 端口转发”在家用场景下不可靠
Tailscale 用”出站打洞”把问题降维成”只要能上网就能连”,
是个人远程开发/私人文件互通场景的默认最优解。
DDNS 那套留着做局域网直连的备用,公网访问交给 Tailscale。


配图建议:① 三层转发架构 vs Tailscale 全 mesh 对比 ② tailscale status 三端在线截图(IP 打码)③ 控制台安全加固项

脱敏说明(发文前自查)

本文已做如下脱敏,发布前请再自查一遍:

  • 真实邮箱 [email protected] → 文中已隐去(只写”同一 Google 账号”)
  • 真实公网出口 IP 122.231.144.252、手机出口 124.160.204.98 → 文中用 xx.xx.xx.xx 占位
  • 真实 tailnet 名 tailb889a7 → 文中用 xxx 占位(devbox.<tailnet>.ts.net
  • 真实内网 172.31.92.47 / 192.168.2.7 → 文中用 172.x.x.x / 192.168.x.7 占位
  • 真实域名 kaliserver.dingdao.me / kaliserver.tailb889a7.ts.net → 文中改为 devbox.dingdao.me / devbox.<tailnet>.ts.net
  • Tailscale IP 100.123.36.49 / 100.113.231.42 / 100.98.143.1 → 文中用 100.123.x.x / 100.113.x.x / 100.98.x.x 占位

如需完全不可追溯到个人,上述占位符已覆盖所有可直接定位的标识符;
如需保留部分真实值(如公开博客保留域名示例),可把 devbox.dingdao.me
换成你自己愿意公开的域名。