IPsec 配置备忘 Part2 - 证书

作为系列的第二篇文章,讲解基本的证书原理和配置方法。没看过第一部分的快去看~
传送门:IPsec 配置备忘 Part1

证书认证基础

我们在 Part1 中看到,PSK 认证的基本思路是使用一个只有通信双方才知道的暗号,如果能确认对方确实知道这个暗号,那么认证就成功了。证书认证的思路非常不同:假设 A 需要向 B 证明自己的身份,同时 A 知道 B 信任 C,那么 A 可以向 C 索取一份“介绍信”,当 B 询问 A 的身份时,A 可以向 B 展示这份 C 出具的“介绍信”,如果 B 能够确认这份“介绍信”确实是由 C 出具的,那么认证就成功了。注意这个认证是单向的,假设 A 也信任 C,那么 B 也可以通过向 C 索取“介绍信”来向 A 证明自己的身份。在 PKI 体系中,A 和 B 持有各自的“私钥”,C 作为 Certificate Authority (CA) 向 A/B 颁发证书(即“介绍信”)。同时,CA 也会向自己颁发一份证书并分发给 A/B,A/B 使用 CA 的证书来确认 B/A 出示的证书确实为 C 所颁发。

IPsec 配置备忘 Part1 - IKEv2 基础

俗话说得好,配置 IPsec 隧道只有零次和无数次,在被 strongSwan 折磨了 N 次以后,我终于决定要把之前试过的配置都记录下来,于是就有了这个系列。我计划基本上每个 PART 会介绍一个(或几个)特定场景下的配置,配置文件样例以 strongSwan vici 为主,之后可能会介绍 iOS, Android 或者是 Mikrotik 路由器的配置方法,如果我能坚持不鸽写到那里的话(画外音:你这 FLAG 立得……) FLAG 回收了。如果各位有想看的配置场景欢迎留言告诉我,会考虑先写。

IKEv2 与 IPsec 基础

严格来说 IKEv2 不是 VPN,它的全称是 Internet Key Exchange,只是一种用于交换密钥的协议罢了。密钥在计算机里一般就表示为一串固定长度的二进制数据,密钥交换就是指在两台设备之间约定一个相同的二进制串,就像两个密友之间约定暗号一样。一旦密钥交换完毕,IKE 的使命就结束了,具体怎么用约定好的密钥加密数据不是 IKE 解决的问题。在 Linux 系统上,实际的数据包加密解密是由内核的 XFRM 框架负责的,你可以使用ip xfrm命令看到配置好的密钥以及加解密使用的算法。事实上,不使用 IKEv2 而完全手动“交换”密钥是可行的,比如朴素VPN:一个纯内核级静态隧道。你可以看到作者直接使用ip xfrm {policy,state} add指令设定密钥,然后内核就会自动用设定的密钥加密流量。

然而,手动管理内核状态是复杂的,人工分发密钥也不怎么安全,这时就轮到 strongSwan 登场啦(或者说,任何实现了 IKE 的 Daemon 服务)。两台服务器的 strongSwan 使用 IKEv2 协议交换密钥,解决了密钥分发的问题。随后 strongSwan 会把交换得来的密钥设定进内核,这样内核就会自动加密指定的流量了。

从数据包层面上看,IKE 是7层协议,密钥交换使用特殊的 UDP 包完成。而一般被加密的数据包会使用 ESP 封装,ESP 头一般紧跟在 IP 头后。ESP 也可以被封装进 UDP 用以穿越 NAT。

没有 TUN 设备

内核 XFRM 的工作方式和基于 TUN 设备的 VPN 很不一样。一般基于 TUN 的 VPN 会加密所有进入 TUN 设备的流量,因此你可以直接使用路由表来控制哪些流量走 VPN,哪些不走。而 XFRM 的匹配基于策略(i.e. 源地址+目标地址+一些别的),如果某个数据包匹配到了一个策略,这个数据包就会根据这个策略指定的方式被加密。

