在上一篇文章中,我们为这台主机搭建了存储。地基已经打好。这篇文章,我们就来深入聊聊网络。

我的目标是,在 Proxmox VE 这个 All-in-One 环境下,利用虚拟化技术,构建一个既能安全地对外提供服务,又能保证家庭网络稳定可靠的网络环境。

设备蓝图

在这个架构中,有几个核心的网络“设备”在协同工作,它们有些是物理的,有些是虚拟的:

  1. 华硕路由器 (ASUS Router): 物理设备,作为家庭网络的主网关,负责拨号上网和 Wi-Fi。它管理着我的主局域网 192.168.1.0/24。
  2. OPNsense: 运行在 pve 主机上的虚拟机。它是一个专业的防火墙,负责创建一个被隔离的“服务器区”,我为其规划了 192.168.111.0/24 网段。
  3. AdGuard Home: 运行在 pve 主机上的 LXC 容器。它是我整个家庭网络的主 DNS 服务器,负责广告过滤和内部域名解析。它的 IP 地址是 192.168.1.250。
  4. Caddy: 运行在 pve 主机上的 LXC 容器,位于 OPNsense 之后。作为反向代理,它是所有需要对外服务的应用的唯一入口。

Proxmox VE 上的虚拟网络搭建

为了实现上述目标,我在 PVE 主机上的网络配置其实相当简洁:

  • vmbr0: 这是主管理网桥。它直接桥接到服务器的物理网卡上。我的 PVE 主机、TrueNAS VM、AdGuard Home LXC 以及 OPNsense VM 的 WAN 口都连接到这个网桥,共享 192.168.1.0/24 网段。
  • vmbr1: 这是一个纯虚拟的内部网桥,不连接任何物理网卡。OPNsense VM 的 LAN 口以及所有需要被它保护的服务(Caddy, Docker VMs)都连接到这个网桥,形成一个隔离的 192.168.111.0/24 网络。
  • 这两个 bridge 之间要通过 OPNsense 来过滤流量,实现防火墙的效果。

Virtual Bridges vmbrs

OPNSense should have two NICs, one for WAN, one for LAN: opnsense-devices

网络详解

1. 华硕路由器:简化职责,做好网关

我让华硕路由器的角色变得非常纯粹。

  • DHCP 设置: 它依然负责为主网络 (192.168.1.0/24) 提供 DHCP 服务。但我特别设置了两个 DNS 服务器推送给所有客户端:
    1. 主 DNS: 192.168.1.250 (我的 AdGuard Home)
    2. 备用 DNS: 192.168.1.1 (路由器自己)

IMPORTANT

这里的 DNS “顺序”非常重要。客户端(比如家人的手机)会首先使用 AdGuard Home,享受去广告和本地解析的便利。只有当 AdGuard Home 整个服务完全无法访问时(例如我正在维护 AdGuard LXC),客户端才会自动切换到备用的路由器 DNS,从而保证家人还能正常上网。 如果只是一个 URL 是 adguard home 无法访问的(比如过滤了的),这个是不会切换了到路由器 DNS 的。

  • 静态路由 (Static Routing): 这是让主网络能“看到”服务器区的关键。我添加了一条静态路由规则:
    • 目标网络: 192.168.111.0/24
    • 网关: 192.168.1.211 (我为 OPNsense WAN 口设定的静态 IP)
    • 作用: 这条规则告诉华硕路由器:“所有想去 192.168.111.x 这个地址的流量,都请打包发给 OPNsense 去处理。”

另外在这里再总结一下 ASUS 上面一些设置的逻辑:

  • 如果 LAN → DHCP 里面 DNS server list 留空了,就会默认广播路由器自己的LAN地址,让客户当作 DNS 用。
    • 也可以显式的把 ASUS Router 的 LAN IP 直接写在 DNS 列表里。
  • 如果 ASUS Router LAN里面让 clients 把 Router 当作 DNS 了。就会使用 WAN 里面定义的 DNS (包括 DNS-over-https 之类的设定)。
    • ASUS Router 是一个 DNS 中继。
  • 在 LAN DHCP 里面如果定义了 domain,比如 lan, 然后再使用 manually assigne IP,同时在 assign 的时候,如果给了 host name,比如:funbox
    • 那么在 ASUS Router 的内置 DNS 中继中就添加了一个 funbox.lan 的full host name,分配给了 funbox 对应的 IP。

