【OPS.0x09】家用软路由手搓日志

本文最后更新于:2026年1月8日 凌晨

阿达西,你的 homelab 里面,我的软路由雄鹰一样地在呢

0x00. 一切开始之前

又是经典的废话环节,愿意听笔者碎碎念一长段没有什么意义的内容的读者可以简单看看:)

以及以下内容仅代表笔者不成熟的主观意见,不包含任何对目前的软路由及 homelab 圈子的评判或审视,且很多观测结论来自于 中国大陆外的软路由圈子 (例如 Reddit 的 /r/homelab 社区, 说真的笔者真不太了解国内各种圈子是啥样子 ),对于部分概念如果你有不认同的可以优先以自己认为对的为准:)

众所周知 Soft Router(软路由) 一直都是 HomeLab 领域中非常火爆的一个可玩物(笔者语文功底下降太多了不知道该用什么词形容总而言之大概是这个意思),因此笔者自然也想在自己家里整一个软路由玩一玩,刚好最近几天有机会回趟国内,于是笔者打算在家里弄个软路由,改造一下自家 homelab 的网络基础设施建设

但是在开始之前,笔者注意到很奇怪的一个点——在仔细调研过后 笔者感觉社区里绝大部分人玩的其实并不是 “路由” 本身,而是将软路由作为非计算机科班出身背景的一个接触 Linux 以及运维的敲门砖,绝大部分人真正玩的其实还是 Linux 而不是软路由 (当然这里笔者的意思并不是说看轻所谓非科班出身的人之类的,而是很多相关背景出身的人其实反而不会在下班之后自己在家折腾本专业的东西,毕竟一天天的上班工作已经够累的了下班回来还要再对着同一套东西折腾的话能不 PTSD 的属于是真的很热爱的神人了,例如笔者同一届读网安的同学每天从实验室回宿舍搓 PCB 板子不亦乐乎,但是另一个专门上班做硬件开发的同学已经到了看到这套东西就下意识想跑路的程度了),因此 你可能会看到很多人在折腾和【路由器】这一概念本身无关的东西 ,例如非常经典的在 J1900 大卡车上跑万物这样的 all in one 之类的玩法:

——当然,不得不指出的一点是就工程实践而言在资源没有紧缺到吃不起饭的情况下 all in one 并不是一个比较好的实践( 毕竟作为计算机专业背景的咱们还是要有点基本的计科素养 ),稍微一个配置错误就很容易从 all in one 变成 all in boom 全都炸了(悲),因此我们至少需要通过类似 Docker 这样的技术为不同类型的服务做好隔离——当然,比较好的工程实践还是每个独立任务专门对应一台独立的计算机,例如家里如果同时要搞 NAS 和软路由,那就整两台独立的计算机,而不是在一台计算机上又跑软路由又跑 NAS 的——就笔者个人而言,虽然说有 Proxmox VE 或者 Docker 一类的基于虚拟化技术的软件的存在使得我们能够将一台物理计算机虚拟化成多个虚拟计算机进行硬件资源的复用,但 all in one 本身就是风险极大的事情,很容易牵一发而动全身,因此笔者还是选择仅在这一台将要被配置成软路由的计算机上 仅配置与路由相关的功能

那么又来了一个核心的问题: 软路由究竟能够又需要做什么? 首先需要确定的一点就是一台路由器的核心功能肯定是 路由 (routing) 和 转发 (forwarding)—— 也就是数据包在三层网络中的路径选择与发送,因此我们的软路由首先需要实现一台传统路由器应当具备的路由功能

在传统路由器的功能点上,软路由和硬路由间的异同如何?简而言之就是 硬路由的路由性能通常都能完全秒杀软路由 ,因为有一点比较核心的区别是,硬路由通常有能够辅助数据包处理的额外的芯片,例如高性能路由器通常都会配备支持绝大部分路由功能(甚至完整支持,例如 Cisco 的 8000 系列路由器 便可在单个 ASIC 上支持完整路由功能)的 专用集成电路 (Application-Specific Integrated Circuit,ASIC)来 直接使用特殊的电路进行数据包的路由与转发 ,从而极大程度地提升了路由器的吞吐量——而软路由则只能使用为通用计算设计的 CPU 进行数据包的转发,每个数据包的处理都需要经过 CPU 进行调度、内存拷贝、协议栈解析等,效率上比专门为路由设计的 ASIC 之流还是要低不少,因此在绝对性能的上限而言有着专门电路的硬路由通常都会能够达到比软路由更高的性能上限

尤其是很多人讲到的一个非常核心的一个区别便是 小包转发的性能 (例如 64~128B 这样的包),数据包从网卡接收->拷贝到内核网络协议栈 sk_buff ->逐层解包处理协议头->查表->发送到网卡的这些步骤,都需要 CPU 介入,也都需要进行内存访问、上下文切换等,对于小包转发而言,每秒处理的数据包数量都非常大的情况下,我们很多的计算资源都会开销在这一些各种内存拷贝和指令执行的步骤上,而哪怕只是普通的家用路由器,都有 ASIC 芯片(例如 Broadcom 的 BCM47xx 芯片)进行硬件层面的加速,数据包的收发、查表、转发都可以由 ASIC 完成,CPU 只需要在各种路由协议的处理时介入,因此小包转发的性能哪怕是家用路由器都是能够秒杀高性能软路由的——当然大包转发性能也是有着 ASIC 的硬路由站优势,但是大数据包意味着每秒数据包数量少,因此 CPU 处理的压力也相对较低

那么软路由的优势在哪呢?在笔者看来,主要的核心点还是在于 软路由作为一个足够便宜的设备,在可折腾性与价格间取得了不错的平衡 , 几百块买一台性能尚可的小主机来折腾不仅对于家用网络而言性能够用,钱包也不会太过于吃紧,还能带来极大的可玩性,因此在 homelab 小圈子当中软路由便逐渐成为了一个非常热门的选项

——当然,对于笔者而言, 笔者更看重的是软路由作为 “路由器” 本身,能够相比起硬路由带来什么不一样的东西

毕竟也玩 Linux 这么多年了,普通 Linux 常见的各种功能对笔者而言并没有太多新奇的值得探索的东西,例如 homelab 圈一直有个很火的概念叫 玩 Docker ,甚至于部分 NAS 居然将支持 Docker 作为其卖点,最初这个说法在笔者看来就有点神奇和意想不到: 难道不是应该是 玩具体的各种应用软件吗? Docker 本身不应该只是一个装载与运行应用的载体吗?为什么要叫玩 Docker? ——后面笔者还是仔细想了想,对于绝大部分初学者而言确实是在 “玩 Docker” ——毕竟 Docker 对于很多人而言确实也还算是一个比较新奇的能够装很多各种有意思的应用的一个入口,同时绝大部分软路由的用户其实也并不是计科专业出身,大部分人更多只是这一块的爱好者,因此 使用行业内的专业标准去审视这样一个民间小圈子的用户是完全没有道理的

