Jinmiaoluo’s blog

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 流程。...

二月 29, 2024

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 负责接收)。...

一月 21, 2024

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 节点。 设备 b 通过默认网关(192....

一月 21, 2024

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 来构建含有调试信息的内核。...

一月 11, 2024

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 > ....

十一月 29, 2023

远程剪切板

前言 我本地是一台 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 包替换 即可。...

十一月 28, 2023

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....

十月 22, 2023

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 开发中,用户权限的管理和判断会通枚举类来实现。...

七月 13, 2023

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 在这里:...

七月 9, 2023

备份系统

前言 最近借鉴 Arch Linux 的 infrastructure 仓库中的 Ansible 代码,部署了本地基础设施用的备份系统,本文是对这个过程中的记录和思考。会包含实施前的考量,具体如何实施,以及备份系统本身如何监控。 备份系统 采用什么方案 采用了 borg 作为备份的解决方案。borg 是一个支持去重、压缩、加密的备份程序。 关于加密的意义: 备份系统本质上是将本地的数据拷贝一份存储到其他位置。备份加密指的是将备份的数据,在发送到其他位置前做一次加密,因为备份一般被存储在不可信的第三方,比如 Aliyun、AWS 的对象存储,所以需要加密。 关于去重和压缩的意义: 第三方存储一般是按照使用空间的大小收费的,占用更少的空间意味着更低的成本。 borg 创建备份的时候,会将大文件分为小的分块(chunk),chunk 的大小根据内容动态调整,每个 chunk 有一个哈希值用于去重,哈希值相同的块只存储一份,最后压缩得到的数据大小才是最终的备份大小。 如图所示,Original size 即原始大小 226.77G 的数据,Compressed size 压缩后的大小为 138.41G,Deduplicated size 去重后的大小为 56.10 GB,实际上每月只需支付 56.10 GB 数据的存储费用。 采用什么备份规则 备份是针对整个系统的,第一次是整个系统的全量备份,后续的备份是 borg chunking 机制提供的增量备份。 具体的备份规则见 Arch Linux infrastructure 仓库的 borg-backup.sh.j2 文件 大概的内容如下: 判断需要备份的服务器上是否有数据库类的服务,如果有,基于官方的 dump 工具创建每个数据库的 dump 文件(确保数据库数据的一致性、版本兼容性) 判断 filesystem 是否是 btrfs 如果:服务器根目录是基于 btrfs 文件系统。由于 btrfs 制作快照的时候,子卷的快照是不会包含所有内嵌子卷的内容的,因此: 遍历 / 及其下面所有的子卷 在所有子卷内制作快照。比如:/ 的快照就是 /backup-snapshot,子卷 /home/jinmiaoluo 的快照就是 /home/jinmiaoluo/backup-snapshot 把所有子卷的快照通过 mount -o bind 映射到 /backup 内,比如: 子卷 / 的快照 /backup-snapshot 对应的路径就是 /backup 子卷 /home/jinmiaoluo 的快照 /home/jinmiaoluo/backup-snapshot 对应的路径就是 /backup/home/jinmiaoluo 对 /backup 创建备份,即实现了对整个系统进行备份的目的 否则:服务器根目录不是基于 btrfs 文件系统。直接对 / 创建备份 基于 systemd timer 每天发起 1 次备份操作 需要指出的是:快照可以快速的实现本地的状态留存,就像拍了一张相片一样。但在创建远端的系统备份的时候,则是对整个系统的一次比对和拷贝。好在通过 borg chunking 机制,实际上从第二次开始,会被压缩、加密、发送并存储到远端的内容只是修改的内容,因此,实际的内网带宽压力并不大。比对、压缩的操作是在发起备份的服务器上进行的,这也在一定程度上把计算的负载很好的分摊了下去,即使是对一定规模的服务器集群进行备份,也不会对 borg 的服务端产生计算压力,borg 服务端只要准备好足够的存储空间即可。...

六月 27, 2023