NOTE

我的 ASUS Router 跑的是 Asuswrt-Merlin 固件。至于原版是不是这样,我也不是很确定了。

2. AdGuard Home:DNS 的控制中心

我没有使用华硕路由器的“手动指定IP→主机名”功能,因为有厂商锁定的风险。所有内部域名解析,都由 AdGuard Home 统一负责。

NOTE

其实对于不像折腾太多的人来讲,Asus Router 里面这个 Manually Assigned IPs can be bound with hostname 的功能还挺好用的。 相当于直接让你可以用 something.lan 来访问你的 host and services。不需要折腾 Adguard Home 或者 OPNsense。

3. OPNsense:防火墙

配置虚拟化的 OPNsense 绝对是整个过程中最折腾的一环。

  • 初次访问的问题: OPNsense 默认会阻止所有来自私有网络的入站流量,这导致我无法从主网络访问它的 Web UI。在配置好防火墙规则之前,pfctl -d (临时禁用防火墙) 这个命令是我的救星。要让 WAN 口能被稳定访问,需要四步:

    1. 在 Interfaces → WAN 中,取消勾选 Block Private Network。
      • private-network
    2. 在 System → Settings → Administration 中,确保 Listen Interfaces 同时选中了 WAN 和 LAN。
      • listen-interface
    3. 在 Firewall → Rules → WAN 中,添加一条规则,允许来自 WAN net 的流量访问防火墙自身。
      • wan-rules
        • 这里另外的是用来给 Caddy 反代开口的。
    4. 重启 OPNsense VM。说来奇怪,不重启,这个 WAN 访问的问题则常常无法完美生效(好像之后设定 rules 的时候没有这个问题),这个“重启”的需要真是个巨坑。浪费了超多时间在上面。
      1. More details: [opnsense-access-through-wan]
  • 核心配置:

    • Unbound DNS: OPNsense 的 LAN (OPN.LAN 网段) (vmbr1) 内的服务,其 DNS 请求由 Unbound 处理。Unbound 的上游 DNS 则通过 OPNsense 的 WAN 口,最终指向了我的 AdGuard Home。
      • unbound-settings
    • 防火墙规则: 我在 WAN 口上创建了一条规则,只允许来自 Cloudflare 的 IP 地址段的流量访问我的 Caddy 反向代理。这能有效防止各种网络扫描和直接攻击,确保所有外部流量都经过了 Cloudflare 的清洗和代理。
      • 当然,还有好多其他的规则,包括但不限于:
        • 允许 ASUS Router 网段的 hosts 直接访问 OPN.LAN 的网段。
        • 允许 OPN.LAN 里面的 Caddy 给 ASUS 内网的服务提供 reverse proxy。
        • 如果 OPN.LAN 里面有些 hosts 需要 TrueNAS 提供的 SAMBA 或者 NFS 的话,可以打开专门的通道。
    • 未来计划: 之后我还会测试开启 IDS/IPS (入侵检测/防御) 功能,例如 Suricata,来进一步增强安全性。
    • 其他优化: 我安装了 os-qemu-guest-agent 插件,并设置将 /var/log 放入内存盘,以减少不必要的磁盘写入。

启动顺序

首先,Adguard Home 作为 ad filter 同时负担了 对 LAN 网段的设备(包括 OPNsense),进行 lan 和 opn.lan domain 的解析,在 Proxmox 启动的时候,应该最先启动。关机的时候应该最后关闭。

然后是 OPNsense。这个启动好之后,才能开始启动 OPN.LAN 网段里面的 VM/LXC (Caddy and Immich)

Proxmox 依赖 TrueNAS 提供一部分 VM 的 vm-disks。但是 Proxmox 依赖的记录是 /etc/hosts,并不那么依赖 Adguard Home。所以 TrueNAS 也可以最先启动。启动之后,Proxmox 才能搞定 NFS 的挂载,从而允许 Immich 在之后被启动。

重要的 command line 命令

