返回

Wireguard源码分析

wireguard内核源码

WireGuard/wireguard-linux-GitHub

main.c

实现了 WireGuard 内核模块的初始化和退出逻辑,主要包括以下步骤:

  1. 初始化各个子系统(allowedipspeerdevicegenetlink 等)
  2. 如果某个子系统初始化失败,按顺序清理已初始化的资源
  3. 在模块卸载时,清理所有子系统

头文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "version.h"
#include "device.h"
#include "noise.h"
#include "queueing.h"
#include "ratelimiter.h"
#include "netlink.h"

#include <uapi/linux/wireguard.h>

#include <linux/init.h>
#include <linux/module.h>
#include <linux/genetlink.h>
#include <net/rtnetlink.h>
  • 本地头文件version.hdevice.h 等是 WireGuard 模块的内部实现文件,定义了模块的核心功能
  • 内核头文件:
    • uapi/linux/wireguard.h:位于/usr/include/
    • linux/init.hlinux/module.h:提供模块初始化和退出的宏和函数
    • linux/genetlink.h:用于通用网络链接(Generic Netlink)通信
    • net/rtnetlink.h:用于路由和网络设备的管理

模块初始化函数

1
static int __init wg_mod_init(void)
  • static:同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性
  • __init:告诉内核该函数仅在模块加载时调用,加载后会释放其内存
  • 功能:初始化 WireGuard 模块的各个子系统
1
2
3
ret = wg_allowedips_slab_init();
if (ret < 0)
    goto err_allowedips;
  • wg_allowedips_slab_init:初始化 allowedips 的内存分配机制(slab 分配器)
  • 错误处理:如果初始化失败,跳转到 err_allowedips 标签进行清理
1
2
3
4
5
#ifdef DEBUG
ret = -ENOTRECOVERABLE;
if (!wg_allowedips_selftest() || !wg_packet_counter_selftest() || !wg_ratelimiter_selftest())
    goto err_peer;
#endif
  • #ifdef DEBUG:仅在编译时启用调试模式时执行
  • wg_allowedips_selftest 等:运行自测函数,确保核心功能正常
  • -ENOTRECOVERABLE:表示不可恢复的错误
1
wg_noise_init();
  • wg_noise_init:初始化加密相关的 noise 子系统
1
2
3
ret = wg_peer_init();
if (ret < 0)
    goto err_peer;
  • wg_peer_init:初始化 peer(对等节点)管理功能
1
2
3
ret = wg_device_init();
if (ret < 0)
    goto err_device;
  • wg_device_init:初始化 WireGuard 网络设备
1
2
3
ret = wg_genetlink_init();
if (ret < 0)
    goto err_netlink;
  • wg_genetlink_init:初始化通用 Netlink 通信,用于与用户空间交互
1
2
pr_info("WireGuard " WIREGUARD_VERSION " loaded. See www.wireguard.com for information.\n");
pr_info("Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.\n");
  • 日志输出:表明模块加载成功,并打印版本信息和版权声明
1
2
3
4
5
6
7
8
err_netlink:
    wg_device_uninit();
err_device:
    wg_peer_uninit();
err_peer:
    wg_allowedips_slab_uninit();
err_allowedips:
    return ret;
  • 错误处理:如果某个子系统初始化失败,按顺序清理已初始化的资源,避免资源泄漏

模块退出函数

1
static void __exit wg_mod_exit(void)
  • __exit:告诉内核该函数仅在模块卸载时调用
  • 功能:清理 WireGuard 模块的各个子系统
1
2
wg_genetlink_uninit();
wg_device_uninit();
  • wg_genetlink_uninit:清理通用 Netlink 通信
  • wg_device_uninit:清理 WireGuard 网络设备

内核加载驱动

1
2
module_init(wg_mod_init);
module_exit(wg_mod_exit);
  • 指定驱动模块加载/卸载时应当执行的初始化/退出函数

模块元信息

1
MODULE_ALIAS_RTNL_LINK(KBUILD_MODNAME);
  • 为模块注册一个 RTNL(路由和网络链接)别名
  • KBUILD_MODNAME 是模块的名称(由内核构建系统定义),通过这个别名,内核可以识别该模块与 RTNL 链接相关的功能
1
MODULE_ALIAS_GENL_FAMILY(WG_GENL_NAME);
  • 为模块注册一个通用 Netlink(Generic Netlink)家族的别名
  • WG_GENL_NAME 是 WireGuard 的通用 Netlink 家族名称,通过这个别名,用户空间程序可以通过 Netlink 与该模块通信

allowedips.c

头文件

1
2
#include "allowedips.h"
#include "peer.h"
  • allowedips.h:定义了 AllowedIPs 数据结构及其相关操作的接口
  • peer.h:与 WireGuard 的对等节点(peer)相关的定义

常量和全局变量

1
2
enum { MAX_ALLOWEDIPS_DEPTH = 129 };
static struct kmem_cache *node_cache;

swap_endian

1
static void swap_endian(u8 *dst, const u8 *src, u8 bits)
  • 功能:将 IP 地址从大端格式(网络字节序)转换为小端格式(主机字节序)
  • 参数:
    • dst:目标缓冲
    • src:源缓冲区(大端格式)
    • bits:IP 地址的位数(32 位表示 IPv4,128 位表示 IPv6)
  • 实现:
    • 对于 IPv4 地址,使用 be32_to_cpu 转换
    • 对于 IPv6 地址,使用 get_unaligned_be64 逐 64 位转换

copy_and_assign_cidr

1
2
static void copy_and_assign_cidr(struct allowedips_node *node, const u8 *src,
                 u8 cidr, u8 bits)
  • 功能:将 IP 地址和 CIDR 前缀复制到节点中,并计算相关位操作的索引
  • 参数:
    • node:目标节点
    • src:源 IP 地址
    • cidr:CIDR 前缀长度
    • bits:IP 地址的位数(32 或 128)
  • 实现:
    • bit_at_abit_at_b:用于快速定位 IP 地址中某一位的索引
    • memcpy:将 IP 地址复制到节点的 bits 字段

choose

1
static inline u8 choose(struct allowedips_node *node, const u8 *key)
  • 功能:根据节点的位索引,从给定的 IP 地址中提取某一位的值(0 或 1)
  • 参数:
    • node:当前节点
    • key:IP 地址
  • 实现:使用 bit_at_abit_at_b 定位 IP 地址中的某一位,并返回其值