因此本篇博客会更多关注于 与路由器本身相关的玩法 (例如 网络功能虚拟化、自定义防火墙、流量控制、软件定义网络等),而不会涉及那些虽然很火但实际上和路由器这一概念本身基本完全无关的东西(例如什么远程下载、跑定时任务、 PCDN 之类的, 这和 “路由器” 这个概念本身有半毛钱的关系吗? 随便一个能够远程访问的普通的服务器都能干的活,没有必要专门放到软路由里去讲,也 没有必要和软路由这个概念产生关联

网络架构设计

一言蔽之,笔者认为 在适合笔者的家庭网络架构当中,一个标准的高可用的软路由所处的层级应当不高于二级路由 ,也就是说软路由上面至少需要再有一个标准的硬路由作为一级路由(以及一个光猫),即下图所示的级联路由架构:

选择这样的网络架构的原因在于可以比较好地保障网络的可用性和健壮性( 保持一个好的鲁棒性有助于你更好地保证你鲁棒的鲁棒性 ),在这种架构当中,软路由本身的引入并不会影响到常规的家用网络(软路由炸了只会影响到你自己, 不会让你被你爸妈痛骂一顿 ),此外当软路由出现一些故障时也可以通过与软路由同级的设备连接到软路由进行修复

关于旁路由的迷思( 炮打旁路由,我的一张大字报

众所周知在 homelab 小圈子里有一种神人架构叫做 旁路由 ,简而言之一般长这个样子:

笔者个人觉得稍微有点网络基础常识的应该都会觉得这个网络架构的设计是比较惊为天人的,而且 “旁路由” 一词 笔者并未在任何正规的网络工程教材中找到与之相关的任何定义文本 ,在 r/homelab 一类的英文社区也基本看不到这类型的网络架构设计(因此笔者哪怕是主观上想给这个词标一个相对正经的英文翻译,也不太想得明白该怎么标, 因为这就不是一个正经玩意 ),但是 这不代表这个东西最初的出现是完全没有意义的 —— 但是与此同时 有意义也不代表这个东西是对的 ,永远记住我们看待问题不能够极端化,善用辩证法进行思考

要弄清楚为什么这个神秘架构为什么会在某个特定的小圈子里流行起来,我们首先自然要先弄明白“旁路由”究竟是什么,能够做什么——首先我们需要明确的一点是,“旁路由”和你在网工教材上学到的任何一种正规网络架构都不相符,旁路由并不是单臂路由(router-on-a-stick),而且两者基本没啥关联

让我们先从这个东西诞生的场景出发,简而言之,“旁路由”最初所设想/面对的场景是 在主路由不能动的情况下,怎么在家用网络中玩点骚操作 ——众所周知对于不处在同一网络中的数据包,计算机会选择将其发送给默认网关(Gateway,一般这个 IP 是你当前直接连接的路由器),由网关将数据包发到别的网络(因此,如果一个数据包不会出网,那么对应的网卡自然就不需要设置默认网关),因此就有点子王想到:既然要出网的数据包会被发送给网关, 那我在网络中部署一台服务器,再手动把要搞操作的设备的默认网关设置成这台服务器不就好了 ——于是 “旁路由” 的概念便应运而生

到这里有人可能就会发出疑问了:我直接把用作旁路由的这个设备作为二级路由再将要搞事的设备接到这个路由器下边不就好了——诚然,相比起我们所称之为「正统」的级联路由网络架构而言,旁路由的架构未免总归让人觉得怪怪的,但不得不说这样的架构很好地满足了部分人的需求: 在不改变主路由的情况下,不需要做过多折腾,同时还能实现设备间的互通 ,因此相比起正统网工思维,旁路由便是很好地满足了部分人想要折腾但是又不想付出太多精力折腾但是又要有折腾感和实用性的这样的需求的一种解决方案

例如如果是级联路由架构,要做设备互通的话你需要一些额外的更加正规化的配置,可以参考笔者图上所示网络结构,或者参考华为的 配置IPv4静态路由示例 等文章做初步了解(看不起华为在网络这一方面造诣的人,👴只能说考个 HCIA 认证就老实了),笔者会在博客的后面详细讲述到这一块怎么配置——当然因为笔者家里就没几台设备,直接配个静态路由是比较省事的,你也可以自己玩玩动态路由,比如说 OSPF, 不过需要明确的一点是这些都是正统网工的做法,并不适合小白 ,因此复杂度相比起简单的架设旁路由而言要复杂的多 ——不过笔者相信阅读笔者博客的各位读者应当都是有着正统计算机科学素养的人,又或者至少是有着这方面志向的人:)

但我们不得不明确的一点是, “旁路由” 最多只是在特殊场景下有一定作用的【能用】的架构 (套用 ChatGPT 说的话便是 旁路由不是错误, 它只是一个“不该长期存在,却不得不存在”的结构。 ),他存在很多 在专业场景中无法接受 的问题(这里笔者就不过多展开了,感兴趣可以自己问问 GPT)——当然,我们不得不说的是,任何事物的存在都具有其合理性, “旁路由”诞生于一个混沌的时代,作为一个 workaround 而流行了起来无可厚非,但是至少我们应当知道 什么是正规的、优雅的网络架构设计 ,虽然在“旁路由”在圈子里大行其道的今天,作为个体我们没必要(至少笔者是没这个精力)跳出来和别人唱反调,但至少在我们自己家里的网络架构设计当中,我们还是要保持点 专业性 ——尤其是作为学习过正统科班课程的人而言,实际上回过头来一看你会发现上面讲的很多东西你在本科的《计算机网络》当中都有学过,甚至在《组网与运维》课程实验上都实操过——既然这样我们又有什么理由选择使用民间自行发明的“旁路由”架构呢 :)

硬件选择

注: 本节内容不包含任何商业推广!

从硬件而言一台合格的软路由至少需要满足以下配置:

  • 两个网口,一个负责连接上游网络,一个负责连接交换机以连接下游设备
  • 一枚待机功耗较低的 CPU(例如 Intel Twinlake 系列)

刚好手上还有一些闲置的内存条和 SSD,因此笔者在京东上随便买了一台有着两个网口的准系统小主机,Intel N150 CPU 是 Intel 最新的低功耗 CPU,在耗电低的同时有着还可以的性能,这也是笔者选择这个 CPU 的主要原因:)

从最后的结果来看,整机功耗 10W,一个月只用不到 10 度电,这个电费开销还是没有太超出笔者的钱包负载能力上限的

操作系统选择

接下来是操作系统的选择,这里笔者的选择是使用笔者最喜欢的 Gentoo Linux ,那么为什么不选择其他的操作系统呢?我们先来看看常见的那些被用来安装到软路由上的操作系统都有哪些:)

首先最经典的也是大家最容易想到的应该是 OpenWrt 系统,这是一个专门为路由器打造的嵌入式 Linux 发行版,简而言之就是在路由器领域的专业性还算拉得比较满, 甚至于部分商业路由器所使用的固件也是基于 OpenWrt 打造的 ,这也是软路由圈子里应该是流行度属于绝对的第一梯队的一个系统,甚至于部分论坛 例如 Chiphell 或者恩山之类的 都会有很多用户分享自己自制的 OpenWrt 固件

首先不得不说 OpenWrt 是一个非常成熟也非常好的系统,但是就是不太满足笔者个人需求 ,如果要对硬路由进行改造,自制 OpenWrt 固件再刷进去或许是个不错的方案,这也是硬路由刷机的常见玩法,但笔者用作软路由的是一台常规的通用计算机,在这种 可以且性能足够正常安装其他普通 Linux 发行版的情况下,OpenWrt 就没啥优势了 ,因为 OpenWrt 本质上是为嵌入式场景设计的,为了能够在性能极端缺失的场景下运行而阉割了很多东西(例如 libc 使用的是 uclibc 而非 glibc),这就导致对于很多常用 Linux 功能的支持并不完善(例如老 OpenWrt 的玩家应该都知道这玩意曾经对 Docker 的支持基本上可以说是比一坨还要一坨),而如果要将其更改为一个对各种通用功能都支持比较完善的 Linux 系统则会需要耗费很多精力,而且也没有必要——这种情况下我为什么不直接安装一个普通且常规的通用 Linux 发行版呢

