一个内网穿透项目的作者,却在采访中反复提醒,严肃生产场景目前仍应优先使用 frp。
听起来反常,却也点出了 NetsGo 的定位。张硕不想在协议和隧道数量上取代成熟工具,而是想解决设备增加之后的管理问题。
他家里的 NAS、软路由、树莓派、PVE 和旧服务器分布在不同内网。当设备从几台增加到十几、二十台,配置文件便不再够用:哪些节点在线、哪些隧道正在运行、配置变更是否生效,都需要统一查看和管理。
于是,他利用业余时间做了 NetsGo——一个将 Web 控制台、API、客户端接入和网络隧道整合进单文件二进制的内网穿透与节点管理平台,主要面向个人开发者、Homelab 用户和小团队。
截至发稿,NetsGo 项目最新稳定版本为 v0.1.11。作者张硕,从 2016 年开始从事开发工作,长期关注网络、服务器和自托管服务。
在这场超过一个半小时的访谈中,我们聊了 NetsGo 为什么出现、它主动放弃过哪些功能,以及他如何让 AI 进入真实环境,完成测试与验证。
由于本次访谈内容较长,大家可以先通过下面的“懒人版”快速了解。
懒人版
NetsGo 是什么? 一个面向个人开发者、Homelab 用户和小团队的自托管内网穿透与节点管理平台,强调单文件部署、统一入口和内置 Web 控制台。
有了 frp,为什么还要做? 张硕认可 frp 在严肃生产场景中的稳定性。但当节点和服务越来越多,配置、状态和变更会变得分散。NetsGo 想解决的是多节点、多服务的集中管理。
它最重要的产品判断是什么? 用户体验不只是增加功能,也包括决定哪些事情不应该替用户做。NetsGo 曾移除目标服务健康检查,撤下升级体验不完整的桌面客户端,也放弃了风险较高的网页远程终端和文件管理。
P2P 改变了什么? P2P 只作用于客户端到客户端隧道的数据路径。业务数据可以在两个客户端之间直接传输,但控制通道仍然连接服务端。
AI 在项目里做了什么? AI 不只参与代码实现,还进入 PVE、LXC 容器、虚拟机、虚拟网络、真实数据库和浏览器环境,执行安装、升级和版本兼容验证。
一句话总结:
NetsGo 是一个以用户体验为本的内网穿透平台。
问题不是隧道不通,而是设备越来越难管
从"洋垃圾"到流量机
小七:什么时候让你觉得,自己需要做一个这样的工具?
张硕:我这个人比较能折腾。从 2016 年工作到现在,差不多十年了,这期间一直在折腾各种设备。
最开始是树莓派,后来买了一些开发板,自己组 NAS、软路由,还有几台 X79、X99 的"洋垃圾",也有 PVE 和服务器。这些机器分布在不同的内网,老家还有摄像头和存储设备。
以前我主要使用云服务,但服务越来越多之后,云上的内存、存储和 CPU 很容易不够,继续扩容也会增加成本。很多服务其实只是我自己使用,比如密码管理、家庭设备监测,并不需要很高的可用性和特别低的延迟,完全可以运行在本地,再通过隧道映射出去。
工作中也有类似需求。我们有一些 B 端项目,为了节省成本,会把线上生产服务的日志通过一台大带宽流量机中转,再完整地传回线下机房。线下机房不适合直接暴露到公网,所以也需要一套隧道来统一管理。
这些需求一直都在。我以前用过 Tailscale 一类的组网工具,也用过 frp,还用 Go 写过一些简单的 TCP、UDP 映射程序。NetsGo 就是从这些实际需求里慢慢提炼出来的。
frp 很好,但管理是另一件事
小七:你平时也用 frp,而且你对它的评价也很好,那么你做的 NetsGo 会是在 frp 基础上更进一步吗?具体是想为哪些用户解决什么问题?
张硕:我最信任的肯定还是 frp。它经过这么多年的发展,在生产环境中做过大量验证,非常稳定,隧道类型全面,还支持插件扩展。如果是严肃的生产场景,我目前仍然建议优先使用 frp。
但对于个人开发者、小团队或者小型网络环境,设备数量到了十几台、二十台时,管理就会比较麻烦。每个节点都要安装客户端、检查配置;有一点改动,就要到处看配置文件,还要检查服务端端口和白名单。
NetsGo 想解决的核心只有一个:统一管理多台设备上的服务暴露,而不是重新做一次设备互联。
有人可能会说,给 frp 外接一个第三方管理面板不也可以吗?当然也可以。但这样需要分别考虑服务端、客户端和管理平台的安装,系统分开之后,需要管理的组件和入口也会增加。NetsGo 想弥补的就是这部分。
之前也和社区朋友们讨论过其他几种工具:ngrok 更偏向商业托管服务,ngrok 是一种托管式反向代理服务,用户在本地运行客户端,就能获得公网入口,把本地应用或网络服务开放给外部访问,更适合不想自己准备公网服务器的用户;Tailscale、可自建的 Headscale 和 EasyTier 偏向组网,解决的是设备之间如何互联。NetsGo 关注的则是服务如何暴露、节点如何被统一管理。组网和服务隧道并不冲突,很多用户会同时使用。
我不是想在穿透能力上超过这些老牌项目,而是想把多地、多节点、多服务的管理体验做好。
NetsGo 这个名字也延续了这种直接的思路:读起来像 Let's Go,Net 代表网络,Go 代表实现语言。至于默认端口 9527——总要选一个端口嘛,那就选一个大家熟悉的“编号 9527”。
但把配置文件换成控制台,只是第一步。当一个项目开始替用户管理客户端、隧道和运行状态,用户还会继续追问客户端显示在线,是否代表数据通道真的可用?隧道已经创建,是否代表目标服务正常?
把操作收进控制台,也要决定哪些事情不做
一个二进制,一个入口
小七:说到"降低用户的认知负担",NetsGo 在这方面具体做了什么?
张硕:用户懂一些网络知识当然更好。比如 HTTP 隧道为什么能把不同域名转发到不同服务,是根据请求头里的 Host 信息判断的。但很多用户并不理解这些概念,他们也不一定需要理解。他只需要知道自己想把哪个服务映射到哪里,剩下的事情交给 NetsGo。
NetsGo 把服务端和客户端放进同一个二进制文件。用户不需要分别下载服务端、客户端和 Web 管理平台,运行时通过 server 子命令启动服务端,通过 client 子命令连接远程节点。
安装后,程序会管理自己的服务、状态和日志。客户端连接服务端后,用户只需要在 Web 控制台创建隧道、设置限流和查看状态,服务端再通过控制通道把指令下发给对应客户端。
Web 控制台不是一个附加面板,它既是核心产品,也是最重要的管理入口。单文件管理的是程序自身,比如运行是否正常、能不能更新或者卸载;客户端、隧道和流量的管理,基本都在 Web 端。
截至 v0.1.11,官方一键安装脚本主要面向 Linux 和 systemd 环境,Windows 和 macOS 建议通过 Docker 运行客户端。【发稿前核对:与 v0.1.11 发布说明原文一致】
单二进制降低的是用户在安装和管理时看到的复杂度。版本兼容、升级回滚和环境测试等工作并没有消失,而是转移到了项目内部。
在线,不等于健康
小七:NetsGo 所说的"客户端在线",代表什么?为什么后来又删掉了目标服务健康检查?
张硕:客户端在线由控制通道和数据通道共同决定。两个通道都要建立成功,并通过心跳维持状态。新的控制指令需要能够快速到达客户端,数据通道也要正常。缺少其中一个,客户端就会显示离线。
但客户端在线和隧道正常是不同的。即使没有配置任何隧道,只要两个通道正常,客户端也可以在线;隧道还可能遇到端口冲突,客户端到客户端的隧道也要看另一端是否在线。
一开始我确实做过目标服务存活探测,后来删除了。
因为 NetsGo 不应该替用户决定,默认去访问一个服务。有些应用对流量敏感,尤其是 UDP 服务,额外探测可能影响真实业务。即使上一秒还正常,也不能证明下一秒服务还在。所以我只判断 NetsGo 自己能够负责的部分:客户端是否在线、隧道端口是否存在异常。
我们只保证自己的通道正常,不能替用户判断目标业务服务是否健康。
NetsGo 负责什么,不负责什么