比方说有A [fd00::1]B [fd00::2],如果你从 A 发送一个数据包到 B,普通情况下这个数据包是明文的。如果你在 A 配置了src=fd00::1,dst=fd00::2,encrypt=<...>的策略并再发一个数据包,这个包就会自动被加密。B 收到了这个数据包,但是它并不知道该如何解密,所以你必须同时在 B 配置一条src=fd00::1,dst=fd00::2,decrypt=<...>的策略,这样 B 才能解密。对于从 B 到 A 的流量也需要类似的两条策略。使用 IKEv2 的话,这些策略 strongSwan 都会自动帮你设置好,无需操心。于是你会发现,尽管我们仍然在使用节点本身的 IP,但是流量却已经被加密了。

对于那些必须使用路由表或是策略匹配不是很有效的场景, Route-based IPsec VPN 也是存在的。我也许会在未来的某一期讲。

Linux + Windows 10 多系统安装 U 盘

Update: Windows 的引导程序似乎有些问题,如果在同一块 U 盘上写入多个 ISO 分区的话,似乎引导会错乱,最终启动的安装程序版本不是引导程序所在分区的版本。所以暂时一个 U 盘还是只能放一个 Windows 版本。垃圾巨硬。

日常折腾中总免不了要用 LiveCD 修理一下系统,或者是重装一下 Windows 之类的。这时候制作一个引导用的 U 盘基本是最方便的选项了。有不少工具都能创建 U 盘引导,比如 ArchLinux 的 ISO 镜像可以直接用dd写入,Windows 的安装盘也能用 Rufus 创建。不过在使用上还是有些不便,比如dd会覆盖整个U盘,在ISO之外不能再存储其他文件。Rufus 只能在 Windows 上运行,而且一只 U 盘也只能放一份 ISO。于是尝试搞明白怎么把 Linux 的 LiveCD 和 Windows 的安装 ISO 写入到同一只 U 盘就很有必要了。

我个人使用的设备都支持 UEFI,所以这里制作的启动盘也只支持 UEFI 启动,需要 MBR 模式启动的读者请往它处寻。当然,Secure Boot 是要关掉的。制作过程我使用 Linux,纯 Windows 用户现在也可以退出了。基本上,我们需要创建一个 EFI 系统分区(EFI System Partition, ESP),其中包含基本的引导程序(Grub2)和 Linux LiveCD 的 ISO 文件。由于 Windows 的安装程序无法以 ISO 形式被引导,因此我们需要给每个 Windows ISO 文件创建一个分区,并将 ISO 中的内容解压进去。但是分区一旦创建不像文件那么好修改,所以创建每个 Windows ISO 分区的时候我都留了一些额外空间,以备以后 ISO 大小变化,这也意味着这些空间就基本浪费了。That's sad but I guess it's how things work.

另外,购买一个优质的 U 盘还是有必要的,不然不管是创建启动盘还是安装系统都会慢得让你痛不欲生。建议用之前先给 U 盘测一下速,什么拷贝速度只有 2MB/s 的金士顿可以直接进垃圾桶了。至于 U 盘大小取决于你要放多少个 ISO 文件和多少个 Windows 分区,一般 Linux 镜像大小在 500MB~3GB 的都有,Windows 10 的分区一般每个需要 5~6GB.

在 Raspberry Pi 4B 上安装 ArchLinux

Neofetch
很久之前就买了一个树莓派,不过一直在吃灰,正好最近有空就再拿出来折腾一下。原装系统是 32 位的,那么就必定要换一个 64 位的啦,不然对不起这 64 位的 CPU 呀。秉承“Arch大法好”的理念,我就决定用 Archlinux ARM 了。我非常建议先用原版系统更新 Bootloader 和 EEPROM到最新版本。这样可以避免各种奇怪的 bug 和使用一些新加入的功能,比如从网络启动什么的。