然后是中国大陆比较流行的 iKuaiOS (为什么要单独提一嘴是因为笔者确实 几乎没有 怎么在国外的 homelab 相关社区看到这个系统,可能是海外宣传比较少),简而言之这个系统是 使用了 Linux kernel 的闭源操作系统 ,首先“闭源”这个东西就不太符合笔者的口味,其次他的可折腾空间并不多(虽然说原生支持的功能很多,用知乎上的话来说就是 “一套精装修的房子” ,个人觉得其开箱即用的特性还是非常适合小白玩玩的), 对于笔者而言属于完全的 OpenWrt 的下位替代 ,因此自然也没有必要使用 iKuaiOS——虽然笔者觉得这个系统在易用性这一块确实做得还是不错的,简化了很多的操作,还有图形化的管理界面,很适合小白上手畅玩

接下来是 BSD 系的 pfSense 和 OPNsense,这两个系统都是基于 FreeBSD 进行开发的, 在海外的 homelab 相关社区当中还算是有着非常不错的流行度 ,其中 OPNsense 是 pfSense 的一个分支,说实话笔者不太熟悉 FreeBSD( 主要还是对整个 BSD 系都没有太多了解 ),相较而言笔者熟悉的各种东西还是主要在 Linux 下工作的,而且由于 马太效应 的缘故 BSD 系相较于 Linux 而言似乎是在逐渐衰落的,整个开源社区生态的注意力其实是在逐渐地收缩至 Linux only 的(例如 WhatsApp 的团队在 2020 年由原先的纯 BSD 转向 Linux,FreeNAS 在升级成 TrueNAS 后也从原先的 BSD-based 变为了 Linux-based),这也导致很多软件都没有对 BSD 的支持(别说 BSD 支持了,现在一些专业软件想要有 Linux 版本都够呛,Windows 还是太权威了),因此出于 可折腾性 的角度,笔者还是优先选择使用 Linux(毕竟家用软路由的搭建某种程度上其核心价值主要就在于通过可折腾性给你提供情绪价值),不过后面如果有机会应该会尝试一下这些小众宝藏系统:)

能出来几个人说说话吗?为什么帖子光有人看,没人回复?也看不见人发帖。什么意思啊?再这样下去热情都磨光了。没人再愿意为大家服务了

以及 Proxmox VE 或者 Vmware ESXi 这样的专门用来搞系统虚拟化的操作系统,首先 不得不说在海内外这都是较为流行的基本解决方案,但对于笔者而言则又有点没太多必要 ,因为笔者并不需要也并不喜欢在这样一台性能一般的机器上跑太多东西,而且某种程度上这也比较违反 Unix 的 “只做一件事,将其做至极致” 的哲学(笔者甚至在国内圈子见到过不少先安一个 PVE 然后再安一个 OpenWRT VM 做主路由然后再安一个 iKuaiOS VM 做旁路由的神人架构, why? ),再加上 PVE 本身也只是一个基于 Debian 的 Linux 发行版罢了,背后使用的虚拟化软件还是 KVM 和 QEMU,对笔者而言不如自己安一个普通的 Linux 发行版去用这些东西,而 VMware ESXi 虽然是一个全新的 Type-I Hypervisor,但是这又回到一个核心的关键问题就是 此类 hypervisor 本身只是用来将一台物理机虚拟化成多台虚拟机的工具,真正实现路由功能的还是要落到具体的虚拟机里操作系统当中,是否选择安装 PVE 或者 ESXi 其实并没有解决笔者的核心问题 ,而笔者又没有在这台仅用作软路由的机子上跑多个不同操作系统的需求,只需要跑一个系统作为软路由就行了,没有必要引入系统虚拟化的架构,因此此类 VMM 系统也被排除出笔者的选择范围

Windows 或者 macOS 之流就更加没必要讨论了,选择用这类系统作为软路由属实是有点神人的选择,虽然说从纯折腾的角度而言似乎确实可行 ,笔者也看到一些帖子在折腾这个,但是 更多还是偏向纯折腾,而非一个常规化的选择 ——当然如果上 Windows Server 的话或许还是可以打造一个切实可行也还算实用的解决方案,但是对于笔者而言还是太过于超前了, 也不太符合笔者自身的 taste,因此就暂且不考虑

——因此最后综合考虑下来,笔者觉得 最适合笔者的选择便是安装一个常规的 Linux 发行版 ,可折腾性拉满的同时不失稳定性

既然我们最后的选择还是通用 Linux 发行版,那么接下来便是到具体的 Linux 发行版的选择,这一步基本上就是多种多样众口难调的了, 而且实际上绝大部分发行版没有太多本质区别,主要的核心区别就是包管理器设计和软件仓库更新速度不太一样 ,因此主要就是看你的个人口味了——至于为什么笔者更喜欢 Gentoo Linux 而不是其他的发行版,之前在 【DISTRO.0x02】在 Surface Pro 8 上安装 Gentoo 这篇博客当中笔者已经有过一次讨论,简而言之便是 Gentoo Linux 的包管理器的的设计理念在笔者看来十分惊艳,不仅是 “你所使用的每一个软件包都是你自己编译的” 这件事极大程度地满足了笔者的情绪价值,USE flag 这样能够按照自己需求自行调整软件包的方式更是让笔者觉得 屌爆了可以说 portage 满足了笔者对于 package manager 的所有 幻想 ,更多的笔者就不在这里详细展开了(懒得打字了主要是),感兴趣的读者可以自行回去看上面那篇博客里笔者写的与 Gentoo Linux 相关的内容:)

0x01. Gentoo Linux 基本系统搭建(可选)

我们首先要进行 Gentoo Linux 的搭建,这一节你也可以参照 Gentoo 官方的 Handbook ,如果你想跟着笔者的博客自己也搭一个软路由玩一玩,但是又不太想用 Gentoo Linux (例如你可能更喜欢社区更流行的 OpenWRT 或者 iKuaiOS 之类的),你也可以直接跳过并安装自己喜欢的其他发行版或是其他操作系统:)

如果你的 ISA 不是 amd64,注意自行修改一些细节:)

创建安装镜像并启动

这一节基本没有什么要讲的,从 Gentoo 官网 下载一个 amd64 的 LiveUSB 镜像然后用 balenaEtcher 一类的工具刷到 U 盘里面就行

不过所有的这类安装镜像其实都是一个对应发行版的 Linux 系统,而笔者之前弄过一个安装在 U 盘上的 Gentoo Linux,因此笔者直接从自己制作的 U 盘上启动,而不是使用官方的镜像,因为其实核心就是各种磁盘和软件管理工具,例如 GParted 和 portage 这些,只要有能够运行这类工具的环境就行

之后从这个 U 盘启动就行,通常需要进 BIOS/UEFI 中进行配置,不过如果主机本身没有安装操作系统通常会默认从可以启动的安装介质进行启动,而无需手动配置

通过 SSH 连接安装环境

如果感觉直接在主机上操作不太方便,可以在启动之后启动 sshd,再用自己的惯用 PC 进行连接:

可以先使用 ip addr 命令查看主机内网 IP,然后确保在同一内网下进行连接即可

1
$ sudo rc-service sshd start

之后将自己的 ssh 公钥放到 /home/gentoo/.ssh/authorized_keys 之后就可以用下面的命令连接安装环境了:

1
$ ssh -o StrictHostKeyChecking=no gentoo@主机内网IP

image.png

