使用 Shorewall zone 配置软路由上特定机器的流量转发

背景

家里的软路由一直一台是跑着 ArchLinux 的 x86 设备。最初是参照着 Arch Wiki: Router 的页面一步一步配置,转发 / 路由 / 防火墙相关的功能就沿着 wiki 的教学选择了 Shorewall,而没有选择(在我看来学习成本更高的)iptables。

去年搞了一个新的 Apple TV,就有了让局域网里面特定设备的流量转发到代理上的需求。折腾一段时间下来发现 Shorewall 上配置类似的事情并不是很难,基于配置文件的 Shorewall 在配置好之后也比较好维护,因此把相关配置的过程记录下来。

Shorewall 里面的 zone 功能 可以很好解决我的需求,后面的内容主要介绍如何在 Shorewall 里面增加 zone 并配置转发。

配置 – 转发流量之前

Shorewall 最基本的软路由配置可以参考他们的官方文档(Basic Two-interfaces Firewall),或者 Arch Wiki

我们这里只用来展示后面 zone 配置的基础: /etc/shorewall/zones 文件里面有两个基本的 zone 对应局域网 loc (实际为 10.0.0.1/24)和外网 net

# content of /etc/shorewall/zones

#ZONE   TYPE    OPTIONS                 IN                      OUT
#                                       OPTIONS                 OPTIONS
fw      firewall
net     ipv4
loc     ipv4

另外也需要按照上面的教程,至少配置好 rulesmasq(或者叫做 snat,如果使用更新 Shorewall 版本)这些文件。

配置 – 新增一个 zone 专门放置要转发流量的设备

这里分成两部分,一个是新的 zone 的定义,另一个是有了 zone 定义之后的转发规则。

zone 的定义

我们需要新定义一个在 loc 里面的新区域,假设叫做 new

# additional content of /etc/shorewall/zones

new:loc         ipv4

按照文档,冒号后面的 loc 指的是 parent_zone,说明我们新的 zone 是在内网 loc 里面的。

进一步我们需要指定 loc 的哪一部分在 new 区域里面,这里需要修改 Shorewall 的 hosts 文件,我自己只添加了一个 IP 地址(预留给我的 Apple TV)。按照文档,这里可以写成 IP 地址段比如 10.0.0.201-10.0.0.220

# content of /etc/shorewall/hosts

#ZONE           HOSTS                           OPTIONS
new             enp4s0:10.0.0.201

# change `enp4s0` to your interface name of `loc`
# you can change to `enp4s0:10.0.0.201-10.0.0.220` to include more IPs (devices).

转发规则

最核心的部分是转发规则,这里的规则并不一定是符合大家需求的。我设置的规则如下:

  • TCP 请求转发到我们准备好的 redir / clash 代理上面 10.0.0.1:8883
  • DNS 请求(53 端口的 UDP)转发到我们专门为 Apple TV 准备的内网 DNS 上 10.0.0.1:8053
  • 内网流量需要正常直通,不要走转发

因此我们这里我们修改 rules文件如下:

# additional content of /etc/shorewall/rules

# redirect outside zone (apple tv) to locally 8883 port transparent redsocks proxy
# ACTION        SOURCE  DEST                    PROTO   DPORT   SPORT   ORIGDEST
ACCEPT+         new     all                     TCP     -       -       10.0.0.0/24
# ACCEPT+         new     net                     TCP     -       -       192.168.1.0/24
REDIRECT        new     8883                    TCP
REDIRECT        new     8053                    UDP     53

问题 – 谁来接受转发?

这里可以选择使用 redsocks 把一个 socks 代理转换为透明代理(我不知道这里用词是否准确),也可以使用 Clash 提供的 redir 端口,这里不再特别展开。

当然,既然我们在配置文件上希望 Apple TV 有 10.0.0.201 这个 IP 地址,就需要我们通过手动修改 Apple TV 设置,或者修改 DHCP 服务器配置来达到这个目的。

这些配置好之后,就可以看到 Apple TV 的流量被转发到我们假设好的服务上面了。

后续

按照这样配置,一些不好修改代理设置的设备都可以通过这种办法来指定强行转发。新的设备只需要修改 DHCP Server 设置落在转发的 IP 地址区间即可。

后续我会考虑:

  • 是否可以把 VLAN 融合进配置?(需要我的交换机配置好 VLAN)
  • 是否可以让特定 AP 的设备进入到我新增的 zone 里面?(可能可以靠改善 DHCP Server 解决)

解决 MSI 主板上安装 ArchLinux 时 UEFI 下 GRUB 启动项失效的问题

背景

过年这几天升级了一下家里的软路由 & NAS,从之前 ASRock 的 H81 MATX 主板换成了新的 MSI B460 TORPEDO,新主板是 ATX 有 2.5G 网口还有更多的 M.2 / PCI-E / SATA 口,做 All in one server 的扩展性已经足够了。

