No description
  • Go 92.7%
  • Shell 3.1%
  • JavaScript 2.5%
  • CSS 0.6%
  • HTML 0.5%
  • Other 0.4%
Find a file
matt 1b01319be5
All checks were successful
CI / lint (push) Successful in 42s
CI / test (push) Successful in 1m48s
CI / cross (amd64, linux) (push) Successful in 1m20s
CI / cross (amd64, windows) (push) Successful in 1m17s
CI / cross (arm64, darwin) (push) Successful in 1m15s
CI / menubar-syntax (push) Successful in 28s
CI / cross (arm64, linux) (push) Successful in 1m12s
CI / openwrt (push) Successful in 1m5s
Release / macpkg (push) Successful in 1m3s
Release / release (push) Successful in 4m24s
feat: IPC status 卡住时自动把 goroutine 栈写进日志
守护进程卡死时外面只看得到 IPC 超时,要知道谁持着哪把锁只能
sudo kill -QUIT,而那会顺带杀掉进程、现场就没了。现在 status 超过
10 秒未返回,就把所有 goroutine 的栈(含等待时长)写进守护进程自己的
日志,30 分钟内只写一次,免得菜单栏每 3 秒一次的轮询把日志刷满。

只盯 status:它几乎碰遍 node 的每把锁,哪里卡住都会先在这里显形;
netcheck、login 本来就慢,放进来只会误报。
2026-10-03 20:29:50 -07:00
.forgejo docs: 开发约定移到 docs/conventions.md,CLAUDE.md 不再入库 2026-09-27 07:18:24 -07:00
assets/icon feat: 新增 macOS / Windows 应用图标(icns、ico 与源 SVG) 2026-08-24 17:35:11 -07:00
cmd feat: 菜单栏区分「无响应」与「未运行」 2026-10-03 19:51:07 -07:00
deploy feat: 有序打洞,持续打不通的一对先静默再指定先发方 2026-09-27 23:56:03 -07:00
docs feat: IPC status 卡住时自动把 goroutine 栈写进日志 2026-10-03 20:29:50 -07:00
internal fix: netcheck 不再把端口保留型 NAT 报成「端口随机分配」 2026-09-27 23:56:15 -07:00
pkg feat: IPC status 卡住时自动把 goroutine 栈写进日志 2026-10-03 20:29:50 -07:00
test/nat feat: 双侧变映射 NAT 生日碰撞打洞——块侧同时扫描 2026-08-24 06:57:28 -07:00
.gitignore docs: 开发约定移到 docs/conventions.md,CLAUDE.md 不再入库 2026-09-27 07:18:24 -07:00
findings.md docs: 竞品 P2P 栈分析归档与吸收(M2.7/2.8、M4.7/4.8、M7) 2026-08-14 18:18:06 -07:00
go.mod feat: M8.2 cmd/meshgate-menubar 菜单栏进程 2026-08-16 04:47:06 -07:00
go.sum feat: M8.2 cmd/meshgate-menubar 菜单栏进程 2026-08-16 04:47:06 -07:00
Makefile build: 钉住 macOS 部署目标 12.0,否则新系统编的 App 在旧系统打不开 2026-09-08 05:48:02 -07:00
progress.md docs: WS 中继的部署要求与发布说明 2026-08-22 21:08:18 -07:00
README.md docs: 开发约定移到 docs/conventions.md,CLAUDE.md 不再入库 2026-09-27 07:18:24 -07:00
task_plan.md feat: M7.6 退出登录(清除凭据、断网、清空对端) 2026-08-16 18:40:22 -07:00

meshgate

meshgate 是给自己用的 WireGuard mesh 组网工具:一台小 server 负责身份、候选交换和打洞协调,三平台客户端持续主动建立 UDP 直连。

不是什么

  • 默认不做出口 VPN,不会自动把你的上网流量送去某个中心节点;M9 起支持手动 opt-in 的出口节点(消费端显式 exit-node set 才生效,没人选就没有出口), 见 docs/exit-node.md。
  • 不做流量混淆,也不伪装成 HTTPS。
  • relay 只是「打不通时的应急路径」,服务端 relay.enabled 默认关闭;开启后也 只在直连打不通时按 fallback 策略介入,不会无条件抢流量。