可以先用 netcat 来传递公钥,例如在目标主机端首先使用 nc -l 4444 开启监听,之后在自己的 PC 上用 nc 主机IP 4444 进行连接,然后在一侧输入的字符在另一侧就能直接看到

进行磁盘分区并挂载

我们主要需要三个分区:

  • EFI 分区:FAT32 格式,放 EFI 启动程序,一般选 GRUB,有特殊癖好的可以选 systemd-boot
  • 系统分区:一般用 EXT4 格式,存放 Gentoo Linux 系统,也可以按个人喜好用 BTRFS 之类的
  • SWAP 分区:SWAP 格式的交换分区,存放一些长时间不用的数据

也有一些人习惯把 /home 单独开一个分区,不过笔者个人习惯是开一个独立的分区专门放数据,而不是放 /home

首先使用 KDE Partition Manager 进行分区,毕竟都 5202 年了能用图形界面就没必要用命令行折磨自己:)

然后用 GNU parted 给 EFI 分区标对应的属性,可能是笔者不熟悉 KDE Partition Manager 的缘故总而言之找不到这么细粒度变更分区属性的办法:

然后先挂载系统分区到 /mnt/gentoo 目录下,后面我们要 chroot 到这个目录里面进行系统的安装:

1
$ sudo mount /dev/nvme0n1p2 /mnt/gentoo

安装 Stage 3

简而言之 stage 3 可以理解成一个最基本的 Gentoo Linux 系统,Stage 可以简单理解为构建 Gentoo Linux 系统的各个不同阶段:

  • Stage 1 :通常仅 Gentoo 内部开发人员使用,由一个基本的 packages,build 文件生成的系统包
  • Stage 2 :通常仅 Gentoo 内部开发人员使用,从 Stage 1 编译而来的可以自举的工具链
  • Stage 3 :由 Stage 2 编译而来的文件,并包含一组 @system set 软件包
  • Stage 4 :在 Stage 3 基础上安装好的完整的 Gentoo 系统

首先我们将 Stage 3 下载到文件系统根目录下解压,笔者比较习惯 systemd 所以还是选择 systemd + desktop 版的:

如果你的安装机器所处的网络环境比较复杂,有可能会出现 Unable to establish SSL connection. 或者 Connection reset by peer 的错误,通常这不是你或 Gentoo 官方的错误(出错了但我们做对了.jpg),可以考虑多次重试下载或使用代理,或者在自己电脑上下载好后再开个 python3 -m http.server 8888 之类的传过去

1
2
3
$ cd /mnt/gentoo
$ sudo wget https://distfiles.gentoo.org/releases/amd64/autobuilds/20250921T170345Z/stage3-amd64-desktop-systemd-20250921T170345Z.tar.xz
$ sudo tar xpvf stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner

配置 portage 选项

主要是编辑我们的文件系统挂载点 /mnt/gentoo 下的 /etc/portage/make.conf 文件以配置 Gentoo 的包管理器的各种编译选项,下面是笔者给出的一份自用的示例:

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
# These settings were set by the catalyst build script that automatically
# built this stage.
# Please consult /usr/share/portage/config/make.conf.example for a more
# detailed example.
COMMON_FLAGS="-O2 -pipe -march=native"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"
FCFLAGS="${COMMON_FLAGS}"
FFLAGS="${COMMON_FLAGS}"

# NOTE: This stage was built with the bindist USE flag enabled

# This sets the language of build output to English.
# Please keep this setting intact when reporting bugs.
LC_MESSAGES=C.utf8

# Compilation
MAKEOPTS="-j4 -l5"
PORTAGE_NICENESS=19

# LLVM Configuration
LLVM_SLOT="20"

# Graphics
VIDEO_CARDS="intel"

# USE flags
USE="X wayland xwayland qt5 qt6 pipewire systemd alsa"

# What to accepted
ACCEPT_KEYWORDS="amd64"
ACCEPT_LICENSE="*"

# boot loader
GRUB_PLATFORMS="efi-64"

# input
INPUT_DEVICES="libinput"

