wireguard内核源码
WireGuard/wireguard-linux-GitHub
main.c
实现了 WireGuard 内核模块的初始化和退出逻辑,主要包括以下步骤:
- 初始化各个子系统(
allowedips、peer、device、genetlink 等)
- 如果某个子系统初始化失败,按顺序清理已初始化的资源
- 在模块卸载时,清理所有子系统
头文件
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.h、device.h 等是 WireGuard 模块的内部实现文件,定义了模块的核心功能
- 内核头文件:
uapi/linux/wireguard.h:位于/usr/include/
linux/init.h 和 linux/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:表示不可恢复的错误
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;
|
MAX_ALLOWEDIPS_DEPTH:定义了 AllowedIPs 树的最大深度。129 是支持 IPv6 地址(128 位)加上根节点的深度
node_cache:一个内核的 slab 缓存,用于高效分配和释放 allowedips_node 节点
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_a 和 bit_at_b:用于快速定位 IP 地址中某一位的索引
memcpy:将 IP 地址复制到节点的 bits 字段
choose
1
|
static inline u8 choose(struct allowedips_node *node, const u8 *key)
|
- 功能:根据节点的位索引,从给定的 IP 地址中提取某一位的值(0 或 1)
- 参数:
- 实现:使用
bit_at_a 和 bit_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)
|
- 功能:释放一个节点的内存
- 参数:
- 实现:
- 使用
container_of 获取包含该 RCU 头的节点结构
- 调用
kmem_cache_free 释放节点
root_free_rcu
1
|
static void root_free_rcu(struct rcu_head *rcu)
|
- 功能:递归释放整个
AllowedIPs 树
- 参数:
- 实现:
- 使用栈模拟递归,避免内核栈溢出
- 从根节点开始,依次将左右子节点压入栈中
- 使用
kmem_cache_free 释放每个节点的内存
root_remove_peer_lists
1
|
static void root_remove_peer_lists(struct allowedips_node *root)
|
- 功能:从
AllowedIPs 树中移除所有与对等节点(peer)相关的列表
- 参数:
- 实现:
- 使用栈模拟递归遍历整个树
- 对每个节点:
- 将左右子节点压入栈中(如果存在)
- 如果节点的
peer 指针有效(通过 rcu_access_pointer 检查),从链表中删除该节点的 peer_list
- 作用:清理
AllowedIPs 树中与对等节点相关的链表,通常在对等节点被移除时调用
fls128
1
|
static unsigned int fls128(u64 a, u64 b)
|
- 功能:计算 128 位整数中最高有效位(MSB,Most Significant Bit)的索引
- 参数:
a 和 b:表示 128 位整数的高 64 位和低 64 位
- 实现:
- 如果高 64 位
a 不为零,调用 fls64(a) 并加上 64(因为它是高位部分)
- 如果高 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 地址之间的公共前缀位数
- 参数:
node:AllowedIPs 树中的节点
key:给定的 IP 地址
bits:IP 地址的位数(32 表示 IPv4,128 表示 IPv6)
- 实现:
- 对于 IPv4 地址(32 位):
- 使用异或操作(
^)计算节点地址和给定地址的差异
- 调用
fls 找到最高不同位的索引
- 用总位数减去该索引,得到公共前缀位数
- 对于 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 地址是否与节点的前缀匹配
- 参数:
node:AllowedIPs 树中的节点
key:给定的 IP 地址
bits:IP 地址的位数(32 表示 IPv4,128 表示 IPv6)
- 实现:
- 调用
common_bits 计算节点的 IP 地址与给定地址之间的公共前缀位数
- 如果公共前缀位数大于或等于节点的 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 地址匹配的节点
- 参数:
trie:AllowedIPs 树的根节点
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)
- 参数:
root:AllowedIPs 树的根节点,受 RCU 保护
bits:IP 地址的位数(32 表示 IPv4,128 表示 IPv6)
be_ip:大端格式的 IP 地址
- 实现:
- 将 IP 地址从大端格式转换为小端格式(
swap_endian)
- 使用 RCU 读锁保护树的并发访问
- 调用
find_node 查找与 IP 地址匹配的节点
- 如果找到节点,尝试获取其关联的对等节点(
wg_peer)
- 如果对等节点无效,重新尝试查找
- 返回找到的对等节点,或返回
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 树中查找适合插入新节点的位置
- 参数:
trie:AllowedIPs 树的根节点,受 RCU 保护
key:要插入的 IP 地址
cidr:要插入的 CIDR 前缀长度
bits:IP 地址的位数(32 或 128)
rnode:输出参数,存储找到的父节点
lock:用于保护树的并发访问的互斥锁
- 实现:
- 从根节点开始,逐步遍历树
- 如果当前节点的 CIDR 小于或等于目标 CIDR,并且前缀匹配,则继续向下遍历
- 如果找到与目标 CIDR 完全匹配的节点,设置
exact 为 true
- 返回找到的父节点,并指示是否存在完全匹配的节点
- 作用:为插入新节点准备位置,同时检查是否存在冲突的节点
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:要连接的节点
- 实现:
- 将父节点指针和子节点位信息打包存储在
node->parent_bit_packed 中
- 使用
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 地址的位值选择子节点位置,并将节点连接到父节点
- 参数:
- 实现:
- 调用
choose 函数,根据父节点的位索引从子节点中选择左(0)或右(1)
- 调用
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 树中添加一个新的节点
- 参数:
trie:AllowedIPs 树的根节点指针
bits:IP 地址的位数(32 或 128)
key:要添加的 IP 地址
cidr:要添加的 CIDR 前缀长度
peer:与节点关联的对等节点
lock:用于保护树的并发访问的互斥锁
- 局部变量:
node:当前节点
parent:找到的父节点
down:子节点
newnode:新创建的节点
- 实现:
- 调用
node_placement 查找插入位置
- 如果存在完全匹配的节点,更新其关联的对等节点
- 如果不存在完全匹配的节点,创建新节点并插入到树中
- 使用
connect_node 或 choose_and_connect_node 建立父子关系
- 作用:动态更新
AllowedIPs 树,支持添加新的 IP 地址和对等节点
wg_allowedips_init
1
|
void wg_allowedips_init(struct allowedips *table)
|
- 功能:初始化
AllowedIPs 表
- 参数:
- 实现:
- 将 IPv4 和 IPv6 的根节点(
root4 和 root6)初始化为 NULL
- 将序列号
seq 初始化为 1
- 作用:为
AllowedIPs 表分配初始状态,准备后续的插入和查找操作
wg_allowedips_free
1
|
void wg_allowedips_free(struct allowedips *table, struct mutex *lock)
|
- 功能:释放
AllowedIPs 表中的所有节点
- 参数:
table:指向 AllowedIPs 表的指针
lock:用于保护表的互斥锁
- 实现:
- 增加序列号
seq,标记表的状态已更改
- 将 IPv4 和 IPv6 的根节点设置为
NULL,并使用 RCU_INIT_POINTER 确保 RCU 安全
- 如果 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:用于保护表的互斥锁
- 实现:
- 增加序列号
seq,标记表的状态已更改
- 将 IPv4 地址从大端格式转换为小端格式(
swap_endian)
- 调用
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:用于保护表的互斥锁
- 实现:
- 增加序列号
seq,标记表的状态已更改。
- 将 IPv6 地址从大端格式转换为小端格式(
swap_endian)
- 调用
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:用于保护表的互斥锁
- 实现:
- 如果对等节点的
allowedips_list 为空,则直接返回
- 遍历
peer->allowedips_list中的所有节点:
- 从链表中删除节点(
list_del_init)
- 将节点的
peer 指针置为 NULL
- 如果节点有两个子节点,则跳过删除操作
- 如果节点只有一个子节点,将子节点连接到父节点
- 如果父节点可以被释放(无子节点且无关联的对等节点),递归释放父节点
- 使用
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 前缀
- 实现:
- 计算 CIDR 前缀占用的字节数(
cidr_bytes)
- 将节点的 IP 地址从小端格式转换为大端格式(
swap_endian)
- 将 IP 地址中超出 CIDR 前缀的部分置为零(
memset)
- 如果 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)
- 实现:
- 检查数据包的协议类型:
- 如果是 IPv4(
ETH_P_IP),调用 lookup 在 IPv4 树中查找目标地址
- 如果是 IPv6(
ETH_P_IPV6),调用 lookup 在 IPv6 树中查找目标地址
- 如果协议类型不匹配,返回
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)
- 实现:
- 检查数据包的协议类型:
- 如果是 IPv4(
ETH_P_IP),调用 lookup 在 IPv4 树中查找源地址
- 如果是 IPv6(
ETH_P_IPV6),调用 lookup 在 IPv6 树中查找源地址
- 如果协议类型不匹配,返回
NULL
- 作用:用于根据数据包的源地址快速定位与之匹配的对等节点
wg_allowedips_slab_init
1
|
int __init wg_allowedips_slab_init(void)
|
- 功能:初始化
AllowedIPs 节点的 slab 缓存
- 参数:无
- 实现:
- 使用
KMEM_CACHE 宏创建一个 slab 缓存,用于高效分配和释放 allowedips_node 节点
- 如果缓存创建成功,返回
0;否则返回 -ENOMEM 表示内存不足
- 作用:为
AllowedIPs 节点的动态分配提供高效的内存管理机制
wg_allowedips_slab_uninit
1
|
void wg_allowedips_slab_uninit(void)
|
- 功能:销毁
AllowedIPs 节点的 slab 缓存
- 参数:无
- 实现:
- 调用
rcu_barrier,确保所有挂起的 RCU 回调函数都已完成
- 调用
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_hash 和 handshake_init_chaining_key:初始化时的哈希值和链式密钥,用于 Noise 协议的握手
keypair_counter:用于生成唯一的密钥对 ID
wg_noise_init
1
|
void __init wg_noise_init(void)
|
- 功能:初始化 Noise 协议的全局变量
- 使用 BLAKE2s 哈希函数生成
handshake_init_chaining_key 和 handshake_init_hash
__init:内核宏,表示该函数仅在模块加载时调用,加载完成后会释放其占用的内存
- 实现:
- 使用
blake2s 哈希函数计算初始链式密钥(handshake_init_chaining_key)
- 初始化 BLAKE2s 哈希状态
- 更新哈希状态,依次加入链式密钥和标识符
- 计算最终的初始哈希值(
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 值。
- 参数:
- 实现:
- 获取对等节点的写锁(
down_write)
- 检查本地静态密钥是否存在(
has_identity)
- 使用 Curve25519 算法计算本地静态密钥和远程静态密钥的 Diffie-Hellman 值
- 如果计算失败,将预计算值清零
- 释放写锁(
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:指向对等节点的指针
实现:
- 清零握手结构
- 初始化读写信号量(
init_rwsem)
- 设置握手条目的类型为
INDEX_HASHTABLE_HANDSHAKE
作用:为新的握手操作分配和初始化必要的资源
handshake_zero
1
|
static void handshake_zero(struct noise_handshake *handshake)
|
- 功能:清零握手结构中的敏感数据
- 参数:
- 实现:
- 将临时私钥(
ephemeral_private)和远程临时公钥(remote_ephemeral)清零
- 将哈希值(
hash)和链式密钥(chaining_key)清零
- 将远程索引(
remote_index)设置为 0
- 将握手状态设置为
HANDSHAKE_ZEROED
- 作用:确保握手结构中的敏感数据在不再使用时被安全清除,防止信息泄露
wg_noise_handshake_clear
1
|
void wg_noise_handshake_clear(struct noise_handshake *handshake)
|
- 功能:清除握手结构并从索引哈希表中移除相关条目
- 参数:
- 实现:
- 获取握手结构的写锁(
down_write)
- 从设备的索引哈希表中移除与远程索引(
remote_index)相关的条目
- 调用
handshake_zero 清零握手结构中的敏感数据
- 释放写锁(
up_write)
- 作用:在握手完成或失败后,安全地清理握手结构并释放相关资源
keypair_create
1
|
static struct noise_keypair *keypair_create(struct wg_peer *peer)
|
功能:为指定的 peer 创建一个新的 Noise 密钥对(noise_keypair)
参数:
实现:
- 使用
kzalloc 分配并清零一个 noise_keypair 结构
- 如果分配失败,返回
NULL
- 初始化接收计数器的自旋锁(
spin_lock_init)
- 使用原子计数器生成密钥对的唯一内部 ID(
internal_id)
- 设置密钥对的类型为
INDEX_HASHTABLE_KEYPAIR
- 将密钥对与指定的对等节点关联
- 初始化引用计数(
kref_init)
- 返回创建的密钥对
作用:为对等节点生成一个新的密钥对,用于加密和解密数据
keypair_free_rcu
1
|
static void keypair_free_rcu(struct rcu_head *rcu)
|
- 功能:释放一个 Noise 密钥对的内存
- 参数:
- 实现:
- 使用
container_of 获取包含该 RCU 头的 noise_keypair 结构
- 调用
kfree_sensitive 释放密钥对的内存
- 作用:安全地释放密钥对的内存,确保敏感数据被清零
keypair_free_kref
1
|
static void keypair_free_kref(struct kref *kref)
|
- 功能:释放一个 Noise 密钥对的引用计数,并从索引哈希表中移除
- 参数:
- 实现:
- 使用
container_of 获取包含该引用计数的 noise_keypair 结构
- 打印调试信息,记录密钥对的销毁操作
- 从设备的索引哈希表中移除密钥对的条目
- 调用
call_rcu 异步释放密钥对的内存
- 作用:管理密钥对的生命周期,确保在引用计数为零时安全释放资源
wg_noise_keypair_put
1
|
void wg_noise_keypair_put(struct noise_keypair *keypair, bool unreference_now)
|
- 功能:减少 Noise 密钥对的引用计数,并在引用计数为零时释放资源
- 参数:
keypair:指向要释放的密钥对
unreference_now:如果为 true,立即从索引哈希表中移除密钥对
- 实现:
- 如果
keypair 为 NULL,直接返回
- 如果
unreference_now 为 true,从设备的索引哈希表中移除密钥对
- 调用
kref_put 减少引用计数,如果引用计数为零,调用 keypair_free_kref 释放资源
- 作用:管理密钥对的引用计数,确保在不再使用时安全释放资源
wg_noise_keypair_get
1
|
struct noise_keypair *wg_noise_keypair_get(struct noise_keypair *keypair)
|
- 功能:增加 Noise 密钥对的引用计数,并返回密钥对的指针
- 参数:
- 实现:
- 使用
RCU_LOCKDEP_WARN 检查是否在持有 RCU BH 读锁的情况下调用
- 如果
keypair 为 NULL 或引用计数为零,返回 NULL
- 调用
kref_get_unless_zero 增加引用计数
- 返回密钥对的指针
- 作用:确保在使用密钥对时,其引用计数被正确管理,防止资源被提前释放
wg_noise_keypairs_clear
1
|
void wg_noise_keypairs_clear(struct noise_keypairs *keypairs)
|
- 功能:清除
noise_keypairs 结构中的所有密钥对
- 参数:
keypairs:包含当前、前一个和下一个密钥对的结构
- 实现:
- 加锁保护
keypairs
- 依次清除
next_keypair、previous_keypair 和 current_keypair
- 调用
wg_noise_keypair_put 释放资源
- 解锁
- 作用:释放所有密钥对的资源,确保生命周期管理正确
wg_noise_expire_current_peer_keypairs
1
|
void wg_noise_expire_current_peer_keypairs(struct wg_peer *peer)
|
- 功能:过期并清除与指定对等节点关联的当前和下一个密钥对
- 参数:
- 实现:
- 调用
wg_noise_handshake_clear 清除对等节点的握手状态
- 调用
wg_noise_reset_last_sent_handshake 重置最后发送的握手状态
- 加锁保护对等节点的密钥对
- 将
next_keypair 和 current_keypair 的 sending.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:要添加的新密钥对
- 实现:
- 加锁保护
keypairs
- 解引用
previous_keypair、current_keypair 和 next_keypair
- 如果新密钥对是由发起者生成的(
i_am_the_initiator为 true):
- 如果存在
next_keypair,将其降级为 previous_keypair
- 释放当前密钥对
- 将新密钥对设置为
current_keypair
- 如果新密钥对不是由发起者生成的:
- 调整引用计数,确保旧密钥对被正确释放
- 解锁
- 作用:动态更新密钥对,支持安全的密钥轮换和状态管理
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:接收到的数据包使用的密钥对
- 实现:
- 初步检查
received_keypair 是否是 next_keypair
- 加锁后再次检查状态
- 如果匹配:
- 将
received_keypair 提升为 current_keypair
- 清除并释放旧的
current_keypair
- 解锁并返回结果
- 作用:支持动态密钥轮换,确保在接收到确认数据包后正确更新密钥对状态
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:静态私钥
- 实现:
- 将
private_key 复制到 static_identity->static_private
- 调用
curve25519_clamp_secret 对私钥进行裁剪(符合 Curve25519 的要求)
- 使用
curve25519_generate_public 生成对应的公钥,并存储在 static_identity->static_public 中
- 设置
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 密钥的长度
- 实现:
- 初始化 BLAKE2s 状态
- 如果密钥长度小于块大小,填充密钥;否则对密钥进行哈希
- 使用密钥和输入数据计算 HMAC
- 将结果存储在
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_dst、second_dst、third_dst:派生密钥的目标缓冲区
data:输入数据
first_len、second_len、third_len:派生密钥的长度
data_len:输入数据的长度
chaining_key:链式密钥,用作 HMAC 的密钥
- 实现:
- 使用 HMAC 提取输入数据的熵到
secret
- 依次扩展密钥:
- 第一个密钥:
key = secret, data = 0x1
- 第二个密钥:
key = secret, data = first-key || 0x2
- 第三个密钥:
key = secret, data = second-key || 0x3
- 清除敏感数据(
secret 和 output)
- 作用:为 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_dst 和 second_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 格式时间戳
参数
实现
- 获取当前真实时间
- 纳秒部分做对齐,防止信息泄露
- 按 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:当前握手状态结构体
实现
- 等待随机数准备好(保证密钥安全)
- 加锁,确保静态身份和握手结构体安全访问
- 检查静态身份是否存在
- 设置消息类型
- 初始化链式密钥和哈希
- 生成临时密钥对(ephemeral),并填充到消息
- 计算 DH(es),混合密钥
- 加密静态公钥(s)
- 计算预计算 DH(ss),混合密钥
- 生成当前时间戳并加密({t})
- 分配 sender_index
- 更新握手状态
- 解锁,清理敏感数据
作用:发起 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 设备结构体
实现
- 加锁,检查本地静态身份
- 初始化链式密钥和哈希
- 处理对端临时公钥(e),混合进哈希和链式密钥
- 计算 DH(es),混合密钥
- 解密静态公钥(s)
- 查找对应 peer
- 计算预计算 DH(ss),混合密钥
- 解密时间戳({t})
- 检查重放攻击和洪水攻击
- 更新握手结构体中的对端信息和状态
- 返回 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:当前握手状态结构体
实现
- 等待随机数准备好
- 加锁,确保静态身份和握手结构体安全访问
- 检查握手状态是否正确
- 设置消息类型和 receiver_index
- 生成临时密钥对(e)
- 处理对端临时公钥(ee),混合密钥
- 处理静态密钥与对端临时公钥(se),混合密钥
- 混合预共享密钥(psk)
- 加密空消息({})
- 分配 sender_index
- 更新握手状态
- 解锁,清理敏感数据
作用:回应对端的握手请求,完成握手的第二步
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 设备结构体
实现
- 加锁,检查本地静态身份
- 查找对应的握手结构体
- 复制握手相关数据
- 检查握手状态
- 处理对端临时公钥(e),混合密钥
- 计算 DH(ee、se),混合密钥
- 混合预共享密钥(psk)
- 解密空消息({})
- 更新握手结构体中的对端信息和状态
- 返回 peer 指针
作用:完成握手流程,准备进入数据传输阶段
wg_noise_handshake_begin_session⭐
1
2
|
bool wg_noise_handshake_begin_session(struct noise_handshake *handshake,
struct noise_keypairs *keypairs)
|
功能:根据已完成的握手,生成新的密钥对,进入会话阶段
参数
handshake:当前握手状态结构体
keypairs:密钥对管理结构体
实现
- 加锁,检查握手状态是否允许进入会话
- 创建新的密钥对
- 判断自己是否为发起者,决定密钥派生方向
- 调用
derive_keys 派生发送和接收密钥
- 清空握手状态
- 将新密钥对加入 keypairs 管理结构
- 更新索引表
- 解锁
作用:将握手阶段的密钥转换为会话密钥,正式进入加密通信