无显示输出时重启 Linux

今天调试 PVE 的防火墙时玩脱了,之前开启的 SSH 因为网络问题断开了,没开个新的就在 WebUI 上把 Firewall 开启。

直接键盘显示器连上主机发现没有显示输出,狂按键盘 Ctrl + Alt + Del 居然触发了重启。安全关机也算救了回来吧。

回来后按 Proxmox VE Firewall 说明在集群防火墙页面添加一个名为 management 的 IPSet 就能避免以后被拦在外面了。

PVE Firewall 阻拦了 ICMPv6 响应

开启 PVE Host 的防火墙后,发现个奇怪的现象。从 PVE 向外 ping 没问题,但从外向内 ping 就被阻拦。

ip6tables -nvL --line-numbers
Chain PVEFW-HOST-IN (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 ACCEPT     all  --  lo     *       ::/0                 ::/0
   50 11883 DROP       all  --  *      *       ::/0                 ::/0                 ctstate INVALID
    0     0 ACCEPT     all  --  *      *       ::/0                 ::/0                 ctstate RELATED,ESTABLISHED
    0     0 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 ipv6-icmptype 133
   20  2560 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 ipv6-icmptype 134
    1    72 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 ipv6-icmptype 135
    0     0 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 ipv6-icmptype 136
    0     0 RETURN     2    --  *      *       ::/0                 ::/0
    0     0 RETURN     tcp  --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src tcp dpt:22
    0     0 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src ipv6-icmptype 128
    0     0 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src ipv6-icmptype 133
    0     0 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src ipv6-icmptype 134
    0     0 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src ipv6-icmptype 135
    0     0 RETURN     ipv6-icmp --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src ipv6-icmptype 136
    0     0 RETURN     tcp  --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src tcp dpt:8006
    0     0 RETURN     tcp  --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src tcp dpts:5900:5999
    0     0 RETURN     tcp  --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src tcp dpt:3128
    0     0 RETURN     tcp  --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src tcp dpt:22
    0     0 RETURN     tcp  --  *      *       ::/0                 ::/0                 match-set PVEFW-0-management-v6 src tcp dpts:60000:60050
    2   152 RETURN     all  --  *      *       ::/0                 ::/0
    0     0            all  --  *      *       ::/0                 ::/0                 /* PVESIG:s8wIEvSJZJINuAc5Bit+Y6u/CaU */

通过对比开关前后的表现,定位到是 ctstate INVALID 这行拦截了 ICMPv6 返程。

也有人落下了相似的结论:

总之对于 PVE-Firewall 为什么把 -A PVEFW-HOST-IN -m conntrack --ctstate INVALID -j DROP 放在前面还是不得而知。

实际原因是 使用 MACVLAN 为主机同时分配静态 IP 并取得动态 IP 多网卡产生了回程非对称路由。最终我打算关掉 PVE 层的防火墙,让 Openwrt 来处理过滤。

Obsidian Linter 格式化斜体下划线和双引号时会添加前后空格

之前在 Obsidian Linter 设置过 Emphasis Style - Content Rules - Linter 的 ” 保持突出样式一致性 ” 为 underscore,想要和 * 粗体 * 使用的 asterisk 区分开。

但同时我又开启了 Space between Chinese Japanese or Korean and English or numbers -Spacing Rules - Linter 确保中日韩文与英文数字之间有一个空格,配置为 -+;:'"°%$)]

代代 “你好”

效果是只要在英文符号边上多了个空格,就会在两边都加上,从而破坏 Markdown 格式

  • "你好" " 你好 "
  • *你好 * * 你好 *

总之感觉是 bug。

把配置改为 -+;:'°%$)] 能缓解引号的问题,但下划线代表斜体还是有问题,只好继续用星号代表斜体了。

PVE 下的网桥应该关闭 multicast snooping

长久以来的问题和临时地解决方案终于有了结论:

PVE 的 Linux Bridge 默认启用了 multicast_snooping,在面对 NS 组播查询时,组播表没有对应的条目,网桥则默认丢弃。所以表现是 PVE 上能看到 neighbor solicitation 包,但 OpenWrt 上没这个包被转发过来。

关闭 snooping 验证一下

echo 0 > /sys/devices/virtual/net/vmbr0/bridge/multicast_snooping

一些有用的调试命令

  • ip -6 maddr
  • bridge -d mdb show router
  • tcpdump -n icmp6

额外发现 ifupdown-addons-interfaces(5) — ifupdown2 — Debian bookworm — Debian Manpages 有自带的拓展语句直接支持了,不再需要魔法般的 post-up 语句。

auto vmbr0
iface vmbr0 inet dhcp
iface vmbr0 inet static
	address 192.168.9.17/24
	gateway 192.168.9.1
	bridge-ports enp5s0
	bridge-stp off
	bridge-fd 0
	bridge-mcsnoop no

另一个办法是设置目标端口为 multicast_router

echo 2 > /sys/class/net/vmbr0/brif/veth104i0/multicast_router

不过使用 multicast router 相比禁用 multicast snooping 在冷启动上处于劣势,要等个几秒。
而且还得计算虚拟网卡 veth104i0 的坐标。

$ ping 2409:8a1e:6940:7c20:be24:11ff:fea3:22a4
 
正在 Ping 2409:8a1e:6940:7c20:be24:11ff:fea3:22a4 具有 32 字节的数据:
无法访问目标主机。
无法访问目标主机。
来自 2409:8a1e:6940:7c20:be24:11ff:fea3:22a4 的回复: 时间=675ms
来自 2409:8a1e:6940:7c20:be24:11ff:fea3:22a4 的回复: 时间<1ms
 
2409:8a1e:6940:7c20:be24:11ff:fea3:22a4 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 2,丢失 = 2 (50% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 0ms,最长 = 675ms,平均 = 337ms

我 PVE 上的 vmbr0 实际上只挂了个 OpenWrt,所以禁用 snooping 其实也没什么广播群发劣化效应。但我还是保留更佳实践,采用 router。

设置这个条目直接 hardcode 不行,因为 PVE 启动时容器还没启动。那么就需要个 pct hookscript 来在容器启动时,设置这个。

拎一个 AI 来 写代码,然后设置上就完了

/var/lib/vz/snippets/multicast-router.pl
#!/usr/bin/perl
# Enable multicast routing for a Proxmox VE container's network interface on vmbr0
 
# You can set this via pct/qm with
# pct set 104 --hookscript local:snippets/multicast-router.pl
 
use strict;
use warnings;
 
# First argument is the vmid
my $vmid = shift;
# Second argument is the phase
my $phase = shift;
 
if ($phase eq 'post-start') {
    # 定义网桥名称 (通常是 vmbr0,根据实际情况修改)
    my $bridge = "vmbr0";
 
    # 定义可能的接口名称模式
    # 1. veth${vmid}i0 : 对应 net0,防火墙关闭时
    # 2. fwpr${vmid}p0 : 对应 net0,防火墙开启时
    # 如果你的 OpenWrt 使用的是 net1,请相应修改为 i1 / p1
    my @interfaces = ("veth${vmid}i0", "fwpr${vmid}p0");
 
    # 稍微等待一下,防止脚本执行时内核还未完成接口创建
    sleep(1);
 
    foreach my $iface (@interfaces) {
        # 构建 sysfs 路径
        my $path = "/sys/class/net/$bridge/brif/$iface/multicast_router";
 
        # 检查路径是否存在(即接口是否已连接到网桥)
        if (-e $path) {
            # 尝试写入 '2' (强制开启)
            if (open(my $fh, '>', $path)) {
                print $fh "2";
                close $fh;
                print "HOOK: Set multicast_router=2 for VM $vmid interface $iface\n";
                last; # 找到一个即可退出循环
            } else {
                warn "HOOK: Failed to write to $path: $!\n";
            }
        }
    }
}
 
exit(0);