一些关键选项简要说明如下:

  • COMMON_FLAGS="-O2 -pipe -march=twinlake -mtune=twinlake" :传递给编译器的标志位, -O2 意为开启 O2 优化, -pipe 意为使用管道缓存编译中间文件, -march=native 意为自动识别本机架构进行优化(笔者的目标安装机器的 CPU 为 Intel N150,Twin Lake 架构,但是 -march 不支持 twinlake 选项,因为这不算一个正经的处理器家族(本质 12 代 Alder Lake 的 Refresh),所以直接用 native ;需要注意的是 -march 包含了 -mtune 所包含的选项,因此无需额外制定 -mtune
  • MAKEOPTS="-j4 -l5" :传递给 makefile 的标志位,这里 -j4 意为允许同时 4 线程编译, -l5 意为系统负载限制为 5,这个数量基于 Gentoo Handbook 指导( job value == nproc, load-average value slightly above nproc
  • VIDEO_CARDS="intel virgl" :开启 emerge 对对应图形卡的支持,笔者 CPU 是 Intel N150 ,只有 Intel 核显所以选上 intel
  • USE :全局 USE flag
  • ACCEPT_KEYWORDSACCEPT_LICENSE :接受的软件包关键字与协议

需要注意的是,如果你在网络环境比较恶劣的地方,包管理器访问 Gentoo 官方仓库的速度可能会慢的令人发指,因此选择使用镜像会是一个值得考虑的选项,我们可以通过 GENTOO_MIRRORS 变量指定需要使用的镜像站地址,下面是一个使用中科大镜像的例子:

1
GENTOO_MIRRORS="https://mirrors.ustc.edu.cn/gentoo/"

你也可以在 make.conf 中添加代理,例如:

1
2
3
http_proxy="http://代理服务器地址:代理服务端口"
https_proxy="http://代理服务器地址:代理服务端口"
ftp_proxy="http://代理服务器地址:代理服务端口"

安装基本系统

接下来我们切换到 root 用户进行操作:

1
$ sudo su root

首先复制当前环境的 DNS 信息,因为我们后面要 chroot 进去:

1
root # cp --dereference /etc/resolv.conf /mnt/gentoo/etc/

接下来挂载必备的一些文件系统:

1
2
3
4
5
6
7
root # mount --types proc /proc /mnt/gentoo/proc
root # mount --rbind /sys /mnt/gentoo/sys
root # mount --make-rslave /mnt/gentoo/sys
root # mount --rbind /dev /mnt/gentoo/dev
root # mount --make-rslave /mnt/gentoo/dev
root # mount --bind /run /mnt/gentoo/run
root # mount --make-slave /mnt/gentoo/run

然后 chroot 切换到 /mnt/gentoo 目录:

1
2
root # chroot /mnt/gentoo /bin/bash
root # source /etc/profile && export PS1="(chroot) ${PS1}"

然后挂载 EFI 目录:

有一种说法是建议使用 /efi 而非 /boot/efi ,不过笔者还没有太具体了解过:)

1
2
(chroot) root # mkdir /boot/efi
(chroot) root # mount /dev/nvme0n1p1 /boot/efi

然后安装 Gentoo ebuild 仓库快照:

1
(chroot) root # emerge-webrsync

接下来选择 portage profile ,简单来说其决定了整个系统为软件包所使用的默认 USE flag,首先可以使用如下命令查看可选配置:

1
(chroot) root # eselect profile list

笔者计划使用 LxQt 桌面 + systemd 的组合,所以这里选择桌面配置默认的 default/linux/amd64/23.0/desktop/systemd ,如果你要选择 Gnome 或 KDE Plasma 可以选择对应的特化的 profile :

1
(chroot) root # eselect profile set 4

可选:添加 binhost( 诶哟我,bin哥(指 binary)牛b!

众所周知 Gentoo 安装软件包默认需要在本机从源码进行编译,但是有的时候也没这个必要,如果你的软路由刚好又是性能比较弱的那一档,在后面编译软件包的时候也会很痛苦,因此可以像其他发行版一样使用官方编译好的二进制软件包(不过这样的话用 Gentoo 的意义在哪呢

首先需要添加 binhost 配置,新建 /etc/portage/binrepos.conf/gentoobinhost.conf 文件并写入如下内容:

1
2
3
[binhost]
priority = 9999
sync-uri = https://distfiles.gentoo.org/releases/amd64/binpackages/23.0/x86-64/

最后一级目录可选的除了 x86-64 以外还有一些其他的,想要安全性可以选 x86-64_hardened

image.png

实际安装二进制软件包有两种方式,一是在 emerge 安装软件包时附上 --getbinpkg 选项,二是在 /etc/portage/make.conf 中为 FEATURES 项添加 getbinpkgbinpkg-request-signature ,后者要求二进制包被签名,以下是一个示例:

1
2
# Appending getbinpkg to the list of values within the FEATURES variable
FEATURES="${FEATURES} getbinpkg"

然后设置时区,笔者这台机器准备放国内,所以时区设定为亚洲上海:

1
(chroot) root # ln -sf ../usr/share/zoneinfo/Asia/Shanghai /etc/localtime

然后是进行本地化配置,在 /etc/locale.gen 中添加对应项即可,例如:

1
2
3
4
en_US ISO-8859-1
en_US.UTF-8 UTF-8
zh_CN GBK
zh_CN.UTF-8 UTF-8

接下来运行 locale-gen 命令,其会根据 /etc/locale.gen 中项进行生成,生成结束后通过 eselect 命令进行选择,一般推荐选择 C.utf8 ,完成之后更新环境变量:

1
2
3
4
(chroot) root # locale-gen
(chroot) root # eselect locale list
(chroot) root # eselect locale set 2
(chroot) root # env-update && source /etc/profile && export PS1="(chroot) ${PS1}"

配置内核

(可选配置:安装固件与微码)

在开始配置内核前我们可以安装可选的微码与固件,笔者的 CPU 是 Intel N150,所以微码应当安装 sys-firmware/intel-microcode 包,固件则都为通用的 sys-kernel/linux-firmware 包:

1
2
(chroot) root # emerge --ask sys-kernel/linux-firmware
(chroot) root # emerge --ask sys-firmware/intel-microcode

以及可以选择安装 Sound Open Firmware,一个开源音频驱动:

1
(chroot) root # emerge --ask sys-firmware/sof-firmware

虽然 Gentoo 官方提供了多种安装方式,但是手动编译内核并安装是原汁原味 Linux 体验的一部分,所以这里我们选择手动从源码安装 Gentoo 的 Linux kernel

首先安装 boot loader,笔者个人还是比较喜欢 GNU GRUB:

1
2
(chroot) root # emerge --ask --verbose sys-boot/grub
(chroot) root # grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Gentoo

然后从 Gentoo 官方仓库安装带有 Gentoo Patches 的内核源码 ( 不会真有人以为👴要直接去 kernel.org 下最原生态的源码吧 ),默认安装的版本是 longterm 版本:

1
(chroot) root # emerge --ask --verbose sys-kernel/gentoo-sources

如果你需要更新的驱动支持,可以选择安装最新版本的 stable 内核,只需要为该软件包启用 ~amd64 关键字,这意味着额外接受 testing 中的软件包,在 /etc/portage/package.accept_keywords/ 目录下新建一个文件 (例如 /etc/portage/package.accept_keywords/kernel)写入如下内容:

1
sys-kernel/gentoo-sources ~amd64

相应地,启用了 ~amd64 的软件包的更新频率也会快很多,但是对于绝大部分常用软件包都能保持 cutting edge

当然,笔者也遇到过有的软件包没人维护的情况,此时就算启用了 ~amd64 也没法跟上官方的最新版本,因为根本还没维护者将其添加到 Gentoo 官方仓库里:(

我们可以使用 eselect kernel list 查看系统中已经安装的内核源码,通常会被安装到 /usr/src 目录下:

1
2
3
(chroot) root # eselect kernel list
Available kernel symlink targets:
[1] linux-6.12.41-gentoo

使用 eselect kernel set 命令将 /usr/src/linux 设为指定内核的符号链接:

1
(chroot) root # eselect kernel set 1

下面开始正式的内核编译过程,首先来到 /usr/src/linux 目录下创建编译配置文件 .config ,这里我们直接以 livegui 的 config 作为模板,然后用 menuconfig 进图形化界面配置一些自己想要的配置:

1
2
3
(chroot) root # cd /usr/src/linux
(chroot) root # zcat /proc/config.gz > .config
(chroot) root # make menuconfig

在这一步你可以尝试在 defconfig 生成之后使用 make localmodconfig 进行内核裁剪,从而减小内核体积,但是很容易把一些必要的东西也裁剪掉,例如笔者遇到过 FAT32 字符集缺失的问题(在重启后挂载 EFI 分区时提示 IO charset iso8859-1 not found 导致系统进 emergency mode,解决方案是开启 CONFIG_NLS_ISO8859_1=m),因此在裁剪完之后最好还是要 手动检查一下必要的各种内核编译选项是否都开启了或者干脆就不裁剪也可以

以及笔者还遇到过一个情况就是 defconfig 中有很多配置都不包含,例如在安装 docker 时会有下面的警告信息,因此不如直接以 liveguid 的内核配置作为模板 (听说 defconfig 似乎近段时间也被大肆批判了一番):

此外,以下配置是我们 必须 要编译进内核的(摘自 Gentoo wiki ,根据当前内核版本略有修改),因为我们的软路由需要这些配置以正常工作:

1
2
3
4
5
6
7
8
9
10
-*- Networking support  --->
Networking options --->
[*] TCP/IP networking
[*] IP: advanced router
[*] Network packet filtering framework (Netfilter) --->
[*] Advanced netfilter configuration
IP: Netfilter Configuration --->
<M> IP tables support (required for filtering/masq/NAT)
IPv6: Netfilter Configuration --->
<M> IP6 tables support (required for filtering)

接下来开始编译,bzImage 为内核本体,modules 为内核模块:

1
(chroot) root # make -j$(nproc) -l$(nproc) bzImage modules

如果你和笔者一样为软路由选择了低功耗低性能的 CPU,那么这个编译过程会非常非常非常非常长……以笔者的 Intel N150 CPU 为例,4 核 4 线程睿频 3.6 GHz,在已经进行了内核裁剪的情况下,总编译时间来到了 44min(作为对比,笔者之前在 Intel Ultra9 285H 上开启了 Compile also drivers which will not load ,且尽可能开启了比较多的编译选项以支持尽可能多的机器的情况下,编译内核与模块的时间为 31m12.943s ,因为当时目的是制作一个类似 livecd 的能够在绝大多数机器上启动的 Gentoo Linux 环境放到 U 盘里)

如果实在等不了可以试试 使用 distcc 利用起闲置计算资源加速编译过程 :)

然后将内核模块安装到 /lib/modules 下:

1
(chroot) root # make modules_install

接下来安装 sys-kernel/installkernel ,这个软件包会自动帮忙进行内核安装过程中一些琐碎的事情(笔者猜测应该是给内核的 make install 添加了 hook),包括生成 initranfs 和更新 boot loader 配置,首先创建文件 /etc/portage/package.use/installkernel 并写入如下 USE flag:

1
sys-kernel/installkernel grub dracut

然后安装:

1
(chroot) root # emerge --ask --verbose sys-kernel/installkernel

最后生成 initramfs 等文件并安装到 /boot 目录:

1
(chroot) root # make install

配置系统

首先配置 fstab,使用 blkid 命令查看不同分区的 UUID 后写到 /etc/fstab 当中,下面是一个 fstab 的例子:

1
2
3
4
# <fs>                  <mountpoint>    <type>          <opts>          <dump> <pass>
UUID=77886e74-1c64-4c47-9d5a-3981a327cfb0 none swap sw 0 0
UUID=139b2a0b-aaa3-4625-8439-5ae51ef1ed0c / ext4 defaults 0 1
UUID=89D2-BED7 /boot/efi vfat utf8 0 2

接下来配置主机名称,随便想一个就行 :

在这个例子中,字符串 c2VydmVyMDAwserver000 的 base62 加密

1
(chroot) root # echo "a3homelab-c2VydmVyMDAw" > /etc/hostname

安装 DHCP:

1
2
(chroot) root # emerge --ask net-misc/dhcpcd
(chroot) root # systemctl enable dhcpcd

设置 root 密码:

1
(chroot) root # passwd

初始化 systemd 的启动配置:

1
2
3
(chroot) root # systemd-machine-id-setup
(chroot) root # systemd-firstboot --prompt
(chroot) root # systemctl preset-all --preset-mode=enable-only

配置 bash 自动补全:

1
(chroot) root # emerge --ask app-shells/bash-completion

配置时间同步:

1
2
(chroot) root # emerge --ask net-misc/chrony
(chroot) root # systemctl enable chronyd.service

配置文件系统和磁盘相关的一些工具,在这台机子上笔者理论上只会用到 ext4 和 fat32 :

1
2
(chroot) root # emerge --ask sys-fs/e2fsprogs sys-fs/dosfstools
(chroot) root # emerge --ask sys-block/io-scheduler-udev-rules

安装网络相关的一些软件:

1
(chroot) root # emerge --ask net-dialup/ppp net-wireless/iw net-wireless/wpa_supplicant

安装 sudo ,并用 visudo 删除 %wheel ALL=(ALL:ALL) ALL 所在行开头的注释符号 # 以允许 wheel 用户组使用 sudo:

1
2
(chroot) root # emerge --ask app-admin/sudo
(chroot) root # visudo

添加一个日常用的管理员账户,这里别忘了把 arttnba3 改成你自己的 id:

1
2
3
(chroot) root # groupadd arttnba3
(chroot) root # useradd -g arttnba3 -m -G users,wheel,audio,cdrom,floppy,portage,usb,video -s /bin/bash arttnba3
(chroot) root # passwd arttnba3

安装 ssh 以供后续连接:

1
2
(chroot) root # emerge -av net-misc/openssh
(chroot) root # systemctl enable sshd

最后重启计算机:

1
2
3
4
(chroot) root # exit
root # umount -l /mnt/gentoo/dev{/shm,/pts,}
root # umount -R /mnt/gentoo
root # reboot

0x02. 配置软路由基本功能

前面安装操作系统的过程基本是千篇一律的,但接下来我们要将其配置成软路由,也就是安装与配置各种软件使得这一台小主机有着路由器的功能,这是我们将软路由相比起通用计算机所多出来的部分,因此我们首先需要明确 现代路由器 都有着哪些功能( 《计算机网络》复习课来了

我们先复习一下计算机网络,首先回顾一下 OSI 七层模型,其中负责设备间如何进行连通的为 物理层、数据链路层、网络层,而自传输层开始往上的几层定义的是端到端消息通信的具体方法和格式,属于与具体物理设备无关的 进程间通信 的范畴,因此我们主要关注于下面三层:

偷的图

这几层的工作 极简总结 便是:

  • 物理层定义接口与传输介质的物理属性,以及数据传输的物理过程,例如定义了 RJ45 网口的形式、线路的组成、数据速率等
  • 数据链路层负责 (frame)从一个节点到另一节点的传输,一张网卡的一个网口通常有着一个自己的 MAC 地址,在传输的帧头部封装着收发方的 MAC 地址,网络设备通过网线连接到具有多个网口的 交换机 (switch)上,交换机在这一层负责根据 MAC 地址将帧从一个网口转发至另一个网口,其通过接收帧学习网口对应的 MAC 地址(并定期清理),对无法确定目标 MAC 地址对应网口的则在其余端口进行广播,交换机间也可以通过网线进行连接以组建更加复杂的网络(单个端口可以对应多个源 MAC 地址)
  • 网络层负责逻辑上更细致地划分与管理网络,网络层(通常使用 IP 协议)中每个设备都有一个地址(以 IPv4为例是32位地址),子网掩码用来指示网络地址中的哪些位用以划分不同的网络(两个地址若与同一子网掩码做与运算相等则说明处在同一网络下),在同一子网中设备(主机或路由器)通过在局域网中广播 ARP 请求询问目标 IP 地址对应的 MAC 地址,若处在不同子网则目标 MAC 地址设为默认网关(通常是一个路由器), 路由器 (router)在这一层负责根据 IP 地址将数据包转发到目标主机或另一路由器,并修改数据包头部数据链路层的目的 MAC 地址为下一跳的设备的 MAC 地址,路由器中存放至少一张路由表用以记录 数据包应当发往哪个端口 (表项内容包括 目的网络、子网掩码、下一跳 IP、物理出口 等,形成过程可以是静态配置、自动生成、通过 OSPF 等协议学习等)

在传统路由架构中一个端口对应一个子网,因此同一子网主机需要先连接到交换机,交换机再连接到路由器,因此传统网络架构一般都长这个样子:

偷的图

而现代路由器(比如说你去商场买的小米路由器)和 三层交换机 通常都同时集成了二层交换机与三层路由的功能 (这两类设备在单一子网结构中 功能基本等效 ,如果我们只考虑网络的连接),因此我们见到的大部分家庭网络结构一般都长这种简化掉交换机的样子:

偷的图

——回到正题,我们要将一台 Linux 主机配置成一台现代路由器,他需要支持这样的功能:

  • 有电时自动启动 -> 配置 上电自启动
  • 能够进行拨号上网(可选) -> 配置 ADSL 与 PPPoE
  • 为连接在网口上的设备分配 IP 与 DNS 服务器 -> 配置 DHCP 服务
  • 允许连接在网口上的设备上网 -> 配置 IP 转发与 NAT

明确了这些核心功能点,下面我们可以开始正式进行配置了

配置上电自启动

关于是否能够进行上电自启动的配置,其实关键点主要在于我们用作软路由的计算机的主板是否支持相关功能, 主板不支持一切都是白搭 (说实话很多笔记本还真不支持这个,例如 ASUS 笔记本的 BIOS 就阉割了很多功能),具体怎么看主板是否支持主要就是看 BIOS/UEFI 配置界面当中是否有对于电源状态相关的设置,以 Dell Wyse 3040 的 BIOS 为例,在 Power Management 一节下边的 AC Recovery 选项规定了上电后的计算机行为,选择 Power On 就意味着上电自启动

如果你的 BIOS 中没有类似这样的配置选项,说明主板不支持上电自启动 :(

配置 WAN 口与 LAN 口

需要注意的是,这一节的配置针对使用 systemd 的系统,如果你使用 OpenRC 或其他 init,请自行查阅资料 问 ChatGPT 进行配置

简而言之我们的软路由至少需要两个网口,并将其配置为如下:

  • WAN 口:用于连接外网,通常需要连接到调制解调器或更上级的网络
  • LAN 口:用于内网服务,用于连接交换机或直连设备

对于 WAN 口,我们通常直接配置为 DHCP 即可(在连接调制解调器的情况下),因此创建文件 /etc/systemd/network/10-wan.network 写入如下内容(注意修改为自己的网卡名):

1
2
3
4
5
[Match]
Name=enp1s0

[Network]
DHCP=yes

对于 LAN 口,我们则需要自行定义一个静态 IP,因此创建 /etc/systemd/network/20-lan.network 文件写入如下内容(注意修改为自己的网卡名):

1
2
3
4
5
[Match]
Name=enp2s0

[Network]
Address=10.0.0.1/24

然后重启网络服务即可 (注意网口必须要连接设备才能是 UP 状态):

1
$ sudo systemctl restart systemd-networkd

需要注意的是这里我们使用的是轻量级的 systemd-networkd 而非 NetworkManager(也不推荐,因为这个东西太笨重了),如果你的系统启用了 NetworkManager,你需要禁用它:)

当然,如果从轻量级的角度考虑,你其实不应该使用 systemd,而应该使用 OpenRC,不过至少笔者个人是比较喜欢 systemd,而且笔者用作软路由的设备似乎也不是很缺这个性能:)

配置 ADSL 与 PPPoE(可选)

如果你的家庭网络架构中已经有一台独立的调制解调器(例如光猫),笔者推荐通过单独的调制解调器进行上网,进行功能分离以保持 KISS 原则, 毕竟 All In One 很多时候就意味着 All In BOOOOOOM…

🕊🕊🕊

配置 DHCP 服务

动态主机配置协议 (Dynamic Host Configuration Protocol, DHCP )用以自动配置主机网络,包括自动分配 IP 和 DNS 以及其他网络参数等,是目前最常用的网络配置协议

这里我们选择安装 dnsmasq 来在软路由侧提供 DHCP 服务,首先安装软件包:

1
$ sudo emerge -av net-dns/dnsmasq

接下来编辑 dnsmasq 配置文件 /etc/dnsmasq.conf ,写入如下内容:

1
2
3
4
5
interface=enp2s0 # 在该接口监听 DHCP 和 DNS 请求,改成你的网口对应的网卡名称,可以用 ip addr 命令查看
bind-interfaces # 绑定到端口
dhcp-range=10.0.0.2,10.0.0.254,255.255.255.0,12h # IP范围、子网掩码、IP 租约时间,IP 范围仅示例
dhcp-option=option:router,10.0.0.1 # 默认网关,设为软路由 IP,这里仅示例
dhcp-option=option:dns-server,10.0.0.1 # DNS 服务器,设为软路由 IP,这里仅示例

以及为了避免启动失败,我们需要让 dnsmasq 在网络准备好之后再启动,运行如下命令编辑:

1
$ sudo systemctl edit dnsmasq.service

写入如下内容:

1
2
3
[Unit]
After=network-online.target
Wants=network-online.target

然后直接启用服务就行,以 systemd 为例:

1
2
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now dnsmasq

配置 IP 转发与 NAT

首先我们需要启用内核的 IPv4 转发功能,创建 /etc/sysctl.d/99-ipforward.conf 文件写入如下配置(如果目录不存在则创建):

1
net.ipv4.ip_forward=1

然后重新载入配置:

1
$ sudo sysctl -p

接下来配置 NAT ,我们要将来自 LAN 口的数据包都转发到 WAN 口,虽然大家可能会比较熟悉更加经典的 iptables,但是在高版本内核当中 legacy iptables 被移除,因此我们首先要将 iptables 后端切换为 nftables,首先安装 nftables:

1
$ sudo emerge -av net-firewall/nftables

创建文件 /etc/portage/package.use/iptables 写入如下内容,为 iptables 添加 nftables 的 USE flag:

1
net-firewall/iptables nftables

重新编译安装 iptables 引入 nftables 后端:

1
$ sudo emerge -av net-firewall/iptables

切换到 nftables 后端:

1
2
3
4
5
$ eselect iptables list 
Available iptables symlink targets:
[1] xtables-legacy-multi *
[2] xtables-nft-multi
$ sudo eselect iptables set 2

接下来编写 iptables 规则:

  • -t nat 选择 NAT 表, -A POSTROUTING 添加规则到 POSTROUTING 链,-o enp1s0 匹配目标为从 enp1s0 网卡流出的包,-j MASQUERADE 进行源地址伪装(修改源 IP 为 enp1s0 的 IP)
  • 选择默认 filter 表,-A FORWARD 添加规则到 FORWARD 链,-i enp2s0 -o enp1s0 从 enp2s0 流入到 enp1s0 流出的包,-j ACCEPT 允许通行
  • 选择默认 filter 表,-A FORWARD 添加规则到 FORWARD 链,-i enp1s0 -o enp2s0 从 enp1s0 流入到 enp2s0 流出的包,-m state 仅允许特定状态的包,--state ESTABLISHED,RELATED 该包为已建立连接或相关连接,-j ACCEPT 允许通行

即允许数据包从 LAN 口通过 WAN 口发出,发出的包伪装成 WAN 口 IP (NAT),仅允许属于已经建立的连接的数据包从 WAN 口流回 LAN 口:

回顾一下 iptables:

1
2
3
$ sudo iptables -t nat -A POSTROUTING -o enp1s0 -j MASQUERADE
$ sudo iptables -A FORWARD -i enp2s0 -o enp1s0 -j ACCEPT
$ sudo iptables -A FORWARD -i enp1s0 -o enp2s0 -m state --state ESTABLISHED,RELATED -j ACCEPT

现在我们的软路由就已经实现普通路由器的能让内网设备上网的功能了 ,插入一台设备进行测试,可以发现能够成功分配 IP 且能正常上网👍

然后是持久化配置:

需要注意的是 不能有其他 iptables 规则干扰 (例如 docker),如果存在的话则会导致规则恢复时出现问题(因为保存了额外的规则)

1
2
$ sudo systemctl start iptables-store.service
$ sudo systemctl enable iptables-restore.service

0x03. 配置软路由更多玩法

虽然说现在我们已经成功完成了软路由基本功能的配置,但 就目前的状态而言和直接买一个成品路由器相比可以认为是没有太大区别的,那我们配软路由的意义在哪里呢? 相比起普通的成品硬路由,软路由的核心优势在于我们是一个功能完整的用户完全可控的通用操作系统,因此我们需要开拓一些更多的常规成品路由器做不到的玩法:)

自建安全可靠的 DNS 服务器

注:你需要一个公网服务器和域名

前面忘了中间忘了后面也忘了,总而言之在部分比较恶劣的网络环境下你可能会面临 網域伺服器快取汙染 的问题,导致对部分网站的访问在 DNS 解析阶段便获取到错误的结果从而无法正常访问部分网站

由于我们已经有了软路由,DNS 服务器是由我们的路由器通过 DHCP 协议下发给设备的,比较朴素的思路便是将 DNS 解析的工作都交由我们的软路由来完成(在软路由侧为下行设备分配 DNS 服务器为软路由),然后在我们的软路由上再配置安全的 DNS 解析,下图是笔者所使用的架构:

首先配置软路由作为所有下行设备的默认 DNS 服务器,这里我们使用经典的 dnsmasq + dnscrypt-proxy 来完成,由于前面我们配置 DHCP 服务的时候已经安装了 dnsmasq,这里我们只需要安装 dnscrypt-proxy:

1
$ sudo emerge -av dnscrypt-proxy

接下来在 dnsmasq 配置文件 /etc/dnsmasq.conf 中添加如下内容,将 DNS 服务器配置为软路由,并将 DNS 请求转发到本地的 53553 端口,我们的 dnscrypt-proxy 服务将监听这一端口:

1
2
3
4
5
6
7
8
# DNS 服务器,设为软路由 IP,这里仅示例
dhcp-option=option:dns-server,10.0.0.1

# DNS服务转发到上游指定服务器,这里是本地的53553端口,注意这里的 # 发挥的不是注释的作用
server=/#/127.0.0.1#53553

# 不使用本地的 /etc/resolv.conf 结果,否则部分 DNS 请求会走这条路径
no-resolv

接下来配置 dnscrypt-proxy 使用安全的 DNS over HTTPS ,修改配置文件 /etc/dnscrypt-proxy/dnscrypt-proxy.toml 对应项为如下内容,这里注意的一点是虽然 DoH 在 RFC8484 中定义的默认路径是 /dns-query ,但是对于中间人攻击而言这个路径对其是可见的,从而可能会阻断我们的 DoH 查询,因此这里用正常的 index.html 做伪装:

这里的 stamp 的生成可以使用 https://dnscrypt.info/stamps/ ,简而言之如图:

1
2
3
4
5
6
7
8
9
server_names = ['mydoh']

listen_addresses = ['127.0.0.1:53553']

bootstrap_resolvers = ['223.5.5.5:53', '114.114.114.114:53']

[static]
[static.mydoh]
stamp = "自己生成"

最后重启 dnsmasq,开启 dnscrypt-proxy :

1
2
$ sudo systemctl restart dnsmasq
$ sudo systemctl enable --now dnscrypt-proxy.service

接下来我们配置公网服务器, dnscrypt-proxy 默认使用 POST 方法,Content-Type 自动设置为 application/dns-message ,这是我们在服务端可以将 DoH 请求与普通 Web 访问请求进行区分的一个方法,因此接下来我们先在公网服务器搭建一个网站(怎么搭建就不用👴多说了8),然后修改 NGINX 相关配置如下:

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
# 匹配咱们的参数并进行标识的变量
map "$request_method:$http_content_type:$uri" $proxy_flag {
default 0;
"POST:application/dns-message:/index.html" "doh";
}

server {
# ...
# 网页基本配置比如说 ssl 证书路径之类的,这里就直接省略了

location = /doh {
if ($proxy_flag != "doh") {
return 404;
}

proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_http_version 1.1;
proxy_buffering off;

proxy_pass http://127.0.0.1:8053/dns-query;
}

location = /index.html {
root 你的网页文件夹路径;

if ($proxy_flag = "doh") {
rewrite ^ /doh last;
}

try_files /index.html =404;
}

# 你的一些其他网页配置
}

完成后重启 NGINX:

1
$ sudo systemctl restart nginx

然后是配置服务器的 DoH 后端,这里我们选择 CoreDNS ,其使用 Golang 进行编写,有着更好的性能表现以及高并发支持,这里我们使用 Docker 进行安装(因为这东西虽然火但不知道为什么没有进到各大发行版软件仓库),首先将镜像拉到本地:

1
$ docker pull coredns/coredns:latest

接下来编写配置文件(例如 /home/你的用户名/coredns/Corefile ,后面我们会将其映射进容器里),配置仅监听 127.0.0.1 的 8053 端口,并将 DNS 请求转发到大家所熟知的 1.1.1.1

这里不使用 8.8.8.8 是因为 8.8.8.8部分解析结果不准 ,例如 dnslog.cn 使用 8.8.8.8 查询 居然得到 127.0.0.1如果查询结果失真的话那咱们搞这个 DoH 就没意义了 ,因此这里笔者使用 Cloudflare 的 1.1.1.1 服务器而非 Google 的 8.8.8.8

1
2
3
4
5
6
7
https://.:8053 {
bind 127.0.0.1
forward . 1.1.1.1
cache 30
log
errors
}

然后启动 docker,这里配置使用本地网络,不然会有一些奇奇怪怪的问题:

1
2
3
4
5
6
$ docker run -d \
--network=host \
--restart=always \
--name=coredns \
-v 你的Corefile配置文件路径:/Corefile \
coredns/coredns:latest

现在咱们的安全的 DNS 服务器就完成配置了:)