push_rcu

1
2
static void push_rcu(struct allowedips_node **stack,
             struct allowedips_node __rcu *p, unsigned int *len)
  • 功能:将 RCU(Read-Copy-Update)保护的节点指针压入栈中
  • 参数:
    • stack:节点栈
    • p:RCU 保护的节点指针
    • len:栈的当前长度
  • 实现:
    • 检查指针是否有效(rcu_access_pointer
    • 如果栈深度超过限制(调试模式下),发出警告
    • 使用 rcu_dereference_raw 解引用指针,并将节点压入栈

node_free_rcu

1
static void node_free_rcu(struct rcu_head *rcu)
  • 功能:释放一个节点的内存
  • 参数:
    • rcu:RCU 头指针
  • 实现:
    • 使用 container_of 获取包含该 RCU 头的节点结构
    • 调用 kmem_cache_free 释放节点

root_free_rcu

1
static void root_free_rcu(struct rcu_head *rcu)
  • 功能:递归释放整个 AllowedIPs
  • 参数:
    • rcu:RCU 头指针
  • 实现:
    • 使用栈模拟递归,避免内核栈溢出
    • 从根节点开始,依次将左右子节点压入栈中
    • 使用 kmem_cache_free 释放每个节点的内存

root_remove_peer_lists

1
static void root_remove_peer_lists(struct allowedips_node *root)
  • 功能:从 AllowedIPs 树中移除所有与对等节点(peer)相关的列表
  • 参数:
    • rootAllowedIPs 树的根节点
  • 实现:
    1. 使用栈模拟递归遍历整个树
    2. 对每个节点:
      • 将左右子节点压入栈中(如果存在)
      • 如果节点的 peer 指针有效(通过 rcu_access_pointer 检查),从链表中删除该节点的 peer_list
  • 作用:清理 AllowedIPs 树中与对等节点相关的链表,通常在对等节点被移除时调用

fls128

1
static unsigned int fls128(u64 a, u64 b)
  • 功能:计算 128 位整数中最高有效位(MSB,Most Significant Bit)的索引
  • 参数:
    • ab:表示 128 位整数的高 64 位和低 64 位
  • 实现:
    1. 如果高 64 位 a 不为零,调用 fls64(a) 并加上 64(因为它是高位部分)
    2. 如果高 64 位为零,直接调用 fls64(b)
  • 作用:用于比较 128 位整数的位差,通常在处理 IPv6 地址时使用

common_bits

1
2
static u8 common_bits(const struct allowedips_node *node, const u8 *key,
              u8 bits)
  • 功能:计算节点的 IP 地址与给定 IP 地址之间的公共前缀位数
  • 参数:
    • nodeAllowedIPs 树中的节点
    • key:给定的 IP 地址
    • bits:IP 地址的位数(32 表示 IPv4,128 表示 IPv6)
  • 实现:
    1. 对于 IPv4 地址(32 位):
      • 使用异或操作(^)计算节点地址和给定地址的差异
      • 调用 fls 找到最高不同位的索引
      • 用总位数减去该索引,得到公共前缀位数
    2. 对于 IPv6 地址(128 位):
      • 分别计算高 64 位和低 64 位的差异
      • 调用 fls128 找到最高不同位的索引
      • 用 128 减去该索引,得到公共前缀位数
  • 作用:在 AllowedIPs 树中查找或插入节点时,用于比较 IP 地址的公共前缀长度

prefix_matches

1
2
static bool prefix_matches(const struct allowedips_node *node, const u8 *key,
               u8 bits)
  • 功能:检查给定的 IP 地址是否与节点的前缀匹配
  • 参数:
    • nodeAllowedIPs 树中的节点
    • key:给定的 IP 地址
    • bits:IP 地址的位数(32 表示 IPv4,128 表示 IPv6)
  • 实现:
    1. 调用 common_bits 计算节点的 IP 地址与给定地址之间的公共前缀位数
    2. 如果公共前缀位数大于或等于节点的 CIDR 前缀长度,则返回 true,表示匹配
  • 注释:
    • 提到了一种可能的优化方法,即通过预计算掩码来加速匹配,但由于现代处理器对 common_bits 的优化效果很好,因此直接使用现有实现
  • 作用:用于判断某个 IP 地址是否属于节点的子网范围

find_node

1
2
static struct allowedips_node *find_node(struct allowedips_node *trie, u8 bits,
                     const u8 *key)
  • 功能:在 AllowedIPs 树中查找与给定 IP 地址匹配的节点
  • 参数:
    • trieAllowedIPs 树的根节点
    • bits:IP 地址的位数(32 或 128)
    • key:要查找的 IP 地址
  • 局部变量:
    • node:当前正在遍历的节点,初始值为树的根节点
    • found:记录最后一个匹配的节点,初始值为 NULL
  • 实现:
    • 遍历 AllowedIPs 树,逐步查找与给定 IP 地址匹配的节点
    • 如果找到匹配的节点,将其存储在 found

lookup

1
2
static struct wg_peer *lookup(struct allowedips_node __rcu *root, u8 bits,
                  const void *be_ip)
  • 功能:在 AllowedIPs 树中查找与给定 IP 地址匹配的对等节点(peer)
  • 参数:
    • rootAllowedIPs 树的根节点,受 RCU 保护
    • bits:IP 地址的位数(32 表示 IPv4,128 表示 IPv6)
    • be_ip:大端格式的 IP 地址
  • 实现:
    1. 将 IP 地址从大端格式转换为小端格式(swap_endian
    2. 使用 RCU 读锁保护树的并发访问
    3. 调用 find_node 查找与 IP 地址匹配的节点
    4. 如果找到节点,尝试获取其关联的对等节点(wg_peer
    5. 如果对等节点无效,重新尝试查找
    6. 返回找到的对等节点,或返回 NULL
  • 作用:快速定位与 IP 地址匹配的对等节点,用于路由或数据转发

node_placement

1
2
3
static bool node_placement(struct allowedips_node __rcu *trie, const u8 *key,
               u8 cidr, u8 bits, struct allowedips_node **rnode,
               struct mutex *lock)
  • 功能:在 AllowedIPs 树中查找适合插入新节点的位置
  • 参数:
    • trieAllowedIPs 树的根节点,受 RCU 保护
    • key:要插入的 IP 地址
    • cidr:要插入的 CIDR 前缀长度
    • bits:IP 地址的位数(32 或 128)
    • rnode:输出参数,存储找到的父节点
    • lock:用于保护树的并发访问的互斥锁
  • 实现:
    1. 从根节点开始,逐步遍历树
    2. 如果当前节点的 CIDR 小于或等于目标 CIDR,并且前缀匹配,则继续向下遍历
    3. 如果找到与目标 CIDR 完全匹配的节点,设置 exacttrue
    4. 返回找到的父节点,并指示是否存在完全匹配的节点
  • 作用:为插入新节点准备位置,同时检查是否存在冲突的节点

connect_node

1
static inline void connect_node(struct allowedips_node __rcu **parent, u8 bit, struct allowedips_node *node)
  • 功能:将一个节点连接到父节点的指定子节点位置
  • 参数:
    • parent:父节点的子节点指针(bit[0]bit[1]
    • bit:指示连接到左子节点(0)或右子节点(1)
    • node:要连接的节点
  • 实现:
    1. 将父节点指针和子节点位信息打包存储在 node->parent_bit_packed
    2. 使用 rcu_assign_pointer 安全地将子节点指针赋值给父节点
  • 作用:在树中建立父子关系,确保 RCU 安全

choose_and_connect_node

1
static inline void choose_and_connect_node(struct allowedips_node *parent, struct allowedips_node *node)
  • 功能:根据 IP 地址的位值选择子节点位置,并将节点连接到父节点
  • 参数:
    • parent:父节点
    • node:要连接的节点
  • 实现:
    1. 调用 choose 函数,根据父节点的位索引从子节点中选择左(0)或右(1)
    2. 调用 connect_node 将节点连接到父节点的指定子节点位置
  • 作用:简化节点连接操作,自动选择子节点位置

add

1
2
static int add(struct allowedips_node __rcu **trie, u8 bits, const u8 *key,
           u8 cidr, struct wg_peer *peer, struct mutex *lock)
  • 功能:向 AllowedIPs 树中添加一个新的节点
  • 参数:
    • trieAllowedIPs 树的根节点指针
    • bits:IP 地址的位数(32 或 128)
    • key:要添加的 IP 地址
    • cidr:要添加的 CIDR 前缀长度
    • peer:与节点关联的对等节点
    • lock:用于保护树的并发访问的互斥锁
  • 局部变量:
    • node:当前节点
    • parent:找到的父节点
    • down:子节点
    • newnode:新创建的节点
  • 实现:
    1. 调用 node_placement 查找插入位置
    2. 如果存在完全匹配的节点,更新其关联的对等节点
    3. 如果不存在完全匹配的节点,创建新节点并插入到树中
    4. 使用 connect_nodechoose_and_connect_node 建立父子关系
  • 作用:动态更新 AllowedIPs 树,支持添加新的 IP 地址和对等节点

wg_allowedips_init

1
void wg_allowedips_init(struct allowedips *table)
  • 功能:初始化 AllowedIPs
  • 参数:
    • table:指向 AllowedIPs 表的指针
  • 实现:
    1. 将 IPv4 和 IPv6 的根节点(root4root6)初始化为 NULL
    2. 将序列号 seq 初始化为 1
  • 作用:为 AllowedIPs 表分配初始状态,准备后续的插入和查找操作

wg_allowedips_free

1
void wg_allowedips_free(struct allowedips *table, struct mutex *lock)
  • 功能:释放 AllowedIPs 表中的所有节点
  • 参数:
    • table:指向 AllowedIPs 表的指针
    • lock:用于保护表的互斥锁
  • 实现:
    1. 增加序列号 seq,标记表的状态已更改
    2. 将 IPv4 和 IPv6 的根节点设置为 NULL,并使用 RCU_INIT_POINTER 确保 RCU 安全
    3. 如果 IPv4 或 IPv6 的根节点存在:
      • 调用 root_remove_peer_lists 清理节点中的对等节点列表
      • 使用 call_rcu 异步释放整个树
  • 作用:安全地释放 AllowedIPs 表中的所有资源,避免内存泄漏

wg_allowedips_insert_v4

1
2
int wg_allowedips_insert_v4(struct allowedips *table, const struct in_addr *ip,
                u8 cidr, struct wg_peer *peer, struct mutex *lock)
  • 功能:向 AllowedIPs 表中插入一个 IPv4 地址
  • 参数:
    • table:指向 AllowedIPs 表的指针
    • ip:要插入的 IPv4 地址
    • cidr:CIDR 前缀长度
    • peer:与该地址关联的对等节点
    • lock:用于保护表的互斥锁
  • 实现:
    1. 增加序列号 seq,标记表的状态已更改
    2. 将 IPv4 地址从大端格式转换为小端格式(swap_endian
    3. 调用 add 函数,将地址插入到 IPv4 的根节点(root4)中
  • 作用:支持动态添加 IPv4 地址及其关联的对等节点

wg_allowedips_insert_v6

1
2
int wg_allowedips_insert_v6(struct allowedips *table, const struct in6_addr *ip,
                u8 cidr, struct wg_peer *peer, struct mutex *lock)
  • 功能:向 AllowedIPs 表中插入一个 IPv6 地址
  • 参数:
    • table:指向 AllowedIPs 表的指针
    • ip:要插入的 IPv6 地址
    • cidr:CIDR 前缀长度
    • peer:与该地址关联的对等节点
    • lock:用于保护表的互斥锁
  • 实现:
    1. 增加序列号 seq,标记表的状态已更改。
    2. 将 IPv6 地址从大端格式转换为小端格式(swap_endian
    3. 调用 add 函数,将地址插入到 IPv6 的根节点(root6)中
  • 作用:支持动态添加 IPv6 地址及其关联的对等节点

wg_allowedips_remove_by_peer

1
2
void wg_allowedips_remove_by_peer(struct allowedips *table,
                  struct wg_peer *peer, struct mutex *lock)
  • 功能:从 AllowedIPs 表中移除与指定对等节点(peer)关联的所有 IP 地址
  • 参数:
    • table:指向 AllowedIPs 表的指针
    • peer:要移除的对等节点
    • lock:用于保护表的互斥锁
  • 实现:
    1. 如果对等节点的 allowedips_list 为空,则直接返回
    2. 遍历peer->allowedips_list中的所有节点:
      • 从链表中删除节点(list_del_init
      • 将节点的 peer 指针置为 NULL
      • 如果节点有两个子节点,则跳过删除操作
      • 如果节点只有一个子节点,将子节点连接到父节点
      • 如果父节点可以被释放(无子节点且无关联的对等节点),递归释放父节点
    3. 使用 call_rcu 异步释放节点
  • 作用:清理与对等节点相关的所有 IP 地址,支持动态更新 AllowedIPs

wg_allowedips_read_node

1
int wg_allowedips_read_node(struct allowedips_node *node, u8 ip[16], u8 *cidr)
  • 功能:读取 AllowedIPs 节点的 IP 地址和 CIDR 前缀,并返回地址族(IPv4 或 IPv6)
  • 参数:
    • node:要读取的节点
    • ip:输出参数,用于存储节点的 IP 地址
    • cidr:输出参数,用于存储节点的 CIDR 前缀
  • 实现:
    1. 计算 CIDR 前缀占用的字节数(cidr_bytes
    2. 将节点的 IP 地址从小端格式转换为大端格式(swap_endian
    3. 将 IP 地址中超出 CIDR 前缀的部分置为零(memset
    4. 如果 CIDR 前缀不是字节对齐的,清除最后一个字节中无效的位
  • 作用:提取节点的 IP 地址和 CIDR 前缀,用于调试或其他操作

wg_allowedips_lookup_dst

1
2
struct wg_peer *wg_allowedips_lookup_dst(struct allowedips *table,
                     struct sk_buff *skb)
  • 功能:根据数据包的目标地址,在 AllowedIPs 表中查找匹配的对等节点
  • 参数:
    • table:指向 AllowedIPs 表的指针
    • skb:网络数据包(sk_buff
  • 实现:
    1. 检查数据包的协议类型:
      • 如果是 IPv4(ETH_P_IP),调用 lookup 在 IPv4 树中查找目标地址
      • 如果是 IPv6(ETH_P_IPV6),调用 lookup 在 IPv6 树中查找目标地址
    2. 如果协议类型不匹配,返回 NULL
  • 作用:用于根据数据包的目标地址快速定位与之匹配的对等节点

wg_allowedips_lookup_src

1
2
struct wg_peer *wg_allowedips_lookup_src(struct allowedips *table,
                     struct sk_buff *skb)
  • 功能:根据数据包的源地址,在 AllowedIPs 表中查找匹配的对等节点
  • 参数:
    • table:指向 AllowedIPs 表的指针
    • skb:网络数据包(sk_buff
  • 实现:
    1. 检查数据包的协议类型:
      • 如果是 IPv4(ETH_P_IP),调用 lookup 在 IPv4 树中查找源地址
      • 如果是 IPv6(ETH_P_IPV6),调用 lookup 在 IPv6 树中查找源地址
    2. 如果协议类型不匹配,返回 NULL
  • 作用:用于根据数据包的源地址快速定位与之匹配的对等节点

wg_allowedips_slab_init

1
int __init wg_allowedips_slab_init(void)
  • 功能:初始化 AllowedIPs 节点的 slab 缓存
  • 参数:无
  • 实现:
    1. 使用 KMEM_CACHE 宏创建一个 slab 缓存,用于高效分配和释放 allowedips_node 节点
    2. 如果缓存创建成功,返回 0;否则返回 -ENOMEM 表示内存不足
  • 作用:为 AllowedIPs 节点的动态分配提供高效的内存管理机制

wg_allowedips_slab_uninit

1
void wg_allowedips_slab_uninit(void)
  • 功能:销毁 AllowedIPs 节点的 slab 缓存
  • 参数:无
  • 实现:
    1. 调用 rcu_barrier,确保所有挂起的 RCU 回调函数都已完成
    2. 调用 kmem_cache_destroy 销毁 slab 缓存
  • 作用:清理 AllowedIPs 节点的内存管理资源,确保模块卸载时不会发生内存泄漏

自测代码

1
#include "selftest/allowedips.c"
  • 功能:包含 AllowedIPs 的自测代码
  • 作用:
    • 通过编译时包含自测代码,验证 AllowedIPs 数据结构及其相关操作的正确性
    • 自测代码通常包括单元测试和边界测试,用于确保功能的可靠性

noise.c

头文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include "noise.h"
#include "device.h"
#include "peer.h"
#include "messages.h"
#include "queueing.h"
#include "peerlookup.h"

#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/bitmap.h>
#include <linux/scatterlist.h>
#include <linux/highmem.h>
#include <crypto/algapi.h>
  • 本地头文件:noise.h 等文件定义了 WireGuard 的内部结构和函数
  • 内核头文件:
    • linux/rcupdate.h:提供 RCU(Read-Copy-Update)机制,用于高效的并发访问
    • linux/slab.h:提供内存分配功能
    • crypto/algapi.h:提供内核加密算法的接口

Noise 协议模式

1
2
3
4
5
6
7
/* This implements Noise_IKpsk2:
 *
 * <- s
 * ******
 * -> e, es, s, ss, {t}
 * <- e, ee, se, psk, {}
 */
  • Noise_IKpsk2:这是 Noise 协议的一种模式,表示:
    • IK:身份密钥(Identity Key)和临时密钥(Ephemeral Key)
    • psk2:使用预共享密钥(Pre-Shared Key)
  • 消息流:
    • 第一条消息(Initiation):发送方发送临时密钥 e、加密的身份密钥 s 和其他数据
    • 第二条消息(Response):接收方发送临时密钥 e 和其他数据
  • 握手的发起者在左边,应答者在右边,箭头方向表示沟通的方向;
  • 单个字母表示发送的公钥,“s”表示静态公钥,“e”表示临时公钥;
  • 第一行表示:在任何实际握手发生之前,发起者已经知道响应者的静态公钥;
    • 在实践中,这通常通过用户设置来实现,就像SSH密钥一样;
  • 两个字母表示正在进行的DH计算
    • 第一个字母表示发起者的密钥对,第二个字母表示响应者的密钥对
    • DH计算发生在交换的双方,因为这意味着共享密钥被添加到链密钥

全局变量

1
2
3
4
5
static const u8 handshake_name[37] = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s";
static const u8 identifier_name[34] = "WireGuard v1 zx2c4 Jason@zx2c4.com";
static u8 handshake_init_hash[NOISE_HASH_LEN] __ro_after_init;
static u8 handshake_init_chaining_key[NOISE_HASH_LEN] __ro_after_init;
static atomic64_t keypair_counter = ATOMIC64_INIT(0);
  • handshake_name:Noise 协议的名称,指定了使用的算法(Curve25519、ChaCha20-Poly1305、BLAKE2s)
  • identifier_name:WireGuard 的标识符
  • handshake_init_hashhandshake_init_chaining_key:初始化时的哈希值和链式密钥,用于 Noise 协议的握手
  • keypair_counter:用于生成唯一的密钥对 ID

wg_noise_init

1
void __init wg_noise_init(void)
  • 功能:初始化 Noise 协议的全局变量
    • 使用 BLAKE2s 哈希函数生成 handshake_init_chaining_keyhandshake_init_hash
  • __init:内核宏,表示该函数仅在模块加载时调用,加载完成后会释放其占用的内存
  • 实现:
    1. 使用 blake2s 哈希函数计算初始链式密钥(handshake_init_chaining_key
    2. 初始化 BLAKE2s 哈希状态
    3. 更新哈希状态,依次加入链式密钥和标识符
    4. 计算最终的初始哈希值(handshake_init_hash
  • 作用:为 Noise 协议的后续操作提供初始的哈希值和链式密钥

wg_noise_precompute_static_static

1
2
/* Must hold peer->handshake.static_identity->lock */
void wg_noise_precompute_static_static(struct wg_peer *peer)
  • 功能:预计算静态密钥对的 Diffie-Hellman 值。
  • 参数:
    • peer:指向对等节点的指针
  • 实现:
    1. 获取对等节点的写锁(down_write
    2. 检查本地静态密钥是否存在(has_identity
    3. 使用 Curve25519 算法计算本地静态密钥和远程静态密钥的 Diffie-Hellman 值
    4. 如果计算失败,将预计算值清零
    5. 释放写锁(up_write
  • 作用:加速后续的密钥交换操作,减少实时计算的开销

wg_noise_handshake_init

1
2
3
4
5
void wg_noise_handshake_init(struct noise_handshake *handshake,
                 struct noise_static_identity *static_identity,
                 const u8 peer_public_key[NOISE_PUBLIC_KEY_LEN],
                 const u8 peer_preshared_key[NOISE_SYMMETRIC_KEY_LEN],
                 struct wg_peer *peer)

功能:初始化一个新的 Noise 握手结构

参数:

  • handshake:指向握手结构的指针
  • static_identity:本地静态身份
  • peer_public_key:对等节点的公钥
  • peer_preshared_key:预共享密钥
  • peer:指向对等节点的指针

实现:

  1. 清零握手结构
  2. 初始化读写信号量(init_rwsem
  3. 设置握手条目的类型为 INDEX_HASHTABLE_HANDSHAKE

作用:为新的握手操作分配和初始化必要的资源

handshake_zero

1
static void handshake_zero(struct noise_handshake *handshake)
  • 功能:清零握手结构中的敏感数据
  • 参数:
    • handshake:指向需要清零的握手结构
  • 实现:
    1. 将临时私钥(ephemeral_private)和远程临时公钥(remote_ephemeral)清零
    2. 将哈希值(hash)和链式密钥(chaining_key)清零
    3. 将远程索引(remote_index)设置为 0
    4. 将握手状态设置为 HANDSHAKE_ZEROED
  • 作用:确保握手结构中的敏感数据在不再使用时被安全清除,防止信息泄露

wg_noise_handshake_clear

1
void wg_noise_handshake_clear(struct noise_handshake *handshake)
  • 功能:清除握手结构并从索引哈希表中移除相关条目
  • 参数:
    • handshake:指向需要清除的握手结构
  • 实现:
    1. 获取握手结构的写锁(down_write
    2. 从设备的索引哈希表中移除与远程索引(remote_index)相关的条目
    3. 调用 handshake_zero 清零握手结构中的敏感数据
    4. 释放写锁(up_write
  • 作用:在握手完成或失败后,安全地清理握手结构并释放相关资源

keypair_create

1
static struct noise_keypair *keypair_create(struct wg_peer *peer)

功能:为指定的 peer 创建一个新的 Noise 密钥对(noise_keypair

参数:

  • peer:指向与密钥对关联的对等节点

实现:

  1. 使用 kzalloc 分配并清零一个 noise_keypair 结构
  2. 如果分配失败,返回 NULL
  3. 初始化接收计数器的自旋锁(spin_lock_init
  4. 使用原子计数器生成密钥对的唯一内部 ID(internal_id
  5. 设置密钥对的类型为 INDEX_HASHTABLE_KEYPAIR
  6. 将密钥对与指定的对等节点关联
  7. 初始化引用计数(kref_init
  8. 返回创建的密钥对

作用:为对等节点生成一个新的密钥对,用于加密和解密数据

keypair_free_rcu

1
static void keypair_free_rcu(struct rcu_head *rcu)
  • 功能:释放一个 Noise 密钥对的内存
  • 参数:
    • rcu:指向 RCU 头的指针
  • 实现:
    1. 使用 container_of 获取包含该 RCU 头的 noise_keypair 结构
    2. 调用 kfree_sensitive 释放密钥对的内存
  • 作用:安全地释放密钥对的内存,确保敏感数据被清零

keypair_free_kref

1
static void keypair_free_kref(struct kref *kref)
  • 功能:释放一个 Noise 密钥对的引用计数,并从索引哈希表中移除
  • 参数:
    • kref:指向引用计数的指针
  • 实现:
    1. 使用 container_of 获取包含该引用计数的 noise_keypair 结构
    2. 打印调试信息,记录密钥对的销毁操作
    3. 从设备的索引哈希表中移除密钥对的条目
    4. 调用 call_rcu 异步释放密钥对的内存
  • 作用:管理密钥对的生命周期,确保在引用计数为零时安全释放资源

wg_noise_keypair_put

1
void wg_noise_keypair_put(struct noise_keypair *keypair, bool unreference_now)
  • 功能:减少 Noise 密钥对的引用计数,并在引用计数为零时释放资源
  • 参数:
    • keypair:指向要释放的密钥对
    • unreference_now:如果为 true,立即从索引哈希表中移除密钥对
  • 实现:
    1. 如果 keypairNULL,直接返回
    2. 如果 unreference_nowtrue,从设备的索引哈希表中移除密钥对
    3. 调用 kref_put 减少引用计数,如果引用计数为零,调用 keypair_free_kref 释放资源
  • 作用:管理密钥对的引用计数,确保在不再使用时安全释放资源

wg_noise_keypair_get

1
struct noise_keypair *wg_noise_keypair_get(struct noise_keypair *keypair)
  • 功能:增加 Noise 密钥对的引用计数,并返回密钥对的指针
  • 参数:
    • keypair:指向要增加引用计数的密钥对
  • 实现:
    1. 使用 RCU_LOCKDEP_WARN 检查是否在持有 RCU BH 读锁的情况下调用
    2. 如果 keypairNULL 或引用计数为零,返回 NULL
    3. 调用 kref_get_unless_zero 增加引用计数
    4. 返回密钥对的指针
  • 作用:确保在使用密钥对时,其引用计数被正确管理,防止资源被提前释放

wg_noise_keypairs_clear

1
void wg_noise_keypairs_clear(struct noise_keypairs *keypairs)
  • 功能:清除 noise_keypairs 结构中的所有密钥对
  • 参数:
    • keypairs:包含当前、前一个和下一个密钥对的结构
  • 实现:
    • 加锁保护 keypairs
    • 依次清除 next_keypairprevious_keypaircurrent_keypair
    • 调用 wg_noise_keypair_put 释放资源
    • 解锁
  • 作用:释放所有密钥对的资源,确保生命周期管理正确

wg_noise_expire_current_peer_keypairs

1
void wg_noise_expire_current_peer_keypairs(struct wg_peer *peer)
  • 功能:过期并清除与指定对等节点关联的当前和下一个密钥对
  • 参数:
    • peer:指向需要清除密钥对的对等节点
  • 实现:
    • 调用 wg_noise_handshake_clear 清除对等节点的握手状态
    • 调用 wg_noise_reset_last_sent_handshake 重置最后发送的握手状态
    • 加锁保护对等节点的密钥对
    • next_keypaircurrent_keypairsending.is_valid 标志设置为 false,表示密钥对已过期
    • 解锁
  • 作用:确保旧密钥对在不再使用时被标记为无效,支持动态密钥更新

add_new_keypair

1
2
static void add_new_keypair(struct noise_keypairs *keypairs,
                struct noise_keypair *new_keypair)
  • 功能:将一个新的密钥对添加到 noise_keypairs 结构中
  • 参数:
    • keypairs:包含当前、前一个和下一个密钥对的结构
    • new_keypair:要添加的新密钥对
  • 实现:
    1. 加锁保护 keypairs
    2. 解引用 previous_keypaircurrent_keypairnext_keypair
    3. 如果新密钥对是由发起者生成的( i_am_the_initiatortrue):
      • 如果存在 next_keypair,将其降级为 previous_keypair
      • 释放当前密钥对
      • 将新密钥对设置为 current_keypair
    4. 如果新密钥对不是由发起者生成的:
      • 将新密钥对设置为 next_keypair
    5. 调整引用计数,确保旧密钥对被正确释放
    6. 解锁
  • 作用:动态更新密钥对,支持安全的密钥轮换和状态管理

wg_noise_received_with_keypair

1
2
bool wg_noise_received_with_keypair(struct noise_keypairs *keypairs,
                    struct noise_keypair *received_keypair)
  • 功能:检查接收到的数据包是否使用了 next_keypair,如果是,则将其提升为 current_keypair
  • 参数:
    • keypairs:包含当前、前一个和下一个密钥对的结构
    • received_keypair:接收到的数据包使用的密钥对
  • 实现:
    1. 初步检查 received_keypair 是否是 next_keypair
    2. 加锁后再次检查状态
    3. 如果匹配:
      • received_keypair 提升为 current_keypair
      • 清除并释放旧的 current_keypair
    4. 解锁并返回结果
  • 作用:支持动态密钥轮换,确保在接收到确认数据包后正确更新密钥对状态

wg_noise_set_static_identity_private_key

1
2
3
void wg_noise_set_static_identity_private_key(
    struct noise_static_identity *static_identity,
    const u8 private_key[NOISE_PUBLIC_KEY_LEN])
  • 功能:设置静态身份的私钥,并生成对应的公钥
  • 参数:
    • static_identity:指向 noise_static_identity 结构的指针
    • private_key:静态私钥
  • 实现:
    1. private_key 复制到 static_identity->static_private
    2. 调用 curve25519_clamp_secret 对私钥进行裁剪(符合 Curve25519 的要求)
    3. 使用 curve25519_generate_public 生成对应的公钥,并存储在 static_identity->static_public
    4. 设置 static_identity->has_identity,指示静态身份已初始化
  • 作用:初始化静态身份的密钥对,用于 Noise 协议的密钥交换

hmac

1
static void hmac(u8 *out, const u8 *in, const u8 *key, const size_t inlen, const size_t keylen)
  • 功能:使用 BLAKE2s 实现 HMAC(基于密钥的消息认证码)
  • 参数:
    • out:输出缓冲区,用于存储 HMAC 结果
    • in:输入数据
    • key:HMAC 密钥
    • inlen:输入数据的长度
    • keylen:HMAC 密钥的长度
  • 实现:
    1. 初始化 BLAKE2s 状态
    2. 如果密钥长度小于块大小,填充密钥;否则对密钥进行哈希
    3. 使用密钥和输入数据计算 HMAC
    4. 将结果存储在 out
  • 作用:为 Noise 协议提供消息认证功能,确保数据完整性和真实性

kdf

1
2
3
static void kdf(u8 *first_dst, u8 *second_dst, u8 *third_dst, const u8 *data,
        size_t first_len, size_t second_len, size_t third_len,
        size_t data_len, const u8 chaining_key[NOISE_HASH_LEN])
  • 功能:实现 HKDF(HMAC-based Key Derivation Function),用于从输入数据派生多个密钥
  • 参数:
    • first_dstsecond_dstthird_dst:派生密钥的目标缓冲区
    • data:输入数据
    • first_lensecond_lenthird_len:派生密钥的长度
    • data_len:输入数据的长度
    • chaining_key:链式密钥,用作 HMAC 的密钥
  • 实现:
    1. 使用 HMAC 提取输入数据的熵到 secret
    2. 依次扩展密钥:
      • 第一个密钥:key = secret, data = 0x1
      • 第二个密钥:key = secret, data = first-key || 0x2
      • 第三个密钥:key = secret, data = second-key || 0x3
    3. 清除敏感数据(secretoutput
  • 作用:为 Noise 协议生成对称密钥,确保密钥派生的安全性

derive_keys

1
2
3
static void derive_keys(struct noise_symmetric_key *first_dst,
            struct noise_symmetric_key *second_dst,
            const u8 chaining_key[NOISE_HASH_LEN])

功能:从输入的 chaining_key(链式密钥)派生出两个对称密钥,分别存储到 first_dstsecond_dst,并设置它们的有效性和生成时间

参数

  • first_dst:指向第一个目标对称密钥结构体
  • second_dst:指向第二个目标对称密钥结构体
  • chaining_key:输入的链式密钥(哈希长度)

实现

  • 获取当前系统启动以来的纳秒数,作为密钥的“出生时间”
  • 调用 kdf(密钥派生函数),用 chaining_key 作为输入,分别生成两个密钥
  • 设置两个密钥的 birthdate 字段和 is_valid 标志

作用:用于 Noise 协议握手完成后,生成用于加密和解密数据的会话密钥

mix_dh

1
2
3
4
static bool __must_check mix_dh(u8 chaining_key[NOISE_HASH_LEN],
                u8 key[NOISE_SYMMETRIC_KEY_LEN],
                const u8 private[NOISE_PUBLIC_KEY_LEN],
                const u8 public[NOISE_PUBLIC_KEY_LEN])

功能:执行 Diffie-Hellman(DH)密钥交换,将结果混合进链式密钥和对称密钥

参数

  • chaining_key:链式密钥,输入和输出都会被更新
  • key:输出的对称密钥
  • private:本地私钥
  • public:对方公钥

实现

  • 用 Curve25519 算法计算 DH 共享密钥
  • 用 DH 结果作为输入,调用 kdf 派生新的链式密钥和对称密钥
  • 清除临时存储的 DH 结果

作用:在 Noise 协议的每一步密钥协商中,安全地混合 DH 结果,保证密钥的前向安全性

mix_precomputed_dh

1
2
3
static bool __must_check mix_precomputed_dh(u8 chaining_key[NOISE_HASH_LEN],
                        u8 key[NOISE_SYMMETRIC_KEY_LEN],
                        const u8 precomputed[NOISE_PUBLIC_KEY_LEN])

功能:与 mix_dh 类似,但输入的是已经预计算好的 DH 结果

参数

  • chaining_key:链式密钥,输入和输出都会被更新
  • key:输出的对称密钥
  • precomputed:预计算的 DH 共享密钥

实现

  • 检查 precomputed 是否为全零(无效)
  • precomputed 作为输入,调用 kdf 派生新的链式密钥和对称密钥

作用:用于优化,避免重复计算静态密钥对之间的 DH 结果

mix_hash

1
static void mix_hash(u8 hash[NOISE_HASH_LEN], const u8 *src, size_t src_len)

功能:将输入数据 src 混合进当前的哈希值 hash,用于 Noise 协议的 transcript hash

参数

  • hash:当前哈希值,输入和输出
  • src:要混合的新数据
  • src_len:新数据的长度

实现

  • 用 BLAKE2s 哈希算法,先更新当前哈希,再混合新数据,最后输出到 hash

作用:保证握手过程的所有消息都被哈希链记录,防止消息篡改

mix_psk

1
2
3
static void mix_psk(u8 chaining_key[NOISE_HASH_LEN], u8 hash[NOISE_HASH_LEN],
            u8 key[NOISE_SYMMETRIC_KEY_LEN],
            const u8 psk[NOISE_SYMMETRIC_KEY_LEN])

功能:将预共享密钥(psk)混合进链式密钥、哈希和对称密钥

参数

  • chaining_key:链式密钥,输入和输出都会被更新
  • hash:握手哈希,输入和输出都会被更新
  • key:输出的对称密钥
  • psk:预共享密钥

实现

  • psk 作为输入,调用 kdf 派生新的链式密钥、临时哈希和对称密钥
  • 用临时哈希混合进握手哈希
  • 清除临时哈希

作用:增强握手安全性,防止量子攻击等

handshake_init

1
2
3
static void handshake_init(u8 chaining_key[NOISE_HASH_LEN],
               u8 hash[NOISE_HASH_LEN],
               const u8 remote_static[NOISE_PUBLIC_KEY_LEN])

功能:初始化握手的链式密钥和哈希

参数

  • chaining_key:输出的链式密钥
  • hash:输出的哈希
  • remote_static:对方的静态公钥

实现

  • 复制全局初始哈希和链式密钥
  • 将对方静态公钥混合进哈希

作用:为每次握手初始化 transcript hash 和 chaining key

message_encrypt

1
2
3
static void message_encrypt(u8 *dst_ciphertext, const u8 *src_plaintext,
                size_t src_len, u8 key[NOISE_SYMMETRIC_KEY_LEN],
                u8 hash[NOISE_HASH_LEN])

功能:用对称密钥加密消息,并更新握手哈希

参数

  • dst_ciphertext:输出密文
  • src_plaintext:输入明文
  • src_len:明文长度
  • key:加密用的对称密钥
  • hash:握手哈希,输入和输出

实现

  • 用 chacha20poly1305 算法加密明文,附加数据为当前哈希
  • 用密文混合进哈希

作用:保证消息机密性和完整性,并将密文纳入握手哈希

message_decrypt

1
2
3
static bool message_decrypt(u8 *dst_plaintext, const u8 *src_ciphertext,
                size_t src_len, u8 key[NOISE_SYMMETRIC_KEY_LEN],
                u8 hash[NOISE_HASH_LEN])

功能:用对称密钥解密消息,并更新握手哈希

参数

  • dst_plaintext:输出明文
  • src_ciphertext:输入密文
  • src_len:密文长度
  • key:解密用的对称密钥
  • hash:握手哈希,输入和输出

实现

  • 用 chacha20poly1305 算法解密密文,附加数据为当前哈希
  • 用密文混合进哈希

作用:保证消息机密性和完整性,并将密文纳入握手哈希

message_ephemeral

1
2
3
4
static void message_ephemeral(u8 ephemeral_dst[NOISE_PUBLIC_KEY_LEN],
                  const u8 ephemeral_src[NOISE_PUBLIC_KEY_LEN],
                  u8 chaining_key[NOISE_HASH_LEN],
                  u8 hash[NOISE_HASH_LEN])

功能:处理握手中的临时密钥,将其混合进哈希和链式密钥

参数

  • ephemeral_dst:输出临时密钥
  • ephemeral_src:输入临时密钥
  • chaining_key:链式密钥,输入和输出
  • hash:握手哈希,输入和输出

实现

  • 拷贝临时密钥(如有需要)
  • 用临时密钥混合进哈希
  • 用临时密钥混合进链式密钥

作用:保证临时密钥的参与被记录在握手哈希和链式密钥中

tai64n_now

1
static void tai64n_now(u8 output[NOISE_TIMESTAMP_LEN])

功能:生成当前时间的 TAI64N 格式时间戳

参数

  • output:输出时间戳(12字节)

实现

  • 获取当前真实时间
  • 纳秒部分做对齐,防止信息泄露
  • 按 TAI64N 格式编码到输出缓冲区

作用:用于握手消息中的时间戳,防止重放攻击

wg_noise_handshake_create_initiation⭐

1
2
bool wg_noise_handshake_create_initiation(struct message_handshake_initiation *dst,
				     struct noise_handshake *handshake)

功能:创建握手初始化消息(Initiation),用于发起 WireGuard 握手

参数

  • dst:输出,生成的握手初始化消息结构体
  • handshake:当前握手状态结构体

实现

  1. 等待随机数准备好(保证密钥安全)
  2. 加锁,确保静态身份和握手结构体安全访问
  3. 检查静态身份是否存在
  4. 设置消息类型
  5. 初始化链式密钥和哈希
  6. 生成临时密钥对(ephemeral),并填充到消息
  7. 计算 DH(es),混合密钥
  8. 加密静态公钥(s)
  9. 计算预计算 DH(ss),混合密钥
  10. 生成当前时间戳并加密({t})
  11. 分配 sender_index
  12. 更新握手状态
  13. 解锁,清理敏感数据

作用:发起 WireGuard 握手,生成安全的初始化消息

wg_noise_handshake_consume_initiation⭐

1
2
3
struct wg_peer * 
wg_noise_handshake_consume_initiation(struct message_handshake_initiation *src,
				      struct wg_device *wg)

功能:处理收到的握手初始化消息,验证并提取对端信息

参数

  • src:收到的握手初始化消息
  • wg:WireGuard 设备结构体

实现

  1. 加锁,检查本地静态身份
  2. 初始化链式密钥和哈希
  3. 处理对端临时公钥(e),混合进哈希和链式密钥
  4. 计算 DH(es),混合密钥
  5. 解密静态公钥(s)
  6. 查找对应 peer
  7. 计算预计算 DH(ss),混合密钥
  8. 解密时间戳({t})
  9. 检查重放攻击和洪水攻击
  10. 更新握手结构体中的对端信息和状态
  11. 返回 peer 指针

作用:验证并接受对端发起的握手,准备响应

wg_noise_handshake_create_response⭐

1
2
bool wg_noise_handshake_create_response(struct message_handshake_response *dst,
					struct noise_handshake *handshake)

功能:创建握手响应消息(Response),用于回应对端的握手请求

参数

  • dst:输出,生成的握手响应消息结构体
  • handshake:当前握手状态结构体

实现

  1. 等待随机数准备好
  2. 加锁,确保静态身份和握手结构体安全访问
  3. 检查握手状态是否正确
  4. 设置消息类型和 receiver_index
  5. 生成临时密钥对(e)
  6. 处理对端临时公钥(ee),混合密钥
  7. 处理静态密钥与对端临时公钥(se),混合密钥
  8. 混合预共享密钥(psk)
  9. 加密空消息({})
  10. 分配 sender_index
  11. 更新握手状态
  12. 解锁,清理敏感数据

作用:回应对端的握手请求,完成握手的第二步

wg_noise_handshake_consume_response⭐

1
2
3
struct wg_peer *
wg_noise_handshake_consume_response(struct message_handshake_response *src,
				    struct wg_device *wg)

功能:处理收到的握手响应消息,完成握手过程

参数

  • src:收到的握手响应消息
  • wg:WireGuard 设备结构体

实现

  1. 加锁,检查本地静态身份
  2. 查找对应的握手结构体
  3. 复制握手相关数据
  4. 检查握手状态
  5. 处理对端临时公钥(e),混合密钥
  6. 计算 DH(ee、se),混合密钥
  7. 混合预共享密钥(psk)
  8. 解密空消息({})
  9. 更新握手结构体中的对端信息和状态
  10. 返回 peer 指针

作用:完成握手流程,准备进入数据传输阶段

wg_noise_handshake_begin_session⭐

1
2
bool wg_noise_handshake_begin_session(struct noise_handshake *handshake,
				      struct noise_keypairs *keypairs)

功能:根据已完成的握手,生成新的密钥对,进入会话阶段

参数

  • handshake:当前握手状态结构体
  • keypairs:密钥对管理结构体

实现

  1. 加锁,检查握手状态是否允许进入会话
  2. 创建新的密钥对
  3. 判断自己是否为发起者,决定密钥派生方向
  4. 调用 derive_keys 派生发送和接收密钥
  5. 清空握手状态
  6. 将新密钥对加入 keypairs 管理结构
  7. 更新索引表
  8. 解锁

作用:将握手阶段的密钥转换为会话密钥,正式进入加密通信

最后更新于 May 15, 2025 17:24 UTC
Built with Hugo
Theme Stack designed by Jimmy