从 NAT 说起:用 frp 内网穿透,随时随地访问家里的设备

最近把家里设备的内网穿透完整跑通了一遍,趁记忆还热乎,整理成这篇文章,给自己留个档,也给有同样需求的朋友一份参考。这篇文章我会从计算机网络的底层原理讲起,先把「为什么外网访问不到家里」这件事彻底讲透,再落到 frp 的部署实践,希望你看完后不仅会用,更明白它为什么能工作。

一、认识 NAT:外网无法访问内网设备的原因

要理解内网穿透,绕不开一个底层概念——NAT(Network Address Translation,网络地址转换)。它是「外网进不来」的根因,也是「穿透」要解决的问题本身。

1.1 公网地址与私网地址

IPv4 地址是 32 位的,理论上最多约 42 亿个。这个数量放在今天的设备规模下早已不够用,因此地址被划分为两类:

类别地址段规范特性
公网地址全球唯一可直接在公网被路由,是「街道门牌号」
私网地址10.0.0.0/8、172.16.0.0/12、192.168.0.0/16RFC 1918仅在局域网内有效,公网不路由
运营商大内网(CGNAT)100.64.0.0/10RFC 6598运营商给用户分发的「套娃」私网地址

于是现实中的家用网络,往往是层层嵌套的:运营商用 CGNAT 把你家整体包进一个大内网,你家路由器再做一层 NAT,把设备分到 192.168.x.x。你的设备手里拿到的,从来不是公网地址。

公网 ⇄ 运营商 CGNAT(100.64.x.x)⇄ 家用路由器 NAT ⇄ 你的设备(192.168.x.x)

1.2 NAT 的地址转换机制

NAT 的核心,是路由器上维护的一张地址转换表(在 Linux 里称为 conntrack,连接跟踪表)。它把「内网地址:端口」和「公网地址:端口」一一对应起来,从而让多个内网设备共用一个公网 IP 上网。

来看一次出站访问的完整过程:

内网设备                     NAT 路由器                      公网服务器
 192.168.1.10:50000     内网口 192.168.1.1              8.8.8.8:443
                        公网口 1.2.3.4
      │                        │                             │
      │ ① 出站 SYN             │                             │
      │  src=192.168.1.10:50000│                             │
      │  dst=8.8.8.8:443       │                             │
      │───────────────────────▶│                             │
      │                        │ ② SNAT(改写源地址)          │
      │                        │  建立映射表项:               │
      │                        │  192.168.1.10:50000          │
      │                        │   ⇋ 1.2.3.4:40000            │
      │                        │  src=1.2.3.4:40000           │
      │                        │  dst=8.8.8.8:443             │
      │                        │─────────────────────────────▶│
      │                        │                             │
      │                        │ ③ 服务器回包                  │
      │                        │  src=8.8.8.8:443             │
      │                        │  dst=1.2.3.4:40000           │
      │                        │◀─────────────────────────────│
      │ ④ DNAT(查表还原)       │                             │
      │  dst=192.168.1.10:50000│                             │
      │◀───────────────────────│                             │

这里有两个关键动作:

  • SNAT(Source NAT,源地址转换):出站时把「源地址」从内网地址改成公网地址,并记录映射关系。
  • DNAT(Destination NAT,目的地址转换):回包时把「目的地址」从公网地址还原成内网地址,靠的就是之前那张表。

1.3 入站连接失败的原因

注意一个细节:上面那张映射表,是由「出站连接」动态创建的。也就是说,只有在你的设备先主动向外发起连接后,表里才会有对应的表项。

反过来,当一个外网设备想「主动」连进你家时,情况是这样的:

 外网设备                  NAT 路由器(查无此表项)           内网设备
 1.2.3.5                                                  192.168.1.10:22
      │                        │                             │
      │  SYN  dst=1.2.3.4:22   │                             │
      │───────────────────────▶│                             │
      │                        │ ✗ 地址转换表中没有 22 端口    │
      │                        │   的映射,无法确定该转发给哪台 │
      │                        │   内网设备,直接丢弃该包        │
      │                        │                             │
      │ ◀── 永远等不到 SYN-ACK ──(连接超时)

这就是「外网进不来」的底层原因:入站连接在 NAT 表里找不到映射,路由器不知道该把数据交给谁,只能丢弃。它不是某个配置错误,而是 NAT 机制本身的限制。

1.4 NAT 的类型(延伸)

顺带一提,NAT 按行为可分为完全锥形、受限锥形、端口受限锥形、对称型等几类。这主要影响 UDP 打洞这类依赖「猜测端口」的穿透技术能否成功——不同的 NAT 类型,打洞难度差别很大。

但本文要讲的 frp 走的是另一条路:它只依赖一条普通的「出站 TCP 长连接」,这条连接天然能被任何类型的 NAT 放行,因此几乎不受 NAT 类型限制。理解了这一点,你就能明白为什么 frp 比 UDP 打洞更通用、更稳定。

