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

我的目标是,在 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 同时选中了 WANLAN
      • 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),进行 lanopn.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 等服务。