ZeroTier 源码解析 (2) 节点 (Node)

在上一章 虚拟网络 (Network) 中,我们把虚拟网络比作一个安全的"线上办公室"。我们知道,只有被邀请的成员才能进入这个办公室并相互交流。

那么,究竟是谁在加入这些"线上办公室"呢?我们用来上网的设备------无论是笔记本电脑、服务器还是手机------在 ZeroTier 的世界里是如何被表示的呢?

这就是 节点 (Node) 概念的作用所在。如果说 Network 是办公室,那么 Node 就是进入办公室工作的"你"的数字分身。

什么是节点 (Node)?

Node 是 ZeroTier 客户端实例的核心,是整个系统的大脑和总指挥。当你在设备上运行 ZeroTier 时,一个 Node 对象就会被创建出来,代表你这台独一无二的设备。

让我们继续使用"线上办公室"的比喻来理解 Node

把一个 Node 想象成你在 ZeroTier 世界里的 "数字身份和个人助理"

  • 独一无二的身份 :每个 Node 都有一个全球唯一的地址(例如 a1b2c3d4e5),这是通过一个加密的身份 (Identity)生成的。这就像是你的数字护照,无论你加入哪个"线上办公室",这个身份都跟你走,证明"你就是你"。
  • 办公室通行证管理员 :你的 Node 持有一串"钥匙",可以进入多个不同的虚拟网络 (Network)(线上办公室)。它负责管理你加入了哪些网络,并处理加入(join)和离开(leave)这些网络的所有事务。
  • 通信总调度员 :当你想和办公室里的同事(另一个节点)说话时,你的个人助理(Node)会负责打包你的信息(数据包),贴上正确的地址标签,然后通过最高效的路径发送出去。反之,当有信息发给你时,也是由它签收、拆包,并交给你。
  • 日程和维护管家 :你的助理会默默地处理所有后台杂务,比如:定期和同事们打个招呼(ping)以确保联系畅通,检查办公室的规章制度(网络配置)有没有更新,保持整个通信系统的健康和稳定。

简而言之,你的设备上所有与 ZeroTier 相关的活动,都是围绕着这个 Node 实例展开的。它是所有其他核心组件的"老板",负责创建和管理它们。

Node 对象的核心职责

Node 对象是 ZeroTier 中最顶层的管理类,它的主要职责包括:

  1. 初始化与生命周期管理 :当 ZeroTier 启动时,Node 对象被创建;当 ZeroTier 关闭时,它被销毁。它负责初始化所有必要的子系统,比如身份 (Identity)管理器、交换机 (Switch)和拓扑 (Topology)管理器。
  2. 网络成员关系管理 :通过 join()leave() 方法,Node 负责将自身加入或脱离一个虚拟网络 (Network)。它内部维护着一个列表,记录了所有已加入的 Network 对象。
  3. 数据包流转中心Node 是数据进出的主要入口。它有两个核心的数据处理函数:
    • processVirtualNetworkFrame():处理来自你本地电脑(虚拟网卡)的数据包。
    • processWirePacket():处理来自物理网络(互联网)的数据包。
  4. 后台任务调度 :通过 processBackgroundTasks() 方法,Node 定期执行维护任务,例如保持与对等节点 (Peer)的连接、向网络控制器请求最新配置等。

代码中的 Node

Node 类的定义可以在 node/Node.hpp 文件中找到。下面是它简化后的结构:

cpp 复制代码
// 文件: node/Node.hpp

class Node
{
private:
    // ... (省略部分成员)
    
    // 运行环境,包含身份、拓扑等所有核心子组件
    RuntimeEnvironment _RR; 

    // 用于与外部(如操作系统服务)交互的回调函数
    ZT_Node_Callbacks _cb;   

    // 已加入的网络列表 (Key: 网络ID, Value: Network对象)
    Hashtable< uint64_t,SharedPtr<Network> > _networks;
    Mutex _networks_m; // 用于保护网络列表的锁

    volatile int64_t _now; // 缓存的当前时间戳
    bool _online;          // 节点的在线状态

public:
    // 构造函数:当 ZeroTier 客户端启动时调用
    Node(void *uptr, void *tptr, const struct ZT_Node_Callbacks *callbacks, int64_t now);

    // 加入一个虚拟网络
    ZT_ResultCode join(uint64_t nwid, void *uptr, void *tptr);

    // 离开一个虚拟网络
    ZT_ResultCode leave(uint64_t nwid, void **uptr, void *tptr);

    // 处理来自物理网络(互联网)的数据包
    ZT_ResultCode processWirePacket(...);

    // 处理来自虚拟网络(你的电脑应用)的数据包
    ZT_ResultCode processVirtualNetworkFrame(...);

    // 执行后台维护任务
    ZT_ResultCode processBackgroundTasks(...);
};
  • _RR (RuntimeEnvironment): 这是一个非常重要的辅助结构体,可以看作是 Node 的"工具箱"。它捆绑了几乎所有 Node 需要的子组件,包括节点的身份 (Identity)、网络拓扑 (Topology)(记录了所有已知的对等节点 (Peer))和核心的交换机 (Switch)等。
  • _networks: 这是一个哈希表,存储了所有当前 Node 已加入的虚拟网络 (Network) 对象。
  • join() / leave(): 这两个函数让你能控制 Node 加入或离开一个网络。调用 join(nwid) 就会在 _networks 哈希表中创建一个新的 Network 对象。
  • process...() 函数:这些是 Node 的主要工作入口,负责接收和调度所有的数据包和任务。

Node 的工作流程:数据包的旅程