设计说明见 docs/specs/2026-07-08-meshgate-design.md。

安装

安装分两件事,别弄混:控制服务器(meshgate-server,一台有公网 IP 的机器,只装一次) 和节点(meshgate,每台要入网的机器)。

服务端(Linux)

curl -fsSL https://git.wrlog.cn/lion1991/meshgate/raw/branch/main/deploy/linux/install-server.sh -o install-server.sh
sudo sh install-server.sh install --domain mesh.example.com

下载校验二进制、建 meshgate 系统账号、写 /etc/meshgate/server.json(随机 admin_token)、装 systemd 服务、尽力放行 UDP,最后引导你建管理员账号。

两处要自己动手:

  • 反代要自己配。 控制面只听 127.0.0.1:8444、tls_mode=none,前面必须有 Caddy 或 nginx 终止 TLS,客户端才连得上 wss://。现成配置见 deploy/caddy/Caddyfile 和 deploy/nginx/meshgate.conf。脚本不去动机器上已有的 Caddy/nginx/宝塔,那一层每台机器都不一样,猜错只会把别人配好的站点弄坏。
  • UDP 8444 不经反代,必须直接对公网开放。云服务器还要在厂商安全组里放行 —— 漏了的症状是「能登录、能看到对端、就是打不通洞」,很难联想到防火墙。
命令 作用
sudo sh install-server.sh install --domain mesh.example.com 全新安装
sudo sh install-server.sh update 只换二进制,配置和数据库不动
sudo sh install-server.sh status 版本、监听地址、服务状态
sudo sh install-server.sh uninstall [--purge] 卸载;--purge 连配置和数据库一起删

装完签发入网密钥:meshgate-server authkey new -config /etc/meshgate/server.json。 完整说明(手动实例接管、升级陷阱、systemd 的 User= 坑)见 deploy/linux/README-server.md;容器部署见 docs/deploy.md。

节点:Linux(推荐:一行脚本)

curl -fsSL https://git.wrlog.cn/lion1991/meshgate/raw/branch/main/deploy/linux/install.sh -o install.sh
sudo sh install.sh install --server wss://mesh.example.com

脚本会下载对应架构的二进制(比对 SHA256SUMS)、装好 systemd 服务、启动, 然后打印一个登录地址 —— 认证是你自己登录,拿那个地址在浏览器里用自己的账号 确认即可,脚本不接触任何凭据。ssh 上装机是常态,所以地址一定会打到终端上。

命令 作用
sudo sh install.sh install --server wss://… 下载 + 装服务 + 启动 + 引导登录
sudo sh install.sh update 只换二进制并重启,保留配置和入网凭据
sudo sh install.sh routes add 192.168.24.0/24 增加对外宣告的子网(不重启、不断开已有直连)
sudo sh install.sh routes list 看当前宣告
sudo sh install.sh status 版本、unit、服务状态、节点状态
sudo sh install.sh uninstall [--purge] 卸载;--purge 连状态目录一起删

常用选项:--advertise-routes CIDR、--tun kernel|netstack、--version vX.Y.Z、 --repo URL、--bin-dir DIR、--no-start、--force、-y。私有仓库设 MESHGATE_TOKEN。完整说明、子网网关注意事项和升级陷阱见 deploy/linux/README.md。

装完一个坑要知道:服务跑在 --state-dir /var/lib/meshgate,而 CLI 默认状态目录是 $XDG_CONFIG_HOME/meshgate,两者不是一个地方。裸敲 meshgate status 时,CLI 在默认 目录找不到守护进程会自动回退去连系统服务,并在 stderr 打一行 「已连接系统服务:/var/lib/meshgate/meshgate.sock」(stdout 的 --json 输出不受影响)。 Linux 上那个 socket 是 root 私有的(0700/0600,权限没有为回退放宽),所以要 sudo meshgate status;非 root 会明确告诉你「检测到系统服务,但当前用户无权连接, 请用 sudo 重试」。只有状态目录不在默认位置的非标准安装才需要手动带 --state-dir;显式给了 --state-dir / --socket / --config 时不回退,脚本的 status / routes 一直是带着的。

