440 lines
9.9 KiB
Go

package netsplit
import (
"encoding/json"
"encoding/xml"
"fmt"
"net"
"net/netip"
"strings"
"github.com/goccy/go-yaml"
"go4.org/netipx"
)
/*
AddrExpand expands a netip.Addr's string format.
Like netip.Addr.StringExpanded() but for IPv4 too.
*/
func AddrExpand(ip netip.Addr) (s string) {
var sb *strings.Builder
if ip.IsUnspecified() || !ip.IsValid() {
return
}
if ip.Is6() {
s = ip.StringExpanded()
} else {
// IPv4 we have to do by hand.
sb = new(strings.Builder)
for idx, b := range ip.AsSlice() {
sb.WriteString(fmt.Sprintf("%03d", b))
if idx != net.IPv4len-1 {
sb.WriteString(".")
}
}
s = sb.String()
}
return
}
/*
AddrCompress returns the shortest possible CIDR representation as a string from a netip.Prefix.
Note that IPv6 netip.Prefix.String() already does this automatically, as IPv6 has special condensing rules.
*/
func AddrCompress(pfx *netip.Prefix) (s string) {
var sl []string
var lastNonzero int
if pfx == nil || !pfx.IsValid() || !pfx.Addr().IsValid() {
return
}
if pfx.Addr().Is6() {
s = pfx.String()
return
}
sl = strings.Split(pfx.Addr().String(), ".")
for idx, oct := range sl {
if oct != "0" {
lastNonzero = idx
}
}
s = fmt.Sprintf("%s/%d", strings.Join(sl[:lastNonzero+1], "."), pfx.Bits())
return
}
/*
AddrFmt provides a string representation for an IP (as a netip.Addr).
`f` is the string formatter to use (without the %). For IPv4, you generally want `d`,
for IPv6, you generally want `x`.
`sep` indicates a character to insert every `every` bytes of the mask.
For IPv4, you probably want `.`,
for IPv6 there isn't really a standard representation; CIDR notation is preferred.
Thus for IPv6 you probably want to set sep as blank and/or set `every` to 0.
`segSep` indicates a character sequence to use for segmenting the string.
Specify as an empty string and/or set `everySeg` to 0 to disable.
`every` indicates how many bytes should pass before sep is inserted.
For IPv4, this should be 1.
For IPv6, there isn't really a standard indication but it's recommended to do 2.
Set as 0 or `sep` to an empty string to do no separation characters.
`everySeg` indicates how many *seperations* should pass before segSep is inserted.
Set as 0 or `segSep` to an empty string to do no string segmentation.
*/
func AddrFmt(ip netip.Addr, f, sep, segSep string, every, everySeg uint) (s string) {
var numSegs int
var doSep = every > 0
var fs = "%" + f
var sb = new(strings.Builder)
if ip.IsUnspecified() || !ip.IsValid() {
return
}
for idx, b := range ip.AsSlice() {
if doSep && idx > 0 {
if idx%int(every) == 0 {
sb.WriteString(sep)
numSegs++
}
if everySeg > 0 {
if numSegs >= int(everySeg) {
sb.WriteString(segSep)
numSegs = 0
}
}
}
fmt.Fprintf(sb, fs, b)
}
s = strings.TrimSpace(sb.String())
return
}
/*
AddrInvert returns an inverted form of netip.Addr as another netip.Addr.
Note that it doesn't really make sense to use this for IPv6.
*/
func AddrInvert(ip netip.Addr) (inverted netip.Addr) {
var b []byte
if !ip.IsValid() {
return
}
b = make([]byte, len([]byte(ip.AsSlice())))
for idx, i := range []byte(ip.AsSlice()) {
b[idx] = ^i
}
inverted, _ = netip.AddrFromSlice(b)
return
}
/*
CheckReserved checks nets for any reserved prefixes; either directly/explicitly,
included *within* a reserved prefix (revRecursive), or *including* a reserved prefix (recursive).
excludePrivate indicates if LAN networks should be considered as "reserved" or not.
If a network is found via revRecursive/recursive, the matching prefix - not the specified one - will be in reservations.
Any found will be returned in reservations.
If no reserved networks are found, reservations will be nil.
Note that prefix-specific broadcasts (e.g. x.255.255.255/8, x.x.x.255/24, ::/64, x:ffff:ffff:ffff:ffff/64, etc.)
will *not* be considered as "reserved" as they are considered normal addresses expected for functionality.
This primarily focuses on prefixes/subnets for this reason.
Additionally, all of nets will be aligned to their proper boundary range/CIDR/subnet.
*/
func CheckReserved(nets []*netip.Prefix, revRecursive, recursive, excludePrivate bool) (reservations map[netip.Prefix]*IANAAddrNetResRecord, err error) {
var ok bool
var res *IANAAddrNetResRecord
var reserved map[netip.Prefix]*IANAAddrNetResRecord
if nets == nil || len(nets) == 0 {
return
}
if _, _, reserved, err = RetrieveReserved(); err != nil {
return
}
for _, n := range nets {
if n == nil {
continue
}
if n.Addr().IsPrivate() && excludePrivate {
continue
}
*n = n.Masked()
if res, ok = reserved[*n]; ok {
if reservations == nil {
reservations = make(map[netip.Prefix]*IANAAddrNetResRecord)
}
reservations[*n] = res
if !revRecursive && !recursive {
continue
}
for p, r := range reserved {
// This... *should* be safe? I don't think any reservations overlap.
// Anyways, revRecursive works because n.Addr() returns the network address, which should be the canonical boundary.
// recursive works for the same reason, just the other end.
// Math!
if revRecursive && p.Contains(n.Addr()) {
if reservations == nil {
reservations = make(map[netip.Prefix]*IANAAddrNetResRecord)
}
reservations[p] = r
} else if recursive && n.Contains(p.Addr()) {
if reservations == nil {
reservations = make(map[netip.Prefix]*IANAAddrNetResRecord)
}
reservations[p] = r
}
}
}
}
return
}
// Contain takes the results of a NetSplitter and returns a StructuredResults. The reservations are only checked against nets.
func Contain(origPfx *netip.Prefix, nets []*netip.Prefix, remaining *netipx.IPSet, splitter NetSplitter) (s *StructuredResults, err error) {
var idx int
var r *IANAAddrNetResRecord
var rem []netip.Prefix
var reserved map[netip.Prefix]*IANAAddrNetResRecord
var sr = StructuredResults{
Original: origPfx,
}
if origPfx == nil {
return
}
if origPfx.Addr() != origPfx.Masked().Addr() {
sr.Canonical = new(netip.Prefix)
*sr.Canonical = origPfx.Masked()
sr.HostAddr = new(netip.Addr)
*sr.HostAddr = origPfx.Addr()
}
if splitter != nil {
sr.Splitter = new(SplitOpts)
switch t := splitter.(type) {
case *CIDRSplitter:
sr.Splitter.CIDR = t
case *HostSplitter:
sr.Splitter.Host = t
case *SubnetSplitter:
sr.Splitter.Subnet = t
case *VLSMSplitter:
sr.Splitter.VLSM = t
default:
err = ErrBadSplitter
return
}
}
if nets != nil {
sr.Allocated = make([]*ContainedResult, len(nets))
for idx, n := range nets {
sr.Allocated[idx] = &ContainedResult{
Network: n,
}
}
}
if remaining != nil {
rem = remaining.Prefixes()
sr.Unallocated = make([]*ContainedResult, len(rem))
for idx, i := range rem {
sr.Unallocated[idx] = &ContainedResult{
Network: new(netip.Prefix),
}
*sr.Unallocated[idx].Network = i
}
}
if nets != nil {
if reserved, err = CheckReserved(nets, true, true, false); err != nil {
return
}
if reserved != nil && len(reserved) > 0 {
s.Reservations = make([]*IANAAddrNetResRecord, len(reserved))
for idx, r = range reserved {
s.Reservations[idx] = r
}
}
}
s = &sr
return
}
/*
MaskExpand expands a net.IPMask's string format.
Like AddrExpand but for netmasks.
*/
func MaskExpand(mask net.IPMask, isIpv6 bool) (s string) {
var sb *strings.Builder
// IPv6 is always expanded in string format, but not split out.
if isIpv6 {
s = MaskFmt(mask, "02x", ":", "", 2, 0)
return
}
sb = new(strings.Builder)
for idx, b := range mask {
sb.WriteString(fmt.Sprintf("%03d", b))
if idx != net.IPv4len-1 {
sb.WriteString(".")
}
}
s = sb.String()
return
}
/*
MaskFmt provides a string representation for a netmask (as a net.IPMask).
Its parameters hold the same significance as in AddrFmt.
*/
func MaskFmt(mask net.IPMask, f, sep, segSep string, every, everySeg uint) (s string) {
var numSegs int
var doSep = every > 0
var fs = "%" + f
var sb = new(strings.Builder)
if mask == nil || len(mask) == 0 {
return
}
for idx, b := range mask {
if doSep && idx > 0 {
if idx%int(every) == 0 {
sb.WriteString(sep)
numSegs++
}
if everySeg > 0 {
if numSegs >= int(everySeg) {
sb.WriteString(segSep)
numSegs = 0
}
}
}
fmt.Fprintf(sb, fs, b)
}
s = strings.TrimSpace(sb.String())
return
}
/*
MaskInvert returns an inverted form of net.IPMask as another net.IPMask.
Note that it doesn't really make sense to use this for IPv6.
*/
func MaskInvert(mask net.IPMask) (inverted net.IPMask) {
var b []byte
b = make([]byte, len([]byte(mask)))
for idx, i := range []byte(mask) {
b[idx] = ^i
}
inverted = net.IPMask(b)
return
}
// Parse parses b for JSON/XML/YAML and tries to return a StructuredResults from it.
func Parse(b []byte) (s *StructuredResults, err error) {
if b == nil {
return
}
if err = json.Unmarshal(b, &s); err != nil {
if err = xml.Unmarshal(b, &s); err != nil {
if err = yaml.Unmarshal(b, &s); err != nil {
return
} else {
return
}
} else {
return
}
}
return
}
/*
ValidateSizes ensures that none of the prefix lengths in sizes exceeds the maximum possible in pfx.
No-ops with nil error if pfx is nil, sizes is nil, or sizes is empty.
err is also nil if validation succeeds.
If validation fails on a prefix length size, the error will be a SplitErr
with only Wrapped and RequestedPrefixLen fields populated *for the first failing size only*.
*/
func ValidateSizes(pfx *net.IPNet, sizes ...uint8) (err error) {
var ok bool
var addr netip.Addr
var familyMax uint8
if pfx == nil || sizes == nil || len(sizes) == 0 {
return
}
if addr, ok = netipx.FromStdIP(pfx.IP); !ok {
err = ErrBadPrefix
return
}
if addr.Is4() {
familyMax = 32
} else {
familyMax = 128
}
for _, size := range sizes {
if size > familyMax {
err = &SplitErr{
Wrapped: ErrBadPrefixLen,
Nets: nil,
Remaining: nil,
LastSubnet: nil,
RequestedPrefixLen: size,
}
return
}
}
return
}