Files
vpsetup/dsh-firewall.nft
eli 1d5af2248b fix(firewall): forward 链改回"作用域白名单 + 默认丢弃"
之前把 forward 链清空成 policy accept 是错的——那不是标准,是相对基线的
**安全退化**。基线(init-iptables.sh)本来就是:

    -A FORWARD -o podman+ -j ACCEPT
    -A FORWARD -j REJECT

配合 net.ipv4.ip_forward=1,清空 forward 等于让主机变成转发器。

现在按基线语义实现:

    chain forward {
        type filter hook forward priority filter; policy drop;
        ct state established,related accept
        iifname/oifname "wt0" accept      # NetBird 覆盖网
        iifname/oifname "zt*" accept      # ZeroTier 覆盖网
        iifname/oifname "podman*" accept  # Podman
        iifname/oifname "docker0" accept
        iifname/oifname "br-*" accept
    }

两处必须显式列出接口的原因(都实测过):

1. **容器发布端口的入站在 forward 里没有任何现成规则可匹配**。实测
   NETAVARK_FORWARD 只有 `saddr 10.89.0.0/24 accept`(出站)和
   `daddr 10.89.0.0/24 established accept`(回程),**没有发布端口的入站
   accept**。基线是靠 `-o podman+ ACCEPT` 放行的,这里同理用 oifname 匹配。

2. **NetBird/ZeroTier 会自己往 `ip filter FORWARD` 写规则**。实测 oraclejp
   的 `ip filter FORWARD` 里有 netbird 加的 `iifname "wt0" accept`。本表的
   `policy drop` 是终局裁决,不同步放行就会把 VPN 转发打死。

验证(oraclezyus-arm):SSH/80/443/容器 9000 全通、容器 DNS 与出站正常、
容器到宿主机 443 正常。测试 77 → 79 项全绿。
2026-11-11 12:24:44 +08:00

110 lines
4.1 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/sbin/nft -f
# dsh_firewall — 代理节点 v4+v6 统一防火墙(白名单 + REJECT 兜底)
# 适用于 dmit / aliuk / alius 等手工栈迁移后的代理节点
# 不碰 ip6 filter / rules.v6(inet 表独立于 iptables-nft 转换层)
# 用法:nft -f dsh-firewall.nft(幂等,重复执行安全)
# 先删旧表(幂等重建)
table inet dsh_firewall
delete table inet dsh_firewall
table inet dsh_firewall {
# ── 入站白名单 ──
chain input {
type filter hook input priority filter; policy accept;
# 本地回环
iifname "lo" accept
# NetBird 接口。官方要求主机防火墙放行 wt0:
# "NetBird automatically adds rules to allow traffic on the wt0 interface."
# 以前是手工 nft insert 的,每次重跑本脚本都会被 delete table 洗掉 —— 必须进模板。
iifname "wt0" accept
# 容器网桥:只放 DNS 和宿主机对外服务的 80/443(实测容器会连 10.89.1.1:443)
iifname "podman*" meta l4proto { tcp, udp } th dport 53 accept
iifname "podman*" tcp dport { 80, 443 } ct state new accept
iifname "docker0" meta l4proto { tcp, udp } th dport 53 accept
iifname "docker0" tcp dport { 80, 443 } ct state new accept
iifname "br-*" meta l4proto { tcp, udp } th dport 53 accept
iifname "br-*" tcp dport { 80, 443 } ct state new accept
# 已建立/相关连接
ct state established,related accept
# 乱序/孤儿包静默丢弃,不要回 RST/ICMP(避免反射)
ct state invalid drop
# SSH
tcp dport 22 ct state new accept
# HTTP(Caddy 反代 + ACME HTTP-01)
tcp dport 80 ct state new accept
# HTTPS(Xray REALITY + Caddy)
tcp dport 443 ct state new accept
# HTTP/3(QUIC)
udp dport 443 accept
# Hysteria2
udp dport 8443 accept
# hy2 端口跳跃
udp dport 20000-30000 accept
# ICMP v4(ping,不限速——后续可加 rate limit)
ip protocol icmp accept
# ICMPv6(按 RFC 4890 §4.4.1 必须放行的 type,用数字最保险)
# 1=dest-unreachable 2=packet-too-big 3=time-exceeded 4=parameter-problem
# 128/129=echo 133/134=RS/RA 135/136=NS/NA 130/131/132=MLD 143=MLDv2 141/142=IND
icmpv6 type { 1, 2, 3, 4, 128, 129, 133, 134, 135, 136, 130, 131, 132, 143, 141, 142 } accept
# DHCPv6(v6 地址分配必需)
udp sport 547 udp dport 546 ip6 saddr fe80::/10 accept
udp dport 546 ip6 saddr fe80::/10 accept
# 兜底:拒绝一切未匹配流量
# v4 用 icmp-host-prohibited,v6 用 icmp6-adm-prohibited
ip protocol tcp reject with tcp reset
ip protocol udp reject with icmp port-unreachable
ip6 nexthdr tcp reject with tcp reset
ip6 nexthdr udp reject with icmpv6 port-unreachable
reject
}
# ── 转发:代理节点不需要转发 ──
# forward 链:作用域白名单 + 默认丢弃。
#
# 为什么不能留 `policy accept`:基线的 iptables 就是 `-o podman+ ACCEPT` + 链尾
# `REJECT`,即"只放容器转发、其余丢弃"。改成 accept 等于把主机变成转发器。
#
# 为什么必须显式列出接口:
# · Podman 发布端口的入站包在 nat prerouting 被 DNAT 后走 forward,而 netavark
# 的 NETAVARK_FORWARD 只放行 saddr(出站)和 daddr+established(回程),
# 入站 NEW 包并不匹配——基线靠 `oifname "podman*" accept` 放行,这里同理。
# · NetBird(wt0)和 ZeroTier(zt*)会自己往 ip filter FORWARD 写
# `iifname "wt0" accept` 之类的规则;本表默认丢弃是终局裁决,必须同步放行。
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wt0" accept
oifname "wt0" accept
iifname "zt*" accept
oifname "zt*" accept
iifname "podman*" accept
oifname "podman*" accept
iifname "docker0" accept
oifname "docker0" accept
iifname "br-*" accept
oifname "br-*" accept
}
# ── 出站:全放行(代理节点需要自由出站) ──
chain output {
type filter hook output priority filter; policy accept;
}
}