二、内网穿透的原理:反向代理

2.1 核心思路:既然入站不行,就让内网主动出站

上一节已经论证:出站连接可以被 NAT 放行,入站连接会被丢弃。frp(fast reverse proxy)正是抓住了这个不对称性——让家里的设备主动向外发起一条到公网服务器的连接,把「入站」的问题,转换成「出站」。

这条由内网主动建立的、长期保持的连接,就成了一条隧道。隧道一旦建立,双向数据都能顺着它走,因为对 NAT 来说,这本质上就是一条已经建立好的出站 TCP 连接。

外地设备                公网服务器 frps                 家里设备 frpc
  (手机)           监听 7000(控制) + 6000(转发)        本机 sshd :22
      │                       │                             │
      │                       │ ◀── ① frpc 主动出站连 7000 ──│
      │                       │    (出站连接,NAT 放行)      │
      │                       │    === 长连接隧道建立 ===     │
      │                       │                             │
      │ ② ssh -p 6000 服务器   │                             │
      │──────────────────────▶│                             │
      │                       │ ③ 请求沿隧道转发               │
      │                       │────────────────────────────▶│
      │                       │                             │ ④ frpc 交给本地 22

2.2 角色与数据流拆解

角色程序部署位置职责
服务端frps有公网 IP 的服务器接收客户端隧道、监听公网端口、转发请求
客户端frpc家里的设备主动连接服务端、注册隧道、转发到本地服务

一次完整的访问,分五步走:

  1. 服务端就绪frps 监听控制端口 7000(供客户端连)和转发端口 6000(供外网访问)。
  2. 建立隧道frpc 主动连到 frps 的 7000,用 token 认证,双方建立长连接。
  3. 注册转发规则:frpc 告知 frps「外网访问你的 6000,就转发给我本地的 22(SSH)」。
  4. 外网访问:外地设备执行 ssh -p 6000 服务器IP,请求先到达 frps 的 6000。
  5. 反向转发:frps 把请求封装后沿隧道推给 frpc,frpc 再交给本地 sshd,响应原路返回。
之所以叫「反向」,是因为连接的发起方是内网的客户端,而不是外网的访问者。frp 在这里扮演的正是反向代理的角色——公网侧收流量,沿内网主动建立的通道反向往回送。

2.3 从 TCP/IP 层次看 frp

如果站到 TCP/IP 协议栈的角度,frp 的原理会更清晰。它本质上是在应用层做了一层「TCP 连接的封装转发」:外网到 frps 是一段 TCP,frpc 到本地服务是另一段 TCP,两段 TCP 通过「隧道」这条长连接在应用层衔接起来。

外地设备                frps                     frpc                家里 sshd
 ┌──────────┐           ┌──────────┐             ┌──────────┐        ┌──────────┐
 │ SSH 客户端│           │ 应用层代理│             │ 应用层代理│        │ SSH 服务端│
 │  (应用层) │           │          │             │          │        │  (应用层) │
 └──────────┘           └──────────┘             └──────────┘        └──────────┘
      │   TCP(公网)          │       TCP(隧道,一条长连接)        │   TCP(本地)  │
      │◀════════════════════▶│◀═══════════════════════════════▶│◀════════════════▶│

这种「在一条连接里承载多个逻辑连接」的做法,本质是多路复用——frp 给每条转发的连接分配一个 ID,隧道里跑的每个数据包都带着 ID,frpc 收到后按 ID 还原成独立的本地连接。这和 SSH 的 -R 反向端口转发是同一个思路,只是 frp 把它做成了更通用、更好管理的服务。

2.4 端口规划

端口位置作用
7000服务器frps 控制端口,frpc 通过它连服务器建立隧道
6000服务器转发端口,外网访问此端口 → 转发到家里 22
22家里本机 SSH 服务端口(转发目标)

三、服务器端:部署 frps

本文示例:服务器为 160.202.47.132(root 用户),家里设备为运行 sshd 的主机。以下命令需区分执行环境,均以「执行位置」标注。

3.1 下载 frp

# 服务器执行 —— 查看最新版本号
curl -s https://api.github.com/repos/fatedier/frp/releases/latest | grep -oP '"tag_name":\s*"v\K[^"]+'
# 服务器执行 —— 下载并解压(v0.61.1 换成上一步看到的版本)
cd /tmp
wget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_linux_amd64.tar.gz
tar -xzf frp_0.61.1_linux_amd64.tar.gz
sudo mv frp_0.61.1_linux_amd64 /usr/local/frp
ls /usr/local/frp   # 应看到 frps、frpc 及对应的 .toml 模板

3.2 配置 frps.toml