按照张硕在采访中的介绍,NetsGo 不解析、不记录、不留存经过服务端中继的业务数据,只进行字节级流量计量。源码发布在GitHub,一切都是公开可审计,包括一键升级和更新的shell脚本。
过度的安全,也会伤害体验
小七:你曾经因为安全设计得太严格,让用户连本机控制台都打不开?
张硕:这个问题让我印象很深。
NetsGo 的 9527 端口既承担控制台入口,也可能接收用户配置的 HTTP 隧道请求。如果一个请求没有匹配到任何 HTTP 隧道,我不希望它直接返回 Web 控制台。否则,只要有人能访问 9527,就可能看到管理页面。
所以早期设计中,我要求用户明确配置哪些 IP 可以访问 Web 控制台。最开始的规则非常严格,连 127.0.0.1 这样的本地回环地址默认都不允许访问,除非用户主动声明它可信。
后来有一名用户,一开始没有使用 Nginx,白名单填写的是公网 IP 范围,直接通过公网 IP 访问完全正常。等他加上 Nginx,把请求反向代理到本机 127.0.0.1:9527 时,Web 控制台反而打不开了。
我知道问题出在哪里,但用户会很困惑:为什么公网可以访问,本机反而不能?
后来我也觉得,这个安全边界设计得有些过度,反而影响了用户体验。后续版本里,我把本地回环地址改成了默认放行。
做出来的功能,也可以撤回去
小七:NetsGo 还有哪些功能做出来后又撤掉了?你怎样判断一项能力应该进入核心,还是留给插件?
张硕:最开始确实做过 macOS 和 Windows 客户端,也发布过一个比较基础的 macOS 版本。
它作为客户端连接服务端并不难,最让我不能接受的是升级问题。客户端出现新版本后,我没有一套完整机制通知用户,也没有把更新和卸载流程做好。用户只能在 Web 端看到客户端版本有些旧了,但不知道接下来该怎么办。这非常影响我对用户使用产品的期望,所以我宁可不要,后来就把这一块的构建移除了。
以后 macOS 和 Windows 客户端一定会重新做。但正式上线时,安装、更新、升级和卸载都应该是完整的,我才会上线。
网页远程终端和文件管理也是一样,安全风险太大。Web 管理端一旦被攻破,后果很严重。我更倾向于让用户通过 NetsGo 映射 SSH 等服务端口,再用自己熟悉的客户端连接,不希望 NetsGo 自己变成远程终端和文件管理器。
核心还是要保持简单。NetsGo 默认应该只提供基础、通用的隧道和管理能力,用户有需要时再通过插件安装。
我有一个做物联网的朋友,经常要到客户现场调试设备:现场电脑通过串口连接硬件,而真正的调试环境在他自己的电脑里,很多时候并不是必须本人过去。如果以后能把一台机器上的串口映射到另一台机器,他在自己电脑上就能完成调试。
这个需求很有价值,但不是每个用户都需要,所以如果做,我希望它以插件包的形式出现——需要的用户在 Web 控制台安装,不需要的人甚至不用看到它。
P2P 上线前,他先改掉写死的数据通道
P2P 是客户端到客户端隧道底层传输方式的变化。它只作用于
client_to_client,也就是客户端到客户端隧道的数据路径,不适用于从公网服务端暴露内网服务的server_expose隧道。【发稿前核对:请张硕确认此口径】
"单端口"的准确边界
小七:Web、API、控制通道和数据通道为什么能共用 9527,TCP、UDP 隧道又为什么需要额外端口?
张硕:目前 Web 控制台、Web API、控制通道和普通数据通道都可以使用 9527 这个入口,它们通过不同的路径或者特殊 URL 区分。
比较特殊的是 HTTP 隧道,因为用户自己的 HTTP 服务和 NetsGo Web 控制台都可能从这个入口进入。服务端会先匹配用户配置的 HTTP 隧道;如果请求没有匹配到隧道,并且来源 IP 在允许访问控制台的白名单中,才会返回 Web 页面。发生冲突时,优先匹配用户的隧道。
TCP、UDP、HTTP 这些隧道类型,是建立在控制通道和数据通道之上的抽象,不需要每一种类型都重新建立一套主连接。以 TCP 为例,用户访问已映射的公网端口时,服务端根据这个端口找到对应隧道,再通过数据通道把字节搬运给客户端,客户端还原成对应的 TCP 或 UDP 连接,交给目标服务。
数据通道就像一条高速公路。它不关心车厢里装的是 TCP、UDP 还是 HTTP,只负责把"车厢"运到目的地,再由客户端恢复成原来的连接类型。
需要说明的是,"单端口"统一的是控制台、API 和客户端通信入口。从公网暴露 TCP 或 UDP 服务时,仍然可能需要额外的业务端口:HTTP 请求可以利用 Host 等应用层信息区分服务,而通用的 TCP、UDP 字节流没有这样的路由字段,服务端只能根据公网监听端口判断流量属于哪条隧道。

