cbfaaddf34
ADDED: * netx: ** docs updated ** added NOTES.adoc for more extensive info that doesn't need to be in the library docs ** Addr4in6Compat() ** Addr4in6Mapped() ** Addr4in6Mapped() ** HasSubnet() ** IP4MapPfx4to6() ** IP4MapPfx6to4() ** IPSetCombined() ** IPSetFrom() ** IPSetFromNative() ** IsAddr4Compat() ** IsPublic() ** MustIPSetCombined() ** MustIPSetFrom() ** MustIPSetFromNative() ** Pfx4in6Compat() (untested) ** Pfx4in6Mapped() (untested) ** UnmapAddr() ** UnmapPfx() ** Better support in other functions for IPv4-Compatible/IPv4-Mapped addressing
1232 lines
29 KiB
Go
1232 lines
29 KiB
Go
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:
|
|
|
|
<addr_or_net>/<prefix_len>
|
|
|
|
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 !<cond> 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
|
|
}
|