想象一下,你的电脑(IP为 10.0.0.1)想要 ping 同一个虚拟网络里的另一台服务器(IP为 10.0.0.2)。这个数据包是如何通过 Node 发送出去的呢?

这是一个简化的流程图:

sequenceDiagram participant App as 你的应用程序 (如ping) participant VNI as 操作系统虚拟网卡 participant Node as Node 对象 participant Switch as Switch 对象 participant Peer as 目标 Peer 对象 participant Internet as 物理互联网 App->>VNI: 发送数据包 (目标: 10.0.0.2) VNI->>Node: 调用 processVirtualNetworkFrame() Node->>Switch: 转交数据包 (onLocalEthernet) Switch->>Peer: 查询目标 Peer (地址解析) Switch->>Peer: 加密并发送数据包 Peer->>Internet: 通过互联网发送
  1. 你的应用程序(如 ping)创建了一个标准网络数据包,希望发送到 10.0.0.2
  2. 操作系统看到这个目标地址属于 ZeroTier 的虚拟网卡,于是将数据包交给了 ZeroTier 服务。
  3. ZeroTier 服务调用 Node 对象的 processVirtualNetworkFrame() 方法,将数据包传入。
  4. Node 并不亲自处理这个数据包的路由逻辑。它扮演的是一个"总指挥"的角色,将这个任务委派给了更专业的下属------交换机 (Switch) 对象。
  5. Switch 就像一个物理交换机,它会查看数据包的目标 MAC 地址(由 IP 地址 10.0.0.2 解析而来),然后在自己的"地址簿"里查找这个 MAC 地址属于哪个对等节点 (Peer)。
  6. 找到对应的 Peer 后,Switch 对数据包进行加密,然后命令 Peer 对象将其发送出去。
  7. Peer 对象负责处理所有与那个特定远程节点的通信细节,比如通过哪条物理路径 (Path) 发送数据包最快。

深入代码:任务的委派

让我们看看 processVirtualNetworkFrame 的代码实现,它完美地体现了 Node 的"委派"角色。

cpp 复制代码
// 文件: node/Node.cpp

ZT_ResultCode Node::processVirtualNetworkFrame(
	void *tptr,
	int64_t now,
	uint64_t nwid,       // 网络ID
	// ... 其他参数
	const void *frameData,
	unsigned int frameLength)
{
	_now = now; // 更新内部时钟

	// 1. 根据网络ID,从 _networks 列表中找到对应的 Network 对象
	SharedPtr<Network> nw(this->network(nwid));

	if (nw) {
		// 2. 如果找到了,就把数据包和网络信息一起交给 Switch 处理
		RR->sw->onLocalEthernet(tptr,nw,MAC(sourceMac),MAC(destMac),etherType,vlanId,frameData,frameLength);
		return ZT_RESULT_OK;
	} else {
		// 如果节点没有加入这个网络,则返回错误
		return ZT_RESULT_ERROR_NETWORK_NOT_FOUND;
	}
}

这段代码非常清晰。Node 做的第一件事是确认这个数据包属于哪个它已经加入的虚拟网络 (Network)。如果确认无误,它就直接把所有信息(包括找到的 Network 对象和数据包本身)一股脑地扔给它的"下属" RR->sw(即 交换机 (Switch) 对象),让它去处理后续所有复杂的路由、加密和发送工作。

这种"高层指挥,底层执行"的设计模式是 ZeroTier 代码结构的核心,它使得 Node 类可以保持相对简洁,专注于宏观管理,而将具体实现细节封装在各自的组件中。

总结

在本章中,我们认识了 ZeroTier 系统的核心------节点 (Node)

  • Node 是你设备在 ZeroTier 网络中的数字体现,是所有操作的中心。它就像一个拥有唯一身份个人助理
  • 它负责管理你的身份 (Identity),维护你加入的所有虚拟网络 (Network) 的列表。
  • Node 是一个总指挥调度员 ,它接收来自本地应用和互联网的数据包,并将其委派给专门的子组件(如 Switch)进行处理。
  • 它还负责执行必要的后台任务,确保整个系统的健康和稳定。

现在我们知道了 Node 是代表我们设备的核心实体,而每个 Node 都必须有一个独一无二的、可验证的身份。这个身份是如何产生的?它又是如何保证我们的通信安全的呢?

在下一章中,我们将深入探讨 Node 的"数字护照"------身份 (Identity)。

相关推荐
网硕互联的小客服2 分钟前
IIS7.5下的https无法绑定主机头,显示灰色如何处理?
网络协议·http·https
R-G-B1 小时前
【04】OpenCV C++实战篇——实战:发票精准定位,提取指定单元格数据。(倾角计算、旋转矫正、产品定位、目标定位、OCR文字提取)
c++·opencv·ocr·发票精准定位·提取指定单元格数据·倾角计算·旋转矫正
稚肩2 小时前
如何在linux中使用Makefile构建一个C++工程?
linux·运维·c++
啊森要自信3 小时前
【QT】常⽤控件详解(七)容器类控件 GroupBox && TabWidget && 布局管理器 && Spacer
linux·开发语言·c++·qt·adb
源代码•宸3 小时前
C++高频知识点(二十)
开发语言·c++·经验分享·epoll·拆包封包·名称修饰
重启的码农3 小时前
ZeroTier源码解析 (3) 身份 (Identity)
c++·网络协议
遇见你的雩风4 小时前
C++结构体的赋形之记
c++
DemonAvenger4 小时前
边缘计算场景下Go网络编程:优势、实践与踩坑经验
网络协议·架构·go
郝学胜-神的一滴4 小时前
Horse3D引擎研发笔记(一):从使用Qt的OpenGL库绘制三角形开始
c++·qt·3d·unity·图形渲染·unreal engine