meshproxy 是一个使用 Go 编写的“去中心化匿名代理网络 + 本地 SOCKS5 Agent + 匿名聊天”原型实现,目前处于早期 MVP 阶段。
安卓客户端 https://github.com/chenjia404/meshproxy-android
cmd/node/main.go:节点主程序入口,负责读取配置、初始化App并处理信号退出。internal/app:聚合配置、身份、P2P、发现、Circuit、SOCKS5、本地 API 等组件的主应用层。internal/config:YAML 配置加载、默认值与校验,支持relay/relay+exit两种模式。internal/identity:Ed25519 私钥生成与持久化,供 libp2p host 使用。internal/p2p:libp2p host、DHT、Gossip、bootstrap 以及协议 ID 定义。internal/discovery:节点 descriptor 定义、签名验签、gossip 广播与缓存管理。internal/protocol:电路协议常量、结构与统一 frame 编码(CREATE/EXTEND/BEGIN_TCP/DATA/END 等),以及多跳分层加密(X25519 + AEAD 洋葱封装)。internal/client:本地 SOCKS5、路径选择器(PathSelector)、CircuitManager、StreamManager。internal/relay/internal/exit:relay / exit 服务实现,relay 仅解一层洋葱并转发,exit 解最后一层并连接目标 TCP。internal/api:本地管理 HTTP API 与内嵌 Web 控制台(/console/),提供节点状态、已知节点、电路、stream、错误与指标等信息。meshserver-api.md:meshserver 中心化群的本地 API 说明,包含连接、服务器、频道、消息和同步接口示例。internal/store:内存 KV store 与 circuit store。proto/meshproxy.proto:后续用于生成 Protobuf 类型。configs/config.example.yaml:示例配置。
控制台:http://127.0.0.1:19080/console
去中心化加密聊天:http://127.0.0.1:19080/chat
meshproxy 现在内置了一套点对点加密聊天原型页面,入口为 http://127.0.0.1:19080/chat。这套聊天能力不是独立再造一张网络,而是直接复用当前 meshproxy 已有的去中心化网络底座:
- 使用现有 libp2p Peer ID 作为设备身份
- 优先尝试 direct stream 直连通信
- 直连失败时自动回退到 relay-e2e 中继转发
- 中间 relay 只负责转发,聊天内容仍然保持应用层端到端加密
- 聊天数据本地持久化到
data/chat.db
当前聊天页支持:
- 修改本机昵称
- 发起聊天请求、接受/拒绝请求
- 联系人备注名与拉黑
- 单聊文本消息发送
delivery_ack已送达确认- 单条消息撤回(双端删除,本地数据库也会删除)
- 会话级自动删除时间设置,并同步到对端
当前实现边界:
- 以单聊文本为主,暂未实现群聊、文件传输和语音媒体流
- 聊天优先依赖当前节点发现能力;如果部署环境禁用了
nodisc,则主要依赖bootstrap + gossip + peer exchange - 对端完全不可达时,像撤回、自动删除同步这类需要双端一致的操作会返回失败,而不会只改本地
go mod tidy
go build -o bin/meshproxy ./cmd/node
./bin/meshproxy -config configs/config.example.yaml启动后可通过 Web 控制台 或 本地 API 管理与查询状态(API 默认 http://127.0.0.1:19080):
-
Web 控制台:在浏览器打开
http://127.0.0.1:19080/console/,可查看节点状态、已知 Relay/Exit、Circuit/Stream 列表、实时流量汇总、日志面板等,无需直接查看 JSON API。 -
GET /api/v1/status:节点状态(
peer_id,mode,socks5_listen,p2p_listen_addrs,uptime_seconds,relays_known,exits_known,circuit_pool)。 -
GET /api/v1/nodes:已知节点 descriptor 缓存。
-
GET /api/v1/relays:已知 relay 节点列表。
-
GET /api/v1/exits:已知 exit 节点列表。
-
GET /api/v1/circuits:当前电路列表(含每条电路路径
plan、relay_peer_id、exit_peer_id、hop_count、stream_count、created_at、updated_at)。 -
GET /api/v1/streams:当前 stream 列表(含目标与状态,以及对应电路的
relay_peer_id、exit_peer_id、hop_count)。 -
GET /api/v1/scores:peer 评分(预留,目前返回空)。
-
GET /api/v1/errors/recent:最近错误记录。
-
GET /api/v1/metrics/summary:汇总指标(circuits_total, streams_active, relays_known, exits_known, errors_recent_count, pool_status 等)。
出口节点专用 API(仅在 mode: relay+exit 时可用):
- GET /api/v1/exit/policy:当前出口策略与运行时配置。
- POST /api/v1/exit/policy:更新策略或运行时(请求体可含
policy/runtime,仅更新内存,重启后以配置文件为准)。 - GET /api/v1/exit/status:运行状态(drain_mode、accept_new_streams、open_connections、recent_rejects)。
- POST /api/v1/exit/drain:进入维护模式(不再接受新 stream)。
- POST /api/v1/exit/resume:结束维护模式。
在 Windows PowerShell 下,将 ./cmd/node 编译为 Android arm64 二进制文件的示例命令为:
$env:GOOS="android"; $env:GOARCH="arm64"; $env:CGO_ENABLED="0"; go build -trimpath -buildvcs=false -o bin/meshproxy.so -ldflags="-w -s" -ldflags "-checklinkname=0" ./cmd/node示例配置位于 configs/config.example.yaml,主要字段:
mode:relay或relay+exit。relay:仅作为中继节点,不直接作为出口。relay+exit:同时具备 relay 与 exit 能力。
data_dir:用于存放身份密钥等持久化数据。identity_key_path:如为空,默认值为${data_dir}/identity.key。p2p.listen_addrs:libp2p 监听 multiaddr 列表,例如"/ip4/0.0.0.0/tcp/0"。p2p.public_ip:手动指定对外广播的公网 IP。设置后,libp2p 会把监听地址里的内网/任意地址改写成这个公网 IP,并关闭 identify 地址发现,避免广播大量内网地址。p2p.bootstrap_peers:其他节点的 multiaddr,用于启动时连接引导。p2p.nodisc:是否禁用 DHT 节点发现。为true时不会执行 rendezvousAdvertise/FindPeers,只依赖bootstrap_peers、已连接节点的 gossip descriptor 与 peer exchange。socks5.listen:本地 SOCKS5 监听地址,例如127.0.0.1:1080。socks5.allow_udp_associate:是否啟用 SOCKS5 UDP ASSOCIATE(預設false);啟用後需出口策略exit.policy.allow_udp: true配合。socks5.tunnel_to_exit:是否开启“本地原样转发到 exit 的 SOCKS5”模式。开启后,本地socks5.listen接收到的 TCP 连接不会在本地解析 SOCKS5,而是直接通过 raw libp2p stream 原样转发到 exit 节点上的 SOCKS5 服务。socks5.exit_upstream:当socks5.tunnel_to_exit: true时,exit 节点实际连接的上游 SOCKS5 地址,默认127.0.0.1:1081。relay+exit节点会额外启动一个标准 SOCKS5(默认 1081),它直接出网,但仍受出口策略限制。api.listen:本地 HTTP API 监听地址,例如127.0.0.1:19080。log_modules:逗号分隔的模块名,用于过滤标准库log输出:只保留消息中带[模块名]标签的行(例如配置chat对应代码里的log.Printf("[chat] ..."));留空则不过滤、输出全部。不带[xxx]前缀的行(如启动提示)仍会输出。也可通过命令行--log_modules=chat,path_selector覆盖配置文件。
出口策略(仅在 mode: relay+exit 时生效):在配置中可加入 exit 段,用于控制出口允许的端口、域名、peer、是否允许私网/回环目标,以及维护模式(drain_mode)。默认配置较为保守:仅允许 TCP、80/443,禁止私网与回环。详见 configs/config.example.yaml 中的注释示例。
出口國家解析(GeoIP):當出口節點的 descriptor 未提供 exit_info.country 時,可通過 client.geoip 啟用「從出口節點 IP 推斷國家」:從節點 listen_addrs 中取首個公網 IP,再經 GeoIP 查詢得到國家代碼,用於 country_only / country_preferred 篩選以及 API /api/v1/client/exit-candidates 的 country 欄位。
client.geoip.provider: ip-api:使用免費服務 ip-api.com(約 45 次/分鐘,建議設cache_ttl_minutes)。client.geoip.provider: geolite2:使用本地 GeoLite2-Country.mmdb(github.com/oschwald/geoip2-golang/v2);數據庫放在data_dir下,若不存在則自動從 P3TERX/GeoLite.mmdb 下載。
项目中有两类日志能力,可并存使用:
-
标准库
log(默认)
业务代码普遍使用log.Printf("[模块名] ...")输出。根配置项log_modules(或命令行--log_modules)按模块名过滤:多个模块用英文逗号分隔,大小写不敏感;仅当一行日志中第一个符合[a-zA-Z0-9_-]+形式的[标签]与列表中某项匹配时才输出该行。
实现位于internal/logfilter,在cmd/node/main.go校验配置后调用logfilter.Apply挂到log的输出上。 -
结构化日志
internal/meshlog(可选,供新代码逐步采用)
API 风格参考 go-ethereum/log:基于log/slog,提供meshlog.SetDefault、meshlog.New("module", "chat")、分级Info/Warn/… 等。默认根 logger 为 丢弃(与 geth 类似),需自行SetDefault(meshlog.NewLogger(meshlog.StderrTextHandler(meshlog.LevelInfo)))后才有输出;与现有log.Printf互不替换,可并行迁移。
示例:
# 仅看 [chat] 与 [path_selector] 相关标准库日志
./bin/meshproxy --log_modules=chat,path_selector -config configs/config.example.yaml// 可选:在进程早期配置 meshlog(示例)
meshlog.SetDefault(meshlog.NewLogger(meshlog.StderrTextHandler(meshlog.LevelInfo)))
l := meshlog.New("module", "chat")
l.Info("offline ok", "peer", peerID, "seq", seq)假设有两个节点:
- 节点 A:
mode: relay+exit,同时作为 relay 与出口。 - 节点 B:
mode: relay,仅作为中继,浏览器连接到 B 的本地 SOCKS5。
基本步骤:
- 在节点 A 上启动 meshproxy,记录其
PeerId与实际p2p_listen_addrs。 - 在节点 B 的
configs/config.example.yaml中,将 A 的 multiaddr 写入p2p.bootstrap_peers。 - 如果希望节点 B 不在本地解析 SOCKS5,而是把原始 SOCKS5 流量直接转发给节点 A 的本地 SOCKS5,可在节点 B 配置:
socks5:
listen: "127.0.0.1:1080"
tunnel_to_exit: true
exit_upstream: "127.0.0.1:1081"- 在节点 B 上启动 meshproxy。
- 使用浏览器将 HTTP/HTTPS 代理设置为 B 的 SOCKS5 地址(默认
127.0.0.1:1080)。 - 尝试访问网站,并通过两端日志及后续的
circuits/streams查询电路路径。
说明:
tunnel_to_exit: true只建议在客户端节点开启;exit 节点自身通常应保持关闭。exit_upstream是 exit 端真正处理 SOCKS5 的监听地址;默认就是 exit 节点本机新增的127.0.0.1:1081。
- 浏览器需设置为 SOCKS5 代理,指向本地节点的
socks5.listen。 - 建议关闭浏览器自带 DNS 缓存或启用“通过 SOCKS 解析 DNS”选项,确保域名解析委托给 exit 节点(remote DNS)。
- 节点状态(含 relay/exit 数量与电路池):
curl http://127.0.0.1:19080/api/v1/status | jq . - 已知节点:
curl http://127.0.0.1:19080/api/v1/nodes | jq . - 已知 relay / exit:
curl http://127.0.0.1:19080/api/v1/relays | jq .、curl http://127.0.0.1:19080/api/v1/exits | jq . - 电路列表(含路径):
curl http://127.0.0.1:19080/api/v1/circuits | jq . - Stream 列表(目标与状态):
curl http://127.0.0.1:19080/api/v1/streams | jq . - 最近错误:
curl http://127.0.0.1:19080/api/v1/errors/recent | jq . - 汇总指标:
curl http://127.0.0.1:19080/api/v1/metrics/summary | jq .
- 补全 SOCKS5 协议状态机与流量转发。
- 基于 libp2p stream 建立 relay / relay+exit 数据通道。
- 利用 DHT/Gossip 做节点发现、路由公告与健康检查。
- 扩展本地 API,支持连接状态、路由表与 debug 信息查询。
- 目前支持 TCP 與 UDP:TCP 經 SOCKS5 CONNECT;UDP 經 SOCKS5 UDP ASSOCIATE(需
socks5.allow_udp_associate: true且出口策略exit.policy.allow_udp: true)。 - 尚未支持 DNS over circuit 等进阶功能。
- DHT / Gossip 仅用于最小节点 descriptor 发现与缓存,尚未设计完整路由/信誉机制。
- Circuit / Stream 管理仍为内存内部状态,尚未考虑持久化与全网健康度评估。


