Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4ab83c9069 | ||
![]() |
a00442c204 | ||
![]() |
3c1bc832c0 | ||
![]() |
fd344f3b8e | ||
![]() |
3c239a4d09 | ||
![]() |
701b598b1c | ||
![]() |
32297d1bba | ||
![]() |
d37aa3eb6b |
8
ACKNOWLEDGEMENTS
Normal file
8
ACKNOWLEDGEMENTS
Normal file
@ -0,0 +1,8 @@
|
||||
The "IPv6 Segment Reference Diagram" output as rendered in the `table`
|
||||
subcommand is from:
|
||||
https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing
|
||||
as fetched on April 07, 2025.
|
||||
It is licensed under Creative Commons "CC BY-SA 4.0";
|
||||
see:
|
||||
https://creativecommons.org/licenses/by-sa/4.0/
|
||||
https://creativecommons.org/licenses/by-sa/4.0/legalcode.en)
|
@ -39,7 +39,7 @@ A tool to assist in design of segregate/segment/split/subnet networks.
|
||||
** For IPv4 addresses, it will be `true` if it is an APIPA (_Automatic Private IP Addressing_) address ({rfc}3927[RFC 3927^]) (in the `169.254.0.0/16` range).
|
||||
* `First` and `Last` refer to the first and last "usable" ("host"/assignable) addresses in a subnet/network.
|
||||
** Note that for IPv6, the first address (`x::`) in a subnet *may* or *may not* be assignable/"usable". If it is assigned to a device, that device *must* be a router for anycast. See {rfc}4291#section-2.6.1[RFC 4291 § 2.6.1^] for details. In the interest of convenience, `subnetter` will report this address as *not usable/addressable* in ranges for this reason as it is technically not a "host" address.
|
||||
** Note that for IPv6, some subnetting calculators erroneously report the last address for /64's (e.g. `x:ffff:ffff:ffff:ffff/64`) as usable. They are actually reserved in strictly RFC-compliant networks for EUI-64 reasons (per {rfc}2526[RFC 2526^]). For this reason, *if and only if* a prefix is a /64 *exactly*, `subnetter` will use `x:ffff:ffff:ffff:fffe` as the last host address.
|
||||
** Note that for IPv6, some subnetting calculators erroneously report the last address as usable. They are reserved in strictly RFC-compliant networks for anycast reasons (per {rfc}2526[RFC 2526^]). Subnetter follows RFC as closely as possible, and any deviation from RFC is considered a bug -- as such, the last address of IPv6 subnets is considered *not usable/addressable*.
|
||||
** There are additional restrictions for /64 subnets, but they fall earlier in the range. These are *not explicitly excluded* in the usable host range, nor are they excluded from the total host count.
|
||||
* Private networks ({rfc}1918[RFC 1918^]), ULA prefixes ({rfc}4193[RFC 4193^]), and documentation prefixes ({rfc}3849[RFC 3849^], {rfc}5737[RFC 5737^], {rfc}9637[RFC 9637^]) are treated as "normal" networks (in that it is allowed to subnet them).
|
||||
* Various other reserved IPv4 and IPv6 addresses/networks will print warnings with their corresponding RFC(s) (unless `-R`/`--allow-reserved` is specified) if they are specified as/included in the initial prefix/network. ({rfc}6890[RFC 6890^] and its update via {rfc}8190[RFC 8190^] are useful summaries.) Note that for checking to function, an Internet connection is required as it pulls it directly from IANA live to ensure the data is accurate to standards. This may be cached locally if `-c`/`--cache-reservations` is specified, in which case a locally-cached copy will be used if present and populated then used if not.
|
||||
@ -50,7 +50,7 @@ A tool to assist in design of segregate/segment/split/subnet networks.
|
||||
**** If the `XDG_CACHE_HOME` environment variable is not present...
|
||||
***** On macOS, an explicit fallback of `~/Library/Caches/subnetter/` will be used. (To my knowledge/understanding, this is the standard user cache directory and cannot be changed.) This usually evaluates to `/Users/<username>/Library/Caches/subnetter/`.
|
||||
***** On all others, an explicit fallback of `~/.cache/subnetter` will be used.
|
||||
****** On most non-macOS \*NIX-like systems , this is usually `/home/<username>/.cache/subetter/`, provided normal user homes. On http://p9f.org/[Plan9^] platforms (e.g. https://9p.io/plan9/index.html[Plan 9 4th Ed.^], https://9front.org/[9front^], http://9legacy.org/[9legacy^]), the `/env/home` environment variable (`$home`) will be used, the `./lib/` subdirectory under there (which typically/should already exist) will be appended to it, and that appended with `./cache/subnetter/` (this usually evaluates to `/usr/<username>/lib/cache/subnetter/`).
|
||||
****** On most non-macOS/*NIX-like systems , this is usually `/home/<username>/.cache/subetter/`, provided normal user homes. On http://p9f.org/[Plan9^] platforms (e.g. https://9p.io/plan9/index.html[Plan 9 4th Ed.^], https://9front.org/[9front^], http://9legacy.org/[9legacy^]), the `/env/home` environment variable (`$home`) will be used, the `./lib/` subdirectory under there (which typically/should already exist) will be appended to it, and that appended with `./cache/subnetter/` (this usually evaluates to `/usr/<username>/lib/cache/subnetter/`).
|
||||
*** For Windows systems...
|
||||
**** If https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#constants[the `LOCALAPPDATA` environment variable^] is present, it will be `%LOCALAPPDATA%\Cache\subnetter\` (or `${env:LOCALAPPDATA}\Cache\subnetter\` in Powershell syntax). This usually evaluates to `C:\Users\<username>\AppData\Local\Cache\subnetter\`.
|
||||
|
||||
@ -61,3 +61,5 @@ This program in general draws inspiration from `ipcalc` (http://jodies.de/ipcalc
|
||||
The `table` subcommand is inspired by `iptab` from https://metacpan.org/pod/Net::IP[Perl Net-IP^].
|
||||
|
||||
Additional notes for certain contexts are primarily taken from https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing[the Wikipedia article on _Classless Inter-Domain Routing_^] (as of _Jan 28, 2025_).
|
||||
|
||||
Reservations are pulled/cached directly from the IANA registries (https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml[IPv4^], https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml[IPv6^]).
|
6
TODO
6
TODO
@ -1 +1,5 @@
|
||||
- add table rendering for reserved networks?
|
||||
- add table rendering for reserved networks?
|
||||
|
||||
- when checking/rendering reserved networks, currently the footnotes aren't returned.
|
||||
-- netsplit.IANARegistryFootnote
|
||||
-- encapsulated in the IANARegistry.Footnotes
|
||||
|
@ -5,7 +5,7 @@
|
||||
{{- if $opts.Plain }}
|
||||
IPV4:
|
||||
{{- else }}
|
||||
{{- bold "IPv4:"}}
|
||||
{{- bold "IPv4:" }}
|
||||
{{- end }}
|
||||
{{- if $opts.Legacy }}
|
||||
{{- if $opts.Plain }}
|
||||
@ -47,13 +47,268 @@ IPV4:
|
||||
{{- end }}
|
||||
|
||||
{{- if not $opts.NoIpv6 }}
|
||||
|
||||
{{- if $opts.Plain }}
|
||||
IPV6:
|
||||
{{- else }}
|
||||
{{ bold "IPv6:"}}
|
||||
{{- end }}
|
||||
{{- if $opts.Plain }}
|
||||
IPV6:
|
||||
{{- else }}
|
||||
{{ bold "IPv6:"}}
|
||||
{{- end }}
|
||||
{{- if not $opts.NoIpv6Seg }}
|
||||
{{- if $opts.Plain }}
|
||||
IPv6 Segment Reference Diagram:
|
||||
{{- if $opts.VertSeg }}
|
||||
|
||||
Example: 2001:0db8:0123:4567:89ab:cdef:1234:5678
|
||||
{{- if not $opts.VertInvert }}
|
||||
2 4
|
||||
0 8
|
||||
0 12
|
||||
1 16
|
||||
:
|
||||
0 20
|
||||
d 24
|
||||
b 28
|
||||
8 32
|
||||
:
|
||||
0 36
|
||||
1 40
|
||||
2 44
|
||||
3 48
|
||||
:
|
||||
4 52
|
||||
5 56
|
||||
6 60
|
||||
7 64
|
||||
:
|
||||
8 68
|
||||
9 72
|
||||
a 76
|
||||
b 80
|
||||
:
|
||||
c 84
|
||||
d 88
|
||||
e 92
|
||||
f 96
|
||||
:
|
||||
1 100
|
||||
2 104
|
||||
3 108
|
||||
4 112
|
||||
:
|
||||
5 116
|
||||
6 120
|
||||
7 124
|
||||
8 127 or 128
|
||||
{{- else }}
|
||||
4 ____ 2
|
||||
8 ____ 0
|
||||
12 ___ 0
|
||||
16 ___ 1
|
||||
___ :
|
||||
20 _____ 0
|
||||
24 _____ d
|
||||
28 _____ b
|
||||
32 _____ 8
|
||||
_____ :
|
||||
36 _______ 0
|
||||
40 _______ 1
|
||||
44 _______ 2
|
||||
48 _______ 3
|
||||
_______ :
|
||||
52 _________ 4
|
||||
56 _________ 5
|
||||
60 _________ 6
|
||||
64 _________ 7
|
||||
_________ :
|
||||
68 ___________ 8
|
||||
72 ___________ 9
|
||||
76 ___________ a
|
||||
80 ___________ b
|
||||
___________ :
|
||||
84 _____________ c
|
||||
88 _____________ d
|
||||
92 _____________ e
|
||||
96 _____________ f
|
||||
_____________ :
|
||||
100 ______________ 1
|
||||
104 ______________ 2
|
||||
108 ______________ 3
|
||||
112 ______________ 4
|
||||
______________ :
|
||||
116 ________________ 5
|
||||
120 ________________ 6
|
||||
124 ________________ 7
|
||||
127 ________________ (8)
|
||||
128 ________________ (8)
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
|
||||
2001:0db8:0123:4567:89ab:cdef:1234:5678
|
||||
|||| |||| |||| |||| |||| |||| |||| ||||
|
||||
|||| |||| |||| |||| |||| |||| |||| |||128
|
||||
|||| |||| |||| |||| |||| |||| |||| |||127
|
||||
|||| |||| |||| |||| |||| |||| |||| ||124
|
||||
|||| |||| |||| |||| |||| |||| |||| |120
|
||||
|||| |||| |||| |||| |||| |||| |||| 116
|
||||
|||| |||| |||| |||| |||| |||| |||112
|
||||
|||| |||| |||| |||| |||| |||| ||108
|
||||
|||| |||| |||| |||| |||| |||| |104
|
||||
|||| |||| |||| |||| |||| |||| 100
|
||||
|||| |||| |||| |||| |||| |||96
|
||||
|||| |||| |||| |||| |||| ||92
|
||||
|||| |||| |||| |||| |||| |88
|
||||
|||| |||| |||| |||| |||| 84
|
||||
|||| |||| |||| |||| |||80
|
||||
|||| |||| |||| |||| ||76
|
||||
|||| |||| |||| |||| |72
|
||||
|||| |||| |||| |||| 68
|
||||
|||| |||| |||| |||64
|
||||
|||| |||| |||| ||60
|
||||
|||| |||| |||| |56
|
||||
|||| |||| |||| 52
|
||||
|||| |||| |||48
|
||||
|||| |||| ||44
|
||||
|||| |||| |40
|
||||
|||| |||| 36
|
||||
|||| |||32
|
||||
|||| ||28
|
||||
|||| |24
|
||||
|||| 20
|
||||
|||16
|
||||
||12
|
||||
|8
|
||||
4
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{ bold "IPv6 Segment Reference Diagram:" }}
|
||||
{{- if $opts.VertSeg }}
|
||||
|
||||
{{ bold "Example:"}} 2001:0db8:0123:4567:89ab:cdef:1234:5678
|
||||
{{- if not $opts.VertInvert }}
|
||||
2 4
|
||||
0 8
|
||||
0 12
|
||||
1 16
|
||||
:
|
||||
0 20
|
||||
d 24
|
||||
b 28
|
||||
8 32
|
||||
:
|
||||
0 36
|
||||
1 40
|
||||
2 44
|
||||
3 48
|
||||
:
|
||||
4 52
|
||||
5 56
|
||||
6 60
|
||||
7 64
|
||||
:
|
||||
8 68
|
||||
9 72
|
||||
a 76
|
||||
b 80
|
||||
:
|
||||
c 84
|
||||
d 88
|
||||
e 92
|
||||
f 96
|
||||
:
|
||||
1 100
|
||||
2 104
|
||||
3 108
|
||||
4 112
|
||||
:
|
||||
5 116
|
||||
6 120
|
||||
7 124
|
||||
8 127 or 128
|
||||
{{- else }}
|
||||
4 ━━━━ 2
|
||||
8 ━━━━ 0
|
||||
12 ━━━ 0
|
||||
16 ━━━ 1
|
||||
━━━ :
|
||||
20 ━━━━━ 0
|
||||
24 ━━━━━ d
|
||||
28 ━━━━━ b
|
||||
32 ━━━━━ 8
|
||||
━━━━━ :
|
||||
36 ━━━━━━━ 0
|
||||
40 ━━━━━━━ 1
|
||||
44 ━━━━━━━ 2
|
||||
48 ━━━━━━━ 3
|
||||
━━━━━━━ :
|
||||
52 ━━━━━━━━━ 4
|
||||
56 ━━━━━━━━━ 5
|
||||
60 ━━━━━━━━━ 6
|
||||
64 ━━━━━━━━━ 7
|
||||
━━━━━━━━━ :
|
||||
68 ━━━━━━━━━━━ 8
|
||||
72 ━━━━━━━━━━━ 9
|
||||
76 ━━━━━━━━━━━ a
|
||||
80 ━━━━━━━━━━━ b
|
||||
━━━━━━━━━━━ :
|
||||
84 ━━━━━━━━━━━━━ c
|
||||
88 ━━━━━━━━━━━━━ d
|
||||
92 ━━━━━━━━━━━━━ e
|
||||
96 ━━━━━━━━━━━━━ f
|
||||
━━━━━━━━━━━━━ :
|
||||
100 ━━━━━━━━━━━━━━ 1
|
||||
104 ━━━━━━━━━━━━━━ 2
|
||||
108 ━━━━━━━━━━━━━━ 3
|
||||
112 ━━━━━━━━━━━━━━ 4
|
||||
━━━━━━━━━━━━━━ :
|
||||
116 ━━━━━━━━━━━━━━━━ 5
|
||||
120 ━━━━━━━━━━━━━━━━ 6
|
||||
124 ━━━━━━━━━━━━━━━━ 7
|
||||
127 ━━━━━━━━━━━━━━━━ (8)
|
||||
128 ━━━━━━━━━━━━━━━━ (8)
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
|
||||
{{ bold "2001:0db8:0123:4567:89ab:cdef:1234:5678" }}
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃128
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃127
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃124
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃120
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ 116
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃112
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃108
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃104
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ 100
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃96
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃92
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃88
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ 84
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃80
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃76
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃72
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃┃ 68
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃┃64
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃┃60
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ ┃56
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃┃ 52
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃┃48
|
||||
┃┃┃┃ ┃┃┃┃ ┃┃44
|
||||
┃┃┃┃ ┃┃┃┃ ┃40
|
||||
┃┃┃┃ ┃┃┃┃ 36
|
||||
┃┃┃┃ ┃┃┃32
|
||||
┃┃┃┃ ┃┃28
|
||||
┃┃┃┃ ┃24
|
||||
┃┃┃┃ 20
|
||||
┃┃┃16
|
||||
┃┃12
|
||||
┃8
|
||||
4
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
|
||||
{{- if $opts.Plain }}
|
||||
|
||||
CIDR:
|
||||
{{- else }}
|
||||
{{ bold "CIDR:" }}
|
||||
|
@ -2,11 +2,12 @@ package main
|
||||
|
||||
type Args struct {
|
||||
Version verArgs `command:"version" alias:"v" description:"Show version information." validate:"omitempty"`
|
||||
SplitCIDR SplitCIDRArgs `command:"split-cidr" alias:"se" description:"Split a network into as many equal subnets of prefix size N as possible." validate:"omitempty"`
|
||||
SplitHost SplitHostArgs `command:"split-hosts" alias:"sh" description:"Split a network into N total number of hosts *per subnet* as cleanly/evenly as possible. (VERY easy to run out of memory for IPv6 prefixes; be sure to specify very small network!)" validate:"omitempty"`
|
||||
SplitCIDR SplitCIDRArgs `command:"split-cidr" alias:"sc" description:"Split a network into as many equal subnets of prefix size N as possible." validate:"omitempty"`
|
||||
SplitHost SplitHostArgs `command:"split-hosts" alias:"sh" description:"Split a network into N total number of hosts *per subnet* as cleanly/evenly as possible." validate:"omitempty"`
|
||||
SplitSubnets SplitSubnetArgs `command:"split-nets" alias:"sn" description:"Split a network into N number of subnets as cleanly as possible." validate:"omitempty"`
|
||||
VLSM VLSMArgs `command:"vlsm" alias:"sv" description:"Use VLSM (Variable-Length Subnet Masks) to split a network into differently sized subnets." validate:"omitempty"`
|
||||
ExplicitNetwork XNetArgs `command:"net" alias:"xn" description:"Print information about an explicit network address." validate:"omitempty"`
|
||||
VLSM VLSMArgs `command:"split-vlsm" alias:"sv" alias:"vlsm" description:"Use VLSM (Variable-Length Subnet Masks) to split a network into differently sized subnets." validate:"omitempty"`
|
||||
ExplicitNetwork XNetArgs `command:"net" alias:"xn" alias:"net" description:"Print information about an explicit network address." validate:"omitempty"`
|
||||
NumNets NNetArgs `command:"num-nets" alias:"nn" alias:"nets" description:"Return the number of subnets of a given size that can fit into a given network size. This is MUCH, MUCH FASTER than splitting (if you do not need addressing)." validate:"omitempty"`
|
||||
Parse ParseArgs `command:"parse" alias:"p" alias:"read" alias:"convert" description:"Parse/convert output from a previous subnetter run." validate:"omitempty"`
|
||||
Table TableArgs `command:"table" alias:"t" alias:"tab" alias:"tbl" description:"Show prefix summaries (by default both IPv4 and IPv6)." validate:"omitempty"`
|
||||
Check CheckArgs `command:"reserved" alias:"r" description:"Check if a subnet is reserved per IANA/RFC." validate:"omitempty"`
|
||||
@ -16,7 +17,7 @@ type verArgs struct {
|
||||
DetailVersion bool `short:"V" long:"detail" description:"Print detailed version info and exit."`
|
||||
}
|
||||
|
||||
type common struct {
|
||||
type commonBase struct {
|
||||
cacheArgs
|
||||
SuppressRemaining bool `short:"r" long:"no-remaining" description:"Don't show leftover/unallocated/remaining space."`
|
||||
Plain bool `short:"p" long:"plain" description:"Show plain output instead of unicode (only used if -f/--format=pretty)."`
|
||||
@ -26,13 +27,17 @@ type common struct {
|
||||
AllowReserved bool `short:"R" long:"allow-reserved" description:"If specified, do not warn about reserved IP addresses/networks."`
|
||||
reservedArgs
|
||||
AllowHostNet bool `short:"H" long:"allow-host" description:"If specified, do not warn about host bits. Host bits are always removed for subnetting (as otherwise there would be errors); this is only used only for output."`
|
||||
Network Net `positional-args:"yes" required:"true" description:"The network/parent prefix to operate on." validate:"required"`
|
||||
}
|
||||
|
||||
type common struct {
|
||||
commonBase
|
||||
Network Net `positional-args:"yes" required:"true" description:"The network/parent prefix to operate on." validate:"required"`
|
||||
}
|
||||
|
||||
type reservedArgs struct {
|
||||
NoRecursive bool `short:"u" long:"no-recursive" description:"Don't show reservations that are children subnets of the subnet(s). Only if -f/--format=pretty, always false for other formats."`
|
||||
NoRevRecursive bool `short:"U" long:"no-rev-recursive" description:"Don't show reservations that are parents of the subnet(s). Only if -f/--format=pretty, always false for other formats."`
|
||||
NoPrivate bool `short:"e" long:"no-private" description:"Consider private subnets of the subnet(s) to be reserved. If you are subnetting private address space, you probably want to leave this disabled. Only if -f/--format=pretty, always true otherwise."`
|
||||
NoRevRecursive bool `short:"U" long:"no-rev-recursive" description:"Don't show reservations that are parents of the subnet(s) -- you almost definitely don't want to suppress this. Only if -f/--format=pretty, always false for other formats."`
|
||||
NoPrivate bool `short:"e" long:"no-private" description:"Consider private subnets to be reserved. If you are subnetting private address space, you probably want to leave this disabled. Only if -f/--format=pretty, always true otherwise."`
|
||||
}
|
||||
|
||||
type splitArgs struct {
|
||||
@ -44,8 +49,17 @@ type cacheArgs struct {
|
||||
DoResCache bool `short:"c" long:"cache-reservations" env:"SBNTR_RSVCACHE" description:"Enable caching/cache lookup for reservation data."`
|
||||
}
|
||||
|
||||
type NNetArgs struct {
|
||||
Verbose bool `short:"v" long:"verbose" description:"Be verbose (more ideal for logging)."`
|
||||
NoV6Check bool `short:"6" long:"no-v6" description:"If specified, do not indicate if the subnetting is IPv6 only (true) or not (false; dual-stack/IPv4 supported)."`
|
||||
Sizes struct {
|
||||
SubnetSize uint8 `positional-arg-name:"<subnet prefix>" required:"1" validate:"lte=128,gtefield=NetworkSize"`
|
||||
NetworkSize uint8 `positional-arg-name:"<parent prefix>" required:"1" validate:"lte=128,ltefield=SubnetSize"`
|
||||
} `positional-args:"yes" required:"2" validate:"required"`
|
||||
}
|
||||
|
||||
type ParseArgs struct {
|
||||
splitArgs
|
||||
commonBase
|
||||
InFile string `short:"i" long:"input" default:"-" description:"Input file to parse. Default is '-' (for STDIN)." required:"true" validate:"required,filepath|eq=-"`
|
||||
}
|
||||
|
||||
@ -55,8 +69,10 @@ type SplitCIDRArgs struct {
|
||||
}
|
||||
|
||||
type SplitHostArgs struct {
|
||||
Strict bool `short:"t" long:"strict" description:"If specified, an error will occur if the number of hosts/assignable addresses in a subnet is not exactly -n/--num-hosts."`
|
||||
Hosts uint `short:"n" long:"num-hosts" required:"true" description:"Number of hosts (usable addresses) per subnet." validate:"required"`
|
||||
InclNetAddr bool `short:"N" long:"incl-net" description:"If specified, -n/--num-hosts is interpreted to include the network address in the count."`
|
||||
InclBcastAddr bool `short:"B" long:"incl-bcast" description:"If specified, -n/--num-hosts is interpreted to include the broadcast/reserved broadcast address in the count."`
|
||||
Strict bool `short:"t" long:"strict" description:"If specified, an error will occur if the number of hosts/assignable addresses in a subnet is not exactly -n/--num-hosts."`
|
||||
Hosts uint `short:"n" long:"num-hosts" required:"true" description:"Number of hosts (usable addresses) per subnet." validate:"required"`
|
||||
splitArgs
|
||||
}
|
||||
|
||||
@ -67,12 +83,15 @@ type SplitSubnetArgs struct {
|
||||
}
|
||||
|
||||
type TableArgs struct {
|
||||
Notes bool `short:"n" long:"notes" description:"Include notes about prefixes (as a separate table)."`
|
||||
Legacy bool `short:"l" long:"legacy" description:"Include legacy/obsolete/deprecated information (as separate table(s))."`
|
||||
Plain bool `short:"p" long:"plain" description:"Show plain table output instead of unicode."`
|
||||
NoV4Mask bool `short:"M" long:"no-mask" description:"Do not include netmasks for IPv4."`
|
||||
NoIpv6 bool `short:"4" long:"ipv4" description:"Only show IPv4 table(s)."`
|
||||
NoIpv4 bool `short:"6" long:"ipv6" description:"Only show IPv6 table(s)."`
|
||||
Notes bool `short:"n" long:"notes" description:"Include notes about prefixes (as a separate table)."`
|
||||
Legacy bool `short:"l" long:"legacy" description:"Include legacy/obsolete/deprecated information (as separate table(s))."`
|
||||
Plain bool `short:"p" long:"plain" description:"Show plain table output instead of unicode."`
|
||||
NoV4Mask bool `short:"M" long:"no-mask" description:"Do not include netmasks for IPv4."`
|
||||
NoIpv6 bool `short:"4" long:"ipv4" description:"Only show IPv4 table(s)."`
|
||||
NoIpv4 bool `short:"6" long:"ipv6" description:"Only show IPv6 table(s)."`
|
||||
NoIpv6Seg bool `short:"D" long:"no-ipv6-seg" description:"Do not show the IPv6 Segment Reference Diagram (ignored if -4/--ipv4 is specified)."`
|
||||
VertSeg bool `short:"V" long:"vert-ipv6-seg" description:"If specified, display the IPv6 Segment Reference Diagram vertically-aligned instead of horizontally."`
|
||||
VertInvert bool `short:"I" long:"vert-invert" description:"When printing a vertical-aligned IPv6 Segment Reference Diagram, flip so the prefix length is on the left. This takes up less width and is recommended for smaller terminals, and may be easier to read in general."`
|
||||
}
|
||||
|
||||
type CheckArgs struct {
|
||||
@ -88,9 +107,11 @@ type XNetArgs struct {
|
||||
}
|
||||
|
||||
type VLSMArgs struct {
|
||||
Asc bool `short:"A" long:"ascending" description:"If specified, place smaller networks (larger prefixes) at the beginning. You almost assuredly do not want to do this."`
|
||||
Explicit bool `short:"O" long:"explicit-order" description:"If specified, ignore -A/--ascending and do no reordering of prefix sizes whatsoever, instead using the order given. This is EXTREMELY suboptimal and can lead to drastic addressing waste."`
|
||||
Sizes []uint8 `short:"s" long:"size" required:"true" description:"Prefix lengths. May be specified multiple times." validate:"required"`
|
||||
Asc bool `short:"A" long:"ascending" description:"If specified, place smaller networks (larger prefixes) at the beginning. You almost assuredly do not want to do this."`
|
||||
Explicit bool `short:"O" long:"explicit-order" description:"If specified, ignore -A/--ascending and do no reordering of prefix sizes whatsoever, instead using the order given. This is EXTREMELY suboptimal and can lead to drastic addressing waste."`
|
||||
// Custom type for now; see https://github.com/jessevdk/go-flags/issues/245
|
||||
// Sizes []uint8 `short:"s" long:"size" required:"true" description:"Prefix lengths. May be specified multiple times." validate:"required"`
|
||||
Sizes []vlsmSize `short:"s" long:"size" required:"true" description:"Prefix lengths. May be specified multiple times or as a comma-delimited list." validate:"required"`
|
||||
splitArgs
|
||||
}
|
||||
|
||||
|
@ -5,5 +5,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadFmt error = errors.New("unknown output format")
|
||||
errBadNet error = errors.New("bad inet/addr family/version")
|
||||
)
|
||||
|
@ -236,7 +236,9 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
|
||||
|
||||
if args == nil {
|
||||
args = &common{
|
||||
Separator: "\n",
|
||||
commonBase: commonBase{
|
||||
Separator: "\n",
|
||||
},
|
||||
}
|
||||
}
|
||||
fmts = sectFmts[args.Plain]
|
||||
@ -399,7 +401,9 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
|
||||
|
||||
// Remaining
|
||||
if !args.SuppressRemaining {
|
||||
if verb >= 1 {
|
||||
if verb <= 0 {
|
||||
fmt.Println("#")
|
||||
} else {
|
||||
fmt.Println()
|
||||
fmt.Println(sectSep1)
|
||||
fmt.Println("Remaining/Left Over/Unallocated:")
|
||||
@ -471,7 +475,180 @@ func printNets(orig *netip.Prefix, origNet *net.IPNet, nets []*netip.Prefix, rem
|
||||
return
|
||||
}
|
||||
|
||||
func printReserved(nets []*netip.Prefix, remaining *netipx.IPSet, args *common) (err error) {
|
||||
func printReserved(records map[netip.Prefix]*netsplit.IANAAddrNetResRecord, origNet netip.Prefix, plain bool, fmtType string) (err error) {
|
||||
|
||||
var b []byte
|
||||
var idx int
|
||||
var pfx netip.Prefix
|
||||
var rec *netsplit.IANAAddrNetResRecord
|
||||
var sortedKeys []netip.Prefix
|
||||
var sb = new(strings.Builder)
|
||||
|
||||
switch fmtType {
|
||||
case "json":
|
||||
if b, err = json.MarshalIndent(records, "", " "); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return
|
||||
case "xml":
|
||||
if b, err = xml.MarshalIndent(records, "", " "); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return
|
||||
case "yml", "yaml":
|
||||
if b, err = yaml.Marshal(records); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(string(b))
|
||||
return
|
||||
}
|
||||
|
||||
if fmtType != "pretty" {
|
||||
err = ErrBadFmt
|
||||
return
|
||||
}
|
||||
|
||||
if records == nil || len(records) == 0 {
|
||||
fmt.Println("No IANA/IETF/RFC-reserved subnet(s) found.")
|
||||
return
|
||||
}
|
||||
|
||||
sortedKeys = make([]netip.Prefix, len(records))
|
||||
idx = 0
|
||||
for pfx, _ = range records {
|
||||
sortedKeys[idx] = pfx
|
||||
idx++
|
||||
}
|
||||
sort.SliceStable(
|
||||
sortedKeys,
|
||||
func(i, j int) (isBefore bool) {
|
||||
isBefore = (netipx.ComparePrefix(sortedKeys[i], sortedKeys[j])) <= 0
|
||||
return
|
||||
},
|
||||
)
|
||||
|
||||
fmt.Fprintf(sb, "= %s =\n", origNet.String())
|
||||
for _, pfx = range sortedKeys {
|
||||
rec = records[pfx]
|
||||
fmt.Fprint(sb, sectFmts[plain][0]+"\n")
|
||||
// Name
|
||||
fmt.Fprintf(sb, "Reservation Name:\t%s\n", rec.Name)
|
||||
fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Networks
|
||||
fmt.Fprint(sb, "\tCanonical Reserved Networks:")
|
||||
if rec.Networks != nil {
|
||||
fmt.Fprint(sb, "\n")
|
||||
for _, recPfx := range rec.Networks.Prefixes {
|
||||
fmt.Fprint(sb, "\t\t"+sectFmts[plain][2]+"\n")
|
||||
fmt.Fprintf(sb, "\t\t%s\n", recPfx.String())
|
||||
// TODO: Print footnotes/refs!
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(sb, "\t(N/A)\n")
|
||||
}
|
||||
fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Reference/Specification
|
||||
fmt.Fprint(sb, "\tSpecification:")
|
||||
if rec.Spec != nil {
|
||||
fmt.Fprint(sb, "\n")
|
||||
for _, line := range strings.Split(rec.Spec.Text, "\n") {
|
||||
fmt.Fprint(sb, "\t\t"+line+"\n")
|
||||
}
|
||||
if rec.Spec.References != nil {
|
||||
fmt.Fprintf(sb, "\t\t%s\n", sectFmts[plain][2])
|
||||
for rIdx, recref := range rec.Spec.References {
|
||||
if recref != nil {
|
||||
fmt.Fprintf(sb, "\t\t[%d] (%s) %s\n", rIdx, recref.Type, recref.Reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(sb, "\t(None)\n")
|
||||
}
|
||||
fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Allocated (always present)
|
||||
fmt.Fprintf(sb, "\tAllocated:\t%s\n", time.Time(rec.Allocation).String())
|
||||
// fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Updated
|
||||
fmt.Fprint(sb, "\tUpdated:\t")
|
||||
if rec.Updated != nil {
|
||||
fmt.Fprintf(sb, "%s\n", time.Time(*rec.Updated).String())
|
||||
} else {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
}
|
||||
// fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Termination
|
||||
fmt.Fprint(sb, "\tTerminated:\t")
|
||||
if rec.Termination != nil {
|
||||
fmt.Fprintf(sb, "%s\n", time.Time(*rec.Termination).String())
|
||||
} else {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
}
|
||||
fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Source
|
||||
fmt.Fprint(sb, "\tValid Source:\t\t\t")
|
||||
if rec.Source != nil {
|
||||
if rec.Source.Applicable != nil && !bool(*rec.Source.Applicable) {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%v\n", bool(*rec.Source.Evaluated))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
}
|
||||
// fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Destination
|
||||
fmt.Fprint(sb, "\tValid Destination:\t\t")
|
||||
if rec.Dest != nil {
|
||||
if rec.Dest.Applicable != nil && !bool(*rec.Dest.Applicable) {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%v\n", bool(*rec.Dest.Evaluated))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
}
|
||||
// fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Forwardable
|
||||
fmt.Fprint(sb, "\tForwardable:\t\t\t")
|
||||
if rec.Forwardable != nil {
|
||||
if rec.Forwardable.Applicable != nil && !bool(*rec.Forwardable.Applicable) {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%v\n", bool(*rec.Forwardable.Evaluated))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
}
|
||||
// fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Globally reachable
|
||||
fmt.Fprint(sb, "\tGlobally Routable/Reachable:\t")
|
||||
if rec.GlobalReach != nil {
|
||||
if rec.GlobalReach.Applicable != nil && !bool(*rec.GlobalReach.Applicable) {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%v\n", bool(*rec.GlobalReach.Evaluated))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
}
|
||||
// fmt.Fprint(sb, "\t"+sectFmts[plain][1]+"\n")
|
||||
// Reserved by Protocol
|
||||
fmt.Fprint(sb, "\tReserved by Protocol:\t\t")
|
||||
if rec.ProtoReserved != nil {
|
||||
if rec.ProtoReserved.Applicable != nil && !bool(*rec.ProtoReserved.Applicable) {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
} else {
|
||||
fmt.Fprintf(sb, "%v\n", bool(*rec.ProtoReserved.Evaluated))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(sb, "(N/A)\n")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print(sb.String())
|
||||
|
||||
return
|
||||
}
|
||||
@ -485,7 +662,7 @@ func printSplitErr(e *netsplit.SplitErr) {
|
||||
os.Stderr.WriteString("\n!! ERROR !!!\n")
|
||||
|
||||
os.Stderr.WriteString("\t" + e.Wrapped.Error() + "\n")
|
||||
os.Stderr.WriteString("\nnetwork Iteration Details\n(when error was encountered):\n\n")
|
||||
os.Stderr.WriteString("\nNetwork Iteration Details\n(when error was encountered):\n\n")
|
||||
if e.Nets == nil {
|
||||
os.Stderr.WriteString("Nets:\t\t\t(N/A)\n")
|
||||
} else {
|
||||
@ -494,7 +671,7 @@ func printSplitErr(e *netsplit.SplitErr) {
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n", n.String())
|
||||
}
|
||||
}
|
||||
if e.Remaining == nil {
|
||||
if e.Remaining == nil || e.Remaining.Prefixes() == nil || len(e.Remaining.Prefixes()) == 0 {
|
||||
os.Stderr.WriteString("Remaining:\t\t(N/A)\n")
|
||||
} else {
|
||||
os.Stderr.WriteString("Remaining:\n")
|
||||
@ -503,7 +680,7 @@ func printSplitErr(e *netsplit.SplitErr) {
|
||||
}
|
||||
}
|
||||
if e.LastSubnet == nil {
|
||||
os.Stderr.WriteString("Last Subnet:\t\t(N/A)")
|
||||
os.Stderr.WriteString("Last Subnet:\t\t(N/A)\n")
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Last Subnet:\t\t%s\n", e.LastSubnet.String())
|
||||
}
|
||||
|
26
cmd/subnetter/funcs_vlsmargs.go
Normal file
26
cmd/subnetter/funcs_vlsmargs.go
Normal file
@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
AllSizes returns a properly parsed and consolidated slice
|
||||
of all specified sizes, as it takes two valid syntaxes (`-s 32 -s 32`, `-s 32,32`)
|
||||
that can be mixed together.
|
||||
*/
|
||||
func (v *VLSMArgs) AllSizes() (sizes []uint8, err error) {
|
||||
|
||||
var sizeSlice []uint8
|
||||
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if v.Sizes == nil || len(v.Sizes) == 0 {
|
||||
return
|
||||
}
|
||||
for _, s := range v.Sizes {
|
||||
if sizeSlice, err = s.Sizes(); err != nil {
|
||||
return
|
||||
}
|
||||
sizes = append(sizes, sizeSlice...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
33
cmd/subnetter/funcs_vlsmsize.go
Normal file
33
cmd/subnetter/funcs_vlsmsize.go
Normal file
@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
`strconv`
|
||||
`strings`
|
||||
)
|
||||
|
||||
// Sizes returns a parsed/split slice of uint8s from a vlsmSize.
|
||||
func (v *vlsmSize) Sizes() (sizes []uint8, err error) {
|
||||
|
||||
var s []string
|
||||
var u uint64
|
||||
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
s = strings.Split(string(*v), ",")
|
||||
for idx, i := range s {
|
||||
s[idx] = strings.TrimSpace(i)
|
||||
}
|
||||
|
||||
sizes = make([]uint8, len(s))
|
||||
|
||||
// No validation is performed since we don't have access to the addr inet family; that's up to the parsers.
|
||||
for idx, i := range s {
|
||||
if u, err = strconv.ParseUint(i, 10, 8); err != nil {
|
||||
return
|
||||
}
|
||||
sizes[idx] = uint8(u)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -26,14 +26,18 @@ func main() {
|
||||
var pfx *net.IPNet
|
||||
var resPfx *netip.Prefix
|
||||
var origPfx netip.Prefix
|
||||
var vlsmSizes []uint8
|
||||
var splitter netsplit.NetSplitter
|
||||
var cmnArgs common
|
||||
var nets []*netip.Prefix
|
||||
var remaining *netipx.IPSet
|
||||
var buf *bytes.Buffer
|
||||
var res *netsplit.StructuredResults
|
||||
var numNets uint
|
||||
var v6Only bool
|
||||
var noStrict bool
|
||||
var strictErr error
|
||||
var reservations map[netip.Prefix]*netsplit.IANAAddrNetResRecord
|
||||
var splitErr *netsplit.SplitErr = new(netsplit.SplitErr)
|
||||
var parser *flags.Parser = flags.NewParser(args, flags.Default)
|
||||
|
||||
@ -65,6 +69,9 @@ func main() {
|
||||
return
|
||||
}
|
||||
case "net":
|
||||
if err = validate.Struct(args.ExplicitNetwork); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if origPfx, err = netip.ParsePrefix(args.ExplicitNetwork.Network.Network); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
@ -77,9 +84,57 @@ func main() {
|
||||
log.Panicln(err)
|
||||
}
|
||||
return
|
||||
case "num-nets":
|
||||
if err = validate.Struct(args.NumNets); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if numNets, v6Only, err = netsplit.NumNets(
|
||||
args.NumNets.Sizes.SubnetSize,
|
||||
args.NumNets.Sizes.NetworkSize,
|
||||
); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if !args.NumNets.Verbose {
|
||||
fmt.Printf("%d\n", numNets)
|
||||
if !args.NumNets.NoV6Check {
|
||||
fmt.Println(v6Only)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Network Size:\t\t\t%d\n", args.NumNets.Sizes.NetworkSize)
|
||||
fmt.Printf("Subnet Size:\t\t\t%d\n", args.NumNets.Sizes.SubnetSize)
|
||||
fmt.Printf("Number of Subnets:\t\t%d\n", numNets)
|
||||
if !args.NumNets.NoV6Check {
|
||||
fmt.Printf("Subnetting is IPv6-Only:\t%v\n", v6Only)
|
||||
}
|
||||
}
|
||||
return
|
||||
case "reserved":
|
||||
// TODO
|
||||
if err = validate.Struct(args.Check); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if origPfx, err = netip.ParsePrefix(args.Check.Network.Network); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
nets = make([]*netip.Prefix, 1)
|
||||
nets[0] = new(netip.Prefix)
|
||||
*nets[0] = origPfx
|
||||
if err = netsplit.SetCachePath(args.Check.CacheDir); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err = netsplit.EnableCache(args.Check.DoResCache); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if reservations, err = netsplit.CheckReserved(nets, !args.Check.NoRevRecursive, !args.Check.NoRecursive, !args.Check.NoPrivate); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if err = printReserved(reservations, origPfx, args.Check.Plain, args.Check.Fmt); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
return
|
||||
case "table":
|
||||
if err = validate.Struct(args.Table); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
// Account for a weird redundant CLI condition.
|
||||
if args.Table.NoIpv4 && args.Table.NoIpv6 {
|
||||
args.Table.NoIpv6 = false
|
||||
@ -92,6 +147,9 @@ func main() {
|
||||
os.Stdout.Write(buf.Bytes())
|
||||
return
|
||||
case "parse":
|
||||
if err = validate.Struct(args.Parse); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
if strings.TrimSpace(args.Parse.InFile) == "-" {
|
||||
buf = new(bytes.Buffer)
|
||||
if _, err = io.Copy(buf, os.Stdin); err != nil {
|
||||
@ -116,7 +174,12 @@ func main() {
|
||||
origPfx = *resPfx
|
||||
}
|
||||
pfx = netipx.PrefixIPNet(origPfx.Masked())
|
||||
cmnArgs = args.Parse.common
|
||||
cmnArgs = common{
|
||||
commonBase: args.Parse.commonBase,
|
||||
Network: Net{
|
||||
Network: res.Original.String(),
|
||||
},
|
||||
}
|
||||
if err = printNets(&origPfx, pfx, nets, remaining, &cmnArgs, res.GetSplitter()); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
@ -138,9 +201,11 @@ func main() {
|
||||
}
|
||||
cmnArgs = args.SplitHost.common
|
||||
splitter = &netsplit.HostSplitter{
|
||||
NumberHosts: args.SplitHost.Hosts,
|
||||
Strict: args.SplitHost.Strict,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
InclNetAddr: args.SplitHost.InclNetAddr,
|
||||
InclBcastAddr: args.SplitHost.InclBcastAddr,
|
||||
NumberHosts: args.SplitHost.Hosts,
|
||||
Strict: args.SplitHost.Strict,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
}
|
||||
noStrict = !args.SplitHost.Strict
|
||||
strictErr = netsplit.ErrBadNumHosts
|
||||
@ -165,15 +230,18 @@ func main() {
|
||||
PrefixLength: args.SplitCIDR.Prefix,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
}
|
||||
case "vlsm":
|
||||
case "split-vlsm":
|
||||
if err = validate.Struct(args.VLSM); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
cmnArgs = args.VLSM.common
|
||||
if vlsmSizes, err = args.VLSM.AllSizes(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
splitter = &netsplit.VLSMSplitter{
|
||||
Ascending: args.VLSM.Asc,
|
||||
Explicit: args.VLSM.Explicit,
|
||||
PrefixLengths: args.VLSM.Sizes,
|
||||
PrefixLengths: vlsmSizes,
|
||||
BaseSplitter: new(netsplit.BaseSplitter),
|
||||
}
|
||||
default:
|
||||
|
@ -96,6 +96,9 @@ type tableFormatter struct {
|
||||
NoBoldTitle bool
|
||||
}
|
||||
|
||||
// vlsmSize is a custom type to let us specify multiple sizes as a repeated or consolidated argument.
|
||||
type vlsmSize string
|
||||
|
||||
type ReservedResults struct {
|
||||
Opts CheckArgs
|
||||
Reserved map[netip.Prefix]*netsplit.IANAAddrNetResRecord
|
||||
|
@ -8,6 +8,12 @@ import (
|
||||
`github.com/go-resty/resty/v2`
|
||||
)
|
||||
|
||||
const (
|
||||
maxBitsv4 uint8 = 32
|
||||
maxBitsv6 uint8 = 128
|
||||
maxBits uint8 = maxBitsv6
|
||||
)
|
||||
|
||||
const (
|
||||
cachedirEnvName string = "SBNTR_RSVCACHE_DIR"
|
||||
// https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
@ -150,19 +152,19 @@ func AddrInvert(ip netip.Addr) (inverted netip.Addr) {
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
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.
|
||||
Any found will be returned in reservations.
|
||||
|
||||
If no reserved networks are found, reservations will be nil.
|
||||
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.
|
||||
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) {
|
||||
|
||||
@ -191,25 +193,26 @@ func CheckReserved(nets []*netip.Prefix, revRecursive, recursive, excludePrivate
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
if recursive && n.Bits() < p.Bits() && n.Contains(p.Addr()) {
|
||||
if reservations == nil {
|
||||
reservations = make(map[netip.Prefix]*IANAAddrNetResRecord)
|
||||
}
|
||||
reservations[p] = r
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -221,7 +224,7 @@ func CheckReserved(nets []*netip.Prefix, revRecursive, recursive, excludePrivate
|
||||
func Contain(origPfx *netip.Prefix, nets []*netip.Prefix, remaining *netipx.IPSet, splitter NetSplitter) (s *StructuredResults, err error) {
|
||||
|
||||
var rem []netip.Prefix
|
||||
var reserved map[netip.Prefix]*IANAAddrNetResRecord
|
||||
// var reserved map[netip.Prefix]*IANAAddrNetResRecord
|
||||
var sr = StructuredResults{
|
||||
Original: origPfx,
|
||||
}
|
||||
@ -274,19 +277,21 @@ func Contain(origPfx *netip.Prefix, nets []*netip.Prefix, remaining *netipx.IPSe
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
idx := 0
|
||||
for _, r := range reserved {
|
||||
s.Reservations[idx] = r
|
||||
idx++
|
||||
/*
|
||||
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))
|
||||
idx := 0
|
||||
for _, r := range reserved {
|
||||
s.Reservations[idx] = r
|
||||
idx++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
s = &sr
|
||||
|
||||
@ -375,6 +380,136 @@ func MaskInvert(mask net.IPMask) (inverted net.IPMask) {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
NumAddrsIn returns the number of addresses in a given prefix length
|
||||
and inet family.
|
||||
|
||||
If isIpv6 is false, it is assumed to be IPv4 (...duh).
|
||||
|
||||
inclNet and inclBcast have the same meanings as in NumAddrsNet and NumAddrsPfx.
|
||||
|
||||
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) {
|
||||
|
||||
var numBits uint
|
||||
var numRemoved int64
|
||||
var maxBitLen uint8 = maxBitsv4
|
||||
|
||||
if isIpv6 {
|
||||
maxBitLen = maxBitsv6
|
||||
}
|
||||
if prefixLen > maxBitLen {
|
||||
err = ErrBadPrefixLen
|
||||
return
|
||||
}
|
||||
if prefixLen == maxBitLen {
|
||||
numAddrs = big.NewInt(1)
|
||||
return
|
||||
}
|
||||
if (prefixLen + 1) == maxBitLen {
|
||||
numAddrs = big.NewInt(2)
|
||||
return
|
||||
}
|
||||
|
||||
numBits = uint(maxBitLen - prefixLen)
|
||||
|
||||
numAddrs = new(big.Int).Lsh(big.NewInt(1), numBits)
|
||||
|
||||
if !inclNet {
|
||||
numRemoved++
|
||||
}
|
||||
if !inclBcast {
|
||||
numRemoved++
|
||||
}
|
||||
if numRemoved > 0 {
|
||||
_ = numAddrs.Sub(numAddrs, big.NewInt(numRemoved))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
NumAddrsNet returns the number of IP addresses in a net.IPNet.
|
||||
|
||||
The network address is included in the count if inclNet is true, otherwise it is excluded.
|
||||
|
||||
The broadcast (or reserved broadcast, in the case of IPv6) address will be included in
|
||||
the count if inclBcast is true, otherwise it is excluded.
|
||||
|
||||
numAddrs will be nil if pfx is nil or invalid.
|
||||
*/
|
||||
func NumAddrsNet(pfx *net.IPNet, inclNet, inclBcast bool) (numAddrs *big.Int) {
|
||||
|
||||
var nPfx netip.Prefix
|
||||
var ok bool
|
||||
|
||||
if pfx == nil {
|
||||
return
|
||||
}
|
||||
if nPfx, ok = netipx.FromStdIPNet(pfx); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
numAddrs = NumAddrsPfx(nPfx, inclNet, inclBcast)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
numBits = uint(pfx.Addr().BitLen() - pfx.Bits())
|
||||
|
||||
numAddrs = new(big.Int).Lsh(big.NewInt(1), numBits)
|
||||
|
||||
if !inclNet {
|
||||
numRemoved++
|
||||
}
|
||||
if !inclBcast {
|
||||
numRemoved++
|
||||
}
|
||||
if numRemoved > 0 {
|
||||
_ = numAddrs.Sub(numAddrs, big.NewInt(numRemoved))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
NumNets returns the number of times prefix size subnet fits into prefix size network.
|
||||
|
||||
It will error if network is larger than 128 or if subnet is smaller than network.
|
||||
|
||||
This is MUCH more performant than splitting out an actual network into explicit subnets,
|
||||
and does not require an actual network.
|
||||
*/
|
||||
func NumNets(subnet, network uint8) (numNets uint, ipv6Only bool, err error) {
|
||||
|
||||
var x float64
|
||||
|
||||
// network cannot be higher than 128, as that's the maximum for IPv6.
|
||||
if network > maxBits {
|
||||
err = ErrBadPrefixLen
|
||||
return
|
||||
}
|
||||
if subnet < network {
|
||||
err = ErrBigPrefix
|
||||
return
|
||||
}
|
||||
ipv6Only = (network > maxBitsv4) || (subnet > maxBitsv4)
|
||||
|
||||
x = float64(subnet - network)
|
||||
|
||||
numNets = uint(math.Pow(2, x))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Parse parses b for JSON/XML/YAML and tries to return a StructuredResults from it.
|
||||
func Parse(b []byte) (s *StructuredResults, err error) {
|
||||
|
||||
|
@ -195,6 +195,7 @@ func SetCachePath(cacheDirPath string) (err error) {
|
||||
}
|
||||
|
||||
if cacheDirPath != oldPath {
|
||||
cacheDir = cacheDirPath
|
||||
if err = os.MkdirAll(cacheDir, cacheDirPerms); err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ func (c *CIDRSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
return
|
||||
}
|
||||
|
||||
if c.PrefixLength > uint8(base.Bits()) {
|
||||
if c.PrefixLength < uint8(base.Bits()) {
|
||||
err = ErrBigPrefix
|
||||
return
|
||||
}
|
||||
@ -48,10 +48,10 @@ func (c *CIDRSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, e
|
||||
// We just hit the end of the prefix.
|
||||
break
|
||||
}
|
||||
subPtr = new(netip.Prefix)
|
||||
*subPtr = sub
|
||||
nets = append(nets, subPtr)
|
||||
}
|
||||
subPtr = new(netip.Prefix)
|
||||
*subPtr = sub
|
||||
nets = append(nets, subPtr)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -1,66 +1,96 @@
|
||||
package netsplit
|
||||
|
||||
import (
|
||||
`math/big`
|
||||
`net`
|
||||
"math/big"
|
||||
"net/netip"
|
||||
|
||||
`github.com/projectdiscovery/mapcidr`
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
/*
|
||||
Split splits the network defined in a HostSplitter alongside its configuration and performs the subnetting.
|
||||
This strategy attempts to split the network into subnets of equal number of hosts.
|
||||
Split splits the network defined in a HostSplitter alongside its configuration and performs the subnetting.
|
||||
This strategy attempts to split the network into subnets of equal number of hosts.
|
||||
|
||||
remaining may or may not be nil depending on if the number of hosts can fit cleanly within equal network sizes on boundaries.
|
||||
remaining may or may not be nil depending on if the number of hosts can fit cleanly within equal network sizes on boundaries.
|
||||
|
||||
An ErrBadNumHosts will be returned if the number of hosts does not match the *addressable* range in a prefix.
|
||||
An ErrBadNumHosts will be returned if the number of hosts does not match the *exact* number of addresses per spec in a prefix.
|
||||
*/
|
||||
func (h *HostSplitter) Split() (nets []*netip.Prefix, remaining *netipx.IPSet, err error) {
|
||||
|
||||
var pfx netip.Prefix
|
||||
var tgt *big.Int
|
||||
var splitCidr int
|
||||
var hosts *big.Int
|
||||
var sub netip.Prefix
|
||||
var subPtr *netip.Prefix
|
||||
var split []*net.IPNet
|
||||
var ipsb *netipx.IPSetBuilder = new(netipx.IPSetBuilder)
|
||||
var found bool
|
||||
var cs *CIDRSplitter
|
||||
|
||||
if h == nil || h.NumberHosts == 0 || h.BaseSplitter == nil || h.network == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if split, err = mapcidr.SplitIPNetByNumber(h.network, int(h.NumberHosts)); err != nil {
|
||||
pfx, _ = netipx.FromStdIPNet(h.network)
|
||||
tgt = new(big.Int)
|
||||
tgt.SetUint64(uint64(h.NumberHosts))
|
||||
|
||||
if NumAddrsPfx(pfx, h.InclNetAddr, h.InclBcastAddr).Cmp(tgt) < 0 {
|
||||
// The number of hosts per-subnet exceeds the number of addresses in the specified network.
|
||||
err = ErrNoNetSpace
|
||||
return
|
||||
}
|
||||
/*
|
||||
Iterate up through prefix lengths for the inet family's maximum length, getting larger and larger,
|
||||
until we reach the first prefix that can contain tgt.
|
||||
If we reach h.network.Bits(), we are forced to use that.
|
||||
(Any case otherwise should be handled by the above checks.)
|
||||
*/
|
||||
for splitCidr = pfx.Addr().BitLen(); splitCidr >= pfx.Bits(); splitCidr-- {
|
||||
if hosts, err = NumAddrsIn(uint8(splitCidr), pfx.Addr().Is6(), h.InclNetAddr, h.InclBcastAddr); err != nil {
|
||||
return
|
||||
}
|
||||
if hosts.Cmp(tgt) >= 0 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// Pragmatically, we should never be able to get to this code.
|
||||
err = ErrNoNetSpace
|
||||
return
|
||||
}
|
||||
|
||||
tgt = big.NewInt(0)
|
||||
tgt.SetUint64(uint64(h.NumberHosts))
|
||||
|
||||
nets = make([]*netip.Prefix, len(split))
|
||||
for idx, n := range split {
|
||||
sub, _ = netipx.FromStdIPNet(n)
|
||||
hosts = mapcidr.CountIPsInCIDR(false, false, n)
|
||||
if hosts == nil || tgt.Cmp(hosts) != 0 {
|
||||
err = &SplitErr{
|
||||
Wrapped: ErrBadNumHosts,
|
||||
Nets: nets,
|
||||
Remaining: remaining,
|
||||
LastSubnet: &sub,
|
||||
RequestedPrefixLen: uint8(sub.Bits()),
|
||||
}
|
||||
ipsb.AddPrefix(sub)
|
||||
} else {
|
||||
subPtr = new(netip.Prefix)
|
||||
*subPtr = sub
|
||||
nets = append(nets, subPtr)
|
||||
// Now that we have an appropriate prefix length for splitting, we can offload a huge portion of that to a CIDRSplitter.
|
||||
cs = &CIDRSplitter{
|
||||
PrefixLength: uint8(splitCidr),
|
||||
BaseSplitter: h.BaseSplitter,
|
||||
}
|
||||
if nets, remaining, err = cs.Split(); err != nil {
|
||||
return
|
||||
}
|
||||
// If strict mode is enabled, we then need to match the number of hosts exactly in the subnet.
|
||||
if !h.Strict {
|
||||
return
|
||||
}
|
||||
// First off, if remaining is not nil/empty, that immediately fails strict.
|
||||
if remaining != nil && remaining.Prefixes() != nil && len(remaining.Prefixes()) != 0 {
|
||||
err = &SplitErr{
|
||||
Wrapped: ErrBadNumHosts,
|
||||
Nets: nets,
|
||||
Remaining: remaining,
|
||||
LastSubnet: nil,
|
||||
RequestedPrefixLen: uint8(splitCidr),
|
||||
}
|
||||
|
||||
nets[idx] = new(netip.Prefix)
|
||||
*nets[idx] = sub
|
||||
return
|
||||
}
|
||||
|
||||
if remaining, err = ipsb.IPSet(); err != nil {
|
||||
// Then we check the cidr we split on, and check its number of hosts.
|
||||
if hosts.Cmp(tgt) != 0 {
|
||||
err = &SplitErr{
|
||||
Wrapped: ErrBadNumHosts,
|
||||
Nets: nets,
|
||||
Remaining: remaining,
|
||||
LastSubnet: nil,
|
||||
RequestedPrefixLen: uint8(splitCidr),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,10 @@ 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"`
|
||||
// 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.
|
||||
InclBcastAddr bool `json:"bcast_addr" xml:"bcast,attr,omitempty" yaml:"Broadcast Address Included,omitempty"`
|
||||
// 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"`
|
||||
@ -78,14 +82,14 @@ type VLSMSplitter struct {
|
||||
(ascending order) instead of larger networks/smaller prefixes (descending order).
|
||||
You almost assuredly do not want to do this.
|
||||
*/
|
||||
Ascending bool
|
||||
Ascending bool `json:"asc,omitempty" xml:"asc,attr,omitempty" yaml:"Ascending Order,omitempty"`
|
||||
/*
|
||||
Explicit, if true, will ignore Ascending completely and split in the explicit order of PrefixLengths.
|
||||
|
||||
This has the potential to be *extremely* wasteful of addressing space as the resulting blocks are
|
||||
VERY unoptimized.
|
||||
*/
|
||||
Explicit bool
|
||||
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"`
|
||||
*BaseSplitter `json:"net" xml:"net,omitempty" yaml:"network,omitempty"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user