🕊🕊🕊

最近没什么时间折腾软路由,后面应该会补充更新几节:)

0xFF. 写在结束之后

说实话笔者各种解决方案调研下来,对部分搞 homelab 的人的一个很大的刻板印象就是 整体上不专业 ——诚然,圈子里是有真正专业的人,但是 非常多的人都在不专业的解决方案上一路狂飙,同时还带动更多新入坑的小白在不专业的道路上越跑越远 ——笔者个人觉得, 要么就花比较少的精力弄个省事点的架构 (比如说直接安个 OpenWrt 或者 iKuaiOS 然后开躺就完事了), 要么就花费足量的精力弄一个相对专业的网络架构,如果又花费大量精力结果又弄出来一个丑陋的不专业的网络架构,还带领着小白学习这样的不专业的做法,那是否未免有点…

诚如古人所言,“学而不思则罔,思而不学则殆”,这些问题的根源其实很大程度上就是 “思而不学” ,想折腾但又未尝深入学习计算机网络工程相关知识,毕竟能用就行 ——于是乎就造出来很多虽然看着能用但是实际上十分难以用偏正面的词汇去形容的解决方案——诚然,自己用的话那没什么好说的, 但真正可怕的是这种能用就行的风气会带着后面越来越多新入坑的人也在这样的道路上一路狂飙,从而使得最后错误的解决方案反而还成为了主流…

