受限出口网穿透:用自有云主机远程桌面(frp + x11vnc)

本文记录:在受限出口网(出站强过滤、对称 NAT)里,不用第三方远控公有云,而是用自己的云主机把本机 Linux 当前桌面透出去的完整方案。

目标链路(端口与域名请按自己的环境替换):

1
2
3
4
5
外网 VNC 客户端
→ <你的域名>:<对外映射端口>
→ 云主机上 frps
← 本机 frpc(经 HTTPS/WSS 出站)
← 本机 x11vnc 共享当前 :0 桌面(仅监听本机回环)

1. 环境与约束

典型情况
被控端 Linux 图形会话已登录(如 lightdm + Xorg :0
中转节点 自有云主机,已有 nginx 与 HTTPS
受限出口网出站 HTTP/HTTPS 正常;普通 SSH 端口常超时
DPI 非 HTTP/TLS 载荷(SSH 横幅、普通二进制)即使改端口也会被 RST
NAT 对称型(Symmetric),打洞不可靠,必须中继
本机常见现状 可能已有 xrdp(新会话,不是当前屏幕);Docker 可用

结论:必须让“控制通道”长得像 HTTPS,桌面内容再通过隧道转发。直接把 VNC 暴露到公网、或走明文中继端口,在受限出口网里都不稳。


2. 走过的弯路(RustDesk)

曾在云主机上部署 RustDesk Server OSS(hbbs / hbbr),两端能配对,但受限出口网连明文中继时:

  • HTTP 探测可保持连接
  • SSH / 二进制握手约几十毫秒后被 RST
  • 客户端常见报错:Failed to receive public key / Connection reset by peer

尝试过 nginx 把 WebSocket 路径反代到 RustDesk 的 WS 端口。社区版桌面端注册 WebSocket 会收到 NOT_SUPPORT,需要 RustDesk Server Pro 才完整支持。因此放弃 RustDesk,改用 frp over WSS + x11vnc

xrdp 适合“另开一套桌面”;若要看到本机当前屏幕,用 x11vnc。


3. 最终架构

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──────────────┐  TCP <对外端口>  ┌─────────────────────────────┐
│ 外网 VNC │ ──────────────→ │ 自有云主机 │
│ TigerVNC 等 │ │ nginx :443 (站点 + WSS) │
└──────────────┘ │ frps 仅本机监听控制口 │
│ 对外映射端口 → 隧道 │
└─────────────▲───────────────┘
│ WSS (443)
┌─────────────┴───────────────┐
│ 受限出口网内电脑 │
│ frpc (Docker, host 网络) │
│ x11vnc → 127.0.0.1:<本地VNC端口> │
│ 共享 DISPLAY=:0 │
└─────────────────────────────┘

要点:

  1. frpc → frpsprotocol = wssserver_port = 443,伪装成 WebSocket over TLS,过受限出口网 DPI。
  2. x11vnc 只绑 localhost,公网只看到云主机上的对外映射端口。
  3. nginx:普通访问仍是站点;带 Upgrade: websocket 的请求转到 frps。

下文用占位符:

  • <你的域名>:云主机域名
  • <token>:frp 鉴权令牌
  • <本地VNC端口>:如常用的本机 VNC 端口
  • <对外映射端口>:frp remote_port,需安全组放行

4. 云主机配置

(若受限出口网 SSH 不通,可用云厂商的网页/Workbench 类通道执行。)

4.1 安装并启动 frps

GitHub Release 在受限出口网/部分云主机上可能下不动,可改用 Docker 镜像(版本与客户端一致即可):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mkdir -p /opt/frp
openssl rand -hex 16 > /opt/frp/token
chmod 600 /opt/frp/token
TOKEN=$(cat /opt/frp/token)
echo "TOKEN=$TOKEN" # 只记在私密处,不要写进公开仓库/博客

cat > /opt/frp/frps.ini << EOF
[common]
bind_addr = 127.0.0.1
bind_port = <frps控制端口>
proxy_bind_addr = 0.0.0.0
token = ${TOKEN}
EOF

docker pull snowdreamtech/frps:<版本>
docker rm -f frps 2>/dev/null
docker run -d --name frps --restart unless-stopped --network host \
-v /opt/frp/frps.ini:/etc/frp/frps.ini \
snowdreamtech/frps:<版本>
  • bind_addr = 127.0.0.1:控制口只本机可连,由 nginx 反代。
  • proxy_bind_addr = 0.0.0.0:让对外映射端口听在公网网卡上(另需安全组放行)。

4.2 nginx:443 上把 WebSocket 转给 frps

保留站点原有路径;对根路径按 Upgrade 分流(示意):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
location @frps_ws {
proxy_pass http://127.0.0.1:<frps控制端口>;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}

location / {
error_page 418 = @frps_ws;
if ($http_upgrade = "websocket") { return 418; }
try_files $uri $uri/ =404;
}
1
nginx -t && systemctl reload nginx

4.3 安全组

入方向增加:

  • 协议:TCP
  • 端口:<对外映射端口>
  • 源:尽量写外网客户端公网 IP,不要长期 0.0.0.0/0

443 / 80 给站点与证书续期用。


5. 被控端(受限出口网内)配置

5.1 安装 x11vnc

若镜像源出现 Hash 校验和不符,换官方/高校源下载对应 deb,校验 SHA256 后再 dpkg -i,并 apt-get install -f

设 VNC 密码(仅本机 x11vnc 用):

1
2
mkdir -p ~/.vnc ~/frp
x11vnc -storepasswd

5.2 常驻 x11vnc(systemd –user)

1
2
3
4
5
6
7
8
9
10
11
12
13
# ~/.config/systemd/user/x11vnc.service
[Unit]
Description=x11vnc on local display :0
After=graphical-session.target

[Service]
Type=simple
ExecStart=/usr/bin/x11vnc -display :0 -auth %h/.Xauthority -forever -shared -localhost -rfbport <本地VNC端口> -rfbauth %h/.vnc/passwd -noxdamage -repeat -o %h/.vnc/x11vnc.log
Restart=always
RestartSec=2

[Install]
WantedBy=default.target
1
2
3
systemctl --user daemon-reload
systemctl --user enable --now x11vnc.service
ss -tlnp | grep <本地VNC端口> # 应只看到 127.0.0.1

5.3 frpc 配置与启动

1
2
3
4
5
6
7
8
9
10
11
12
# ~/frp/frpc.ini
[common]
server_addr = <你的域名>
server_port = 443
protocol = wss
token = <token>

[vnc]
type = tcp
local_ip = 127.0.0.1
local_port = <本地VNC端口>
remote_port = <对外映射端口>

受限出口网直连 Docker Hub 可能被 RST,可用国内镜像加速拉取同版本 frpc

1
2
3
4
5
6
7
docker pull <镜像加速前缀>/snowdreamtech/frpc:<版本>
docker rm -f frpc 2>/dev/null
docker run -d --name frpc --restart unless-stopped --network host \
-v $HOME/frp/frpc.ini:/etc/frp/frpc.ini:ro \
<镜像加速前缀>/snowdreamtech/frpc:<版本>
docker logs frpc
# 期望:login to server success / start proxy success

6. 外网怎么连

  1. 安全组已放行你的公网 IP → TCP <对外映射端口>
  2. 安装任意 VNC 客户端,例如 TigerVNC / RealVNC / Remmina,或 macOS 自带 vnc://
  3. 连接:<你的域名>:<对外映射端口>
  4. 密码:本机 x11vnc -storepasswd 设置的密码

看到的是被控机当前 :0 桌面


7. 自检清单

步骤 期望
本机 VNC 127.0.0.1:<本地VNC端口> 在听
frpc 日志含 login to server success
云主机映射 frps 在听 <对外映射端口>
隧道握手 在云主机对本机映射端口读到 RFB 003.008
nginx nginx -t 通过;站点首页仍可打开
外网不通 先查安全组,再查 frpc 是否掉线

8. 安全建议

  • 不要把 frp token、VNC 密码、真实域名/IP/端口写进公开博客或 git。
  • 安全组对外映射端口尽量锁源 IP。
  • x11vnc 保持 -localhost,不要直接对公网监听。
  • 不用时停掉 frpc 或关掉安全组对应端口。

9. 小结

受限出口网的核心限制往往不是“没有公网 IP”,而是 DPI 只放行像 HTTPS 的流量,加上对称 NAT。
在自有云主机上用 nginx(443) + frp WSS + 本机 x11vnc,可以把当前桌面稳定透到外网,且不依赖第三方远控厂商的中继云。

若以后要网页里看桌面,可在云主机再加 noVNC/websockify 指到对外映射端口(同样建议鉴权与 IP 限制);当前方案以外网原生 VNC 客户端为主,链路最短。