在 Go 中实现 Yggdrasil 网络编程
概述
使用此技能构建连接到 Yggdrasil 端到端加密 IPv6 网状网络并提供服务的 Go 程序:客户端、HTTP/TCP/UDP 服务器、SOCKS5 代理、端口转发器以及发布发现元数据的节点。节点在 用户态 基于 gVisor netstack 运行 — 无需 TUN 设备、无需 root 权限、无需外部 yggdrasil 守护进程,也无需 yggstack 二进制文件。您可以使用标准的 Go 接口(net.Conn、net.Listener、net.PacketConn、http.Transport)通过 Yggdrasil 进行拨号和监听。
嵌入库为 github.com/voluminor/ratatoskr:使用 ratatoskr.New 创建节点,并通过其 Go API 驱动一切。其包的范围和稳定性总结如下。
针对受限网状网络的设计
Yggdrasil 是一个端到端加密的覆盖 网状网,而不是数据中心架构。链路存在高延迟、低带宽、丢包和间歇性特点;当前可达的对等节点下一秒可能消失,流量可能经过多跳。每一个 Yggdrasil 方案都应按照针对弱、不稳定连接来构建。这不是可选的优化 — 这是这里正确代码的默认形态。
- 绝无无界操作。 为每个拨号和请求设置截止时间(
context.WithTimeout);限制重试次数;限制队列大小。绝无无限等待。 - 限制并发。 限制在途工作:SOCKS 的
MaxConnections、转发的SetMaxUDPSessions、http.Transport.MaxConnsPerHost/MaxIdleConnsPerHost,以及您自己的工作池。两者默认都是 无限制 — 请设置实际限制。 - 分块传输大文件,验证并支持断点续传。 绝不在一次操作中传输大文件。拆分为小分块(例如 HTTP
Range),验证每个分块(大小/校验和),并使其可续传,这样连接断开时仅损失一个分块,而非整个传输。 - 使用退避 + 抖动进行幂等重试。 预期操作过程中可能断开。使操作可续传/幂等;使用带抖动的指数退避;限制重试次数。
- 保持有效载荷较小。 避免高频率协议和轮询;进行压缩;使用紧凑的编码格式。保持 NodeInfo/符号远低于 16 KB 的限制。
- 根据链路调整缓冲区大小。 使用
node.MTU()获取数据包缓冲区大小(用户态 MTU 是动态的,最低为 1280)。 - 优雅降级,不要猛攻。 失败时退避并返回部分结果;绝不对对等节点进行激进重试或向网络洪泛。
一个简单的 "通过 net/http 提供大文件服务" 或 "单次大请求/响应" 是错误的形态。正确的形态是默认有界、分块、可续传、容错的。请参阅 references/integrations.md 中的受限网状传输模式。
事实来源
活跃模块是权威来源 — 而不是内存、过时的 README 文本或此技能。在编写特定版本的指导之前,检查项目实际依赖的内容:
go list -m github.com/voluminor/ratatoskr # 选定的版本
go doc github.com/voluminor/ratatoskr # 编译时的根 API
go doc github.com/voluminor/ratatoskr/mod/peermgr ConfigObj
要读取选定版本的实际源代码,请从模块缓存中解析其路径,而不是猜测路径:
go list -m -f '{{.Dir}}' github.com/voluminor/ratatoskr
不要从 README 文本中固定最低 Go 版本 — 它可能落后于 go.mod 指令。请读取选定 Ratatoskr 版本的活跃 go.mod。
默认工作流程
- 确定应用形态:嵌入节点、HTTP 客户端/服务器、原始 TCP/UDP、SOCKS5、端口转发、对等节点管理,或 NodeInfo/符号。
- 对于应用,使用根包
github.com/voluminor/ratatoskr。仅通过core.Interface契约进入mod/core;除非底层包确实需要,否则避免使用*core.Obj/UnsafeCore()。 - 拥有一个
context.Context,将其作为ratatoskr.ConfigObj.Ctx传递,并且在成功启动后仍然使用defer node.Close()。两者都是必需的。 - 在恰好一个地方配置对等节点:
- 静态 Yggdrasil 对等节点:
cfg.Config.Peers - Ratatoskr 对等节点管理器:
ratatoskr.ConfigObj.Peers(然后将cfg.Config.Peers留空,否则New将返回ErrPeersConflict)。
- 静态 Yggdrasil 对等节点:
- 使用
node.DialContext进行出站 TCP/UDP 连接,使用node.Listen/node.ListenPacket在 Yggdrasil 内部提供服务。 - 在 URL 和主机-端口字符串中使用方括号括起 IPv6 地址:
http://[200:abcd::1]:8080,绝不要使用http://200:abcd::1:8080。 - 将链路视为弱连接:为每个调用设置截止时间,限制并发,并对大传输进行分块/验证/续传(请参阅“针对受限网状网络的设计”)。
- 当您将代码生成到实际项目中时,添加编译检查或针对性测试,而不是假设它能构建。
模块范围与稳定性
Yggdrasil 地址从节点密钥派生,因此网络表面小且稳定。优先使用库视为已完成的部分;明确标记其余部分,而不是在其上构建。
| 包 | 状态 | 通过其使用 |
|---|---|---|
根包 ratatoskr | 稳定,主要 API | New、EnableSOCKS、Ask/AskAddr、Snapshot、Close |
mod/core | 稳定契约 | 嵌入在节点上的 core.Interface 方法 |
mod/peermgr | 稳定 | ratatoskr.ConfigObj.Peers |
mod/socks | 稳定 | node.EnableSOCKS(不要直接实例化) |
mod/resolver | 稳定 | 由 EnableSOCKS 自动创建;直接使用属高级用法 |
mod/forward | 稳定 | forward.New → Add* → Start |
mod/sigils(+ info、services、public、inet) | 稳定 | ratatoskr.ConfigObj.Sigils |
mod/ninfo | 稳定 | node.Ask / node.AskAddr |
mod/probe | 高级/不稳定 — 需要 UnsafeCore(),API 可能变更 | 仅在明确需要拓扑/路由追踪时使用 |
mod/settings | 进行中 — 架构未最终确定 | 避免使用;直接使用 yggdrasil-go/src/config |
cmd/* | 示例应用,不可导入的库 API | 作为模式阅读,切勿导入 |
根包与子模块: 默认使用 ratatoskr.New(核心 + SOCKS + 对等节点管理器 + ninfo + 符号,通过一个 Close 管理)。仅在需要最小表面(原始拨号/监听)时通过 core.New 降级到 mod/core,或围绕 core.Interface 组合子模块以用于自定义传输和测试。每个子模块(peermgr/socks/forward/resolver/ninfo)都接受一个 core.Interface。请参阅 references/overview.md。
系统符号 — 优先在此发布
NodeInfo 是节点向网络描述自身的方式。每个节点都会自动解析四种 内置(“系统”)符号,因此您放入其中的数据会被其他参与者识别。在发明自定义符号之前,优先使用这些符号;通过 ratatoskr.ConfigObj.Sigils 传递它们。保持每个符号非常小 — 它们共享一个 16 KB 的 NodeInfo 预算。
| 符号 | 属于此处的内容 | 为什么使用它 |
|---|---|---|
info | 节点身份:名称、类型、位置、联系人、描述 | 让其他人知道您的节点是什么。 |
services | 您在 Yggdrasil 内部暴露的端口,格式为 name → port | 无需端口扫描即可进行服务发现。 |
public | 您按网络分组的公共对等连接 URI | 让其他人直接与您对等连接。 |
inet | 您的现实世界地址(域名/IP) | 将您的 Yggdrasil 节点映射到明文网络身份。 |
将可发现的、标准的数据放入这些符号中,以便 整个网络 都能读取。仅为这四个符号都不涵盖的数据定义自定义符号 — 并记住只有运行您代码的对等节点才会解析它。构造函数会进行验证并返回错误;在 ratatoskr.New 之前处理它们。详细信息、精确限制、类型化访问和自定义符号编写:请参阅 references/nodeinfo-sigils.md 和 references/custom-sigils.md。
参考地图
仅读取当前任务所需的文件:
- 嵌入节点基础知识、Ratatoskr 与 yggstack 对比、架构、范围: references/overview.md
ratatoskr.New、ConfigObj、生命周期、线程安全、根方法、错误、快照: references/root-api.md- TCP、UDP、
DialContext、Listen、ListenPacket、HTTP 客户端/服务器、多播/管理、测试接口: references/networking.md - 静态对等节点、对等节点管理器、SOCKS5、解析器、端口转发: references/peers-socks-forwarding.md
- NodeInfo、
Ask/AskAddr、带精确限制的内置符号: references/nodeinfo-sigils.md - 类型化访问以及编写/接入您自己的符号(无原始
map[string]any): references/custom-sigils.md - 客户端/服务器模板和框架集成(net/http、fasthttp、gRPC、gnet bridge): references/integrations.md
- 高级/进行中区域(probe、settings)和 CLI/示例应用模式: references/advanced-and-examples.md
- 可直接改编的应用配方: references/recipes.md
常见错误
| 错误 | 更正 |
|---|---|
| 为嵌入应用启动外部 Yggdrasil 守护进程 | 使用 ratatoskr.New;它运行用户态 gVisor netstack。 |
推荐 yggstack 用于库集成 | yggstack 是最终用户二进制文件。对于 Go API 控制,请使用 Ratatoskr。 |
同时设置 cfg.Config.Peers 和 ConfigObj.Peers | 选择静态对等节点或对等节点管理器;绝不能同时设置 → ErrPeersConflict。 |
传递 Ctx 但跳过 Close(或反之) | 两者都执行:取消触发关闭,Close 释放资源。 |
| 构建没有 IPv6 方括号的 URL | 使用 fmt.Sprintf("http://[%s]:%d", ip, port)。 |
为基本查询创建 ninfo.New | 使用 node.Ask / node.AskAddr;根对象已构建 ninfo。 |
| 在运行时动态组装或注册符号 | 符号 严格在代码中 组装;重复或覆盖键在设计中是硬错误,而非静默合并。通过 ConfigObj.Sigils 发布;从 res.Node.Extra 读取您自己的键。 |
默认就使用 UnsafeCore() / mod/probe / mod/settings | 保持在根 API 上;这些是高级或进行中的功能。 |
| 从 README 硬编码 Go 版本 | 读取选定模块版本的活跃 go.mod。 |
验证
保持 SKILL.md 作为导航层,将详细的 API 信息放在 references/ 中。在发布前,根据 evals/evals.json 中的场景对更改进行前向测试。