• Codex 可以方便地访问云服务器上的项目代码;
  • Petoi 机器狗和 STM32H747I-DISCO 却通过 USB 连接在本地 Ubuntu 上;
  • 本地 Ubuntu 没有公网 IP,云服务器无法直接 SSH 到它。

如果不解决这个问题,调试流程就会变成下面这样:

Codex 给出命令

我复制命令

粘贴到本地 Ubuntu 执行

复制命令输出

粘贴回 Codex 分析

Codex 给出下一步

这个流程非常痛苦。

对于普通软件项目,这样手动来回复制几次也许还能忍。但对于硬件调试来说,情况完全不同。硬件调试往往需要频繁执行命令、观察输出、调整脚本、重新烧录、读取串口或 OpenOCD 状态。如果每一步都要手动复制粘贴,效率会非常低,也很容易出错。

后来我通过 FRP 打通了云服务器与本地 Ubuntu 之间的反向 SSH 通道,使 Codex 可以通过云服务器直接登录到本地 Ubuntu,从而直接调试连接在本地 Ubuntu 上的硬件设备。

这篇文章记录一下这个过程。

问题背景

我的实际开发环境大致如下:

  • Windows PC 可以通过 Codex / SSH 访问云服务器;
  • 云服务器有公网 IP;
  • 本地 Ubuntu 在局域网内,没有公网 IP;
  • Petoi 机器狗和 STM32H747I-DISCO 通过 USB 连接在本地 Ubuntu 上。

其中,Windows PC 可以访问云服务器,云服务器也有公网 IP。

但是本地 Ubuntu 位于局域网中,没有公网 IP。云服务器不能直接 SSH 到本地 Ubuntu。

这就导致 Codex 虽然可以在云服务器上帮我分析代码、构建固件、生成命令,但无法直接操作连接在本地 Ubuntu 上的硬件。

而硬件调试又恰恰需要直接访问本地 Ubuntu,例如:

lsusb
ls /dev/ttyACM*
openocd ...
arduino-cli ...
python scripts/xxx.py

如果 Codex 无法直接执行这些命令,整个调试体验就会退化成“人工搬运命令和输出”。

为什么选择 FRP

为了解决这个问题,我需要一种方式,让云服务器能够访问本地 Ubuntu。

常见方案有几种:

  • 给本地 Ubuntu 配置公网 IP;
  • 在路由器上做端口映射;
  • 使用 Tailscale / ZeroTier 这类组网工具;
  • 使用 FRP 做反向隧道。

我的场景下,FRP 是一个很合适的选择。

原因是:

  • 云服务器有公网 IP,可以作为 FRP server;
  • 本地 Ubuntu 虽然没有公网 IP,但可以主动连接云服务器;
  • 一旦本地 Ubuntu 主动连上云服务器,就可以把本地 SSH 端口反向暴露到云服务器;
  • Codex 已经可以访问云服务器,因此也就间接获得了访问本地 Ubuntu 的能力。

最终效果是:Codex 仍然只操作云服务器,但云服务器上的 127.0.0.1:REMOTE_SSH_PORT 会通过 FRP 转发到本地 Ubuntu 的 SSH 服务。

从 Codex 的角度看,它只需要在云服务器上执行:

ssh -p REMOTE_SSH_PORT ubuntu@127.0.0.1

就能登录到本地 Ubuntu。

整体架构

整个链路可以画成这样:

SSH

访问 127.0.0.1:REMOTE_SSH_PORT

主动连接云服务器

转发到 127.0.0.1:LOCAL_SSH_PORT

USB / 串口 / OpenOCD

Windows / Codex

云服务器

frps

frpc

本地 Ubuntu SSH

Petoi 机器狗 / STM32H747I-DISCO

其中:

  • frps 运行在云服务器;
  • frpc 运行在本地 Ubuntu;
  • 本地 Ubuntu 主动连接云服务器;
  • 云服务器监听一个本地端口,例如 REMOTE_SSH_PORT
  • 访问云服务器的 127.0.0.1:REMOTE_SSH_PORT,实际会被转发到本地 Ubuntu 的 SSH 服务。

这里有一点很重要:云服务器上的反向 SSH 入口只绑定到 127.0.0.1,而不是 0.0.0.0

这样外部网络无法直接访问这个 SSH 隧道,只有云服务器本机上的进程可以访问它,安全性更好。

云服务器上的 frps 配置

云服务器上运行 frps,配置大致如下:

bindAddr = "0.0.0.0"
bindPort = FRP_SERVER_PORT
proxyBindAddr = "127.0.0.1"

auth.method = "token"
auth.token = "REPLACE_WITH_YOUR_TOKEN"

说明:

  • bindPort = FRP_SERVER_PORT:本地 Ubuntu 的 frpc 会连接云服务器的这个端口;
  • proxyBindAddr = "127.0.0.1":反向暴露出来的 SSH 端口只监听云服务器本机;
  • auth.token:认证 token,不能提交到 Git,也不要写进公开博客。

启动 frps

./frps -c frps.toml

启动成功后,云服务器会等待本地 Ubuntu 上的 frpc 连接。

本地 Ubuntu 上的 frpc 配置

本地 Ubuntu 上运行 frpc,配置大致如下:

serverAddr = "YOUR_CLOUD_PUBLIC_IP"
serverPort = FRP_SERVER_PORT

auth.method = "token"
auth.token = "REPLACE_WITH_YOUR_TOKEN"

[[proxies]]
name = "local-ubuntu-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = LOCAL_SSH_PORT
remotePort = REMOTE_SSH_PORT

说明:

  • serverAddr 是云服务器公网 IP;
  • serverPort 对应云服务器上的 frps 监听端口;
  • localPort = LOCAL_SSH_PORT 是本地 Ubuntu 的 SSH 端口;
  • remotePort = REMOTE_SSH_PORT 是云服务器上暴露出来的反向 SSH 端口;
  • 由于云服务器上配置了 proxyBindAddr = "127.0.0.1",所以 REMOTE_SSH_PORT 只会绑定在云服务器本机。

启动 frpc

./frpc -c frpc.toml

连接成功后,云服务器上的 frps 日志里会出现类似信息:

client login info ...
new proxy [local-ubuntu-ssh] type [tcp] success
tcp proxy listen port [REMOTE_SSH_PORT]

验证 SSH 隧道

在云服务器上执行:

ssh -p REMOTE_SSH_PORT ubuntu@127.0.0.1

如果能登录到本地 Ubuntu,就说明链路已经打通。

也可以执行一个简单命令验证:

ssh -p REMOTE_SSH_PORT ubuntu@127.0.0.1 "ho
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