# 服务器执行
cat > /usr/local/frp/frps.toml <<'EOF'
bindPort = 7000
auth.method = "token"
auth.token = "请替换为你的强密码"
EOF
配置项作用
bindPort = 7000服务端监听端口,客户端通过此端口建立隧道
auth.method = “token”启用 token 认证
auth.token认证密钥,服务端与客户端必须一致,用于防止未授权连接

3.3 启动 frps

# 服务器执行
/usr/local/frp/frps -c /usr/local/frp/frps.toml

启动后若看到 frps started successfully 即表示运行正常。-c 参数用于指定配置文件路径。

3.4 放行端口(安全组)

务必留意:云服务器除了系统防火墙,通常还有一层「安全组」。需在云控制台放行 7000 与 6000 两个端口,否则客户端无法连接、外网也无法访问。

四、客户端:部署 frpc

4.1 下载 frp

# 家里设备执行(与服务端同版本)
cd /tmp
wget https://github.com/fatedier/frp/releases/download/v0.61.1/frp_0.61.1_linux_amd64.tar.gz
tar -xzf frp_0.61.1_linux_amd64.tar.gz
sudo mv frp_0.61.1_linux_amd64 /usr/local/frp

4.2 配置 frpc.toml

# 家里设备执行
cat > /usr/local/frp/frpc.toml <<'EOF'
serverAddr = "160.202.47.132"
serverPort = 7000
auth.method = "token"
auth.token = "请替换为你的强密码"

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
EOF
配置项作用
serverAddr服务端地址,即你的公网服务器 IP
serverPort = 7000连接服务端控制端口,与 frps 的 bindPort 对应
auth.token与服务端一致
name = “ssh”隧道名称,可自定义
type = “tcp”转发协议类型,SSH 属于 TCP
localIP = “127.0.0.1”本地服务地址
localPort = 22本地服务端口(转发目标,这里是 SSH)
remotePort = 6000服务端对外暴露端口,外网访问它即转发到本地 22

4.3 启动 frpc

# 家里设备执行
/usr/local/frp/frpc -c /usr/local/frp/frpc.toml

看到 start proxy success 即隧道建立成功。

五、连接验证

# 任意外网设备执行(手机、公司电脑等)
ssh -p 6000 家里用户名@160.202.47.132
参数作用
-p 6000指定端口,连接服务器的 6000(而非默认 22)
家里用户名家里设备上的登录账号
160.202.47.132服务器 IP
成功进入家里设备的命令行,即表示整条链路打通。注意:这里输入的密码是家里设备的登录密码,与 frp 的 token、服务器的密码均无关系。

六、进程守护与开机自启(systemd)

目前 frps / frpc 都是前台运行,关闭终端即中断。通过 systemd 托管后,可实现开机自启、异常自动重启。

6.1 服务端 frps.service

# 服务器执行
sudo cat > /etc/systemd/system/frps.service <<'EOF'
[Unit]
Description=frp server
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/frp/frps -c /usr/local/frp/frps.toml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo systemctl status frps

其中 ExecStart 为服务启动命令,Restart=on-failure 表示异常退出自动重启,enable –now 表示立即启动并设为开机自启。

6.2 客户端 frpc.service

# 家里设备执行
sudo cat > /etc/systemd/system/frpc.service <<'EOF'
[Unit]
Description=frp client
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/frp/frpc -c /usr/local/frp/frpc.toml
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now frpc
sudo systemctl status frpc

七、安全加固

  1. 使用强 token:避免弱口令被扫描利用。
  2. 优先密钥登录:家里设备的 SSH 建议关闭密码登录、仅保留公钥,降低爆破风险。
  3. 端口选择冷门值:转发端口不必用 6000,可换为高位端口以降低被扫描概率。
  4. 最小化暴露:云安全组仅放行必要端口,其余保持关闭。

关闭家里设备 SSH 密码登录(可选,操作前确保密钥登录已可用):

# 家里设备执行
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd

八、常见问题排查

现象原因与处理
frpc 报 connection refused服务端 7000 端口未放行,或 frps 未启动
frpc 报 authorization failed两端 auth.token 不一致
SSH 提示 Permission denied确认使用的是家里设备的用户名与密码,而非服务器账号
连接 6000 超时云安全组未放行 6000,或本地 sshd 未运行
隧道中断查看 systemctl status frpc,网络波动会自动重连

实时查看日志:

sudo journalctl -u frps -f   # 服务端
sudo journalctl -u frpc -f   # 客户端

九、写在最后

内网穿透的核心,就是抓住「出站连接不受 NAT 限制」这一特性,让内网设备主动连出、建立反向隧道。理解了这一点,frp、ngrok、tailscale 等工具背后的逻辑都是相通的。

本文以 SSH 为例,但同样思路可以穿透任意 TCP 服务——把 localPort 换成 NAS 的网页端口,就能在外网访问家里的管理界面;换成 Jellyfin 的端口,就能在外面看家里的影视库。希望这篇文章能帮你少走弯路。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
下一篇