package netsplit import ( "encoding/xml" "net" "net/netip" `time` "go4.org/netipx" ) // SplitErr is used to wrap an error with context surrounding when/how that error was encountered. type SplitErr struct { // Wrapped is the originating error during a split (or other parsing operation). Wrapped error // Nets are the subnets parsed out/collected so far. Nets []*netip.Prefix // Remaining is an IPSet of subnets/addresses that haven't been, or were unable to be, split out. Remaining *netipx.IPSet // LastSubnet is the most recently split out subnet. LastSubnet *netip.Prefix // RequestedPrefixLen is the network prefix length size, if relevant, that was attempted to be split out of Remaining. RequestedPrefixLen uint8 } // NetSplitter is used to split a network into multiple nets (and any remaining prefixes/addresses that didn't fit). type NetSplitter interface { SetParent(pfx net.IPNet) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) } // BaseSplitter is used to encapsulate the "parent" network to be split. type BaseSplitter struct { network *net.IPNet } /* CIDRSplitter is used to split a network based on a fixed prefix size. It attemps to split the network into as many networks of size PrefixLength as cleanly as possible. */ 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"` *BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"` } /* HostSplitter is used to split a network based on total number of hosts. 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"` // Strict, if true, will return an error from Split if the network cannot split into subnets of NumberHosts-addressable networks exactly. Strict bool `json:"strict" xml:"strict,attr,omitempty" yaml:"Strictly Equal Hosts Per Subnet"` *BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"` } /* SubnetSplitter is used to split a network into a specific number of subnets of equal prefix lengths 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"` // 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"` } /* VLSMSplitter is used to split a network via VLSM (Variable-Length Subnet Masks) into multiple PrefixLengths, in which there are multiple desired subnets of varying lengths. */ type VLSMSplitter struct { /* Ascending, if true, will subnet smaller networks/larger prefixes near the beginning (ascending order) instead of larger networks/smaller prefixes (descending order). You almost assuredly do not want to do this. */ Ascending bool // PrefixLengths contains the prefix lengths of each subnet to split out from the network. PrefixLengths []uint8 `json:"prefixes" xml:"prefixes>prefix" yaml:"Prefix Lengths"` *BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"` } /* StructuredResults is used for serializing prefixes into a structured/defined data format. */ type StructuredResults struct { XMLName xml.Name `json:"-" xml:"results" yaml:"-"` // Original is the provided parent network/prefix. Original *netip.Prefix `json:"orig" xml:"orig,attr,omitempty" yaml:"Original/Parent network"` // HostAddr is nil if Original falls on a network prefix boundary, otherwise it is the specified host address. HostAddr *netip.Addr `json:"host" xml:"host,attr,omitempty" yaml:"Host Address,omitempty"` // Canonical is the canonical network of Original (e.g. with host bits masked out). It is nil if Original.Addr() falls on the (lower) boundary. Canonical *netip.Prefix `json:"masked" xml:"masked,attr,omitempty" yaml:"Bound Original/Parent network"` // Splitter contains the spplitter and its options used to split the network. Splitter *SplitOpts `json:"splitter" xml:"splitter,omitempty" yaml:"Splitter,omitempty"` // Allocated contains valid subnet(s) in Original per the user-specified subnetting rules. Allocated []*ContainedResult `json:"subnets" xml:"subnets>subnet,omitempty" yaml:"Subnets"` // Unallocated contains subnets from Original that did not meet the splitting criteria or were left over from the split operation. Unallocated []*ContainedResult `json:"remaining" xml:"remaining>subnet,omitempty" yaml:"Remaining/Unallocated/Left Over,omitempty"` // Reservations contains any reserved addresses/prefixes within this set that are considered "special usage" and thus are likely to not be usable. Reservations []*IANAAddrNetResRecord `json:"reserved,omitempty" xml:"reserved>reservation,omitempty" yaml:"Matching Reserved Subnets,omitempty"` } type SplitOpts struct { XMLName xml.Name `json:"-" xml:"splitter" yaml:"-"` CIDR *CIDRSplitter `json:"cidr,omitempty" xml:"cidr,omitempty" yaml:"CIDR Splitter,omitempty"` Host *HostSplitter `json:"host,omitempty" xml:"host,omitempty" yaml:"Host Splitter,omitempty"` Subnet *SubnetSplitter `json:"subnet,omitempty" xml:"subnet,omitempty" yaml:"Subnet Splitter,omitempty"` VLSM *VLSMSplitter `json:"vlsm,omitempty" xml:"vlsm,omitempty" yaml:"VLSM Splitter,omitempty"` } // ContainedResult is a single Network (either an allocated subnet or a remaining block). type ContainedResult struct { XMLName xml.Name `json:"-" yaml:"-" xml:"subnet"` Network *netip.Prefix `json:"net" xml:"net,attr,omitempty" yaml:"network,omitempty"` } type IANADate time.Time /* WHAT a PITA. IANA publishes their reservations in XML (YAY!) ... except they use inner XML all over the place in their text. So. */ type IANARegistry struct { XMLName xml.Name `json:"-" yaml:"-" xml:"registry"` Title string `json:"title" yaml:"Title" xml:"title"` Category string `json:"category,omitempty" yaml:"Category,omitempty" xml:"category,omitempty"` Created IANADate `json:"created" yaml:"Created" xml:"created"` Updated *IANADate `json:"updated,omitempty" yaml:"Updated,omitempty" xml:"updated,omitempty"` Notice *IANARegistryData `json:"notice" yaml:"Notice" xml:"registry"` Footnotes []*IANARegistryFootnote `json:"footnotes,omitempty" yaml:"Footnotes,omitempty" xml:"footnote,omitempty"` } type IANAPrefix struct { // IANA may include multiple prefixes in the same record. Prefixes []*netip.Prefix `json:"prefix" yaml:"prefix" xml:"prefixes>prefix,attr"` References []*IANARef `json:"refs,omitempty" yaml:"References,omitempty" xml:"refs,omitempty"` } type IANAString struct { Text string `json:"text" yaml:"Text" xml:"text"` References []*IANARef `json:"refs" yaml:"References" xml:"references"` } type IANARegistryFootnote struct { XMLName xml.Name `json:"-" yaml:"-" xml:"footnote"` ReferenceIdx uint `json:"ref" yaml:"Reference Index/ID" xml:"anchor,attr"` Note *IANAString `json:"note" yaml:"Note" xml:"node"` } type IANARegistryData struct { XMLName xml.Name `json:"-" yaml:"-" xml:"registry"` ID string `json:"id" yaml:"ID" xml:"id,attr"` Title string `json:"title" yaml:"Title" xml:"title"` Refs []*IANARef `json:"refs" yaml:"Referencess" xml:"xref"` Rule string `json:"rule" yaml:"Registration Rule" xml:"registration_rule"` Note *IANAString `json:"note,omitempty" yaml:"Note,omitempty" xml:"note,omitempty"` Records []*IANAAddrNetResRecord `json:"records,omitempty" yaml:"Records,omitempty" xml:"record,omitempty"` } // IANARef is used to hold references to RFCs etc. type IANARef struct { XMLName xml.Name `json:"-" yaml:"-" xml:"xref"` Type string `json:"type" yaml:"Type" xml:"type,attr"` // TODO: This may have inner XML. Reference string `json:"ref" yaml:"Reference ID" xml:"data,attr"` } type IANABool struct { Applicable *bool `json:"applicable,omitempty" yaml:"Is Applicable,omitempty" xml:"applicable,attr,omitempty"` Evaluated *bool `json:"bool,omitempty" yaml:"As Boolean,omitempty" xml:"evaluated,attr,omitempty"` } type IANAAddrNetResRecord struct { XMLName xml.Name `json:"-" yaml:"-" xml:"record"` Updated *IANADate `json:"updated,omitempty" xml:"updated,omitempty"` Networks *IANAPrefix `json:"net,omitempty" yaml:"Address/Network,omitempty" xml:"address,omitempty"` Name string `json:"name" yaml:"Name" xml:"name"` // TODO: This has inner XML. Spec *IANAString `json:"spec" yaml:"Spec" xml:"spec"` Allocation IANADate `json:"alloc" yaml:"Allocation Month" xml:"allocation"` Termination *IANADate `json:"term,omitempty" yaml:"Termination,omitempty" xml:"termination,omitempty"` Source *IANABool `json:"source,omitempty" yaml:"Is Source,omitempty" xml:"source,omitempty"` Dest *IANABool `json:"dest,omitempty" yaml:"Is Destination,omitempty" xml:"dest,omitempty"` Forwardable *IANABool `json:"forwardable,omitempty" yaml:"Is Forwardable,omitempty" xml:"forwardable,omitempty"` GlobalReach *IANABool `json:"global,omitempty" yaml:"Is Globally Reachable,omitempty" xml:"global,omitempty"` ProtoReserved *IANABool `json:"reserved,omitempty" yaml:"Is Reserved by Protocol,omitempty" xml:"reserved,omitempty"` }