脚本是 POSIX sh(不需要 bash),依赖 systemd、curl 或 wget、iproute2。

其它平台 / 手动安装

从 releases 下载对应平台的 meshgate-<os>-<arch>,比对 SHA256SUMS,放进 PATH,然后:

sudo meshgate service install --server wss://mesh.example.com --tun kernel
sudo systemctl start meshgate     # macOS 上由 launchd 自动加载
meshgate login --no-browser       # 打印登录地址

Windows 创建 wintun 需要管理员,并自备 wintun.dll(见 deploy/windows/README-windows.txt)。 非 root 会自动走 netstack(无系统路由、无 TUN)。

macOS 另有菜单栏 App,见下文。

快速开始

  1. 部署 server(一台有公网 IP 的机器)。步骤、反代和 Docker 见 docs/deploy.md。
  2. 装节点:见上面的「安装」。
  3. 入网:浏览器登录(meshgate login,或菜单栏里的「登录以加入网络…」)。 无人值守装机可以改用 authkey:meshgate-server authkey new 签发,明文只打印一次, 然后 meshgate up --server … --authkey <key>。
  4. 看路径:meshgate status / meshgate peers。两台节点都上线后,peers 的 STATE 应为 direct。

命令速查

命令 作用
up 启动守护进程(默认前台;--detach 后台;--no-detach 显式前台)
down 停止守护进程并清理本进程写过的路由 / DNS / 防火墙
status 节点、NAT、per-peer 路径 / RTT / TTD / MTU / 握手 / 流量
peers 一行一个对端的表格;--json 输出 []PeerStatus
ping <peer> 在当前选中路径上发认证 probe(不是 ICMP)
netcheck NAT / IPv6 / 端口映射诊断
routes 运行时增删本节点宣告的子网
exit-node 选择/解除一个已批准的出口节点,让本机全部流量经它上网(仅 Linux)
dns 列出 <主机名>.mesh → 虚拟 IP
service install|uninstall|start|stop|status(开机自启)
version 构建版本(--version 相同)

核心概念

  • 候选:host / srflx / mapped / static / prflx / peer-observed(对端实测回灌)。节点自己收集,经 server 交换,再主动探测。
  • 直连诊断:meshgate netcheck 给出一句结论 + 一条建议。读法见 docs/netcheck-diagnostics.md。
  • relay policy:fallback(默认)或 never。打洞失败时经服务端 UDP 中继兜底(P4):直连不健康 15s 内切中继,恢复后经 trust window 验证再切回;never 双向拒绝中继。服务端 relay.enabled 目前默认关闭。
  • TUN MTU:基线由本端 relay_policy 决定 —— 非 never 取 1160(给中继头留 36 字节),never 取 1200;不看服务端有没有开中继,接口 MTU 不随路径抖动。显式 tun_mtu / --mtu 优先级最高。 行为变更(升级注意):装上带中继的版本后,默认策略(fallback)的节点 TUN MTU 从 1200 变成 1160,直连路径上的分片行为随之改变。要保住 1200,显式设 relay_policy=never 或 tun_mtu=1200(后者的代价是中继路径上的大包会被分片或黑洞)。没有过渡开关。
  • 子网路由:节点可宣告 LAN 前缀,管理员批准后对端走 TUN 访问。见 docs/subnet-routes.md。子网路由这条管线本身拒绝默认路由(0.0.0.0/0/::/0);整机出口走独立的出口节点机制,见 docs/exit-node.md。
  • MeshDNS:<hostname>.mesh → 虚拟 IP,只做分域,不接管系统 DNS。见 docs/meshdns.md。
  • 漫游/切网:换 Wi-Fi、切热点后如何重新打洞、要等多久、哪些配置会让它不生效。见 docs/roaming.md。

排障

先跑 meshgate netcheck,按 docs/netcheck-diagnostics.md 读结论。常见失败原因:

