← 文档索引

家庭内网远程访问方案
frp 反向隧道 × pangolin × 分流 DNS

2026-07-15 · 在外链路已上线(锚点 ali)· 家内直连(P5/P6)待做 · 相关:Tailscale 公网访问(保留作应急后门)

⚠️ 2026-07-15 落地修正(与下文原设计的差异)

目标:①在家直连内网、不出外网;②在外开着 pangolin 就能穿透回家(不装第二个 VPN 客户端);③内外用同一套地址——git.yanmeiai.com(Forgejo)、nas.yanmeiai.com(DSM/Photos)、win.yanmeiai.com(Windows RDP/SSH)。
核心思路:NAS 上的 frpc 主动外连 pangolin1 上的 frps 建反向隧道(家里零开放端口、不暴露家庭 IP);同一批域名公网解析到 pangolin1、家内 DNS 解析到内网 IP(split-horizon);pangolin 客户端配置加一条「这几个域名走本地 DNS、私网结果直连」的分流规则,保证 pangolin 常开时内外自动走对路。

1 · 现状盘点(事实依据)

对象现状与本方案的关系
NAS(DS925+)192.168.3.200;跑 Forgejo(:3000 HTTP / :2222 SSH)、mihomo 透明代理(:53 fake-ip DNS / TUN)、Tailscale(100.95.151.13装 frpc;mihomo 加 hosts 覆盖;Tailscale 作本次部署跳板
Windows192.168.3.88,网关/DNS 均指向 NAS;有 OpenSSH server(ssh windows基本不改;frpc 从 NAS 直接转发其 RDP/SSH
pangolin1103.119.13.48,Debian 12,2C/1G;:443 sing-box REALITY、:8080 控制面(仅 127.0.0.1)、:9443 gRPC、cloudflared 出站隧道;ufw 默认拒入站装 frps(~20MB 内存);7000/2222/3000/3389/5001/10022/10023 均空闲无冲突
家宽有真实公网 IP(动态),路由器无端口映射保持零开放端口,不做 DDNS
DNSyanmeiai.com 在 Cloudflare(管理走 cf-api);git.51yanmei.com 公网 A 记录指 192.168.3.200(仅家内可达)新增 3 条 DNS-only A 记录;旧 git 域名可选迁移

2 · 架构总览

在外(pangolin 开) Mac / 手机 pangolin TUN REALITY 隧道 pangolin1 · 103.119.13.48 sing-box frps 机内回环(hairpin):出口连自己 业务端口 ufw 不开放 → 外网扫不到 frp 反向长连接 (家里主动拨出,TLS+token) 家庭内网 192.168.3.0/24 NAS .200 frpc·git·DSM·DNS Windows .88 RDP 3389 · SSH 22 路由器零端口映射,家庭 IP 不出现在公网 DNS 在家(pangolin 照样开) Mac / 手机 pangolin TUN 客户端分流规则(BuildClientConfig 下发) 这 3 个域名 → 本地 DNS 解析 → 得内网 IP 私网地址命中 LAN 直连规则 → 不进隧道 纯局域网直连 NAS .200 / Windows .88 mihomo hosts 把 3 个域名 解析成内网 IP 同一个域名,两处答案(split-horizon): git.yanmeiai.com 公网 DNS(Cloudflare)→ 103.119.13.48(pangolin1/frps) 家内 DNS(NAS mihomo)→ 192.168.3.200(直连)

3 · 统一地址与端口规划

选端口的原则:内外端口号一致(同一个 URL 全球通用)。唯一例外是裸 SSH——:22 在 pangolin1 上被它自己的 sshd 占用,只能错位到 10022/10023,由 ~/.ssh/config 别名消化(见 §7)。

服务统一地址frpc 转发目标(家内)pangolin1 remotePort备注
Forgejo Git SSHssh://git@git.yanmeiai.com:2222127.0.0.1:22222222内外一致 ✓
Forgejo Webhttp://git.yanmeiai.com:3000127.0.0.1:30003000内外一致 ✓
DSM / Photoshttps://nas.yanmeiai.com:5001127.0.0.1:50015001内外一致 ✓;Photos App 填这个地址
Windows RDPwin.yanmeiai.com:3389192.168.3.88:33893389内外一致 ✓
NAS SSH外:nas.yanmeiai.com:10022
家:192.168.3.200:22
127.0.0.1:2210022端口错位,ssh alias 兜底
Windows SSH外:win.yanmeiai.com:10023
家:192.168.3.88:22
192.168.3.88:2210023端口错位,ssh alias 兜底

不转发的:SMB/445(协议不适合过公网,文件走 Synology Drive/Photos 或 git);DSM 5000(明文 HTTP,只留 5001 HTTPS);mihomo 9090 管理口(有需要时临时经 NAS SSH 端口转发)。

4 · 安全模型(三层)

第一层:业务端口对公网「隐形」(关键设计)

pangolin1 的 ufw 默认拒绝入站。frps 的业务端口(2222/3000/3389/5001/10022/10023)一条 allow 规则都不加。你在外面走 pangolin 时,流量从 sing-box 出站连 103.119.13.48——目的地是本机自己的公网 IP,内核走 loopback 回环(hairpin)送达,而 ufw 对 lo 接口默认全放行。效果:这些端口只有「连着 pangolin 的你」能访问,公网扫描器看到的是全拒。RDP 这种绝不能裸奔的服务因此是安全的。

验证兜底:若实测 hairpin 未走 lo(§9 验收步骤 P1-4 会测),补一条 ufw allow from 103.119.13.48 等价实现,语义不变。

第二层:frp 控制通道加固

:7000 是唯一新开的公网端口(frpc 从家里拨入用,家宽 IP 动态、无法限源)。加固:auth.token(32 字节随机,存 Bitwarden)+ transport.tls.force = true(强制 TLS,未握手连接直接断)。

第三层:业务自身认证不放松

Forgejo 密钥登录、DSM 账号 2FA + 自动封锁、Windows RDP 强密码/NLA——原有认证全部保留,隧道只是网络通路。Tailscale 保留不动,作为 frp 链路故障时的应急后门。

5 · pangolin1 怎么配(装 frps)

机器:ssh pangolin1 · 人在外也能直接做(公网可达)

5.1 生成 token(本机 Mac 上)

# 生成 32 字节随机 token,存进 Bitwarden(条目名:frp home-tunnel)
openssl rand -hex 32
rbw add "frp home-tunnel"   # 密码字段贴上面的输出;两端配置从这里取,不落明文文件

5.2 安装 frps 二进制 + systemd

# ssh pangolin1 之后(root)。VER 以 frp releases 最新版为准,下例为示意
VER=0.61.1
curl -LO https://github.com/fatedier/frp/releases/download/v${VER}/frp_${VER}_linux_amd64.tar.gz
tar xzf frp_${VER}_linux_amd64.tar.gz
install -m 755 frp_${VER}_linux_amd64/frps /usr/local/bin/frps
mkdir -p /etc/frp

/etc/frp/frps.tomltoken 不入 git,与 /etc/pangolin* 密钥同规矩):

bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.token = "<Bitwarden: frp home-tunnel>"
transport.tls.force = true          # 拒绝非 TLS 的 frpc
# 不开 dashboard;业务端口按 frpc 请求动态监听
allowPorts = [
  { start = 2222, end = 2222 }, { start = 3000, end = 3000 },
  { start = 3389, end = 3389 }, { start = 5001, end = 5001 },
  { start = 10022, end = 10023 },
]                                    # 白名单锁死,token 泄露也开不了别的口

/etc/systemd/system/frps.service

[Unit]
Description=frp server (home reverse tunnel)
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=always
RestartSec=5
User=nobody                          # 非特权端口,无需 root
LimitNOFILE=65536
# 1G 小机安全带:内存超限就重启,绝不拖垮 sing-box/控制面
MemoryMax=128M

[Install]
WantedBy=multi-user.target
chmod 600 /etc/frp/frps.toml && chown nobody /etc/frp/frps.toml
systemctl daemon-reload && systemctl enable --now frps
ufw allow 7000/tcp comment 'frp control (frpc dial-in from home)'
# 验证:只多了 7000 这一个对外口
systemctl status frps --no-pager | head -5
ss -tlnp | grep frps

⚠️ 台账提醒:pangolin1 续费到期 2026-07-19(baize billing.renew_at)——动工前先确认已续费。

6 · DNS 怎么配(Cloudflare + 家内 mihomo)

6.1 公网侧:Cloudflare 加 3 条 A 记录(本机 Mac,cf-api)

必须 DNS only(灰云,proxied:false——git SSH / RDP 是任意 TCP,CF 橙云只代理 HTTP。zone id 见 baize:2325730de45276d87180a8b66bd4cca0

cf-api -X POST "/zones/2325730de45276d87180a8b66bd4cca0/dns_records" \
  --data '{"type":"A","name":"nas","content":"103.119.13.48","proxied":false,"ttl":300}'
cf-api -X POST "/zones/2325730de45276d87180a8b66bd4cca0/dns_records" \
  --data '{"type":"A","name":"git","content":"103.119.13.48","proxied":false,"ttl":300}'
cf-api -X POST "/zones/2325730de45276d87180a8b66bd4cca0/dns_records" \
  --data '{"type":"A","name":"win","content":"103.119.13.48","proxied":false,"ttl":300}'

代价说明:灰云记录会暴露 pangolin1 的 IP——它本来就是 VPN 数据面入口(客户端 endpoint 直连该 IP),无新增暴露。改完回写 baize domains.yaml

6.2 家内侧:NAS mihomo 加 hosts 覆盖

/volume1/docker/proxy/mihomo/config.yaml 两处改动(仓库 docker/proxy/mihomo/config.yaml 同步改):

hosts:                              # ① 家内答案:直接给内网 IP
  nas.yanmeiai.com: 192.168.3.200
  git.yanmeiai.com: 192.168.3.200
  win.yanmeiai.com: 192.168.3.88
  git.51yanmei.com: 192.168.3.200  # 兼容既有 git remote(可选,见 §10)

dns:
  fake-ip-filter:                   # ② 追加:不给这几个域名发 fake-ip,走 hosts 真实值
    # ……既有条目保持不动(含 +.tailscale.com 等)……
    - nas.yanmeiai.com
    - git.yanmeiai.com
    - win.yanmeiai.com
    - git.51yanmei.com
# 热重载(不重启容器),沿用仓库既有做法
ssh nas "curl -s -X PUT 'http://127.0.0.1:9090/configs?force=true' \
  -H 'Content-Type: application/json' \
  -d '{\"path\":\"/root/.config/mihomo/config.yaml\"}'"
# 验证:家内解析应返回 192.168.3.200
ssh nas "nslookup git.yanmeiai.com 127.0.0.1"

6.3 路由器:DHCP 的 DNS 指到 NAS(回家后做)

Windows 已手动指 NAS;要让 Mac/手机在家也吃到 hosts 覆盖,把路由器 DHCP 下发的 DNS 改为 192.168.3.200(一处改动、全家生效)。取舍:NAS 由此成为家内 DNS 单点——不要配第二个公共 DNS 兜底(客户端会随机用它,覆盖时灵时不灵);NAS 重启的几分钟内家里断解析,可接受。

7 · NAS 怎么配(装 frpc)

仓库新增:docker/frp/docker-compose.yml + docker/frp/frpc.toml + docker/frp/.env.example · NAS 路径:/volume1/docker/frp/

沿用本仓库 docker-compose 惯例(forgejo/proxy 同款)。wangjia 不在 docker 组,部署用 sudo 或 Container Manager UI。

docker-compose.yml

services:
  frpc:
    image: snowdreamtech/frpc:0.61   # 多架构维护镜像;版本与 frps 对齐
    container_name: frpc
    network_mode: host                # 需直达 127.0.0.1 各服务 + 192.168.3.88
    restart: unless-stopped
    volumes:
      - ./frpc.toml:/etc/frp/frpc.toml:ro

frpc.toml(token 同样不入 git,仓库放 frpc.toml.example 占位):

serverAddr = "103.119.13.48"
serverPort = 7000
auth.method = "token"
auth.token = "<Bitwarden: frp home-tunnel>"
transport.tls.enable = true

[[proxies]]
name = "git-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 2222
remotePort = 2222

[[proxies]]
name = "git-web"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 3000

[[proxies]]
name = "dsm"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5001
remotePort = 5001

[[proxies]]
name = "nas-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 10022

[[proxies]]
name = "win-rdp"
type = "tcp"
localIP = "192.168.3.88"       # 同网段代转 Windows,一个 frpc 管全家
localPort = 3389
remotePort = 3389

[[proxies]]
name = "win-ssh"
type = "tcp"
localIP = "192.168.3.88"
localPort = 22
remotePort = 10023
# 部署(经 Tailscale 跳板 ssh 到 NAS 后)
sudo mkdir -p /volume1/docker/frp && cd /volume1/docker/frp
# (把仓库里的两个文件放进来,frpc.toml 填真 token)
sudo docker compose up -d
sudo docker logs frpc --tail 20      # 应见 "login to server success" + 6 个 proxy 启动成功

Mac 的 ssh 别名收尾(~/.ssh/config

# 既有 Host nas / windows(内网 IP 直连)保持不动,家里继续用
Host nas-r                            # 在外用:ssh nas-r
  HostName nas.yanmeiai.com
  Port 10022
  User wangjia

Host win-r                            # 在外用:ssh win-r
  HostName win.yanmeiai.com
  Port 10023
  User wangjia

8 · Windows 怎么改(几乎不用)

动作说明
网络不改网关/DNS 已指 NAS,天然吃到 hosts 覆盖
RDP确认已启用(设置 → 系统 → 远程桌面),保持 NLA 开启frpc 从 NAS(同网段 .200)连入,默认防火墙「专用网络」范围即放行
OpenSSH server已有ssh windows 在用),确认服务设为自动启动同上,无需改防火墙
防火墙仅当连不通时检查:RDP/SSH 入站规则的作用域含本地子网不需要也不要开「任意来源」

9 · pangolin 客户端分流(BuildClientConfig,让家里直连成立)

代码:server/internal/httpapi/clientconfig.go::BuildClientConfig · 单独一刀,四端零改动(配置服务端渲染下发)

pangolin TUN 的首条路由规则是 hijack-dns,所有 DNS 都被劫去隧道解析——家里也会拿到公网 IP、绕道外网。解法是给这几个域名开「本地解析 + 按结果路由」的侧门:

// 伪代码示意。域名清单走 server 配置(env PRIVATE_SPLIT_DOMAINS),默认空 = 行为不变,
// 不影响其他用户。
// ① dns.servers 增本地解析器
{ "tag": "dns-local", "address": "local", "detour": "direct" }
// ② dns.rules 前插:这 3 个域名用本地 DNS(家=mihomo hosts→内网IP;外=公网DNS→pangolin1)
{ "domain": ["nas.yanmeiai.com","git.yanmeiai.com","win.yanmeiai.com"], "server": "dns-local" }
// ③ route.rules 在 hijack-dns 之后插:先解析再按 IP 匹配后续规则
{ "domain": ["…同上…"], "action": "resolve" }
// ④ 解析结果为私网 → 命中既有 LAN 直连规则 → 家里直连;
//    公网 IP(pangolin1)→ 不匹配 → 走默认代理出口 → 机内 hairpin 到 frps

实现时必须验证action: resolve 是 sing-box ≥ 1.11 语法——先确认四端内嵌 libbox 版本;不够就先升 libbox(scripts/build-libbox.sh 重编,产物 gitignore 需各端重新放置)。按仓库惯例先在 macOS 端端到端验证,再推 iOS/Android/Windows。

此刀未落地前的过渡用法:在外一切正常(隧道 DNS 拿公网 IP 本来就该走隧道);在家访问这几个域名会绕外网一圈(功能可用、不满足目标①),或临时关 pangolin 直连。

10 · 执行顺序(人在外,现在就能开工)

P0 · 前置(5 分钟)

P1 · pangolin1 装 frps(直接可做,§5.2)

P2 · Cloudflare 加 3 条灰云 A 记录(直接可做,§6.1)

P3 · 经 Tailscale 跳板配 NAS(§6.2 + §7)

P4 · 在外端到端验收(pangolin 开着)

P5 · 回家后收尾(§6.3)

P6 · pangolin 分流规则(单独一刀,§9)

P7 · 台账与文档回写

11 · 风险与回滚

风险影响缓解 / 回滚
NAS 成家内 DNS 单点NAS 挂 → 全家断解析接受(NAS 稳定);应急把路由器 DHCP DNS 改回运营商
token 泄露他人可向 frps 注册代理allowPorts 白名单锁死端口范围;业务端口本就 ufw 隐形;换 token 重启两端即失效
frps 占用拖累 1G 小机控制面/数据面受影响systemd MemoryMax=128M,超限自杀重启,隔离爆炸半径
hairpin 假设不成立在外连不上业务端口P1-4 提前实测;兜底 ufw allow from 103.119.13.48
pangolin1 迁移/换 IP公网侧全断frps 配置随 deploy 脚本走;改 3 条 A 记录即恢复;家内直连不受影响
libbox 版本不支持 resolveP6 延期P1–P5 不依赖 P6;过渡期家里关 pangolin 或接受绕行
整体回滚NAS:docker compose down;pangolin1:systemctl disable --now frps + 删 ufw 7000;CF 删 3 条记录;mihomo 还原备份重载。全程不动业务服务

12 · 可选增强(不阻塞主线)