我一直是一个有些特立独行的人,因此对一些小众、非主流的事物有着偏好:通常不使用 Windows 作为电脑的操作系统,通常使用 Linux 或着 MacOS;使用 Markdown 来写文档,如果有精细的排版需求我会选择 LaTex,不使用 Word;Shell 的解释器我会使用 Fish 而不是 Zsh 或 Bash;浏览器也是 Firefox 的忠实用户;编程语言我也尽量往冷门的学,比如 Elixir……因此,在知道服务器的操作系统除了 Linux 还有 BSD 之后,使用 FreeBSD 作为服务器操作系统的念头便在心里埋下了,这埋下的念头之所以一直没有发芽,除了 FreeBSD 的软件相比 Linux 要少很多之外,更多的原因在于 FreeBSD 一直不支持 BBR 拥塞控制算法,索性,在最近的 FreeBSD 13 版本中,已经可以支持 BBR 了,于是我立刻订购了一台服务器开始了 FreeBSD 的折腾之路。

一波三折的安装

大多数的 VPS 服务提供商虽然在安装操作系统时提供了多种选择,但是大多都是 Linux 的不同发行版,换汤不换药。因此我费了不少力气才找到一家宣称支持 FreeBSD 的提供商。

我开始选择的是 San Jose 的机房(他们提供有 Dallas、Los Angeles、),因为实测 San Jose 机房的带宽我跑得最满,于是我就发了一个工单和客服说我需要安装 FreeBSD,请他们帮忙挂载一下 FreeBSD 13 的镜像。十几分钟后他们回复说成功挂载了,需要我登陆 VNC 继续后续的操作,当我登陆 VNC 后虽然进入了 FreeBSD 的引导界面,但是报错了:

2021-08-10 10.33.50-1

于是我和客服反应这个情况,他们怀疑是我的镜像的问题,于是我换了一个 bootonly 版本的镜像,让他们重新挂载,问题依旧。来回试了好几个不同版本的镜像,始终都存在这个问题,我都快没有耐心了,终于他们说会标记这个问题让高级管理员来查看。

过了几个小时,高级管理员回复了我,说他们正在探讨这个问题,并且怀疑是 San Jose 机房的内核、libvirt 版本不一致导致的,查验这个问题的修复结果需要重启,但由于机房存在大量的用户,所以这并不是一个很快就能解决的问题。于是他们提出可以为我免费迁移到他们确认可以安装 FreeBSD 的机房,一番测试后,我选择了 Seattle 的机房。

虽然这番操作耗费了我数天的时间,但我的体验并不算差,因为客服回复的速度很快、也并没有推卸责任,也展现出了专业性。

这里就不得不踩一下 WebHosting24 这家服务提供商了,这家虽然在重装系统列表里可以直接选择 FreeBSD 13 进行安装,但是在安装完成之后我发现 FreeBSD 系统没有 IPv6 的地址,于是我发工单询问客服,客服居然说他们的技术支持仅限于 Linux 系统,不针对 FreeBSD,所以需要我自行处理这个问题,如果需要他们提供帮助的话,我需要每十五分钟额外支付二十欧元的咨询费!

开启 BBR 拥塞控制算法

BBR 拥塞控制算法能够极大程度上地改善网络状况比较差时的吞吐量(尤其是在远距离传输时),因此它特别适合身在中国大陆地区但是 VPS 却在太平洋另一边的用户使用。

BBR 是一种拥塞控制算法,但是在 Freebsd 的实现却并没有包含在系统拥塞控制模块里,而是另一个 TCP stack,因此我们不能像 Linux 那样直接用 sysctl 指定一下 tcp_congestion_control=bbr 就行,而是需要自行载入 BBR 模块并重新编译内核来实现。

我也不清楚为啥 Freebsd 开发组不默认将其集成在内核中,可能美帝的网络连接质量已经很高了,大多数用户已经不太需要 BBR 了吧~、

下载内核

我安装 Freebsd 13 时执行的是最小安装,没有包括内核的源码,所以需要自行下载:

1
>>> wget ftp://ftp.freebsd.org/pub/FreeBSD/releases/amd64/13.0-RELEASE/src.txz

内核里面的文件已经包含了目录结构,所以直接将其解压到根目录即可:

1
2
3
>>> tar -C / -zxf src.txz	
>>> cd /usr/src/sys/amd64/conf 	# 进入内核目录
>>> cp GENERIC GENERIC-BBR			# 创建 BBR 内核