迁移系统的时候发现新买的十代 CPU (i5-10105T) 上面想用集显就必须用 UEFI 启动(不然会报错,说集显没有对应的 VBIOS 无法在 CSM 下启动)。不知道是不是我买了个 QS 散片的原因;但不管怎么样我必须要把之前的 ArchLinux BIOS/MBR 启动方式改为 UEFI/GPT 了

MBR 换成 GPT 我直接用的 DiskGenius 软件,想着按照 Arch Wiki 里面介绍 GRUB 安装 的说明一步一步走就可以了,没想到一直出错……就有了这篇文章。

问题

尝试按照 Arch Wiki 流程安装 GRUB,进入 Arch Installation Media,chroot 后运行下面的命令:

# grub-install --target=x86_64-efi --efi-directory=esp --bootloader-id=GRUB

提示安装成功。在安装环境下运行 efibootmgr 也可以看到确实成功添加了启动项,但是重启之后完全找不到启动项,主板 BIOS 页面甚至不允许添加这块硬盘作为启动设备,GRUB 的安装是失败的。

解法1

TL;DR

核心原因是 MSI 主板需要 EFI 文件在 EFI 的特定路径上。Arch 的 GRUB 会默认安装到 {ESP}/EFI/GRUB/grubx64.efi 上,但主板启动时希望的地方是 {ESP}/EFI/BOOT/BOOTx64.efi

因此根据 wiki 有两种办法:一个是安装时增加参数

# grub-install --target=x86_64-efi --efi-directory=esp --removable

另外也可以 grub-install 之后再把安装好的 efi 启动程序复制 / 移动到目标位置上

# mv esp/EFI/grub esp/EFI/BOOT
# mv esp/EFI/BOOT/grubx64.efi esp/EFI/BOOT/BOOTX64.EFI

这个问题在很多其他的设备上也存在,根据 Arch Wiki,看起来联想和部分 Thinkpad 的型号也有一样的问题

解法2(当时折腾的过程)

当时折腾的时候并没发现有这样的解法,而是绕了些弯路:进入 EFI Shell 手动去添加启动项。

当时的思路是在 Arch Media 里面 efibootmgr 宣称安装成功了,但重启后却失败,可能是因为 Arch Media 读写 UEFI 启动项位置 NVRAM 的过程是有问题的,所以得想办法换个方式写启动项。没什么思路,感觉只能靠 EFI Shell 了:

UEFI 方式启动的 Arch Installation Media

用 UEFI 启动 Arch Installation Media 的时候有一个选项是 UEFI Shell,进入之后可以看到类似下面的界面

直接敲 help 可以看到命令列表。这里输入 alias 命令可以看到 UEFI Shell 里找到的 EFI 分区(一般至少找到两个,有一个是安装镜像自带的)的 Mapping Table,根据对应的文字说明找到真正需要启动的分区 FS[n]: 假设是 FS1

首先的操作类似 DOS,先输入 FS1: 回车进入对应的分区

之后可以通过 cd ls 等命令看到自己的 EFI 文件,也可以间接检查自己是否进入了正确的分区。注意,在这里启动器安装好的 EFI 文件是可以直接运行的,运行对应的 .EFI 文件就进入安装好的启动器了。(手动进入 grubx64.efi 之后,我才确认 GRUB 的安装完全没问题,单纯是引导项的问题)

之后用 bcfg 命令(参见文档)确认启动项

Shell> bcfg boot dump -v 

添加启动项,EFI 文件的路径可以是相对或者绝对路径

Shell> bcfg boot add 1 FS0:\EFI\GRUB\grubx64.efi "GRUB"

到这里重启就发现确实成功添加了启动项,主板 BIOS 里也能看到刚才添加的 GRUB 启动条目了。

这么做其实很折腾而且在我的系统上依旧不稳定,硬盘分区大小发生变化都会导致主板上的启动项丢失,需要重新添加。

结论 & 遗留问题

  1. 看起来这个问题在很多设备上都出现过,并非不常见,这么看来以后安装时应该默认安装到这个 fallback 路径下(EFI/BOOT/BOOTX64.EFI)才合理吧。
  2. 如果 UEFI 只认固定路径的 EFI 文件,那么在 UEFI Shell 里通过 bcfg 命令的方法,我不确定在其他设备上是否也成立;
  3. bcfg 添加启动项的方法在我的微星主板上是一个不稳定的方法,更换硬盘,甚至调整分区大小后这个启动项都会消失,需要再次手动重新添加,因此还是少折腾,直接把 EFI 文件放在正确的位置吧。
  4. 你可千万不要像隔壁叔叔一样,大年初二还在修电脑.jpg