almost done ackshually
This commit is contained in:
356
netsplit/funcs.go
Normal file
356
netsplit/funcs.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"github.com/goccy/go-yaml"
|
||||
"go4.org/netipx"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
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 bool = every > 0
|
||||
var fs string = "%" + f
|
||||
var sb *strings.Builder = 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
|
||||
}
|
||||
|
||||
// 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 = 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 bool = every > 0
|
||||
var fs string = "%" + f
|
||||
var sb *strings.Builder = 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
|
||||
}
|
||||
Reference in New Issue
Block a user