原因 意思 怎么办
mapping_varies 出口映射随目标变化(对称 NAT / 多 WAN) 对端若是端口受限,直连理论失败;等 M4 relay。已触发的有界 aux binding 不会无限扫描
no_viable_candidate 探测过了但没有任何候选对能通 看 netcheck(UDP 是否出站、IPv6 是否 blocked);确认双方都在线
probe_timeout 认证 probe 没回来 可能是中间态,也可能是过滤型 NAT;看 peers 是否最终变成 direct。持续超时且无其他原因时,两边互相打不到

meshgate ping <peer> 走的是当前选中路径上的认证 probe,不是 ICMP,不需要额外特权。

平台支持

Linux macOS Windows
TUN kernel 已真机 已真机(utun) 已编译,未真机验证(wintun)
TUN netstack 已真机 已真机 已编译,未真机验证
子网路由(消费端) 已真机 已真机 已编译,未真机验证
子网网关(nft/iptables) 已真机 不支持(按设计拒绝) 不支持
MeshDNS 已真机(hosts / resolvectl) 已真机(resolver) NRPT 已写,未真机验证
service install 已编译;install.sh 走的就是这条路径 已真机(LaunchDaemon) 已编译,未真机验证
菜单栏 App — arm64:make macapp,未签名 —

NAT 矩阵只在 Linux netns 实跑,见 docs/nat-matrix-report.md。冒烟步骤见 docs/smoke-checklist.md。

当前限制

  • relay 默认关闭(服务端 relay.enabled)。开启前打不通就是打不通,没有应急中继。
  • Windows 整条路径未真机验证(named pipe / wintun / 路由 / 服务)。
  • overlay 仍是 IPv4;公网 IPv6 只作为 underlay 直连候选。
  • 出口节点(exit-node)消费端仅 Linux,默认不开启,需消费端手动选择,见 docs/exit-node.md。
  • 不做混淆、不做移动端。

构建与发布

make lint      # go vet + gofmt
make test      # go test -race ./...
make release   # 五平台二进制 + SHA256SUMS 到 dist/
make macapp    # macOS Apple Silicon:dist/Meshgate.app(见下节)

版本号来自 git describe --tags,由 ldflags 注入,meshgate version 可查 —— tag 是版本的唯一来源。打 vX.Y.Z tag 会触发 CI 交叉编译并把二进制挂到 Forgejo release 上,install.sh 就是从那里下载的。提交约定、打 tag 的规则和 CI 布局见 docs/conventions.md。

macOS 菜单栏 App(arm64)

菜单栏 App 以登录用户身份跑,通过 IPC 指挥 root 的 LaunchDaemon,不自己碰网络、不画 Dock 图标。只编 darwin/arm64,不签名、不公证。

  1. 先装 daemon(需 root)。菜单里的「安装服务…」会做同样的事,只要填服务器地址:
sudo meshgate service install --server wss://mesh.example.com

service install 后面的参数会原样拼进 LaunchDaemon 的 ProgramArguments。 不要加 -- 分隔符 —— Go 的 flag 包把它当解析终止符,--server 会被当成 位置参数丢掉,装出来的服务起不来且报错指向别处。

装好后节点处于「未入网」,点菜单里的「登录以加入网络…」在浏览器里用自己的账号登录即可 (无人值守装机仍可用 --authkey <key>)。要离开网络就点「退出登录…」,或 meshgate logout —— 凭据被清除、对端全部移除,设备密钥保留,再次登录仍是同一个节点。详见 docs/browser-login.md。

  1. 打包 App:在 Apple Silicon Mac 上 make macapp,得到 dist/Meshgate.app。
  2. 拖进 /Applications。
  3. 首次打开:未签名,系统会拦。右键 → 打开,或:
xattr -dr com.apple.quarantine /Applications/Meshgate.app
  1. 开机自启:菜单里勾「开机自启」,写的是用户级 LaunchAgent(cn.wrlog.meshgate.menubar),和 daemon 的 LaunchDaemon(cn.wrlog.meshgate)不是一回事。