配置内核参数

使用编辑器打开 GENERIC-BBR 文件,然后将 ident 参数的值由默认的 GENERIC 改成 GENERIC-BBR,然后在下面加上 optionsmakeoptions 参数,见下:

1
2
3
ident           GENERIC-BBR
options         TCPHPTS
makeoptions     WITH_EXTRA_TCP_STACKS=1

保存后退出。然后新建 /etc/src.conf 文件并填入以下内容:

1
2
KERNCONF=GENERIC-BBR
MALLOC_PRODUCTION=yes

编译并安装

1
2
3
>>> /usr/sbin/config GENERIC-BBR	# 注意此时目录还应该在 /usr/src/sys/amd64/conf
Kernel build directory is ../compile/GENERIC-BBR	# 执行完后,就应该切换到对应的目录了
Don't forget to do ``make cleandepend && make depend''

看,还贴心的告诉你内核的 build 目录在哪,以及告诉你怎么编译依赖~编译依赖完成之后,我们使用 make -j2 来正式开始编译内核。这个编译的过程可能持续十几分钟,具体取决于 VPS 的 CPU 性能。我的 CPU 是 2 核心的 AMD 3900X,这个过程耗时了十来分钟。

编译完成之后,使用 make install 来安装新内核,然后重启。重新登录之后,在欢迎信息里应该就能看到内核变成了 GENERIC-BBR 了。

开启 BBR

使用 kldload tcp_bbr 来载入 BBR 模块。 不过直接这样载入在重启后会失效,需要将 tcp_bbr 加入 kld list:

1
>>> sysrc kld_list+="tcp_bbr" # 或者直接在 /etc/rc.conf 文件末尾加上 `kld_list="tcp_bbr"`

随后就可以开启 BBR 了:将 net.inet.tcp.functions_default=bbr 写入 /etc/sysctl.conf 文件,然后重启即可。重启之后,sysctl net.inet.tcp.functions_default 输出的是 bbr 就表示已经开启成功。

贴一个对比测试吧,下面的 OUTPUT 文件是我在 Freebsd 开启 BBR 之后的速度;下面的 1000MB.text 文件是服务提供商在 Seattle 机房提供的测速文件(默认是没有开启 BBR 的)~

image-20210909233756955

配置 Jails

Jails 是 Freebsd 中提供的容器化技术,可以类比成 Linux 里的 Docker,但是会比 Docker 使用起来麻烦一点,因为 Jails 有许多配置项都需要手动配置(网络配置、磁盘划分等),就像重新安装一个 Freebsd 系统一样。

配置网卡

由于 IPv4 地址的匮乏,大多数的服务器提供商都不会提供一个 IPv4 的子网段供我们使用(IPv6 地址倒是一般都会分配一个 /64 的网段),因此我这一步会将 Jails 的网络配置成 NAT 模式:所有的 Jails 出口共用一个公网的 IP(即提供商分配的 IP)。

首先创建一个虚拟网卡分配给 Jails:在 /etc/rc.conf 里面加上两行:

1
2
cloned_interfaces="lo1"
ifconfig_lo1_alias1="inet 10.6.0.1/26"	# /26 的子网足够用了

然后使用以下的命令让配置生效:

1
service netif restart

注意这里填写的 IPv4 地址段要写当前没有被占用的,否则在运行命令之后你可能就连接不上你的服务器了,不过重启之后可以恢复。

配置主机 NAT

我们开启 pf 组件用来映射主机流量到 Jails 里。在 /etc/rc.conf 里写入 pf_enable="YES",然后我们创建 /etc/pf.conf 文件并填入:

1
2
3
4
5
6
7
8
ext_if="em0"
IP_PUB="xx.xx.xx.xx"

nat on $ext_if from lo1:network to any -> ($ext_if)

# 下面配置可以将主机对应的端口直接转发到 jails 里
TROJAN_PORT="{443}"
rdr on $ext_if proto tcp from any to $IP_PUB port $TROJAN_PORT -> "10.6.0.3"

随后启动 pf:service pf start

网络相关的配置已经完成,接下来可以正式开始配置 Jails 了。

配置 qjail

刚刚提到,jail 的配置很多都需要手动管理,qjail 可以让 jail 的创建和配置更方便一些,它内置了包括 jails 的创建、删除、重启、备份等基本的子命令。使用 pkg install qjail 来安装。

安装完 qjail 之后,我们用 qjail install 来创建我们 jail 的模板,其实也就是下载 FreeBSD 的 base.txz 组件,它是一个可运行的最小 FreeBSD 系统。

