☰
go-libp2p 实战:用 AutoTLS + Secure WebSockets 让浏览器直接连上你的 libp2p 节点
2026/10/15 22:39:19 网站建设 项目流程
  • 后端
  • 网络
  • 通信

【免费下载链接】go-libp2p

libp2p implementation in Go

项目地址:https://gitcode.com/gh_mirrors/go/go-libp2p
点击查看免费下载

本指南以仓库内 examples/autotls 示例为核心,讲解如何在 go-libp2p 中通过 AutoTLS 自动申请一张与节点 PeerID 绑定的 Let's Encrypt 通配符证书(*.<PeerID>.libp2p.direct),并将它与基于 TCP 的 libp2p WebSockets 传输层结合,开放出浏览器可直接访问的wss://安全连接。读完本文,你将掌握示例的运行方式、p2p-forge 证书管理器的完整配置项、/tls/sni/形式的监听地址写法,以及背后ShareTCPListener、AutoNAT 可达性检测与 WebSocket 传输层对 SNI 的处理原理。

示例解决的问题:浏览器原生无法跑原始 libp2p 协议

浏览器里既没有原始 TCP 套接字,也没有 libp2p 的 QUIC、yamux 等协议栈实现,因此传统 libp2p 节点无法被浏览器直接访问。WebSockets 是浏览器与生俱来支持的协议,libp2p 社区为此提供了基于 WebSocket 的传输层封装:浏览器用ws://(明文)或wss://(TLS 加密)连接到 libp2p 节点,连接内部依然承载完整的 libp2p 多路复用与加密握手。而要让浏览器使用wss://,节点就必须有一张合法的 TLS 证书——这正是本示例要解决的核心问题。

传统方案需要开发者自备域名并手工维护证书续期,而 AutoTLS 的思路是:证书与节点的 PeerID 绑定,由公共基础设施自动签发。证书域名格式为*.<PeerID>.libp2p.direct,其中libp2p.direct是 libp2p 生态提供的 forge 域名。这样无需任何自有域名,即可让任意一个具有公网地址的 libp2p 节点获得可被浏览器信任的 TLS 证书。

运行示例

示例位于 examples/autotls,依赖 libp2p host 基础示例 介绍的 host 概念(用libp2p.New构造节点并配置各项选项),在其之上叠加了 AutoTLS 与 WebSocket 传输层。运行方式很简单:

# 进入 examples 目录 cd examples/ # 进入 autotls 示例目录并运行 cd autotls/ go run .

examples是一个独立的 Go module(见 examples/go.mod),其中声明了github.com/ipshipyard/p2p-forge v0.9.1(AutoTLS 客户端库)、github.com/caddyserver/certmagic v0.25.3(证书存储与 ACME 客户端)以及github.com/libp2p/go-libp2p v0.48.0等依赖,首次运行时会自动下载。

硬性前置条件:必须有公网可达地址

原文档明确指出,要让该示例正常工作,节点必须拥有公网 IP 并且从公网可达。原因在于 AutoTLS 有连通性检查守卫:只有当节点的 AutoNAT 子系统确认本机可达公网、并通过事件总线发出event.EvtLocalReachabilityChanged且取值为network.ReachabilityPublic时,证书管理器才会真正向 CA 申请证书。

  • 事件定义见 core/event/reachability.go:EvtLocalReachabilityChanged携带一个network.Reachability字段,通常由 AutoNAT 子系统发出;
  • ReachabilityPublic常量定义见 core/network/network.go(表示本节点可被公网其他节点访问);
  • 事件的实际发射逻辑见 p2p/host/autonat/autonat.go:AutoNAT 通过h.EventBus().Emitter(...)创建 stateful 发射器,并在状态变化时Emit(event.EvtLocalReachabilityChanged{Reachability: status})。

如果节点位于 NAT 之后且未成功映射端口,AutoNAT 判定为ReachabilityPrivate或ReachabilityUnknown,证书不会被申请,程序会一直停留在等待证书的状态。

