前言
朋友送了一台畅网 X86-P5 N305 的设备(以下简称“本设备”)过来让我玩耍。
正好我有想要换掉 NanoPi R2S,并在家里托管一些服务(例如本博客)的想法,于是最近对内网进行了改造。
架构图 TLDR
最后图中的 2.5G 交换机被拔掉以省电。改用 “MT7621 OpenWrt AP” 同时兼任 VLAN 交换机的使命。
本次需求
- 仅使用这一台设备实现 “主路由” ,“隧道旁路由”,“容器/虚拟机服务器” 等所有目的,即所谓的 AIO(All In One) Server
- 主路由系统为 OpenWrt,要求虚拟化带来的损失最小化
- 原有 NanoPi R2S 方案中,iEdon-Net 内网路由用隧道等均集中在一个 OpenWrt 主路由当中,这造成了接口众多,管理混乱的局面,本次希望按照目标分离虚拟机,使其各司其职
- 因为 GreenCloud VPS 屡次爆炸丢失数据,本次将 Acorle 及 本 WordPress 博客由一台专门的虚拟机托管,好久没有玩 Windows Server 了,正好用一用 IIS
- 家里购置了 Sharp 的空气净化器。本设备提供了 IoT 功能,出于对日本 IT 的极度不信任,需要划分一个独立的 VLAN 及一个独立的 SSID 专门给访客及这类不受信任的设备提供互联网访问能力
- 多线接入(IoT 网采用 open.ad.jp 提供的免费 PPPoE 出口,服务器网段采用 SoftBank IPoE 的准静态 IP 出口,自用网段采用 SoftBank 的 PPPoE 出口)
重要内容记录
主 OpenWrt LXC 的配置
本次的 OpenWrt 主路由采用了 PVE 提供的 LXC 功能。由于是容器,有必要暴露一些宿主机的设备文件给容器,以提供我所需的功能。这里我从 网络上 摘抄并使用了以下配置。
# /etc/pve/lxc/100.conf arch: amd64 cores: 8 hostname: OpenWrt memory: 512 nameserver: fd42:4242:2189:ac76::1 onboot: 1 ostype: unmanaged rootfs: local-lvm:vm-100-disk-0,size=256M swap: 0 lxc.include: /etc/lxc-openwrt.conf lxc.net.0.type: phys lxc.net.0.link: enp1s0 lxc.net.0.name: eth0 lxc.net.0.flags: up lxc.net.1.type: phys lxc.net.1.link: enp2s0 lxc.net.1.name: eth1 lxc.net.1.flags: up lxc.net.2.type: veth lxc.net.2.link: vmbr0 lxc.net.2.name: server0 lxc.net.2.flags: up # /etc/lxc-openwrt.conf # /path/to/openwrt.common.conf # LXC OpenWrt config, derived from /usr/share/lxc/config/openwrt.common.conf # Default console settings lxc.tty.dir = lxc lxc.tty.max = 4 lxc.pty.max = 1024 # Default capabilities lxc.cap.drop = mac_admin lxc.cap.drop = mac_override lxc.cap.drop = sys_admin lxc.cap.drop = sys_module lxc.cap.drop = sys_nice lxc.cap.drop = sys_pacct lxc.cap.drop = sys_ptrace lxc.cap.drop = sys_rawio lxc.cap.drop = sys_resource lxc.cap.drop = sys_time lxc.cap.drop = sys_tty_config lxc.cap.drop = syslog lxc.cap.drop = wake_alarm lxc.apparmor.profile = unconfined lxc.apparmor.allow_nesting = 1 # Default cgroups - all denied except those whitelisted lxc.cgroup2.devices.deny = a ## /dev/null and zero lxc.cgroup2.devices.allow = c 1:3 rwm lxc.cgroup2.devices.allow = c 1:5 rwm ## consoles lxc.cgroup2.devices.allow = c 5:0 rwm lxc.cgroup2.devices.allow = c 5:1 rwm ## /dev/{,u}random lxc.cgroup2.devices.allow = c 1:8 rwm lxc.cgroup2.devices.allow = c 1:9 rwm ## /dev/pts/* lxc.cgroup2.devices.allow = c 5:2 rwm lxc.cgroup2.devices.allow = c 136:* rwm ## rtc lxc.cgroup2.devices.allow = c 254:0 rm ## tun lxc.cgroup2.devices.allow = c 10:200 rwm ## dev/tty0 lxc.cgroup2.devices.allow = c 4:0 rwm ## dev/tty1 lxc.cgroup2.devices.allow = c 4:1 rwm ## /dev/ppp lxc.cgroup2.devices.allow= c 108:0 rwm ## To use loop devices, copy the following line to the container's ## configuration file (uncommented). #lxc.cgroup2.devices.allow = b 7:* rwm # Blacklist some syscalls which are not safe in privileged # containers lxc.seccomp.profile = /usr/share/lxc/config/common.seccomp lxc.mount.entry = /dev/ppp dev/ppp none bind,create=file lxc.mount.entry = tmp tmp tmpfs rw,nodev,relatime,mode=1777 0 0 lxc.mount.auto = cgroup:rw lxc.mount.auto = proc:rw lxc.mount.auto = sys:rw lxc.autodev = 1
本方案暴露了 “enp1s0” 及 “enp2s0” 接口给 OpenWrt LXC,并使其做到类似 PCIe 直通的物理独占效果。后者的 “vmbr0” 则提供了 PVE 宿主机及其他 LXC / VM 之间的网络访问专用段。
多出口情况下的内网与出口配置
Linux 下的多出口一般采用 ip rules 实现。
以前,我高度依赖 “mwan3″ 软件包。但是,”mwan3” 自身的设计并不是很满足我的需求,也容易与其它使用场景产生冲突(如我遇到的,与 BIRD 一起使用,会造成 mwan3 扫描内核路由表的时候耗时大量时间)。
后来,我开始手写脚本来实现 ip rule 的添加。虽然能很好实现分流,但彼时的 OpenWrt 并没有提供类似的图形操作界面,每次版本升级特别麻烦,需要手动搬运脚本。好在本次,23.05 版本带来了 IPv6 NAT,ip rule 的图形化操作功能(“Networking” -> “Routing” -> “IPv4(6) Rules”)。
在 /etc/iproute2/rt_tables 中创建好三个出口的专用路由表,便可以在 OpenWrt 中操作。
本次参考了 这篇文章,让不同出口的网段的设备可以访问内网的特定路由——前缀抑制功能。
这里需要使用一个 “前缀抑制” 功能,目标是使该规则不匹配指定路由表中的指定前缀。在刚刚新建的规则之前,再新建一个规则,让指定的主机先匹配 mian 路由表,然后设置前缀抑制为 0,那么其意思就是不匹配 main 表中默认路由,但是匹配其他路由条目,若这些路由条目都没有匹配中,那么则命中下一条规则,即匹配 4837 表。 —— 《OpenWrt 下的策略路由》
如图所示,图中 “server” 区域的设备进站流量的路由匹配,首先查阅 “main” 表,看看是否命中内网路由条目。如果是,直接送出数据包。如果否,由于屏蔽了 “/0” 的默认路由,导致 “main” 表没有匹配,则继续向下执行第二条规则,查阅 “ipoe” 表,命中 “ipoe” 表中的默认路由,送出数据包。
这两条规则组合实现了 “server” 区域的主机,既可以访问内网网段,又从 “ipoe” 表中设定的专用出口的默认路由出口上网。
而 “iot” 区域绝对不能与内网有任何交集,一概送入 “untrustpub” 路由表 —— 顾名思义,不信任的公用出口。
主路由的软 VLAN
与 Linux 类似,直接使用 “if.<VLAN ID>” 即可。如:eth0.10。即代表 eth0 接口上的 VLAN 10 的虚拟接口。
防火墙的配置
需要阻止 IoT 专网与内网任何其他网段的通信。仅允许与 WAN 区域通信。
旁路由的配置
旁路由主要用于组网,它用于收纳一系列隧道接口。旁路由通过 BIRD 的 OSPF,将这些内网路由传递给主路由上的 BIRD。内网机器访问其他内网网段时,首先路由到主路由,主路由将其指引到旁路由上,通过隧道出口。
虽然这个设计显得性能略微低效,但有效地避免了将过多接口收纳在一个主路由上的尴尬情况。
LXC Debian 中 sshd 更改端口号后无法正常反映
这是配置旁路由时遇到的唯一一个比较头疼的问题。好在 这则讨论 提供了解决方案。
systemctl mask ssh.socket
systemctl mask sshd.socketsystemctl disable sshd
systemctl enable ssh
就可以了。
LXC 中 nano 等工具中非英文内容乱码
locales 需要重新配置。这则讨论 提供了解决方案。
localectl set-locale LANG=en_US.UTF-8
set LC_ALL=en_US.UTF-8locale-gen “en_US.UTF-8”
reboot
就可以了。
旁路由 LXC 加载 WireGuard
本次,旁路由也是 LXC。由于容器共享宿主机内核,所以需要宿主机在启动时加载 wireguard 模块——在 “/etc/modules” 中追加 “wireguard” 即可。
由于 WireGuard 依赖 TUN/TAP 设备,所以也自然需要将宿主机的 /dev/tun 暴露给 LXC。在 LXC 的配置文件中追加 “lxc.cgroup2.devices.allow: c 10:200 rwm” 即可。
本 LXC 可以是 Unprivileged,开启 Nest 功能即可正常使用。
无线 AP 的配置
从 Mercari 收了一台二手 MT7621 设备。1400 日元。参照 这篇文章 刷好同样的 OpenWrt 23.05 系统。
本设备起 AP + Managed Switch 之功能。按照如下图配置了 VLAN。LAN4 连接主路由(类似 Trunk 口)。
同样利用 “接口名.VLAN ID” 的方法划分好区域,用来让无线接口附加。
由于新版 OpenWrt (23.05) 此时的硬件交换机实现已经改用了 DSA。本次 VLAN 划分参考了官方论坛的 这则讨论,及官方教程的 这篇关于 DSA 的文章。
服务器的配置
网络总算是构建好了,现在开始老本行——Windows Server 的虚拟机。终于不是 LXC 了。
用 IIS PHP FastCGI 跑 WordPress 是已经玩了十几年的老招。
兼容 WP Super Cache 插件的 IIS 用的 URL 重写规则
该插件能自动为 Apache 生成 URL 重写所需的 “.htaccess” 文件。但不太适用于 nginx 和 IIS。网传的适配版 IIS 规则不是最新就是有遗漏,本次自己整理了一份。整体的 “web.config” 看起来是这样:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers> <add name="PHP FastCGI" path="*.php" verb="*" modules="FastCgiModule" scriptProcessor="C:\ServerFiles\PHP\php-cgi.exe" resourceType="File" /> </handlers> <defaultDocument> <files> <clear /> <add value="index.html" /> <add value="index.htm" /> <add value="index.php" /> </files> </defaultDocument> <httpProtocol> <customHeaders> <remove name="Vary"></remove> <add name="Vary" value="Accept-Encoding"></add> </customHeaders> </httpProtocol> <staticContent> <clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" /> </staticContent> <rewrite> <rules> <rule name="WP super cache Windows WordPress" enabled="true" stopProcessing="true"> <match url="(.*)" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_METHOD}" pattern="^POST$" negate="true" /> <add input="{QUERY_STRING}" pattern=".*=.*" negate="true" /> <add input="{HTTP_COOKIE}" pattern="^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$" negate="true" /> <add input="{HTTPS}" pattern="on" /> <add input="{DOCUMENT_ROOT}/wp-content/cache/supercache/{HTTP_HOST}/{R:1}index-https.html" matchType="IsFile" /> </conditions> <action type="Rewrite" url="wp-content/cache/supercache/{HTTP_HOST}/{R:1}index-https.html" /> </rule> <!-- End WP Super Cache --> <rule name="WordPress Basic" enabled="true" patternSyntax="Wildcard" stopProcessing="false"> <match url="*" /> <conditions> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="index.php" /> </rule> </rules> </rewrite> </system.webServer> </configuration>
IIS 改用 CloudFlare 的 IP 及环境变量
这两条规则首先将把 Cloudflare 传递过来的真实客户端 IP 覆盖到服务器变量 “REMOTE_ADDR” 和 “REMOTE_HOST”。
其次,由于 IIS 作为后端,并不终结 HTTPS 流量,但后端 PHP 业务代码高度依赖 “HTTPS” 环境变量来判断通讯是否是安全的,此时我们将 “HTTPS” 环境变量覆盖为 Cloudflare 传递过来的 schema。
<rule name="Set CF IPs to local variables"> <match url=".*" /> <conditions> <add input="{HTTP_CF_CONNECTING_IP}" pattern="(.|\s)*\S(.|\s)*" /> </conditions> <serverVariables> <set name="REMOTE_ADDR" value="{C:0}" /> <set name="REMOTE_HOST" value="{C:0}" /> <set name="REMOTE_PORT" value="0" /> </serverVariables> <action type="None" /> </rule> <rule name="Set HTTPs on"> <match url=".*" /> <conditions> <add input="{HTTP_X_FORWARDED_PROTO}" pattern="https" /> </conditions> <serverVariables> <set name="HTTPS" value="on" /> </serverVariables> <action type="None" /> </rule>
由于修改了重要的服务器变量,需要在根配置下的 URL Rewrite 配置中,添加允许。
整体规则一览
上传文件时,图片文件的 “HTTP Error 500.50 – URL Rewrite Module Error” 错误
参考 这篇文章,我们可以得知,PHP 接收上传文件时,将文件首先放到 OS 的 Temp 目录下,待接收完成后,移动到 WordPress 目录中。
由于 Temp 目录没有 IIS 所需要的 “IIS_IUSR” 用户权限,导致上述整体规则一览中的最后一条命中时,无法判断 “REQUEST_FILENAME” 是文件还是目录(即文件访问被拒绝),导致了 500 错误。
解决方法可以将 OS 的 Temp 目录添加 “IIS_IUSR” 的读写权限,但这可能不太安全。可以修改 “php.ini” 中的 “upload_tmp_dir”,指定一个自己设置好权限的文件夹。这样可以解决此问题。
参考
- PVE里用LXC容器部署OpenWrt, https://zenofhacking.com/zh/posts/openwrt-from-kvm-to-lxc/
- OpenWrt 下的策略路由, https://blog.kuretru.com/posts/fc5a70d/
- SSH doesn’t work as expected in LXC, https://forum.proxmox.com/threads/ssh-doesnt-work-as-expected-in-lxc.54691/page-2#post-451843
- WHR-1166DHP2 を openwrt-18.06インストール&リカバーリー, https://f0573.blogspot.com/2018/08/whr-1166dhp2-openwrt-18.html
- Mini tutorial for DSA network config, https://forum.openwrt.org/t/mini-tutorial-for-dsa-network-config/96998
- DSA Mini-Tutorial, https://openwrt.org/docs/guide-user/network/dsa/dsa-mini-tutorial
- IIS WordPress images 500 error, https://www.amixa.com/blog/2011/04/29/iis-wordpress-images-500-error/
Leave a Reply to iEdon Cancel reply