// SPDX-License-Identifier: GPL-3.0-or-later // Package netutil contains stdlib address helpers matching the Python oracle. package netutil import ( "net/netip" "strings" ) var carrierGradeNAT = netip.MustParsePrefix("100.64.0.0/10") // ParseAddr accepts brackets and IPv6 zone suffixes used by ss. func ParseAddr(value string) (netip.Addr, bool) { value = strings.Trim(strings.TrimSpace(value), "[]") if index := strings.IndexByte(value, '%'); index >= 0 { value = value[:index] } address, err := netip.ParseAddr(value) return address, err == nil } // IsPublicIP reports globally routable addresses and excludes CGNAT. func IsPublicIP(value string) bool { address, ok := ParseAddr(value) // netip's GlobalUnicast includes CGNAT, so explicitly exclude 100.64/10 // to match Python ipaddress.is_global and avoid treating carrier-local // traffic as public C2 egress. if !ok || !address.IsGlobalUnicast() || address.IsPrivate() || address.IsLoopback() || address.IsLinkLocalUnicast() { return false } return !carrierGradeNAT.Contains(address) } // IPInCIDRs checks an address against valid same-family trust prefixes. func IPInCIDRs(value string, cidrs []string) bool { address, ok := ParseAddr(value) if !ok { return false } for _, raw := range cidrs { // Invalid operator entries are ignored just like Python Config. A typo // must not crash or disable the detector sweep. prefix, err := netip.ParsePrefix(strings.TrimSpace(raw)) if err == nil && prefix.Addr().BitLen() == address.BitLen() && prefix.Contains(address) { return true } } return false } // SplitHostPort splits ss-style IPv4, IPv6, and zone-qualified endpoints. func SplitHostPort(endpoint string) (string, string) { endpoint = strings.TrimSpace(endpoint) if strings.HasPrefix(endpoint, "[") { if end := strings.Index(endpoint, "]"); end >= 0 { return endpoint[1:end], strings.TrimPrefix(endpoint[end+1:], ":") } } index := strings.LastIndex(endpoint, ":") if index < 0 { return "", endpoint } return endpoint[:index], endpoint[index+1:] }