Ubuntu 24.04 上面查看真正使用中的 DNS。传统的 /etc/resolv.conf 里面估计只会写着 127.0.0。1:53

resolvectl status

Ubuntu 重启整个 network stack,用于切换 DHCP → Static IP 的最后一步

sudo systemctl restart systemd-networkd.service

nslookup,以及如何指定一个 DNS,用来测试某个 DNS 是否正常工作

nslookup [your.box.domain]
 
# Ask a specific DNS
nslookup [your.box.domain] [your.dns.server]

最终效果

最终,这套复杂的底层架构,转化为了一个比较简单、相对可靠的上层体验。从一个普通用户的角度来看:

  • 用户可以用 caddy.opn.lan 或者 jellyfin.lan 这样的域名访问这些主机。这些服务上也可以用这些域名访问其依赖的其他服务(比如 jellyfin 依赖 TrueNAS)。
  • Adguard Home 提供广告过滤和 DNS。
  • ASUS Router 提供备用 DNS
  • OPNSense 对所有外面进来的流量做防火墙,未来还可以进行 IPS/IDS.
  • 如果有新增的 VM / LXC,可以用两个 script 方便的更新 PVE 和 Adguard Home 上面的 DNS records。虽然不是自动的,但是 one click away 已经足够方便了。
    • 如果一切 DNS records 都更新好了,那么所有服务之间的依赖,如果用的是 DNS records (FQDN),那么,最差情况,PVE重启之后应该可以保证所有链接都被更新到最新的 DNS records 对应的设定。

胡思乱想

静态路由的利与弊

我选择使用静态路由,让主网络可以直接通过 192.168.111.x 的 IP 访问服务器区的服务。

  • 好处: 直观,不需要在 OPNsense 上配置任何端口转发。而且不耽误我在不用VPN的情况下,随时 SSH 到 OPN.LAN 的主机。
  • 坏处 (非对称路由): 从主网络发起的连接,其回复包会被 OPNsense 的状态表拦截。为了解决这个问题,我(最好)必须在 OPNsense 的 Firewall -> NAT -> Outbound 中,创建一个针对性的 NAT 规则来“伪装”回复流量,从而修复路由对称性。 这是一个为了便利性而增加的额外配置。
    • 我其实还没做这个 Outbound rule。之后如果遇到问题再做吧。

“内部 DMZ”的思考

传统的 DMZ (非军事区) 位于互联网和内部局域网之间。而我的架构,则是将 DMZ (我的 OPNsense LAN) 放在了家庭网络的“最深处”。

  • 这种架构的优势: 它可以有效地保护我的主网络 (192.168.1.0/24) 不受服务器区潜在风险的影响。即使 Caddy 或某个对外服务的 VM 被攻破,攻击者也只是进入了一个被 OPNsense 严密监控的“笼子”里,很难再横向移动到我的个人电脑或 TrueNAS 所在的网络。
  • 这种架构的劣势: 它无法保护服务器区的服务免受来自主网络内部的攻击(例如,某台电脑中毒)。

安全的未来:VPN 与 Cloudflare Tunnel

说到底,直接通过路由器端口转发将服务暴露给公网,始终存在风险。

  • 对于一个 homelab,更安全的做法是完全关闭所有端口转发,转而使用 VPN (如 WireGuard) 或 Cloudflare Tunnel 这样的“零信任”方案来访问您的内部服务。
    • 如果 ISP 给的已经是 NAT 之后的IP,那除了打洞,将别无选择。
    • Cloudflare free tier 的 Tunnel 给的流量好像很有限,所以如果 DDNS 能用,还是 DDNS 比较自由吧。
  • 这可能是我下一步会去探索的方向。目前的架构,可以看作是一个跳板。
    • 至少也够用了。

NEXT

network-arch

至此,我们的网络层也基本搞定了。我们在一台机器内部,构建起了一个具备安全隔离区、中央 DNS 和反向代理的网络架构。也算是麻雀虽小,五脏俱全了。

下一篇聊聊应用服务的部署(呃,后来还是决定先讲讲 backup 了。。。)。我会分享我是如何具体部署 Immich, Jellyfin 等服务。