2025-02-02 11:09:52 -05:00

376 lines
7.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 or included within the prefix depending on recurse.
excludePrivate indicates if LAN networks should be considered as "reserved" or not.
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.
*/
func CheckReserved(nets []*netip.Prefix, recurse, excludePrivate bool) (reservations map[netip.Prefix]string, err error) {
// TODO
return
}
// Contain takes the results of a NetSplitter and returns a StructuredResults.
func Contain(origPfx *netip.Prefix, nets []*netip.Prefix, remaining *netipx.IPSet, splitter NetSplitter) (s *StructuredResults, err error) {
var rem []netip.Prefix
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
}
}
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
}