先清技术债,再上 P2P
小七:P2P 上线前,为什么要先做一次大规模重构?
张硕:早期只有 TCP 和 UDP,实现时考虑得没有那么全面。客户端到客户端的数据通道、代码中的枚举,甚至数据库设计里都有一些东西被写死了。
准备做 P2P 时,我发现底层数据通道必须能够动态支持不同的传输方式,不能让上层隧道和底层传输完全绑在一起。
所以在 P2P 之前,我先做了一次比较大的重构。范围很大,越改心里越慌。因为如果这部分不先清理干净,继续做 P2P,只会让技术债越来越重。那次重构做完后,我才赶紧把 P2P 接着实现了。
三种传输策略
小七:P2P 改变了 NetsGo 的哪一层?三种传输策略分别是什么?
张硕:它改变的是底层数据通道,而非重新增加一种上层隧道。加入 P2P 后,同一种上层 TCP 或 UDP 隧道可以选择不同的传输策略,但用户创建和管理隧道的方式不用跟着变化。
第一种是仅中继,行为和原来一样,业务数据全部通过服务端中转,已有隧道默认仍然使用这种方式。
第二种是优先点对点。NetsGo 会优先尝试在两个客户端之间建立直连数据通道,直连不可用时再回到服务端中继。这也是我更推荐的选择。
第三种是仅点对点,业务数据不通过服务端中转。
数据可以直连,管理不能消失
小七:业务数据已经直连了,为什么控制通道还要留在服务端?
张硕:即使选择仅点对点,控制通道仍然由服务端管理。
业务数据可以不经过服务端,但两个客户端之间仍然需要协调控制指令。这里面包括 P2P 连接的加密和协商、流量计量上报,以及限速等功能——这些没办法再按照原来的方式放在服务端执行。P2P 建连前,两端仍然需要完成身份确认、连接协商和权限校验;业务流量不经过服务端之后,流量统计需要由客户端参与上报,限速也需要客户端参与执行,服务端则通过控制通道协调双方。
数据可以绕开服务端,但平台的管理关系不会随之消失。
对用户来说,他不需要关心数据具体由哪条底层通道搬运。就像寄快递一样,只需要知道包裹能不能到达,不需要知道中间经过了哪些节点。
新的数据路径跑通之后,问题还没有结束。NetsGo 不仅要验证服务端中继和 P2P 分别能不能工作,还要面对新旧服务端、新旧客户端、不同传输策略以及不同反向代理环境之间的组合。单个功能测试通过,不代表用户升级之后也能继续使用。
版本组合越来越多,AI 开始进入真实环境测试
最难的是测试,不是算法
小七:开发 NetsGo 最耗费精力的是什么?如果升级失败了,怎样避免影响用户原本能用的服务?
张硕:最难的其实就是测试的工作量,场景特别多。
用户的 Linux 发行版不同,部署方式也不同,有人使用 Docker,有人使用系统服务。NetsGo 又同时存在服务端和客户端,而且这两端都掌握在用户自己手里。新服务端可能面对旧客户端,新客户端也可能连接旧服务端;有人只升级服务端,有人只升级部分客户端。只要出现破坏兼容性的改动,需要考虑的组合就会非常多。
有些问题在单元测试里测不出来,必须放到真实虚拟机和完整安装流程里才能复现。我自己服务器比较多,就会尽可能模拟这些环境。整个 CI,也就是持续集成流水线里,占比最大的就是这些测试。
升级前,NetsGo 会先停止当前服务,把数据和正在使用的二进制文件打包到临时目录,再执行升级。升级成功后,清理旧文件,再检查服务状态;如果升级失败,就恢复原有数据和二进制文件,让之前的服务继续运行,同时提示用户提交 Issue。
能成功很重要,能安全地失败也一样重要。开发者不可能覆盖所有用户环境,但至少不能把用户原本能用的服务直接"升挂掉"。
让 AI 看到真实运行结果
小七:在 NetsGo 的开发中,AI 最重要的贡献是什么?
张硕:代码当然有很多是 AI 写的。
项目最初的方向、架构和 Web 端应该是什么样子,主要是我规划的,有些设计也是我和 AI 一起聊出来的。基础代码、隧道监听和转发,以及 Web 端的具体实现,有相当一部分由 AI 完成。
但这个项目里,最大头其实还是测试。这些测试场景如果全部让我自己做,我简直搞不定。

