v0.2.5
ADDED: * num-addrs subcommand, to get the number of hosts/addresses in a given prefix length * get-net subcommand, to more easily get a single subnet from either the beginning or the end of a prefix. (MUCH FASTER than CIDR-splitting!)
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
`net/netip`
|
||||
`sync`
|
||||
|
||||
`github.com/go-playground/validator/v10`
|
||||
`github.com/go-resty/resty/v2`
|
||||
)
|
||||
|
||||
@@ -39,8 +40,12 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO
|
||||
cacheLock sync.RWMutex
|
||||
// validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled(), validator.WithPrivateFieldValidation())
|
||||
validate *validator.Validate = validator.New(validator.WithRequiredStructEnabled())
|
||||
)
|
||||
|
||||
var (
|
||||
cacheLock sync.RWMutex // TODO
|
||||
cacheClient *resty.Client
|
||||
// IPv4: https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml#iana-ipv4-special-registry-1
|
||||
// IPv6: https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
||||
|
||||
@@ -11,4 +11,5 @@ var (
|
||||
ErrBadSplitter error = errors.New("invalid or unknown splitter when containing")
|
||||
ErrBigPrefix error = errors.New("prefix length exceeds remaining network space")
|
||||
ErrNoNetSpace error = errors.New("reached end of network space before splitting finished")
|
||||
ErrUnknownPos error = errors.New("unknown subnet position")
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
`bytes`
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
@@ -298,6 +299,113 @@ func Contain(origPfx *netip.Prefix, nets []*netip.Prefix, remaining *netipx.IPSe
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
LastSubnetNet returns the last subnet of prefix length pfxLen from network pfx.
|
||||
|
||||
lastSubnet will be nil if pfx is nil or invalid.
|
||||
*/
|
||||
func LastSubnetNet(pfx *net.IPNet, pfxLen uint8) (lastSubnet *net.IPNet, err error) {
|
||||
|
||||
var nPfx netip.Prefix
|
||||
var ok bool
|
||||
|
||||
if pfx == nil {
|
||||
return
|
||||
}
|
||||
if nPfx, ok = netipx.FromStdIPNet(pfx); !ok {
|
||||
return
|
||||
}
|
||||
if !nPfx.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
if nPfx, err = LastSubnetPfx(nPfx, pfxLen); err != nil {
|
||||
return
|
||||
}
|
||||
if !nPfx.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
lastSubnet = netipx.PrefixIPNet(nPfx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LastSubnetPfx is exactly like LastSubnetNet but using netip.Prefix instead.
|
||||
func LastSubnetPfx(pfx netip.Prefix, pfxLen uint8) (lastSubnet netip.Prefix, err error) {
|
||||
|
||||
var ok bool
|
||||
var pfxBytes []byte
|
||||
var bitOffset uint
|
||||
var offset *big.Int
|
||||
var ipInt *big.Int
|
||||
var ipBytes []byte
|
||||
var byteLen int
|
||||
var lastNet *net.IPNet
|
||||
var maxBitLen uint8 = maxBitsv4
|
||||
|
||||
if !pfx.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
pfx = pfx.Masked()
|
||||
|
||||
if pfx.Addr().Is6() || pfxLen > maxBitsv4 {
|
||||
maxBitLen = maxBitsv6
|
||||
}
|
||||
if pfxLen > maxBitLen {
|
||||
err = ErrBadPrefixLen
|
||||
return
|
||||
}
|
||||
if pfxLen < uint8(pfx.Bits()) {
|
||||
err = ErrBigPrefix
|
||||
return
|
||||
}
|
||||
|
||||
if pfxBytes, err = pfx.Addr().MarshalBinary(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
byteLen = pfx.Addr().BitLen() / 8
|
||||
bitOffset = uint(pfxLen - uint8(pfx.Bits()))
|
||||
|
||||
offset = new(big.Int).Lsh(big.NewInt(1), bitOffset)
|
||||
offset.Sub(offset, big.NewInt(1))
|
||||
|
||||
// Cast the prefix (as represented as bytes) into a *big.Int for some number magic.
|
||||
ipInt = new(big.Int).SetBytes(pfxBytes)
|
||||
// Shift to the first "address" in the prefix/mask it.
|
||||
offset.Lsh(offset, uint(pfx.Addr().BitLen()-int(pfxLen)))
|
||||
// Now add the offset to the base network.
|
||||
ipInt.Add(ipInt, offset)
|
||||
|
||||
// If the base address starts at the "0 address" (e.g. 0.0.0.0 or :: etc....),
|
||||
// this can cause some strange behavior when casting the *big.Int to bytes.
|
||||
// So it gets left-null-padded to the appropriate length for the inet family.
|
||||
ipBytes = ipInt.Bytes()
|
||||
if len(ipBytes) < byteLen {
|
||||
ipBytes = append(
|
||||
bytes.Repeat([]byte{0x00}, byteLen-len(ipBytes)),
|
||||
ipBytes...,
|
||||
)
|
||||
}
|
||||
|
||||
// Create an explicit net.IPNet...
|
||||
lastNet = &net.IPNet{
|
||||
IP: net.IP(ipBytes),
|
||||
Mask: net.CIDRMask(int(pfxLen), pfx.Addr().BitLen()),
|
||||
}
|
||||
// And then make it a netip.Prefix.
|
||||
if lastSubnet, ok = netipx.FromStdIPNet(lastNet); !ok {
|
||||
return
|
||||
}
|
||||
if !lastSubnet.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
MaskExpand expands a net.IPMask's string format.
|
||||
Like AddrExpand but for netmasks.
|
||||
@@ -391,29 +499,29 @@ func MaskInvert(mask net.IPMask) (inverted net.IPMask) {
|
||||
Note that for the single-host prefix (/32 for IPv4, /128 for IPv6), numAddrs will *always* be 1.
|
||||
For point-to-point prefix (IPv4 /31, IPv6 /127), numAddrs will *ALWAYS* be 2.
|
||||
*/
|
||||
func NumAddrsIn(prefixLen uint8, isIpv6, inclNet, inclBcast bool) (numAddrs *big.Int, err error) {
|
||||
func NumAddrsIn(pfxLen uint8, isIpv6, inclNet, inclBcast bool) (numAddrs *big.Int, err error) {
|
||||
|
||||
var numBits uint
|
||||
var numRemoved int64
|
||||
var maxBitLen uint8 = maxBitsv4
|
||||
|
||||
if isIpv6 {
|
||||
if isIpv6 || pfxLen > maxBitsv4 {
|
||||
maxBitLen = maxBitsv6
|
||||
}
|
||||
if prefixLen > maxBitLen {
|
||||
if pfxLen > maxBitLen {
|
||||
err = ErrBadPrefixLen
|
||||
return
|
||||
}
|
||||
if prefixLen == maxBitLen {
|
||||
if pfxLen == maxBitLen {
|
||||
numAddrs = big.NewInt(1)
|
||||
return
|
||||
}
|
||||
if (prefixLen + 1) == maxBitLen {
|
||||
if (pfxLen + 1) == maxBitLen {
|
||||
numAddrs = big.NewInt(2)
|
||||
return
|
||||
}
|
||||
|
||||
numBits = uint(maxBitLen - prefixLen)
|
||||
numBits = uint(maxBitLen - pfxLen)
|
||||
|
||||
numAddrs = new(big.Int).Lsh(big.NewInt(1), numBits)
|
||||
|
||||
@@ -451,6 +559,9 @@ func NumAddrsNet(pfx *net.IPNet, inclNet, inclBcast bool) (numAddrs *big.Int) {
|
||||
if nPfx, ok = netipx.FromStdIPNet(pfx); !ok {
|
||||
return
|
||||
}
|
||||
if !nPfx.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
numAddrs = NumAddrsPfx(nPfx, inclNet, inclBcast)
|
||||
|
||||
@@ -460,21 +571,16 @@ func NumAddrsNet(pfx *net.IPNet, inclNet, inclBcast bool) (numAddrs *big.Int) {
|
||||
// NumAddrsPfx is the exact same as NumAddrsNet but for a net/netip.Prefix instead.
|
||||
func NumAddrsPfx(pfx netip.Prefix, inclNet, inclBcast bool) (numAddrs *big.Int) {
|
||||
|
||||
var numBits uint
|
||||
var numRemoved int64
|
||||
var err error
|
||||
|
||||
numBits = uint(pfx.Addr().BitLen() - pfx.Bits())
|
||||
|
||||
numAddrs = new(big.Int).Lsh(big.NewInt(1), numBits)
|
||||
|
||||
if !inclNet {
|
||||
numRemoved++
|
||||
if !pfx.IsValid() {
|
||||
return
|
||||
}
|
||||
if !inclBcast {
|
||||
numRemoved++
|
||||
}
|
||||
if numRemoved > 0 {
|
||||
_ = numAddrs.Sub(numAddrs, big.NewInt(numRemoved))
|
||||
|
||||
// Since we're dealing with existing prefixes/networks, we should never get an error.
|
||||
if numAddrs, err = NumAddrsIn(uint8(pfx.Bits()), pfx.Addr().Is6(), inclNet, inclBcast); err != nil {
|
||||
// But *somehow* in case we do...
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -22,6 +22,10 @@ func (c *CIDRSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.Struct(c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if base, ok = netipx.FromStdIPNet(c.network); !ok {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
@@ -41,6 +45,15 @@ func (c *CIDRSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: This is *super* slow for tiny subnets in a large net.
|
||||
For future optimzation, first off benchmark to make sure it makes a difference, but
|
||||
chunk out the network (how to find appropriate length?) of n < x < y, where n is the network pfx len,
|
||||
and goroutine each split into a channel.
|
||||
Because this splits *on CIDR boundaries* and we aren't VLSM-ing, remaining never has to be considered-
|
||||
it'll always be clean splitting.
|
||||
See CIDRSplitter.LenSwitch.
|
||||
*/
|
||||
for {
|
||||
if sub, remaining, ok = remaining.RemoveFreePrefix(c.PrefixLength); !ok {
|
||||
if !sub.IsValid() {
|
||||
|
||||
@@ -28,6 +28,10 @@ func (h *HostSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.Struct(h); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pfx, _ = netipx.FromStdIPNet(h.network)
|
||||
tgt = new(big.Int)
|
||||
tgt.SetUint64(uint64(h.NumberHosts))
|
||||
|
||||
102
netsplit/funcs_prefixgetter.go
Normal file
102
netsplit/funcs_prefixgetter.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`net/netip`
|
||||
|
||||
`go4.org/netipx`
|
||||
)
|
||||
|
||||
// Split is to conform to a NetSplitter, though a PrefixGetter is *technically* not a splitter.
|
||||
func (p *PrefixGetter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {
|
||||
|
||||
var ok bool
|
||||
var base netip.Prefix
|
||||
var vlsmS *VLSMSplitter
|
||||
var addr netip.Addr
|
||||
var maxPfxLen uint8 = maxBitsv4
|
||||
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
|
||||
|
||||
if p == nil || p.PrefixLength == 0 || p.BaseSplitter == nil || p.network == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.Struct(p); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If the position is "first", we can simply call a VLSM.
|
||||
if p.Pos == "first" {
|
||||
vlsmS = &VLSMSplitter{
|
||||
Ascending: false,
|
||||
Explicit: false,
|
||||
PrefixLengths: []uint8{p.PrefixLength},
|
||||
BaseSplitter: p.BaseSplitter,
|
||||
}
|
||||
if nets, remaining, err = vlsmS.Split(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if p.Pos != "last" {
|
||||
err = ErrUnknownPos
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise this gets... messy.
|
||||
if base, ok = netipx.FromStdIPNet(p.network); !ok {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
}
|
||||
if !base.IsValid() {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
}
|
||||
ipsb = new(netipx.IPSetBuilder)
|
||||
ipsb.AddPrefix(base.Masked())
|
||||
|
||||
// First if it's a single host prefix, ezpz gg no re.
|
||||
if base.Addr().Is6() {
|
||||
maxPfxLen = maxBitsv6
|
||||
}
|
||||
if p.PrefixLength == maxPfxLen {
|
||||
nets = make([]*netip.Prefix, 1)
|
||||
nets[0] = new(netip.Prefix)
|
||||
addr = netipx.PrefixLastIP(base)
|
||||
fmt.Println(addr.String())
|
||||
if *nets[0], err = addr.Prefix(int(p.PrefixLength)); err != nil {
|
||||
return
|
||||
}
|
||||
ipsb.Remove(addr)
|
||||
if remaining, err = ipsb.IPSet(); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise this gets... interesting.
|
||||
/*
|
||||
For IPv4, performance NORMALLY would be "fine" on modern hardware with:
|
||||
1. straight CIDR-splitting
|
||||
2. grabbing the last prefix
|
||||
3. condensing the leading prefixes to a new IPSet
|
||||
But even this can take a long time (see CIDRSplitter.Split comments).
|
||||
|
||||
In almost all cases (unless subnetting like, n+12 prefix length),
|
||||
IPv6 takes WAY too long.
|
||||
|
||||
So use the same function (LastSubnetPfx) for both cases.
|
||||
*/
|
||||
nets = make([]*netip.Prefix, 1)
|
||||
nets[0] = new(netip.Prefix)
|
||||
if *nets[0], err = LastSubnetPfx(base, p.PrefixLength); err != nil {
|
||||
return
|
||||
}
|
||||
ipsb.RemovePrefix(*nets[0])
|
||||
if remaining, err = ipsb.IPSet(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -27,6 +27,10 @@ func (s *SubnetSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet,
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.Struct(s); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if base, ok = netipx.FromStdIPNet(s.network); !ok {
|
||||
err = ErrBadBoundary
|
||||
return
|
||||
|
||||
@@ -21,7 +21,15 @@ func (v *VLSMSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
var base netip.Prefix
|
||||
var sub netip.Prefix
|
||||
var subPtr *netip.Prefix
|
||||
var ipsb = new(netipx.IPSetBuilder)
|
||||
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
|
||||
|
||||
if v == nil || v.PrefixLengths == nil || len(v.PrefixLengths) == 0 || v.BaseSplitter == nil || v.network == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = validate.Struct(v); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = ValidateSizes(v.network, v.PrefixLengths...); err != nil {
|
||||
return
|
||||
@@ -38,10 +46,6 @@ func (v *VLSMSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
But, as I expected, netipx ftw again.
|
||||
*/
|
||||
|
||||
if v == nil || v.PrefixLengths == nil || len(v.PrefixLengths) == 0 || v.BaseSplitter == nil || v.network == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !v.Explicit {
|
||||
sort.SliceStable(
|
||||
v.PrefixLengths,
|
||||
|
||||
@@ -31,7 +31,16 @@ type NetSplitter interface {
|
||||
|
||||
// BaseSplitter is used to encapsulate the "parent" network to be split.
|
||||
type BaseSplitter struct {
|
||||
network *net.IPNet
|
||||
network *net.IPNet `validate:"required"`
|
||||
}
|
||||
|
||||
// PrefixGetter is a "pseudo-splitter"; it splits according to a given prefix but only returns a specfic subnet (either the first or the last).
|
||||
type PrefixGetter struct {
|
||||
// Pos is the position in BaseSplitter.network for the selected PrefixLength.
|
||||
Pos string `json:"pos" xml:"pos,attr" yaml:"Position" validate:"required,oneof=first last"`
|
||||
// PrefixLength specifies the CIDR/prefix length of the subnet.
|
||||
PrefixLength uint8 `json:"prefix" xml:"prefix,attr" yaml:"network Prefix Length" validate:"required,lte=128"`
|
||||
*BaseSplitter
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -40,7 +49,11 @@ It attemps to split the network into as many networks of size PrefixLength as cl
|
||||
*/
|
||||
type CIDRSplitter struct {
|
||||
// PrefixLength specifies the CIDR/prefix length of the subnets to split out.
|
||||
PrefixLength uint8 `json:"prefix" xml:"prefix,attr" yaml:"network Prefix Length"`
|
||||
PrefixLength uint8 `json:"prefix" xml:"prefix,attr" yaml:"network Prefix Length" validate:"required,lte=128"`
|
||||
// TODO: See CIDRSplitter.Split for future optimization using this.
|
||||
// LenSwitch specifies the threshold bit offset after which (inclusive) it switches from a repeated prefix removal to manual recursive binary split.
|
||||
// If 0, 12 is the default.
|
||||
// LenSwitch uint8 `json:"switch_at" xml:"switchAt,attr" yaml:"Switch Offset Threshold"`
|
||||
*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
|
||||
}
|
||||
|
||||
@@ -50,7 +63,7 @@ It attempts to evenly distribute addresses amoungs subnets.
|
||||
*/
|
||||
type HostSplitter struct {
|
||||
// NumberHosts is the number of hosts to be placed in each subnet to split out.
|
||||
NumberHosts uint `json:"hosts" xml:"hosts,attr" yaml:"Number of Hosts Per Subnet"`
|
||||
NumberHosts uint `json:"hosts" xml:"hosts,attr" yaml:"Number of Hosts Per Subnet" validate:"required"`
|
||||
// InclNetAddr, if true, specifies that NumberHosts includes the network address.
|
||||
InclNetAddr bool `json:"net_addr" xml:"netAddr,attr,omitempty" yaml:"Network Address Included,omitempty"`
|
||||
// InclBcastAddr, if true, specifies that NumberHosts includes the broadcast address.
|
||||
@@ -66,7 +79,7 @@ as cleanly as poossible.
|
||||
*/
|
||||
type SubnetSplitter struct {
|
||||
// NumberSubnets indicates the number of subnets to split the network into.
|
||||
NumberSubnets uint `json:"nets" xml:"nets,attr" yaml:"Number of Target Subnets"`
|
||||
NumberSubnets uint `json:"nets" xml:"nets,attr" yaml:"Number of Target Subnets" validate:"required"`
|
||||
// Strict, if true, will return an error from Split if the network sizes cannot split into equally-sized networks.
|
||||
Strict bool `json:"strict" xml:"strict,attr,omitempty" yaml:"Strictly Equal Subnet Sizes"`
|
||||
*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
|
||||
@@ -91,7 +104,7 @@ type VLSMSplitter struct {
|
||||
*/
|
||||
Explicit bool `json:"explicit,omitempty" xml:"explicit,attr,omitempty" yaml:"Explicit Ordering,omitempty"`
|
||||
// PrefixLengths contains the prefix lengths of each subnet to split out from the network.
|
||||
PrefixLengths []uint8 `json:"prefixes" xml:"prefixes>prefix" yaml:"Prefix Lengths"`
|
||||
PrefixLengths []uint8 `json:"prefixes" xml:"prefixes>prefix" yaml:"Prefix Lengths" validate:"required,dive,lte=128"`
|
||||
*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user