package fetchguard import "net/netip" // privateV4 is the IANA IPv4 special-purpose non-public set. It is a strict // superset of ManualCoverUrlFetcher.php's lists (06-VERIFICATION gap 3). var privateV4 = mustPrefixes( "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.0.0.0/24", "192.0.2.0/24", "192.88.99.0/24", "192.168.0.0/16", "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", ) // privateV6 is the IANA IPv6 special-purpose non-public set. 2002::/16 and // 64:ff9b::/96 are handled by embeddedTransitionIPv4 instead. var privateV6 = mustPrefixes( "::/96", "100::/64", "2001::/23", "2001:db8::/32", "3fff::/20", "5f00::/16", "fc00::/7", "fe80::/10", "fec0::/10", "ff00::/8", ) func mustPrefixes(cidrs ...string) []netip.Prefix { out := make([]netip.Prefix, len(cidrs)) for i, c := range cidrs { out[i] = netip.MustParsePrefix(c) } return out } var ( nat64WellKnownPrefix = netip.MustParsePrefix("64:ff9b::/96") nat64LocalUsePrefix = netip.MustParsePrefix("64:ff9b:1::/48") sixToFourPrefix = netip.MustParsePrefix("2002::/16") ) // isReservedOrPrivate classifies addr against the PHP private/loopback/ // reserved/CGNAT table, including IPv4 embedded in supported IPv6 transition // formats. func isReservedOrPrivate(addr netip.Addr) bool { if !addr.IsValid() { return true } addr = addr.WithZone("").Unmap() if addr.IsMulticast() || addr.IsUnspecified() { return true } if embedded, ok := embeddedTransitionIPv4(addr); ok { return isReservedOrPrivate(embedded) } table := privateV4 if !addr.Is4() { table = privateV6 } for _, prefix := range table { if prefix.Contains(addr) { return true } } return false } // embeddedTransitionIPv4 extracts IPv4 from the transition formats supported // by fetchguard. A recognized but malformed RFC 6052 /48 address returns an // invalid address with ok=true so the classifier fails closed. func embeddedTransitionIPv4(addr netip.Addr) (netip.Addr, bool) { if !addr.Is6() { return netip.Addr{}, false } b := addr.As16() switch { case nat64WellKnownPrefix.Contains(addr): return netip.AddrFrom4([4]byte{b[12], b[13], b[14], b[15]}), true case nat64LocalUsePrefix.Contains(addr): if b[8] != 0 { return netip.Addr{}, true } return netip.AddrFrom4([4]byte{b[6], b[7], b[9], b[10]}), true case sixToFourPrefix.Contains(addr): return netip.AddrFrom4([4]byte{b[2], b[3], b[4], b[5]}), true default: return netip.Addr{}, false } }