WireGuard:C 实现
内核模块逻辑 指定模块初始化的入口函数 wg_device_init 注册虚拟网络设备 rtnl_link_register(&link_ops) net_device 结构体的初始化操作 wg_setup 指定 ip link add 命令时对应的初始化操作(是主要的功能逻辑所在) wg_newlink 指定虚拟网络设备启动时的操作 wg_open 指定虚拟网络设备停止时的操作 wg_stop 指定虚拟网络设备开始发送第一个封包的操作 wg_xmit wireguard 初始化的操作是: 通过 ip link add dev wg0 type wireguard 添加虚拟网络设备 通过 ip addr add 10.0.0.4/24 dev wg0 配置虚拟网络设备地址 通过 wg set wg0 添加 wireguard 配置 通过 ip link set wg0 up 启动虚拟网络设备 因此我们应先看 wg_newlink 的逻辑,再看 wg_open 的逻辑
WireGuard:Golang 实现
前言 本文基于 WireGuard Golang 实现,平台是 Linux,Git 版本是:12269c2 内容 关于收发包 通过创建 TUN 设备,跟系统的网络栈交互。 数据抽象的介绍 关于限速器 WireGuard 在最终进行数据的加密、解密之前,需要通过加密协议 Noise Protocol 基于加密算法协商出对称加密密钥(临时的),这个协商的过程(握手)会涉及到加/解密,哈希运算。为了避免针对响应方 CPU 负载的 DoS 攻击,响应方会根据实时负载情况对握手的请求进行限速,根据请求方的来源 IP 实例化对应的令牌桶(每个来源 IP 对应一个令牌桶),基于令牌桶对握手包进行频率限制。默认的限制策略是每秒钟 20 个握手包,每个时刻(即每纳秒)最多允许 5 个握手包的突发流量。 关于索引表 每个数据包的包头第 4 到第 8 字节会存储一个 32 bits 的无符号整数作为索引,通过索引和索引表(map[uint32]IndexTableEntry),可以查到对应的 Peer、Handshake、Keypair。 索引值本身是个随机值,在: func (table *IndexTable) NewIndexForHandshake(peer *Peer, handshake *Handshake) (uint32, error) 函数中被创建,使用该索引值时使用了两阶段锁(索引表首先基于读锁检查索引是否已被占用,如果该索引未被使用,再基于写锁创建对应的索引条目)。 索引表的作用是? 索引表会在两种场合被使用。 第一种是握手时,WireGuard 初始方创建握手包和消费响应包,完成 Triple-way DH 流程。初始方创建、发送握手包,然后等待、消费响应包,中间有个发送并等待响应的中间状态,这个中间状态的值被存储在 Handshake 实例中,Handshake 作为索引表的值(值是一个结构体,其中一个字段),通过索引和该握手包绑定在一起。当开始消费响应包时,通过响应包头部的接收方 ID(响应方将初始握手包中的发送方 ID 作为接收方 ID 返回),查询索引表从而获得创建握手包时的 Handshake 实例,从而完成整个 Triple-way DH 流程。 第二种是接收到数据包时。响应方在消费握手包并发送响应后,会在本地创建 Peer、Keypair,并更新索引表。接收到的数据包头部的索引值,允许 WireGuard 通过索引表找到握手时创建的 Keypair 和 Peer。Keypair 用于确认该数据包是否有对应的密钥对,加密密钥是否已经过期。Peer 用于定位数据包的队列。数据包基于 Keypair 进行认证和解密,解密后的数据包将添加到 Peer 的队列内等待消费。 ...
WireGuard:远程办公网络
前言 本文是对上一篇文章:WireGuard:局域网跨城通信 的补充。将围绕远程办公的开发者,即不在办公网内的开发者,如何基于 WireGuard® 跨城访问办公网中的服务。 场景 团队提倡远程办公,开发者位于全国各地。公司在某个城市设有办公地点,办公地点的局域网内自建了虚拟化,并部署了开发相关的一系列服务(GitLab,CI,K8s)。 不同城市的开发者需访问办公地点局域网中的多个服务。 不同城市的开发者之间需互相访问。 挑战 开发者如何稳定的跨公网访问局域网内所有设备(如同在办公地点的局域网内)。 局域网内的 WireGuard 节点位于运营商 NAT 设备之后,不支持 NAT 穿透且无公网 IP。 局域网内只运行一个 WireGuard 节点(而不是将所有服务加入 WireGuard 网络)。 如何解决局域网网关不支持静态路由规则的问题。 如何避免路由冲突。 架构 思路 假设有两个局域网 LAN1,LAN2 分别是公司在深圳(LAN1)和北京(LAN2)的办公网。 有三台服务器,z(IP 前的字母)是公网服务器(有公网 IP),a 和 c 是内网服务器(无公网 IP)。公网服务器(z)和内网服务器(a 和 c)组建了一个 WireGuard 网络,且三台服务器开启封包转发(IP Forward)。 e 和 f 是两个远程办公的开发者,其中 e 在广州,f 在上海,每个开发者是一个 WireGuard Client,通过各自的 WireGuard 客户端配置加入 WireGuard 网络。 开发者访问服务 假设广州的开发者 e(10.70.0.4)要访问北京办公网(LAN2)的中的设备 d(192.168.200.244)。 e 通过 WireGuard 客户端接入 WireGuard 网络。客户端与 WireGuard Server 建立连接,并向系统路由表写入相关路由规则(192.168.200.0/24 由 wg0 负责接收)。 ...
WireGuard:局域网跨城通信
前言 物理专线可以实现多个局域网之间的设备互通(像在同一个局域网内),但由于复杂的流程(运营商审批与实施),对于个人开发者和中小型团队并不友好。 本文将介绍如何基于 WireGuard® 的 “云专线”,以较低的成本(无需审批,每年只需 60 元1)实现多个局域网之间设备互通。 挑战 局域网层面的流量转发。即局域网 A 中任意设备可以访问局域网 B 中任意设备(如同在同一个局域网内)。 局域网内的 WireGuard 节点位于运营商 NAT 设备之后,不支持 NAT 穿透且无公网 IP。 每个局域网内只运行一个 WireGuard 节点。 场景 公司有多个办公地点(分布在不同城市),每个办公地点一个局域网,局域网内自建了私有服务(K8s,GitLab,CI)。 不同办公地点的开发者之间需互相访问。 不同办公地点的私有服务之间需互相访问。 A 办公地点的开发者需要访问 B 办公地点的私有服务。 A 办公地点的开发者需要接收来自 B 办公地点私有服务的回调。 架构 思路 假设有两个局域网 LAN1,LAN2,对应的网段是 192.168.201.0/24,192.168.200.0/24。其中 z(IP 前的字母)是公网服务器(有公网 IP),a 和 c 是内网服务器(无公网 IP)。 公网服务器(z)和内网服务器(a 和 c)组建了一个 WireGuard 网络,且三台服务器开启封包转发(IP Forward)。 LAN1 的设备 b(192.168.201.55)请求 LAN2 的设备 d(192.168.200.244),比如在 b 上执行:ping 192.168.200.244,b 和 d 是局域网内的普通设备,而不是 WireGuard 节点。 ...
WireGuard:调试
前言 WireGuard® 是 Linux 的内核模块之一(主流发行版都已内置)。 最近会通过调试的方式来理解 WireGuard 的代码,本文是对这个过程(调试 WireGuard 内核模块源码)的记录。 挑战 内核(含调试信息)如何构建 QEMU 如何启动调试环境 GDB 如何进行远程调试 VSCode 如何整合图形化调试流程 内容 思路 基于最新的 WireGuard 内核模块代码构建出含有 DEBUG symbol 的 Linux 内核 通过 QEMU 和内核(上一步构建出来的)启动虚拟机 通过 GDB 进行远程调试 准备 本地开发环境需要满足的条件: 安装了构建内核所需依赖包1 安装了虚拟化所需 QEMU 包 安装了调试所需 GDB 包 当前开发环境支持虚拟化(如果是虚拟机,需确保当前环境支持虚拟化嵌套2) 操作 克隆 WireGuard 内核模块源码到本地(实际上就是含有最新 WireGuard 修改的内核源码): git clone git://git.zx2c4.com/wireguard-linux 构建内核(需要等一会,我本地构建一次需 3 分钟)命令如下: cd wireguard-linux # 为了确保后面的操作体验一致,这里我默认所有操作基于:fa41884c1c6d commit git checkout fa41884c1c6d DEBUG_KERNEL=yes ARCH=x86_64 make -C tools/testing/selftests/wireguard/qemu build -j$(nproc) 如何你对手动构建内核感兴趣,可以看看 Arch Linux Wiki。 源码仓库中的 Makefile 封装了构建内核所需的所有操作, 上面的命令直接调用仓库中的 Makefile 来构建含有调试信息的内核。 ...
WireGuard:抓包和实时解密
前言 最近在读 WireGuard® 的源码和论文时使用了 Wireshark 进行抓包分析,本文是对这个过程(如何通过 Wireshark 实时抓取 WireGuard 加密包并解密1)的一点记录。 内容 思路 在虚拟化准备两台虚拟机用以构建一个 WireGuard VPN 测试网络,其中虚拟机 A 作为请求发起方,虚拟机 B 作为响应方。 在本地 Windows 笔记本上安装 Wireshark(包含 sshdump 功能)通过 SSH 的形式连接虚拟机 A,实现 Wireshark 远程实时抓包。 挑战 前向保密性2对流量分析的挑战。WireGuard 为了保证前向保密性,每隔 120s 会更换一个临时密钥(一次性的)来加密数据包。因此,如果要获取实时的明文数据,Wireshark 需要动态的获取临时密钥,并基于临时密钥实时解密。 需要更换内核。WireGuard 是 Linux 内核模块,笔者的测试环境需要重新编译并替换内核才能基于 Kprobe 的方式动态获取临时密钥。 具体操作 编译并更换内核 我是在虚拟机 A 上抓包,所以需要编译和替换虚拟机 A 的内核,操作见:Arch Linux Wiki 我使用的内核是这个: # 切到 stable 分支,我实践的时候,内核版本是 6.5,commit:fa41884c1c6d git clone git://git.zx2c4.com/wireguard-linux 按照 Arch Linux Wiki 操作,大概有这些步骤: 准备内核配置 zcat /proc/config.gz > .config 开启 WireGuard Debug make nconfig 构建内核 make -j 构建内核模块 make modules 安装内核模块 make modules_install 构建引导镜像 make bzImage 安装引导镜像 cp -v arch/x86/boot/bzImage /boot/vmlinuz-linux-wg 准备引导配置 cp /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux-wg.preset && vim /etc/mkinitcpio.d/linux-wg.preset 更新引导配置 mkinitcpio -p linux-wg && grub-mkconfig -o /boot/grub/grub.cfg 一切顺利你会得到一个这样的内核: ...
远程剪切板
前言 我本地是一台 Windows 11 的 ThinkPad T480,开发环境运行在一台 Linux 服务器上,基于 SSH 的方式进行访问和控制。我希望在 Linux 服务器的 Vim 中,通过 y 复制代码到本地 Windows 11 的剪切板内,通过 p 将本地 Windows 11 剪切板中最新的内容黏贴到服务器的 vim 内。本文将提供一种低成本的解决方案。 思路 Windows 的 WSL 支持直接运行 Linux X11 GUI 程序,并且在 Linux X11 GUI 中,是可以直接访问 Windows 本地的剪切板的。这意味着在 WSL 内,我们可以通过 SSH 的 X11Forward 将远端的 X11 转发到 WSL 内,这样 Linux 服务器上的 X11 GUI 程序就可以经 WSL 中转,访问 Windows 11 本地的剪切板了。 因此,我们只需: 基于 WSL 来登录 Linux 服务器 开启 SSH 的 X11Forward 将 Linux 服务器的 Vim 包用 GVim 包替换 即可。 ...
Miniflux:开源 SaaS 项目源码笔记
前言 我从 9 月的 27 号开始阅读 Miniflux 项目的代码,阅读成熟的开源项目就像在阅读一本适合自己的书,节奏虽慢但充实。因为快接近阅读这个项目代码的尾声,故整理这篇博文作为过程的记录和分享。 开始之前简单介绍一下 Miniflux。Miniflux 是一个开源的 RSS 订阅服务实现,类似 Tiny Tiny RSS,使用过 RSS 订阅服务的朋友应该不陌生,部署 Miniflux 服务相当于拥有了自己的 Feedly 服务。该项目是基于 Go 和 Vanilla JS (没有使用任何框架的原生 JavaScript API) 的 SaaS APP 实现,提倡尽可能少的外部依赖,简单可维护易扩展,所以这个项目会有很多基础功能的实现(这些细节在参与 Web 框架本身的开发时才会涉及,平时基于 Web 框架的项目开发,比如 Django/Rails 会自带这些功能,直接用就可以)。 本文会通过少量的代码,记录阅读过程中发现的有趣的点,如果你对这些点感兴趣,再去阅读代码即可。我更希望这篇博客像在介绍一本书,让你知道这本书哪里有趣,然后你再自己去判断适不适合你,或者有哪些点适合你。读书不一定要整本通读,发现感兴趣的点,只读感兴趣的部分,也是一种不错的选择。 软件工程应该是件有趣的事,或者说,作为从业者我们应该去发现那些有趣的点,穿透高度抽象带来的认知迷雾。 内容 不使用 ORM 如何实现数据库抽象 如果你想在项目内使用 Golang 标准库的 database/sql 来封装 SQL 操作,可以参考。 Miniflux 中的数据库表的管理,代码分成两类,第一类是对这个表的 SQL 操作的封装,在 storage 包内,不同的表对应不同的 storage 文件夹内的文件,比如 users 表对应 user.go 文件,user.go 中一个操作: // SetLastLogin updates the last login date of a user. func (s *Storage) SetLastLogin(userID int64) error { query := `UPDATE users SET last_login_at=now() WHERE id=$1` _, err := s.db.Exec(query, userID) if err != nil { return fmt.Errorf(`store: unable to update last login date: %v`, err) } return nil } 第二类是对表结构和相关的改动请求的抽象,在 model 包内,不同的表对应不同的 model 包内的文件,比如 users 表对应 user.go 文件,user.go 中的两个抽象: ...
Python 枚举模块的实现
前言 为什么看 Python 枚举模块实现?Python 是面向对象(OOP)的语言,标准库中的枚举模块的实现涉及很多 OOP 的概念,比如:mixin、鸭子类型、元类、魔法函数等。阅读枚举模块的源码是了解面向对象编程一个比较有效的方式。本文基于 CPython 的 3.11 分支,对应的 Python 版本是 3.11.4 版本。 会包含如下内容: 枚举类 鸭子类型 魔法函数 协议 装饰器 描述符类 元类 概念 枚举类 枚举类的成员是枚举类的特殊实例。也就是说枚举类的属性名不变,值变成了枚举类的一个特殊实例。在枚举类中,原先定义的属性名和属性值被存储到该特殊实例的 name 属性和 value 属性内。 from enum import Enum class Role(Enum): ADMIN = 'admin' DEVELOPER = 'developer' GUEST = 'guest' print(type(Role.ADMIN)) # 输出: <enum 'Role'> # 原本的属性名被更新为一个 Role 的实例 print(Role.ADMIN.name) # 'ADMIN' print(Role.ADMIN.value) # 'admin' # 原先属性的名和值被保存到特殊实例的 name 属性和 value 属性内 枚举类的用法 在 Python Web 开发中,用户权限的管理和判断会通枚举类来实现。 不同的权限对应不同的枚举类实例,比如上面的例子中的:GUEST,DEVELOPER,ADMIN。 在使用 ORM(比如:SQLAlchemy)定义用户表时,用户角色一列会被设为枚举类相关的数据库类型。写数据库的时候,ORM 会将枚举实例的值存到数据库。即: ...
IAM 服务
前言 这篇文章是阅读 Arch Linux 团队部署、管理、整合身份与访问管理( Identity and Access Management)服务相关代码的笔记,会包含: 如何部署和管理 Keycloak 服务:Ansible 相关 如何管理 Keycloak 上的用户和权限:Terraform 相关 如何将 Web 站点接入通过 Keycloak:OAuth2.0、OIDC、Python、Authlib 相关 如何基于 Keycloak 中的信息,实现权限管理:装饰器和枚举类相关 IAM 服务 有什么用 先说说没有 IAM 服务会有什么问题。 一般团队内会有多种服务,比如:GitLab、Jira、Confluence、MatterMost、Grafana、Kibana、Argo 等,会产生的问题: 每个团队成员入职/离职时,需在每个服务上添加/删除账号 每个团队成员使用服务时,需在每个服务上输入一遍账号密码 每个团队成员需要管理多个服务的密码,如果密码泄漏且相同时,每个服务需要手动修改一遍 IAM 服务的作用: 统一的权限管理。授权服务器可以管理用户对各个应用的访问权限,例如,哪些用户可以访问哪些应用,用户在各个应用中的角色和权限是什么,另外用户账号的增删也只需要一遍。 单点登录。用户只需要在授权服务器上进行一次登录,就可以访问所有连接到该授权服务器的应用。提高用户体验,减少了用户需要记住多个凭证的负担。 降低业务复杂度。授权码流程可以将认证功能分离出业务代码,业务代码只需根据 OAuth2.0、OIDC 获取到的用户信息,更新和管理用户权限相关的用户表,而无需再每个业务内实现2FA、验证码等验证逻辑。 微服务改造的基础。微服务旨在将业务拆分成独立小服务(独立的数据库)从而降低复杂度、提高容错率和扩展性。IAM 在微服务架构中为各个微服务提供了集中、统一的身份验证和权限管理,从而实现安全性,减少重复验证的开销,并提高系统的可维护性和扩展性。 Keycloak 是什么 Keycloak 是 RedHat 开源的身份认证和授权服务实现。通过部署 Keycloak,我们就可以拥有自己的 IAM 服务,Arch Linux 团队的 IAM 服务就是基于 Keycloak。 Keycloak 的部署 Arch Linux 团队的 Keycloak 通过 Ansible 进行部署和管理,相关的 Playbook 和 Role 在这里: ...