From 00d084a18870fc94264a605ce48b8502aaf42040 Mon Sep 17 00:00:00 2001 From: sysops Date: Sun, 12 Jul 2026 20:44:29 +0200 Subject: [PATCH] Fix nftables auto-merge syntax and block private/reserved ranges auto-merge is a standalone set statement, not an nft "flags" value - "flags interval, auto-merge;" is a syntax error; fixed to "flags interval;" followed by "auto-merge" on its own line. Needed because large public blocklists (FireHOL, Spamhaus) contain overlapping CIDRs that nftables otherwise refuses as "conflicting intervals". Also add a hard guard: reject any "block" entry that overlaps a private/ reserved/bogon range (RFC1918, CGNAT, loopback, link-local, etc.) in both the single-entry and bulk-import paths. Public feeds like FireHOL level1 routinely include ranges like 10.0.0.0/8 and 172.16.0.0/12, meant for WAN-only edge firewalls - applied host-wide here (where WireGuard/LAN subnets legitimately live in that same private space), those entries would silently block a server's own internal/VPN traffic instead of actual bad actors. Co-Authored-By: Claude Sonnet 5 --- firewall/global.go | 4 ++-- handler/routes.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/firewall/global.go b/firewall/global.go index 5b8ac82..4282298 100644 --- a/firewall/global.go +++ b/firewall/global.go @@ -33,13 +33,13 @@ func GenerateGlobalRuleset(entries []model.IPListEntry) string { fmt.Fprintf(&b, "table inet %s {\n", GlobalTableName) - fmt.Fprintf(&b, " set allowlist {\n type ipv4_addr; flags interval;\n") + fmt.Fprintf(&b, " set allowlist {\n type ipv4_addr; flags interval;\n auto-merge\n") if len(allow) > 0 { fmt.Fprintf(&b, " elements = { %s }\n", strings.Join(allow, ", ")) } fmt.Fprintf(&b, " }\n\n") - fmt.Fprintf(&b, " set blocklist {\n type ipv4_addr; flags interval;\n") + fmt.Fprintf(&b, " set blocklist {\n type ipv4_addr; flags interval;\n auto-merge\n") if len(block) > 0 { fmt.Fprintf(&b, " elements = { %s }\n", strings.Join(block, ", ")) } diff --git a/handler/routes.go b/handler/routes.go index a6bd11e..6f4559a 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -1789,6 +1789,53 @@ func FirewallListsPage() echo.HandlerFunc { } } +// dangerousBlockRanges are private/reserved/bogon IPv4 supernets that public +// threat-intel feeds (FireHOL level1, some Spamhaus-derived lists, etc.) +// often include, since they're meaningless on a public WAN edge. On a host +// that also uses private address space internally (LAN, WireGuard client +// subnets - as this one does), blocking these at the global (host-wide) +// priority-10 hook would drop that host's own internal/VPN traffic. So +// "block" entries overlapping any of these are rejected outright; "allow" +// entries are never affected. +var dangerousBlockRanges = mustParseCIDRs([]string{ + "0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", + "169.254.0.0/16", "172.16.0.0/12", "192.168.0.0/16", + "192.0.0.0/24", "192.0.2.0/24", "198.18.0.0/15", "198.51.100.0/24", + "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4", +}) + +func mustParseCIDRs(cidrs []string) []*net.IPNet { + nets := make([]*net.IPNet, 0, len(cidrs)) + for _, c := range cidrs { + _, n, err := net.ParseCIDR(c) + if err != nil { + panic(err) + } + nets = append(nets, n) + } + return nets +} + +// overlapsDangerousRange reports whether cidr (an IP or CIDR string) +// overlaps any private/reserved/bogon supernet in dangerousBlockRanges. +func overlapsDangerousRange(cidr string) bool { + var ipNet *net.IPNet + if ip, n, err := net.ParseCIDR(cidr); err == nil { + ipNet = n + _ = ip + } else if ip := net.ParseIP(cidr); ip != nil { + ipNet = &net.IPNet{IP: ip, Mask: net.CIDRMask(32, 32)} + } else { + return false + } + for _, dangerous := range dangerousBlockRanges { + if dangerous.Contains(ipNet.IP) || ipNet.Contains(dangerous.IP) { + return true + } + } + return false +} + func validateIPListEntry(entry model.IPListEntry) error { if entry.ListType != "allow" && entry.ListType != "block" { return fmt.Errorf("list_type must be 'allow' or 'block'") @@ -1796,6 +1843,9 @@ func validateIPListEntry(entry model.IPListEntry) error { if !util.ValidateServerAddresses([]string{entry.CIDR}) && net.ParseIP(entry.CIDR) == nil { return fmt.Errorf("cidr must be a valid IP or CIDR") } + if entry.ListType == "block" && overlapsDangerousRange(entry.CIDR) { + return fmt.Errorf("refusing to block %s: overlaps a private/reserved range - this host uses private address space internally (LAN/WireGuard subnets), blocking it here would cut off your own traffic", entry.CIDR) + } if len(entry.Comment) > 200 { return fmt.Errorf("comment too long") }