当然笔者并不认为自己学过一点计网和网工就高人一等或是怎么样的,但是如果哪怕稍微拉一些有思科或者华为认证的网络工程师来看一遍现在部分小圈子里的各种 主流解决方案,我相信没有一个是会不破口狂喷的——当然不得不承认的是 很多需要折腾的东西虽然丑陋但是据称也 work well ,但这应该成为主流吗?

诚然,笔者非常乐意看到简单易上手的 iKuaiOS 之流成为小白的心头之好,笔者也很愿意看到大家分享自己的各种奇妙的解决方案,这些都是很好的事情, 但如果最终非专业的解决方案成了圈子的主流,入门软路由上来言必旁路由,在不清楚自己需求的情况下一定要 PVE 里再套 OpenWrt 和 iKuaiOS,那笔者不得不感叹一句这是这个时代的悲哀

当然,笔者自认为自己没办法改变一些我所不喜欢的现状, 我自认也没有资格去这么做 ,笔者唯一能够做的就是尽量保持自己在一定程度上依然能够保有科班出身的专业性(虽然很多专业课的知识似乎都开始有点慢慢忘却了), 在这之外期盼一下笔者的这个基本没什么人会来看的博客能够为一小部分读者带来一定的收获, 其他的笔者也就不奢求了,因为也没有必要抱有期待,毕竟期望越大失望越大

最后的最后,如果你的观点和笔者有所不同,可以在本篇博客下方讨论,如果想转载的话按照博客的 CC 那个啥协议(看下边,👴也忘了叫啥了)就行——但是最好还是不要引流太多人过来,笔者可不希望成为众矢之的 :(


【OPS.0x09】家用软路由手搓日志
http://example.com/2025/09/30/OPS-0X09-SOFT_ROUTER_GUIDE/
作者
arttnba3
发布于
2025年9月30日
许可协议