接下来我们就可以创建 jails 了:qjail create -4 10.6.0.2 -n lo1 test1,-4 表示的是 IPv4 地址,可以在配置网卡这一步填写的范围里面随便挑一个2,记得要挑没有被占用的。

接下来我们使用 qjail start test 来启动这个 jail,并使用 qjail console test 进入这个 jail。

进入 jail 之后,可以使用 telent 或者 host 3命令来测试网络连通性。

更新 TLS 证书

我目前有一些网站没有使用 Cloudflare 代理,比如自己搭建的 Trojan 翻墙服务,用 Cloudflare 会大大拖慢访问速度,而 Trojan 又是需要 TLS 证书伪装的,故而我专门创建了一个 jail 用来创建并更新 TLS 证书。

安装 acme.sh

1
curl  https://get.acme.sh

acmes.sh 默认会安装在用户根目录的 .acme.sh 文件夹内。

配置 DNS 验证

申请 TLS 证书的时候,有两种常见的方式验证域名的所有权:

  1. HTTP 的方式:这种方式要求你要申请的域名已经搭建好了一个网站,并且要在网站根目录放一个验证文件,这种方式的要求有点「苛刻」:需要安装 Nginx、Apache 等 Web 服务,或者服务器的 80 端口是空闲的(让 acme.sh 自己伪装 Web 服务),如果是在普通主机上到也没那么麻烦,毕竟都是一次性操作;可是要在 Jails 里面运行 Web 服务的话,必须得在外部主机开启端口映射的服务,可我又不可能将主机的 80 端口映射到运行 acme.sh 的 jail 里,所以每次续签证书还得要手动切换映射服务,这太麻烦了
  2. DNS 的方式:幸好, acme.sh 还提供了另一种的方式来验证域名的所有权,只需要获取域名解析商处对应于明的的 Token 等信息写入环境变量,acme.sh 会自动创建一条对应域名的 TXT Record 来验证所有权,这种方式好在完全没有外部的依赖,在 jail 和主机运行没有任何区别。
1
2
3
4
5
6
setenv CF_Token="xxxxxx"
setenv CF_Account_ID="xxxxxxx"
setenv CF_Zone_ID="xxxxxx"

~/.acme.sh/acme.sh --issue --dns dns_cf --server letsencrypt -d example.com	# 生成证书
~/.acme.sh/acme.sh --install-cert -d example.com --key-file /cert-path/key.pem --fullchain-file /cert-path/cert.pem	# 安装证书

这部分完成之后,acme.sh 就会创建并自动更新证书了。

证书的应用

此时还剩下一个最后一个问题:因为不同的 jail 的文件系统是相互隔离的,那么在这个 jail 里生成的证书如何让其他的 jail 能使用呢?

不过还好,在主机端可以访问所有的 Jails 的文件系统(在 /usr/jails 里),所以我们可以把运行 acme.sh 的 jail 里的证书目录直接挂载到使用证书的 jail 的文件系统里:

1
mount -t nullfs -o ro /usr/jails/acme-sh/cert-path/ /usr/jails/trojan/root/certs

第一个地址是源目录(运行 acme.sh 的 jail 的文件系统),第二个地址是目的目录(使用证书的文件系统)。

这样我们就可以在对应的 jail 里的 /root/certs 目录看到证书的文件了。

参考:


  1. 这里的 -n lo1 参数非常重要,因为 qjail 帮我们简化了很多 jails 的配置,这也导致某些配置可能并不符合我们的期望,比如如果这里不加 -n 参数的话,这个地址就会被添加到默认网卡上,而不是 lo1,所以我们这里需要指定为 lo1 网卡添加地址 ↩︎

  2. 我们在前一步配置的时候只标注了子网范围,而在我们创建 jail 的时候实际上是给这个字网内加了一台机器,而 pf 的规则并不会主动去应用于新加入的字网的机器,因此我们创建完新的 jail 之后需要重启 pf 进程 ↩︎

  3. Jails 相比主机有诸多限制,比如默认 jails 内部不支持 raw_sockets,也就是说你无法使用 ping 命令来测试网络连通性,当然:qjail config -k jail_name 命令可以开启 jail 对 raw_sockets 的支持 ↩︎

Last Updated in Mon Nov 29, 2021
© 2017 - 2022 Wincer's Blog
Theme Cirrus designed by Wincer, built with Hugo