- Go 92.7%
- Shell 3.1%
- JavaScript 2.5%
- CSS 0.6%
- HTML 0.5%
- Other 0.4%
|
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
守护进程卡死时外面只看得到 IPC 超时,要知道谁持着哪把锁只能 sudo kill -QUIT,而那会顺带杀掉进程、现场就没了。现在 status 超过 10 秒未返回,就把所有 goroutine 的栈(含等待时长)写进守护进程自己的 日志,30 分钟内只写一次,免得菜单栏每 3 秒一次的轮询把日志刷满。 只盯 status:它几乎碰遍 node 的每把锁,哪里卡住都会先在这里显形; netcheck、login 本来就慢,放进来只会误报。 |
||
|---|---|---|
| .forgejo | ||
| assets/icon | ||
| cmd | ||
| deploy | ||
| docs | ||
| internal | ||
| pkg | ||
| test/nat | ||
| .gitignore | ||
| findings.md | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| progress.md | ||
| README.md | ||
| task_plan.md | ||
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,见下文。
快速开始
- 部署 server(一台有公网 IP 的机器)。步骤、反代和 Docker 见 docs/deploy.md。
- 装节点:见上面的「安装」。
- 入网:浏览器登录(
meshgate login,或菜单栏里的「登录以加入网络…」)。 无人值守装机可以改用 authkey:meshgate-server authkey new签发,明文只打印一次, 然后meshgate up --server … --authkey <key>。 - 看路径:
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,不签名、不公证。
- 先装 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。
- 打包 App:在 Apple Silicon Mac 上
make macapp,得到dist/Meshgate.app。 - 拖进 /Applications。
- 首次打开:未签名,系统会拦。右键 → 打开,或:
xattr -dr com.apple.quarantine /Applications/Meshgate.app
- 开机自启:菜单里勾「开机自启」,写的是用户级 LaunchAgent(
cn.wrlog.meshgate.menubar),和 daemon 的 LaunchDaemon(cn.wrlog.meshgate)不是一回事。