ArchLinux ARM 其实已经提供了树莓派的安装教程,基本上只要跟着做即可,我用的是 AArch64 镜像,并且把根文件系统从 ext4 换成了 f2fs,希望在 SD 卡上能有一点点加成效果。装完以后发现串口没有输出,自然不能忍,继续折腾。RPi 4B 一共有两个串口控制器,一个 PL011,另一个被称作 MiniUART。默认情况下,PL011 连接到蓝牙模块,并且 MiniUART 被禁用,但是我们可以通过 config.txt 加载 dtb overlay 来调整。一些常见的配置有:

  • 启用 MiniUART 串口,PL011 继续负责蓝牙
  • 禁用蓝牙,让 PL011 负责串口通信
  • 启用 MiniUART,让 MiniUART 负责蓝牙,PL011 负责串口

但是 ArchLinux ARM 使用 U-Boot 来启动内核,并不遵循 config.txt (╯°Д°)╯ ┻━┻

那么我们只能把 U-Boot 干掉了 (<ゝω・)☆

MikroTik RB4011 访客网络配置备忘

前言

Speedtest
由于之前陆陆续续添置了不少电子设备,以及更换 ISP 的原因,机架上连了5台设备,每台各负责一点点事情,不管是配置还是调试都很麻烦。再加上旧路由器不能很好同时处理千兆 NAT 和 VLAN,于是最近入手了一台 RB4011iGS+5HacQ2HnD-IN,把这一堆乱七八糟的设备统统换掉。主要需求有三点:

  1. 划分2个 VLAN,一个内部网络,一个访客网络。
  2. IPv4 和 IPv6 双栈接入。
  3. 因为路由器直接暴露在 Internet 上了,所以防火墙一定要配好,包括 VLAN 之间的访问也是靠防火墙来控制的。

WinRAR 恢复记录添加及使用教程

本文中的所有图片及文字均使用 CC0 发布,可任意转载使用。

本文属于疫情期间的摸鱼之作,旨在推广 RAR 压缩格式的正确压缩方法,让资源分享更轻松一些。

获取 WinRAR

先说一下为什么用 WinRAR 而不用 7zip, 因为 7zip 没有恢复记录。在百度网盘等平台分享文件时,文件可能发生损坏,没有恢复记录的话只能尝试重新下载浪费时间,而有恢复记录的话有大概率可以成功修复,正确解压。

国内特供版的 WinRAR 可以免费使用,但是有广告。如果不想要广告,你可以从以下链接下载官方无广告简体中文版

https://www.win-rar.com/fileadmin/winrar-versions/sc/sc20200409/rrlb/winrar-x64-590sc.exe

注意,如果你没有rarreg.key文件进行注册的话依然是有广告的。至于具体的注册方法请自行搜寻。

CMake 项目生成脚本

C++ 比较尴尬的一点就是缺少比较“傻瓜”的工具库,不得不依靠第三方来补充。想快速开始写个小的 Demo 的时候光找库就花去不少时间。于是糊了一个脚本去自动生成这些基础的东西。放在这里方便自己以后参考。

用到的库列表:

  • {fmt}: 字符串格式化库
  • spdlog: 日志
  • backward-cpp: 崩溃时的堆栈输出
  • benchmark: 快速性能测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#!/bin/bash

print_help() {
echo "Usage:"
echo " cproject <type> <name>"
echo "Types:"
echo " bin, bench"
}

die() {
echo "Unexpected error"
exit 2
}

# Ninja does not work with backward-cpp
new_bin() {
local name="$1"
mkdir "$name" || die
cd "$name" || die
git clone 'https://github.com/fmtlib/fmt.git' third_party/fmt || die
git clone 'https://github.com/bombela/backward-cpp.git' third_party/backward-cpp || die
git clone 'https://github.com/gabime/spdlog.git' third_party/spdlog || die
cat << EOF > CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(${name})

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug)
endif()

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "\${CMAKE_CXX_FLAGS_DEBUG} -ggdb -O0")
set(CMAKE_EXE_LINKER_FLAGS "\${CMAKE_EXE_LINKER_FLAGS} -rdynamic")