我会给 AI PVE 的控制权限,让它自己创建 LXC 容器或者虚拟机,完成初始化、安装和版本升级。有时还会创建虚拟网络,让不同客户端处在不同环境中,再执行连接和升级测试。
服务端需要真实数据库,就给它真实数据库;需要验证网络,就提供完整网络环境;测试 Web 端时,再给它浏览器,让它打开页面、查看状态,而不是只看代码猜结果。单元测试、客户端的 E2E 测试(覆盖完整使用链路的端到端测试),很多都是 AI 帮我完成的。
当然,我要先给它设定边界,比如必须保证哪些版本到哪些版本的升级路线稳定,什么结果算通过。这一套环境也是一点点堆起来的。关键是不能只让 AI 闷头写代码,还要让它看到实际效果。
计划落进文档,让模型交叉审查
小七:具体到协作方式上,你有什么和别人不一样的习惯?
张硕:我不太使用工具内置的计划模式。因为内置计划通常只存在于当前工具的上下文里,其他 AI 很难参与。
我更习惯让 AI 把目标、工作规划、实现方向和关键决策写进一份文档。只有落到文档里,我才能审查,其他模型也能交叉检查,看看有没有漏掉边界情况。
目标不够清晰时,我不会直接让它开始写。我会先跟它聊,也允许它反过来向我提问。很多规划甚至是在上下班路上通过手机完成的。一切都在沟通,沟通花的时间更多,真正实施其实很快。
不同功能需要并行推进时,我也会使用 Git worktree,让不同任务在各自的目录里执行,减少互相影响。
有时候周末我没有在工作,但 AI 还在工作。因为规划文档和目标已经交给它,它可以继续实现和测试,等我回来后再检查结果。前期规划非常重要,中间的实现反而没有那么重要。
没有遥测、没有固定排期,他怎样推进一个开源项目
不收集数据,靠社区反馈
小七:你不做用户行为遥测,也没有非常固定的路线图,那你靠什么了解用户,并决定下一步做什么?
张硕:我不需要知道用户安装了多少台设备、使用了哪些隧道,也不想知道这些信息。
用户通过一键脚本安装 NetsGo,脚本需要从服务器下载。从技术上说,如果我想记录,可以知道哪些 IP 调用过脚本,但我不会去记录。安装脚本本身只是通过 curl 获取文件,也不会额外上传用户的设备信息。
控制台需要展示公网 IP。最开始我用过第三方接口,后来考虑到隐私和针对性攻击的风险,就在自己的文档站实现了一个很简单的接口:它只把请求到达时的 IP 地址返回给用户,不接收其他信息。
真正了解用户,主要还是靠社区反馈。NetsGo 没有做太多宣传,很多用户通过帖子、私信和 GitHub Issue 联系我。前面提到的本地回环地址白名单问题,就是用户反馈后修正的。
SOCKS5 也是一个用户提出的需求。我觉得这本来就是一个很重要的隧道类型,反正后面也要加,就在一周内做完了。有意思的是,那名用户最初想用 SOCKS5 解决一个 P2P 下载工具的远程访问问题,功能上线后并没有解决他的原始需求,因为他对 SOCKS5 能做什么有一些误解。但这个功能没有白做,我自己后来也在用。
用户提出的方案不一定能够解决问题,但真实反馈能让我看到真实场景。
NetsGo 基本都是利用业余时间开发。工作日中午大概有一个小时,是比较固定的开发时间;上下班路上通常不会写代码,但会思考功能和实现方式,或者通过手机和 AI 沟通;晚上等孩子睡觉后,大概九点、十点到十二点,是一天里最重要的更新时间。周末反而做得少一些,周末要陪孩子。
项目没有非常固定的路线图。一方面,开源项目进度受个人时间影响;另一方面,我也希望先看看用户反馈,再根据真实使用调整,不想一开始就把隧道类型和功能越做越多。
先做个"垃圾"出来
小七:如果给第一次做开源项目的开发者一条建议,你会说什么?
张硕:我这个人做事偏向于尽量想得全面、做好规划,这是我的性格特点。
但如果做开源项目,或者在 AI 时代想快速试错、做更多事情,我想劝大家不要在前期想太多。
NetsGo 真正开始做只有几个月,但这个想法已经持续很久了。以前总觉得设计还不够,担心以后会不会有隐患,所以一直没有真正动手。
我想劝和我一样的朋友,先做。就像网上经常说的,先做个"垃圾"出来。这一点对我自己也一样。我可能很难突破,但现在不也已经迈出第一步了吗?先从 0 到 1,先做,做了之后,你就知道该怎么调整。
尾声
采访开始时,张硕反复提醒我们,严肃生产场景目前仍然应该优先使用经过长期验证的工具。
这没有削弱 NetsGo 的价值,反而让它要解决的问题变得更清楚:把安装、配置、节点、状态、升级和数据路径这些散落的复杂性收拢起来,让用户不必为了管理十几台设备,在不同机器和配置文件之间来回切换。
在这个过程中,张硕也不断把已经做出来的东西往回收。删掉可能干扰业务的健康检查,撤下无法完整升级的桌面客户端,放弃风险过高的网页远程管理功能;加入 P2P 时,也没有把它做成独立于原有管理体系的新功能,而是先改造底层数据通道,再把新的传输策略放回同一套控制面中。
NetsGo 才刚刚开始。张硕自己也承认,它的隧道类型还不完整,产品体验也没有达到最终目标。对严肃生产环境的谨慎提醒,正是这种阶段判断的一部分。
但那个被他搁置了很久的想法,至少已经从 0 走到了 1。剩下的问题,可以交给真实用户、真实环境等。
文末彩蛋|张硕的一条 AI Coding 建议
张硕:尽量使用当下能力最好的模型。选择时当然要考虑自己的订阅条件和实际场景,但模型不要凑合。能力不合适的模型不只会让实现变慢,还会让你花更多时间帮它排错和返工。最后浪费的,还是开发者自己的时间。