从代码看 AutoTLS 完整工作流

示例代码由两个文件组成:main.go(主流程)和 identity.go(持久化 peer 身份)。整体流程分四步:

  1. 创建 p2p-forge 证书管理器(cert manager)并启动;
  2. 加载或生成持久化的节点私钥(保证 PeerID 不变);
  3. 用一系列libp2p.Option构造 host,其中 WebSocket 传输层使用证书管理器提供的TLSConfig;
  4. 启动 DHT 客户端引导 AutoNAT 验证公网可达性,待证书就绪后打印可对外广播的地址。

第一步:配置 p2p-forge 证书管理器

证书管理器由 p2p-forge 客户端库构造,位于 main.go:

certManager, err := p2pforge.NewP2PForgeCertMgr( // 配置 ACME CA 端点 // 注意:示例默认使用 Let's Encrypt staging CA(p2pforge.DefaultCATestEndpoint), // 它签发的证书浏览器不信任,仅用于初步测试;生产环境应切换为 // Let's Encrypt 生产 CA(p2pforge.DefaultCAEndpoint)。 p2pforge.WithCAEndpoint(p2pforge.DefaultCATestEndpoint), // 配置证书存储位置(基于 certmagic 的文件存储) p2pforge.WithCertificateStorage(&certmagic.FileStorage{Path: "p2p-forge-certs"}), // 配置日志 p2pforge.WithLogger(rawLogger.Sugar().Named("autotls")), // DNS-01 ACME 挑战时使用的 User-Agent p2pforge.WithUserAgent(userAgent), // 可选:首次注册前的额外延迟 p2pforge.WithRegistrationDelay(10*time.Second), // 可选:证书就绪后的回调钩子 p2pforge.WithOnCertLoaded(func() { certLoaded <- true }), )

各配置项的作用与取值说明:

配置项作用本示例取值
WithCAEndpoint指定 ACME 证书颁发机构端点p2pforge.DefaultCATestEndpoint(staging 测试 CA,代码中DefaultCAEndpoint为生产 CA,见 main.go 中的 TODO 注释)
WithCertificateStorage证书与 ACME 账号的持久化存储位置certmagic.FileStorage{Path: "p2p-forge-certs"},证书落盘到本地目录
WithLogger指定日志记录器基于 go-log 的 zap 日志,Named("autotls")
WithUserAgentDNS-01 挑战请求的 User-Agent"go-libp2p/example/autotls"
WithRegistrationDelay初始注册前的等待时间10*time.Second,用于错开大量节点同时注册
WithOnCertLoaded证书加载完成后的回调向certLoaded通道发送信号,驱动主流程继续

创建完成后,调用certManager.Start()启动后台工作,并通过defer certManager.Stop()保证程序退出时优雅关闭。

第二步:持久化节点身份

identity.go 实现了身份密钥的加载与生成。核心逻辑是:如果identity.key文件已存在则直接读取,否则生成新的 Ed25519 密钥并写入文件,写入权限为0400(仅属主可读)。密钥序列化使用crypto.MarshalPrivateKey/crypto.UnmarshalPrivateKey。

持久化身份非常重要:PeerID 由公钥哈希派生,如果每次重启都重新生成密钥,PeerID 就会改变,AutoTLS 证书域名*.<PeerID>.libp2p.direct也随之变化,已有客户端将无法再通过旧地址连接。

第三步:构造带 Secure WebSockets 的 libp2p host

host 的构造选项在 main.go,逐项拆解如下:

opts := []libp2p.Option{ libp2p.Identity(privKey), // 使用持久化身份密钥,保证 PeerID 稳定 libp2p.DisableRelay(), // 关闭中继,因为本示例要求公网直连 libp2p.NATPortMap(), // 尝试用 UPnP 为 NAT 环境下的主机打开端口映射 libp2p.ListenAddrStrings( "/ip4/0.0.0.0/tcp/5500", // IPv4 普通 TCP "/ip6/::/tcp/5500", // IPv6 普通 TCP // 在同一 TCP 端口上配置 Secure WebSockets 监听。 // AutoTLS 会自动为这个 host 生成证书, // 并将 forge 域名(libp2p.direct)用作 SNI 主机名。 fmt.Sprintf("/ip4/0.0.0.0/tcp/5500/tls/sni/*.%s/ws", p2pforge.DefaultForgeDomain), fmt.Sprintf("/ip6/::/tcp/5500/tls/sni/*.%s/ws", p2pforge.DefaultForgeDomain), ), // 配置 TCP 传输层 libp2p.Transport(tcp.NewTCPTransport), // 让 TCP 与 WebSocket 传输层共享同一个 TCP 监听器 libp2p.ShareTCPListener(), // 配置 WS 传输层,并把 AutoTLS 证书管理器的 TLS 配置注入其中 libp2p.Transport(ws.New, ws.WithTLSConfig(certManager.TLSConfig())), // 为 libp2p identify 协议配置 User-Agent libp2p.UserAgent(userAgent), // 通过 AddrsFactory 把监听地址里的 `*` 替换为检测到的真实 IP 后对外广播 libp2p.AddrsFactory(certManager.AddressFactory()), }

ShareTCPListener是本次配置的关键。它的实现位于 options.go:通过设置cfg.ShareTCPListener = true,让 TCP 与 WebSocket 两个传输层复用同一个 TCP 端口(当前为可选开启,源码注释说明未来版本将默认开启并移除该选项)。共享监听器的行为在 config/config.go 有对应字段,并在配置组装时(config.go、config.go)生效。

这样做的好处显而易见:明文 TCP 和加密 WebSocket 共用5500端口,端口数量减半,NAT 端口映射、防火墙放行都只需配置一次。实际效果可从仓库测试与可达性跟踪代码中印证:p2p/host/basic/addrs_reachability_tracker.go 明确描述了共享监听器下"主地址(primary)与次地址(secondary)"的对应关系:

Primary: /ip4/.../tcp/port Secondary: /ip4/.../tcp/port/tls/sni/*.libp2p.direct/ws

当 AutoNAT v2 探测次协议(如 Secure WebSocket)失败时,如果主 TCP 地址已被确认为公网可达,次地址会继承该可达性结论(协议探测失败通常意味着探测方不支持该协议或证书尚未签发完毕,而非端口不可达)。

第四步:启动 DHT 引导 AutoNAT,等待证书就绪

host 构造完成后,示例启动了一个客户端模式的 DHT 并接入默认 bootstrap 节点(main.go):

dhtOpts := []dht.Option{ dht.Mode(dht.ModeClient), dht.BootstrapPeers(dht.GetDefaultBootstrapPeerAddrInfos()...), } dht, err := dht.New(ctx, h, dhtOpts...) go dht.Bootstrap(ctx)

DHT 的作用是让 AutoNAT 子系统通过公网节点对自己的地址进行可达性探测——探测方尝试回连本节点,能连上即判定为ReachabilityPublic,进而触发EvtLocalReachabilityChanged事件,证书管理器才会开始申请证书。随后certManager.ProvideHost(h)把 host 交给证书管理器,主流程在select中等待certLoaded通道信号或 context 取消:

select { case <-certLoaded: logger.Info("TLS certificate loaded ") logger.Info("Addresses: ", h.Addrs()) case <-ctx.Done(): logger.Info("Context done") }

证书就绪后,程序打印 host 的完整广播地址,最后等待中断信号退出。通过certManager.AddressFactory(),监听地址中的*会被替换为节点检测到的真实 IP,因此打印出的地址即为浏览器可直连的最终地址。

理解关键地址格式:/tls/sni/*.libp2p.direct/ws

/ip4/0.0.0.0/tcp/5500/tls/sni/*.libp2p.direct/ws是一段多协议 multiaddr,按顺序表达:

  • /ip4/0.0.0.0/tcp/5500:监听所有 IPv4 网卡的 5500 TCP 端口;
  • /tls:启用 TLS 加密(等价于浏览器语境下的wss://);
  • /sni/*.libp2p.direct:指定 TLS 握手时的 SNI(Server Name Indication)主机名,通配符*会由证书管理器的地址工厂替换为节点真实 IP;
  • /ws:使用 WebSocket 协议承载连接。

WebSocket 传输层对该地址的解析逻辑见 p2p/transport/websocket/addrs.go:parseWebsocketMultiaddr会剥离/ws组件并向前回溯提取/tls与/sni组件(addrs.go),其中sni既用于 TLS 握手也用于设置 HTTP Host 头。在 p2p/transport/websocket/websocket.go 的拨号路径中,客户端会从 multiaddr 提取 SNI 值并写入tls.Config.ServerName。

服务端侧,WithTLSConfig(certManager.TLSConfig())注入的正是 AutoTLS 动态管理的 TLS 配置(包含通配符证书与自动续期逻辑)。如果对wss地址监听却未提供tls.Config,传输层会直接报错,见 p2p/transport/websocket/listener.go:cannot listen on wss address %s without a tls.Config。这从侧面印证了:Secure WebSocket 监听必须与证书管理器配合,而 AutoTLS 恰好让证书的获取与续期全部自动化。

浏览器连接与生产环境切换

证书就绪后,浏览器客户端可用如下形式连接节点:

wss://<节点公网IP>:5500/tcp/ws

更准确的连接地址是打印出的多地址经过wss://转换后的 URL:WebSocket 传输层会把/ip4/.../tcp/5500/tls/sni/.../ws规范化为浏览器可用的wss地址(scheme 转换逻辑见 p2p/transport/websocket/websocket.go 与 addrs.go)。

需要注意两点:

  1. 测试 CA 与生产 CA:示例默认使用 Let's Encrypt staging CA(p2pforge.DefaultCATestEndpoint),其签发的证书不会被浏览器信任,仅供功能自测。要上线供真实浏览器访问,须把 main.go 中的 CA 端点切换为p2pforge.DefaultCAEndpoint(生产端点)。
  2. 公网可达性:证书仅在 AutoNAT 判定ReachabilityPublic后才会签发,因此部署环境必须保证 5500 端口公网可达、防火墙放行;libp2p.NATPortMap()只对支持 UPnP 的家用路由器生效,云主机通常需要自行配置安全组。

补充:与本示例相关的基础设施

  • 基础的 host 构造方式可参考 examples/libp2p-host/host.go,其中演示了libp2p.New()默认构造、libp2p.Identity自定义密钥、libp2p.ListenAddrStrings多地址监听、libp2p.ConnectionManager连接管理等选项;
  • 本示例中的libp2p.Transport(ws.New, ws.WithTLSConfig(...))与 WebSocket 传输层的其他可用选项(如WithTLSClientConfig、WithHandshakeTimeout、WithHTTPHandler)定义见 p2p/transport/websocket/websocket.go;
  • 关于共享 TCP 监听器在可达性判定中的细节,可进一步阅读 p2p/host/basic/addrs_reachability_tracker.go 中的注释与实现。

小结

AutoTLS 示例展示了 go-libp2p 生态里"浏览器可达"的完整解决方案:以 PeerID 为域名主体自动获取 Let's Encrypt 通配符证书,通过/tls/sni/多地址在同一 TCP 端口上叠加 Secure WebSockets,再借 AutoNAT 的连通性检测保证证书只在节点真正公网可达时才被申请。对希望让浏览器用户无需安装任何软件即可接入 libp2p 网络的开发者而言,这套配置是直接可复用、可上生产(切换 CA 端点后)的成熟范式。

  • 后端
  • 网络
  • 通信

【免费下载链接】go-libp2p

libp2p implementation in Go

项目地址:https://gitcode.com/gh_mirrors/go/go-libp2p
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询