package netx import ( `math/bits` `net` `net/netip` `strconv` `strings` `go4.org/netipx` ) /* 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]"). 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() { rfcStr = "[" + addr.String() + "]" } return } /* Cidr4ToIPMask takes an IPv4 CIDR/bit size/prefix length and returns the [net.IPMask]. It's (essentially) the inverse of [net.IPMask.Size]. See also: * [Cidr4ToMask] * [Cidr4ToStr] 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*. See also: * [Cidr4ToIPMask] * [Cidr4ToStr] 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)). See also: * [Cidr4ToIPMask] * [Cidr4ToMask] 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]. See also [GetIpFamily]. 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.) See also [GetAddrFamily]. 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 } /* 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]"). If the version can't be determined, rfcStr will be an empty string. See also [IpRfcStr] for providing an IP address as a string. */ func IpRfc(ip net.IP) (rfcStr string) { if ip.To4() != nil { rfcStr = ip.To4().String() } else if ip.To16() != nil { rfcStr = "[" + ip.To16().String() + "]" } 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]. See also: * [IPMask4ToMask] * [IPMask4ToStr] 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]. See also: * [IPMask4ToCidr] * [IPMask4ToStr] 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]. See also: * [IPMask4ToCidr] * [IPMask4ToMask] 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 } /* 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 } /* 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 } /* Mask4ToCidr converts an IPv4 netmask *in bitmask form* to a CIDR prefix size/bit size/bit length. See also: * [Mask4ToIPMask] * [Mask4ToStr] 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]. See also: * [Mask4ToCidr] * [Mask4ToStr] 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*. See also: * [Mask4ToCidr] * [Mask4ToIPMask] 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 am IPv4 CIDR/bit size/prefix length. See also: * [Mask4StrToIPMask] * [Mask4StrToMask] Inverse of [Cidr4ToMaskStr]. */ 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]. See also: * [Mask4StrToCidr] * [Mask4StrToMask] 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*. See also: * [Mask4StrToCidr] * [Mask4StrToIPMask] 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 } /* 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 }