package netx import ( "fmt" "math/bits" "net" "net/netip" "strconv" "strings" "go4.org/netipx" ) /* Addr4in6Compat takes IP address `addr` and, if an IPv4 address, returns it as an "IPv4-Compatible IPv6 Address" (RFC [4291 § 2.5.5.1]). If `addr` is already an IPv6 address and NOT in the '::/96' or '::0:0/96' prefix, `addr` will be returned as `compatAddr`. If `addr` is an IPv6 address and is an IPv4-Mapped prefix ('::0:0/96'), then `compatAddr` will be an IPv4-Compatible address conversion of `addr`. If `addr` is an IPv6 address and IS in the '::/96' prefix, `addr` will be returned as `compatAddr`. If `addr` is invalid, `compatAddr` will be the [netip.Addr] returned by [netip.IPv6Unspecified]. Note that this is the DEPRECATED format for IPv4-addresses-as-IPv6! The modern, proper equivalent is "IPv4-Mapped IPv6 Address" (RFC [4291 § 2.5.5.2]), which can be performed via [Addr4in6Mapped] instead. - If you need an IPv6 address format for an IPv4 address and don't know if you need this function or not, you want [Addr4in6Mapped] instead. - If you THINK you want this function, you want [Addr4in6Mapped] instead. - If you ABSOLUTELY KNOW you need this function, you're probably dealing with some very old IPv6 implementations. But you at least know you need this function. [4291 § 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func Addr4in6Compat(addr netip.Addr) (compatAddr netip.Addr) { if !addr.IsValid() { compatAddr = netip.IPv6Unspecified() return } compatAddr = addr if addr.Is6() { if addr.Is4In6() { compatAddr = Addr4in6Compat(UnmapAddr(addr)) } return } if !addr.Is6() { compatAddr = netip.MustParseAddr(fmt.Sprintf("%s%s", ip4in6Legacy96, addr.String())) } return } /* Addr4in6Mapped takes IP address `addr` and, if an IPv4 address, returns it as an "IPv4-Mapped IPv6 Address" (RFC [4291 § 2.5.5.2]). You should almost assuredly be using this function instead of [Addr4in6Compat]. If `addr` is already an IPv6 address and NOT in the '::/96' or '::0:0/96' prefix, `addr` will be returned as `mappedAddr`. If `addr` is an IPv6 address and is an IPv4-Compatible prefix ('::/96'), then `mappedAddr` will be an IPv4-Mapped address conversion of `addr`. If `addr` is an IPv6 address and IS in the '::ffff:0:0/96' prefix, `addr` will be returned as `mappedAddr`. If `addr` is invalid, `mappedAddr` will be the [netip.Addr] returned by [netip.IPv6Unspecified]. [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func Addr4in6Mapped(addr netip.Addr) (mappedAddr netip.Addr) { if !addr.IsValid() { mappedAddr = netip.IPv6Unspecified() return } mappedAddr = addr if addr.Is6() { if !IsAddr4Compat(addr) { return } mappedAddr = UnmapAddr(addr) } if !mappedAddr.Is6() { mappedAddr = netip.MustParseAddr(fmt.Sprintf("%s%s", ip4in6Modern96, mappedAddr.String())) } return } /* AddrRfc returns an RFC-friendly string from an IP address ([net/netip.Addr]). If addr is an IPv4 address, it will simply be the string representation (e.g. "203.0.113.1"). If addr is an IPv6 address, it will be enclosed in brackets (e.g. "[2001:db8::1]"). IPv4-Compatible IPv6 addresses will be represented as e.g. "[::203.0.113.1]". IPv4-Mapped IPv6 addresses will be represented as e.g. "[::ffff:203.0.113.1]". Note that if addr is an IPv6 unspecifified address derived as an "IPv4-Compatible", then `rfcStr` will always be the IPv6 unspecified address (::), NOT an IPv4-Compatible unspecified (::0.0.0.0). Refer to the documentation for [IsAddr4Compat] as to why this happens. If the version can't be determined, rfcStr will be an empty string. */ func AddrRfc(addr netip.Addr) (rfcStr string) { if addr.Is4() { rfcStr = addr.String() } else if addr.Is6() { switch { case IsAddr4Compat(addr): rfcStr = fmt.Sprintf("%s%s", ip4in6Legacy96, UnmapAddr(addr).String()) // addr.Is4In6() addrs format properly default: rfcStr = addr.String() } rfcStr = "[" + rfcStr + "]" } return } /* Cidr4ToIPMask takes an IPv4 CIDR/bit size/prefix length and returns the [net.IPMask]. It's (essentially) the inverse of [net.IPMask.Size]. Inverse of [IPMask4ToCidr]. */ func Cidr4ToIPMask(cidr uint8) (ipMask net.IPMask, err error) { if cidr > 32 { err = ErrBadNetFam return } ipMask = net.CIDRMask(int(cidr), 32) return } /* Cidr4ToMask takes an IPv4 CIDR/bit size/prefix length and returns the netmask *in bitmask form*. Inverse of [Mask4ToCidr]. */ func Cidr4ToMask(cidr uint8) (mask uint32, err error) { if cidr > 32 { err = ErrBadNetFam return } // COULD do (1 << 32) - (1 << (32 - ip.Bits())) instead but in EXTREME edge cases that could cause an overflow. // We're basically converting the CIDR size ("number of bits"/"number of ones") to an integer mask ("number AS bits") mask = uint32(0xffffffff) << uint32(32-cidr) return } /* Cidr4ToStr is a convenience wrapper around [IPMask4ToStr]([Cidr4ToMask](cidr)). Inverse of [Mask4StrToCidr]. */ func Cidr4ToStr(cidr uint8) (maskStr string, err error) { var ipMask net.IPMask if ipMask, err = Cidr4ToIPMask(cidr); err != nil { return } if maskStr, err = IPMask4ToStr(ipMask); err != nil { return } return } /* FamilyToVer returns a more "human-friendly" IP version from a system/lower-level IP family ([AFUnspec], [AFInet], [AFInet6]). ipVer will be int(4) for [AFInet], int(6) for [AFInet6], int(0) for [AFUnspec], or int(-1) for an unknown family. */ func FamilyToVer(family uint16) (ipVer int) { switch family { case AFInet: ipVer = 4 case AFInet6: ipVer = 6 case AFUnspec: ipVer = 0 default: ipVer = -1 } return } /* GetAddrFamily returns the network family of a [net/netip.Addr]. Note that this returns [AFInet] or [AFInet6], NOT uint16(4) or uint16(6). (See [FamilyToVer] to get the associated higher-level value.) If addr is not a "valid" IP address or the version can't be determined, family will be AFUnspec (usually 0x00/0). */ func GetAddrFamily(addr netip.Addr) (family uint16) { family = AFUnspec if !addr.IsValid() { return } if addr.Is4() { family = AFInet } else if addr.Is6() { family = AFInet6 } else { return } return } /* GetIpFamily returns the network family of a [net.IP]. Note that this returns [AFInet] or [AFInet6], NOT uint16(4) or uint16(6). (See [FamilyToVer] to get the associated higher-level value.) If ip is not a "valid" IP address or the version can't be determined, family will be [golang.org/x/sys/unix.AF_UNSPEC] or [golang.org/x/sys/windows.AF_UNSPEC] depending on platform (usually 0x00/0). */ func GetIpFamily(ip net.IP) (family uint16) { var ok bool var addr netip.Addr if addr, ok = netipx.FromStdIP(ip); !ok { return } family = GetAddrFamily(addr) return } /* HasSubnet returns true if prefix `sub` is within prefix `parent`. If `incl` is true, HasSubnet will return true if `sub` and `parent` are the same network. If `incl` is false, HasSubnet will return false if `sub` and `parent` are the same network. This can be further determined by doing the following: var sub netip.Prefix var parent netip.Prefix // ... if !netx.HasSubnet(parent, sub) && parent.Compare(sub) != 0 { // .Compare() returns 0 if the prefixes are the same. // ... } // ... It is expected that both prefixes are valid; HasSubnet will always return false if `parent` or `sub` are invalid. HasSub will return false if `parent` and `sub` are different IP address families, but will properly handle IPv4-Mapped/IPv4-Compatible IPv6 prefixes when compared with IPv6 prefixes. Host bits may be set; it will not affect the comparison. */ func HasSubnet(parent, sub netip.Prefix, incl bool) (hasSub bool) { // rule out unpredictable comparison if !parent.IsValid() || !sub.IsValid() { return } // different families if parent.Addr().Is4() != sub.Addr().Is4() { return } // parent is smaller network size than sub if parent.Bits() > sub.Bits() { return } // parent does not contain network (or host) addr of sub // (returns true even for parent's network addr, etc.) if !parent.Contains(sub.Addr()) { return } // parent and sub are same canon (masked) network // (.Compare() compares host bits as well, hence the .Masked()) if parent.Masked().Compare(sub.Masked()) == 0 { hasSub = incl return } hasSub = true return } /* IP4MapPfx4to6 converts a native IPv4 prefix length (<= 32) to an IPv6 prefix length for "IPv4-Compatible" (RFC [4291 § 2.5.5.1]) and "IPv4-Mapped" (RFC [4291 § 2.5.5.2]) addressing. If `len4` is > 32, then `len6` will be equal to `len4`. If this is not desired, ensure that you are gating calls to IP4MapPfx4to6 with [netip.Addr.Is4] == true on the prefix in question (it will return false for IPv6 addresses, IPv4-Mapped IPv6 addresses, AND IPv4-Compatible IPv6 addresses): var pfx netip.Prefix // ... ipv6BitLen = pfx.Bits() if pfx.Addr().Is4() { ipv6BitLen = netx.IP4MapPfx4to6(ipv6BitLen) } // ... [4291 § 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func IP4MapPfx4to6(len4 uint8) (len6 uint8) { len6 = len4 if len4 <= 32 { len6 = 96 + len4 } return } /* IP4MapPfx6to4 converts a native IPv6 prefix length (>= 96) to an IPv4 prefix length from "IPv4-Compatible" (RFC [4291 § 2.5.5.1]) and "IPv4-Mapped" (RFC [4291 § 2.5.5.2]) addressing. If `len6` is < 96, then `len4` will be equal to `len6`. If this is not desired, ensure that you are gating calls to IP4MapPfx6to4 with [netip.Addr.Is4In6] == true on the prefix in question: var pfx netip.Prefix // ... ipv4BitLen = pfx.Bits() if pfx.Addr().Is4In6() { ipv4BitLen = IP4MapPfx6to4(ipv4BitLen) } if ipv4BitLen > 32 { panic("lol oops") } // ... [4291 § 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func IP4MapPfx6to4(len6 uint8) (len4 uint8) { len4 = len6 if len6 >= 96 { len4 = len6 - 96 } return } /* IpRfc returns an RFC-friendly string from an IP address ([net.IP]). If ip is an IPv4 address, it will simmply be the string representation (e.g. "203.0.113.1"). If ip is an IPv6 address, it will be enclosed in brackets (e.g. "[2001:db8::1]"). IPv4-Compatible IPv6 addresses will be represented as e.g. "[::203.0.113.1]". IPv4-Mapped IPv6 addresses will be represented as e.g. "[::ffff:203.0.113.1]". If the version can't be determined, `rfcStr` will be an empty string. */ func IpRfc(ip net.IP) (rfcStr string) { var err error var a netip.Addr if a, err = netip.ParseAddr(ip.String()); err != nil { return } rfcStr = AddrRfc(a) return } /* IpRfcStr implements [IpRfc]/[AddrRfc] for string representations of an IP address `s`. If `s` is an IPv6 address already in the bracketed RFC format, then `rfcStr` will be equal to `s`. If `s` is not a string representation of an IP address, `rfcStr` will be empty. See [IpStripRfcStr] for the inverse (removing any brackets from `s` if present). */ func IpRfcStr(s string) (rfcStr string) { var ip net.IP if !IsIpAddr(s) { return } if IsBracketedIp6(s) { rfcStr = s return } ip = net.ParseIP(s) if ip == nil { return } rfcStr = IpRfc(ip) return } /* IpStripRfcStr returns IP address string `s` without any brackets. If `s` is not a valid IP address, `stripStr` will be empty. */ func IpStripRfcStr(s string) (stripStr string) { if !IsIpAddr(s) { return } if !IsBracketedIp6(s) { stripStr = s return } stripStr = strings.TrimPrefix(s, "[") stripStr = strings.TrimSuffix(stripStr, "]") return } /* IPMask4ToCidr returns a CIDR prefix size/bit size/bit length from a [net.IPMask]. Inverse of [Cidr4ToIPMask]. */ func IPMask4ToCidr(ipMask net.IPMask) (cidr uint8, err error) { var ones int var total int ones, total = ipMask.Size() if total != 32 { err = ErrBadNetFam return } if ones > 32 { err = ErrBadNetFam return } cidr = uint8(ones) return } /* IPMask4ToMask returns the mask *in bitmask form* from a [net.IPMask]. Inverse of [Mask4ToIPMask]. */ func IPMask4ToMask(ipMask net.IPMask) (mask uint32, err error) { var cidr uint8 if cidr, err = IPMask4ToCidr(ipMask); err != nil { return } if mask, err = Cidr4ToMask(cidr); err != nil { return } return } /* IPMask4ToStr returns a string representation of an IPv4 netmask (e.g. "255.255.255.0" for a /24) from a [net.IPMask]. Inverse of [Mask4StrToIPMask]. */ func IPMask4ToStr(ipMask net.IPMask) (maskStr string, err error) { var idx int var b []byte var quads []string = make([]string, 4) b = []byte(ipMask) if len(b) != 4 { err = ErrBadNetFam return } for idx = 0; idx < len(b); idx++ { quads[idx] = strconv.Itoa(int(b[idx])) } maskStr = strings.Join(quads, ".") return } /* IPSetCombined combines multiple [go4.org/netipx.IPSet] sets into a single set. The original `sets` will only be read and be untouched otherwise; `combined` is a new set. Nil sets in `sets` will be silently skipped. */ func IPSetCombined(sets []*netipx.IPSet) (combined *netipx.IPSet, err error) { var idx int var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder) for idx = range sets { if sets[idx] == nil { continue } ipsb.AddSet(sets[idx]) } if combined, err = ipsb.IPSet(); err != nil { return } return } /* IPSetFrom skips some round-tripping and returns a [go4.org/netipx.IPSet] from a given slice of strings. `addrs` must be individual address strings but may be nil. See [net/netip.ParseAddr] for conventions. `pfxs` must be prefix strings but may be nil. See [net/netip.ParsePrefix] for conventions. `ranges` must be range strings but may be nil. See [go4.org/netipx.ParseIPRange] for conventions. IPSetFrom will return immediately on first error, otherwise `ipset` will contain all provided addresses/prefixes/ranges. */ func IPSetFrom(addrs, pfxs, ranges []string) (ipset *netipx.IPSet, err error) { var idx int var a netip.Addr var p netip.Prefix var r netipx.IPRange var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder) for idx = range addrs { if a, err = netip.ParseAddr(addrs[idx]); err != nil { return } ipsb.Add(a) } for idx = range pfxs { if p, err = netip.ParsePrefix(pfxs[idx]); err != nil { return } ipsb.AddPrefix(p) } for idx = range ranges { if r, err = netipx.ParseIPRange(ranges[idx]); err != nil { return } ipsb.AddRange(r) } if ipset, err = ipsb.IPSet(); err != nil { return } return } /* IPSetFromNative is like [IPSetFrom] but takes native types instead of strings. */ func IPSetFromNative(addrs []netip.Addr, pfxs []netip.Prefix, ranges []netipx.IPRange) (ipset *netipx.IPSet, err error) { var idx int var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder) for idx = range addrs { ipsb.Add(addrs[idx]) } for idx = range pfxs { ipsb.AddPrefix(pfxs[idx]) } for idx = range ranges { ipsb.AddRange(ranges[idx]) } if ipset, err = ipsb.IPSet(); err != nil { return } return } /* IpVerStr provides the IP family of IP address/network string `s`. `s` may be one of the following formats/syntaxes: - 203.0.113.0 - 203.0.113.1 - 203.0.113.0/24 - 203.0.113.1/24 - 2001:db8:: - 2001:db8::1 - 2001:db8::/32 - 2001:db8::1/32 - [2001:db8::] - [2001:db8::1] Unlike [GetAddrFamily]/[GetIpFamily], this returns a more "friendly" version - if `s` is not valid syntax, `ipVer` will be int(0), otherwise ipVer will be int(4) for family IPv4 and int(6) for family IPv6. (See [VerToFamily] to get the associated system/lower-level value.) */ func IpVerStr(s string) (ipVer int) { var err error var ipstr string var p netip.Prefix ipstr = strings.TrimPrefix(s, "[") ipstr = strings.TrimSuffix(ipstr, "]") if p, err = netip.ParsePrefix(ipstr); err != nil { return } if p.Addr().Is6() { ipVer = 6 } else { ipVer = 4 } return } /* IsAddr4Compat returns true if `addr` is in the "IPv4-Compatible IPv6 Address" prefix. Note that if `addr` is from an IPv4-Compatible and was the unspecified address (::0.0.0.0), isCompat will always return false by design. This is because ::0.0.0.0 is, as a fully exploded IPv6 address: 0000:0000:0000:0000:0000:0000:0000:0000 Or, in other words, [16]byte{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } which is the *exact same* bytes as the native IPv6 "unspecified" address (::). Because the IPv4-Compatible prefix itself is ::/96 (and no prefix information is available from a netip.Addr), this function errs on the side of caution and treats `addr` as the IPv6 unspecified address instead of the IPv4-Compatible unspecified. This "quirk" is not present in IPv4-Mapped addresses, as they use an explicit non-zero prefix. (There are many reasons why IPv4-Compatible addressing was legacy/deprecated the moment it was canonized in RFC; this is one of them.) */ func IsAddr4Compat(addr netip.Addr) (isCompat bool) { if addr.Is6() && addr.IsUnspecified() { return } isCompat = ip4in6Legacy.Contains(addr) return } /* IsBracketedIp6 returns a boolean indicating if `s` is a valid bracket-enclosed IPv6 in string format (e.g. "[2001:db8::1]"). It will return false for *non-bracketed* IPv6 addresses (e.g. "2001:db8::1"), IPv4 addresses, or if s is not a valid IPv6 address string. [IpRfcStr] or [IpStripRfcStr] can be used to coerce a string to a specific format. */ func IsBracketedIp6(s string) (isBrktdIp bool) { var ip net.IP var ipstr string if IpVerStr(s) != 6 { return } ipstr = strings.TrimPrefix(s, "[") ipstr = strings.TrimSuffix(ipstr, "]") if ip = net.ParseIP(ipstr); ip == nil { return } isBrktdIp = ipstr == s return } /* IsIpAddr returns a boolean indicating if `s` is an IP address (either IPv4 or IPv6) in string format. For IPv6, it will return true for both of these formats: - 2001:db8::1 - [2001:db8::1] [IsBracketedIp6] can be used to narrow down which form. */ func IsIpAddr(s string) (isIp bool) { var err error var a netip.Addr if a, err = netip.ParseAddr(s); err != nil { return } isIp = a.IsValid() return } /* IsPrefixNet returns true if `s` is a (valid) IP address or network (either IPv4 or IPv6) in: / format. */ func IsPrefixNet(s string) (isNet bool) { var err error var p netip.Prefix if p, err = netip.ParsePrefix(s); err != nil { return } isNet = p.Masked().IsValid() return } /* IsPublic returns true if ALL of the following conditions are *true* for `ip`: - [net/netip.Addr.IsGlobalUnicast] - [net/netip.Addr.IsValid] - (IPv4) Is not in 192.0.2.0/24, 198.51.100.0/24, or 203.0.113.0/24 ([RFC 5737]) - (IPv4) Is not in 100.64.0.0/10 ([RFC 6598]) - (IPv6) Is not in 2001:db8::/32 or 3fff::/20 ([RFC 3849], [RFC 9637]) AND ALL of the following conditions are *false* for ip: - [net/netip.Addr.IsLinkLocalMulticast] - [net/netip.Addr.IsInterfaceLocalMulticast] - [net/netip.Addr.IsLinkLocalUnicast] - [net/netip.Addr.IsMulticast] - [net/netip.Addr.IsLoopback] - [net/netip.Addr.IsPrivate] - [net/netip.Addr.IsUnspecified] For 4-in-6 addresses: - ::0:0/96 (RFC [4291 § 2.5.5.1], "IPv4-Compatible IPv6 Address") (Deprecated) - ::ffff:0:0/96 (RFC [4291 § 2.5.5.2], "IPv4-Mapped IPv6") they will be internally "unwrapped"/unmapped (via [UnmapAddr]) to native IPv4 first before performing conditional checks. [RFC 5737]: https://datatracker.ietf.org/doc/html/rfc5737 [RFC 6598]: https://datatracker.ietf.org/doc/html/rfc6598 [4291 § 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 [RFC 3849]: https://datatracker.ietf.org/doc/html/rfc3849 [RFC 9637]: https://datatracker.ietf.org/doc/html/rfc9637 */ func IsPublic(ip netip.Addr) (isPub bool) { var err error var v bool var addr netip.Addr // Clone to avoid modification to the passed in value. if addr, err = netip.ParseAddr(ip.String()); err != nil { return } // Unmap to properly determine routedness. addr = UnmapAddr(addr) // Short-circuit on invalid first to avoid any possible logic oddness. if !addr.IsValid() { return } // Following must be FALSE for _, v = range []bool{ addr.IsLinkLocalMulticast(), addr.IsInterfaceLocalMulticast(), addr.IsLinkLocalUnicast(), addr.IsMulticast(), addr.IsLoopback(), addr.IsPrivate(), addr.IsUnspecified(), // these are in the FALSE to keep syntax pattern but it's cleaner to doc as if it's a ! in TRUE. ipDoc.Contains(addr), cgnat4.Contains(addr), } { if v { return } } // Following must be TRUE for _, v = range []bool{ addr.IsGlobalUnicast(), } { if !v { return } } // All conditions pass isPub = true return } /* Mask4ToCidr converts an IPv4 netmask *in bitmask form* to a CIDR prefix size/bit size/bit length. Inverse of [Cidr4ToMask]. */ func Mask4ToCidr(mask uint32) (cidr uint8, err error) { cidr = 32 - uint8(bits.LeadingZeros32(mask)) return } /* Mask4ToIPMask returns mask *in bitmask form* as a [net.IPMask]. Inverse of [IPMask4ToMask]. */ func Mask4ToIPMask(mask uint32) (ipMask net.IPMask, err error) { var cidr uint8 if cidr, err = Mask4ToCidr(mask); err != nil { return } ipMask = net.CIDRMask(int(cidr), 32) return } /* Mask4ToStr returns a string representation of an IPv4 netmask (e.g. "255.255.255.0" for a /24) from a netmask *in bitmask form*. Inverse of [Mask4StrToMask]. */ func Mask4ToStr(mask uint32) (maskStr string, err error) { var ipMask net.IPMask if ipMask, err = Mask4ToIPMask(mask); err != nil { return } if maskStr, err = IPMask4ToStr(ipMask); err != nil { return } return } /* Mask4StrToCidr parses a "dotted-quad" IPv4 netmask (e.g. "255.255.255.0" for a /24) and returns an IPv4 CIDR/bit size/prefix length. Inverse of [Cidr4ToStr]. */ func Mask4StrToCidr(maskStr string) (cidr uint8, err error) { var ipMask net.IPMask if ipMask, err = Mask4StrToIPMask(maskStr); err != nil { return } if cidr, err = IPMask4ToCidr(ipMask); err != nil { return } return } /* Mask4StrToIPMask parses a "dotted-quad" IPv4 netmask (e.g. "255.255.255.0" for a /24) and returns a [net.IPMask]. Inverse of [IPMask4ToStr]. */ func Mask4StrToIPMask(maskStr string) (mask net.IPMask, err error) { var idx int var s string var u64 uint64 var b []byte = make([]byte, 4) var sl []string = strings.Split(maskStr, ".") if len(sl) != 4 { err = ErrBadMask4Str return } // A net.IPMask is just a []byte. for idx = 0; idx < len(sl); idx++ { s = sl[idx] if u64, err = strconv.ParseUint(s, 10, 8); err != nil { return } b[idx] = byte(u64) } mask = net.IPMask(b) return } /* Mask4StrToMask parses a "dotted-quad" IPv4 netmask (e.g. "255.255.255.0" for a /24) and returns a netmask *in bitmask form*. Inverse of [Mask4ToStr]. */ func Mask4StrToMask(maskStr string) (mask uint32, err error) { var ipMask net.IPMask if ipMask, err = Mask4StrToIPMask(maskStr); err != nil { return } if mask, err = IPMask4ToMask(ipMask); err != nil { return } return } // MustIPSetCombined wraps [IPSetCombined] but will panic on error instead of returning it. func MustIPSetCombined(sets []*netipx.IPSet) (combined *netipx.IPSet) { var err error if combined, err = IPSetCombined(sets); err != nil { panic(err) } return } // MustIPSetFrom wraps [IPSetFrom] but will panic on error instead of returning it. func MustIPSetFrom(addrs, pfxs, ranges []string) (ipset *netipx.IPSet) { var err error if ipset, err = IPSetFrom(addrs, pfxs, ranges); err != nil { panic(err) } return } // MustIPSetFromNative wraps [IPSetFromNative] but will panic on error instead of returning it. func MustIPSetFromNative(addrs []netip.Addr, pfxs []netip.Prefix, ranges []netipx.IPRange) (ipset *netipx.IPSet) { var err error if ipset, err = IPSetFromNative(addrs, pfxs, ranges); err != nil { panic(err) } return } /* Pfx4in6Compat takes network prefix `pfx` and, if an IPv4 network, returns it as an "IPv4-Compatible IPv6 Address" (RFC [4291 § 2.5.5.1]) network. If `pfx` is already an IPv6 network and NOT in the '::/96' or '::0:0/96' prefix (inclusive), `pfx` will be returned as `compatPfx`. If `pfx` is an IPv6 network and is an IPv4-Mapped prefix ('::0:0/96'), then `compatPfx` will be an IPv4-Compatible network conversion of `pfx`. If `pfx` is an IPv6 network and is in the '::/96' prefix (inclusive), `pfx` will be returned as `compatPfx`. If `pfx` is invalid or the zero network, `compatPfx` will be ::/0. Note that this is the DEPRECATED format for IPv4-addresses-as-IPv6! The modern, proper equivalent is "IPv4-Mapped IPv6 Address" (RFC [4291 § 2.5.5.2]), which can be performed via [Pfx4in6Mapped] instead. - If you need an IPv6 network format for an IPv4 network and don't know if you need this function or not, you want [Pfx4in6Mapped] instead. - If you THINK you want this function, you want [Pfx4in6Mapped] instead. - If you ABSOLUTELY KNOW you need this function, you're probably dealing with some very old IPv6 implementations. But you at least know you need this function. [4291 § 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func Pfx4in6Compat(pfx netip.Prefix) (compatPfx netip.Prefix) { var bitLen uint8 var a netip.Addr if !a.IsValid() || !pfx.IsValid() { compatPfx = netip.PrefixFrom(netip.IPv6Unspecified(), 0) return } a = pfx.Addr() bitLen = uint8(pfx.Bits()) compatPfx = pfx if a.Is6() { if a.Is4In6() { a = Addr4in6Compat(UnmapAddr(a)) compatPfx = netip.PrefixFrom(a, int(bitLen)) } return } a = netip.MustParseAddr(fmt.Sprintf("%s%s", ip4in6Legacy96, a.String())) bitLen = IP4MapPfx4to6(bitLen) compatPfx = netip.PrefixFrom(a, int(bitLen)) return } /* Pfx4in6Mapped takes network prefix `pfx` and, if an IPv4 network, returns it as an "IPv4-Mapped IPv6 Address" (RFC [4291 § 2.5.5.2]) network. You should almost assuredly be using this function instead of [Pfx4in6Compat]. If `pfx` is already an IPv6 network and NOT in the '::/96' or '::0:0/96' prefix, `pfx` will be returned as `mappedPfx`. If `pfx` is an IPv6 network and is an IPv4-Compatible prefix ('::/96'), then `mappedPfx` will be an IPv4-Mapped network conversion of `pfx`. If `pfx` is an IPv6 address and is in the '::ffff:0:0/96' prefix (inclusive), `pfx` will be returned as `mappedPfx`. If `pfx` is invalid or the zero network, `mappedPfx` will be ::/0. [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func Pfx4in6Mapped(pfx netip.Prefix) (mappedPfx netip.Prefix) { var bitLen uint8 var a netip.Addr if !a.IsValid() || !pfx.IsValid() { mappedPfx = netip.PrefixFrom(netip.IPv6Unspecified(), 0) return } a = pfx.Addr() bitLen = uint8(pfx.Bits()) mappedPfx = pfx if a.Is6() { if !IsAddr4Compat(a) { return } a = UnmapAddr(a) } a = netip.MustParseAddr(fmt.Sprintf("%s%s", ip4in6Modern96, a.String())) bitLen = IP4MapPfx4to6(bitLen) mappedPfx = netip.PrefixFrom(a, int(bitLen)) return } /* UnmapAddr unmaps both a current-supported RFC [4291 § 2.5.5.2] format ("IPv4-Mapped") *and* legacy RFC [4291 § 2.5.5.1] ("IPv4-Compatible") address. The Unmap method of a [netip.Addr] only works for the former. This function works for both. If `ip` is not either of those formats (or a "true" IPv6 address, etc.), then `unmapped` will be equal to `ip` (but a new/copied [net/netip.Addr]). If `ip` is the "unspecified" address (0.0.0.0, ::) for either family, it is returned as-is. [4291 § 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func UnmapAddr(ip netip.Addr) (unmapped netip.Addr) { if ip.IsUnspecified() { unmapped = ip return } // Do an initial .Unmap() - this should be safe regardless. unmapped = ip.Unmap() // If it's in the legacy prefix, it requires manual unmapping. if ip4in6Legacy.Contains(unmapped) { unmapped, _ = netip.AddrFromSlice(unmapped.AsSlice()[12:16]) } return } /* UnmapPfx unmaps both a current-supported RFC 4291 § 2.5.5.2 format ("IPv4-Mapped") *and* legacy RFC 4291 § 2.5.5.1 ("IPv4-Compatible") prefix. It's much like [UnmapAddr] in this sense, but requires some additional parsing/conversion. [4291 § 2.5.5.1]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.1 [4291 § 2.5.5.2]: https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2 */ func UnmapPfx(pfx netip.Prefix) (unmapped netip.Prefix) { var a netip.Addr = pfx.Addr() unmapped = pfx if IsAddr4Compat(a) || a.Is4In6() { a = UnmapAddr(a) unmapped = netip.PrefixFrom(a, int(IP4MapPfx6to4(uint8(pfx.Bits())))) } return } /* VerToFamily takes a "human-readable" IP version `ipVer` (4 or 6) and returns a system-level constant (e.g. [AFUnspec], [AFInet], [AFInet6]). If not a known IP version (i.e. neither 4 nor 6), family will be [AFUnspec]. It is the inverse of [FamilyToVer]. */ func VerToFamily(ipVer int) (family uint16) { switch ipVer { case 4: family = AFInet case 6: family = AFInet6 default: family = AFUnspec } return }