add_subdirectory(third_party/fmt)
add_subdirectory(third_party/spdlog)
add_subdirectory(third_party/backward-cpp)

add_executable(main)
target_sources(main PRIVATE main.cpp \${BACKWARD_ENABLE})
target_link_libraries(main PRIVATE fmt spdlog -ldw)
EOF

cat << 'EOF' > main.cpp
#include <iostream>
#include <string>
#include "fmt/format.h"
#include "spdlog/spdlog.h"

#define CRASH(...) \
{ ::spdlog::critical(__VA_ARGS__); \
int _ [[maybe_unused]] = \
*reinterpret_cast<int*>(0); }

inline int get_return_code() {
CRASH("unimplemented function: {}", __func__);
return 0;
}

int main(int argc, char* argv[]) {
fmt::print("{}, {}\n\n", "hello", "world");

spdlog::set_level(spdlog::level::debug);
spdlog::debug("debug message");
spdlog::info("info message");
spdlog::warn("warning message");
spdlog::error("Some error message with arg: {}", 1);
return get_return_code();
}
EOF
echo
echo "Skeleton generated. Run the command and you shall see " \
"the stacktrace demo."
echo " cd ${name} && cmake -B build && " \
"make -C build -j main && build/main"
}

new_bench() {
local name="$1"
mkdir "$name" || die
cd "$name" || die
git clone 'https://github.com/fmtlib/fmt.git' third_party/fmt || die
git clone 'https://github.com/bombela/backward-cpp.git' third_party/backward-cpp || die
git clone 'https://github.com/gabime/spdlog.git' third_party/spdlog || die
git clone 'https://github.com/google/benchmark.git' third_party/benchmark || die
git clone 'https://github.com/google/googletest.git' third_party/benchmark/googletest || die
cat << EOF > CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(${name})

if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -Wall -Wextra -g")
set(CMAKE_EXE_LINKER_FLAGS "\${CMAKE_EXE_LINKER_FLAGS} -rdynamic")

set(BENCHMARK_ENABLE_LTO true)

add_subdirectory(third_party/fmt)
add_subdirectory(third_party/spdlog)
add_subdirectory(third_party/backward-cpp)
add_subdirectory(third_party/benchmark)

add_executable(bench)
target_sources(bench PRIVATE bench.cpp \${BACKWARD_ENABLE})
target_link_libraries(bench PRIVATE fmt spdlog benchmark::benchmark_main -ldw)
EOF

cat << 'EOF' > bench.cpp
#include "benchmark/benchmark.h"
#include <cinttypes>
#include <cstdlib>

constexpr size_t ARRAY_LEN = 1024*64;
static void BM_SeqArrayAccess(benchmark::State& state) {
int stride = state.range(0);
uint64_t arr[ARRAY_LEN];
for (auto _ : state) {
for (size_t idx = 0; idx < ARRAY_LEN; idx += stride) {
arr[idx] = random();
}
}
}
BENCHMARK(BM_SeqArrayAccess)
->Arg(1)
->Arg(2)
->Arg(3)
->Arg(4)
->Arg(5)
->Arg(6)
->Arg(7)
->Arg(8);
EOF
echo
echo "Skeleton generated."
echo " cd ${name} && cmake -B build && " \
"make -C build -j bench && build/bench"
echo "You may also need to change your CPU scheduler:"
echo " sudo cpupower frequency-set -g performance"
}

if [[ "$2" == "" ]]; then
echo "Missing project name"
echo
print_help
exit 1
fi

if [[ -e "$2" ]]; then
echo "File \"$2\" already exists"
exit 1
fi

if [[ "$1" == "bin" ]]; then
new_bin "$2"
elif [[ "$1" == "bench" ]]; then
new_bench "$2"
else
echo "Unknown type: $1"
echo
print_help
exit 1
fi