-
Notifications
You must be signed in to change notification settings - Fork 63
Code Examples 3: Subnetting and Other Subnet Operations
- Iterate through Subnet
- Iterate at Subnet Boundaries
- Sequential or Parallel Operations on Subnets using Streams
- From Start and End Address, Get Minimal List of CIDR Blocks Spanning the Range
- From Start and End Address, Get Single CIDR Block Covering Both
- Merge List of Addresses or Subnets into Minimal List of CIDR Blocks
- Break CIDR Prefix Block into Direct Component Blocks
- Iteratively Break CIDR Prefix Block into Component Blocks
- Derive New Subnet from Existing CIDR Subnet
- Remove Address or Subnet from Subnet
- Variable Length Subnetting
- Automatic Subnetting
- Allocate Multi-Nested Equal-Size Subnet Blocks
- From Start and End Address, Get Minimal List of Spanning Subnets with Single-Valued or Full-Range Segments
Use with caution, subnets can get quite large, an IPv6 /64 subnet has 18,446,744,073,709,551,616 addresses.
The basic structure is this:
IPAddress subnet = new IPAddressString("192.168.1.0/24").getAddress().withoutPrefixLength();
Iterator<? extends IPAddress> iterator = subnet.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
The call to withoutPrefixLength()
is optional, it removes the prefix length from the subnet and all iterated addresses. The first 3 of the 256 lines of output:
192.168.1.0
192.168.1.1
192.168.1.2
Here we iterate through all, but print only beginning and end. The same iterator can be obtained using the Iterable
instance from getIterable()
.
show("192.168.5.0/24");
static void show(String subnetStr) throws AddressStringException {
iterateAll(new IPAddressString(subnetStr).toAddress(), 3);
}
static void iterateAll(IPAddress subnet, int edgeCount) {
PrintStream out = System.out;
BigInteger count = subnet.getCount();
BigInteger bigEdge = BigInteger.valueOf(edgeCount), currCount = count;
int i = 0;
for(IPAddress addr: subnet.getIterable()) {
currCount = currCount.subtract(BigInteger.ONE);
if(i < edgeCount) { // initial values
out.println(++i + ": " + addr);
} else if(currCount.compareTo(bigEdge) < 0) { // end values
out.println(count.subtract(currCount) + ": " + addr);
} else if(i == edgeCount) {
out.println("skipping " +
count.subtract(BigInteger.valueOf(2 * edgeCount)) +
" addresses");
i++;
}
}
}
Output:
1: 192.168.5.0/24
2: 192.168.5.1/24
3: 192.168.5.2/24
skipping 250 addresses
254: 192.168.5.253/24
255: 192.168.5.254/24
256: 192.168.5.255/24
You can use toPrefixBlock()
to get the containing block.
IPAddress subnet = new IPAddressString("1.186.0.1/15").getAddress().
toPrefixBlock().withoutPrefixLength();
Iterator<? extends IPAddress> iterator = subnet.iterator();
int listNum = 4;
for(int i = 0; i < listNum && iterator.hasNext(); i++) {
System.out.println(iterator.next());
}
System.out.println(subnet.getCount().subtract(BigInteger.valueOf(listNum)) +
" more addresses...");
Output:
1.186.0.0
1.186.0.1
1.186.0.2
1.186.0.3
131068 more addresses...
The addresses are iterated in order starting from the zero host, so this variation omits the zero host and max host. In IPv4, those are the network and broadcast addresses, respectively.
IPAddress subnet = new IPAddressString("192.168.1.0/29").getAddress();
Iterator<? extends IPAddress> iterator = subnet.iterator();
if (iterator.hasNext()) {
iterator.next();
for (IPAddress addr = iterator.next(); iterator.hasNext(); addr = iterator.next()) {
System.out.println(addr);
}
}
Output:
192.168.1.1/29
192.168.1.2/29
192.168.1.3/29
192.168.1.4/29
192.168.1.5/29
192.168.1.6/29
List addresses at the beginning and end of a subnet's address range. Unlike the previous example, no caution is required with large subnets.
show("2001:db8:abcd:0012::/64");
static void show(String subnetStr) throws AddressStringException {
iterateEdges(new IPAddressString(subnetStr).toAddress(), 3);
}
static void iterateEdges(IPAddress subnet, int edgeCount) {
PrintStream out = System.out;
if(subnet.getCount().
compareTo(BigInteger.valueOf(2 * (edgeCount + 1))) < 0) {
// the subnet is small, just print it
int i = 0;
for(IPAddress addr: subnet.getIterable()) {
out.println(++i + ": " + addr);
}
} else {
// print low boundary addresses
IPAddress lower = subnet.getLower();
for(int increment = 0; increment < edgeCount; increment++) {
out.println((increment + 1) + ": " +
lower.increment(increment));
}
BigInteger count = subnet.getCount();
out.println("skipping " +
count.subtract(BigInteger.valueOf(2 * edgeCount)) +
" addresses");
// print high boundary addresses
IPAddress upper = subnet.getUpper();
for(int decrement = 1 - edgeCount; decrement <= 0; decrement++) {
out.println(count.add(BigInteger.valueOf(decrement)) +
": " + upper.increment(decrement));
}
}
}
Output:
1: 2001:db8:abcd:12::/64
2: 2001:db8:abcd:12::1/64
3: 2001:db8:abcd:12::2/64
skipping 18446744073709551610 addresses
18446744073709551614: 2001:db8:abcd:12:ffff:ffff:ffff:fffd/64
18446744073709551615: 2001:db8:abcd:12:ffff:ffff:ffff:fffe/64
18446744073709551616: 2001:db8:abcd:12:ffff:ffff:ffff:ffff/64
The output for the example shows the worker threads in use for parallel processing.
boolean inParallel = true;
operate("1.2.3.8/29", inParallel);
operate("::/124", inParallel);
static void operate(String str, boolean inParallel) {
forEach(str, inParallel, addr -> {
System.out.println("Handling " + addr + " in thread " +
Thread.currentThread());
});
}
static void forEach(String str, boolean inParallel,
Consumer<? super IPAddress> action) {
IPAddress addr = new IPAddressString(str).getAddress();
Stream<? extends IPAddress> stream = addr.stream();
if(inParallel) {
stream = stream.parallel();
}
stream.forEach(action);
}
Output:
Handling 1.2.3.12/29 in thread Thread[ForkJoinPool.commonPool-worker-7,5,main]
Handling 1.2.3.8/29 in thread Thread[ForkJoinPool.commonPool-worker-15,5,main]
Handling 1.2.3.14/29 in thread Thread[ForkJoinPool.commonPool-worker-9,5,main]
Handling 1.2.3.15/29 in thread Thread[ForkJoinPool.commonPool-worker-3,5,main]
Handling 1.2.3.11/29 in thread Thread[ForkJoinPool.commonPool-worker-13,5,main]
Handling 1.2.3.10/29 in thread Thread[ForkJoinPool.commonPool-worker-5,5,main]
Handling 1.2.3.9/29 in thread Thread[ForkJoinPool.commonPool-worker-11,5,main]
Handling 1.2.3.13/29 in thread Thread[main,5,main]
Handling ::e/124 in thread Thread[ForkJoinPool.commonPool-worker-5,5,main]
Handling ::d/124 in thread Thread[ForkJoinPool.commonPool-worker-15,5,main]
Handling ::7/124 in thread Thread[ForkJoinPool.commonPool-worker-7,5,main]
Handling ::b/124 in thread Thread[ForkJoinPool.commonPool-worker-3,5,main]
Handling ::5/124 in thread Thread[ForkJoinPool.commonPool-worker-11,5,main]
Handling ::4/124 in thread Thread[ForkJoinPool.commonPool-worker-3,5,main]
Handling ::2/124 in thread Thread[ForkJoinPool.commonPool-worker-9,5,main]
Handling ::a/124 in thread Thread[main,5,main]
Handling ::3/124 in thread Thread[ForkJoinPool.commonPool-worker-3,5,main]
Handling ::9/124 in thread Thread[ForkJoinPool.commonPool-worker-13,5,main]
Handling ::1/124 in thread Thread[ForkJoinPool.commonPool-worker-11,5,main]
Handling ::6/124 in thread Thread[ForkJoinPool.commonPool-worker-7,5,main]
Handling ::f/124 in thread Thread[ForkJoinPool.commonPool-worker-5,5,main]
Handling ::c/124 in thread Thread[ForkJoinPool.commonPool-worker-15,5,main]
Handling ::/124 in thread Thread[ForkJoinPool.commonPool-worker-3,5,main]
Handling ::8/124 in thread Thread[ForkJoinPool.commonPool-worker-9,5,main]
toPrefixBlocks("2001:db8:abcd:0012:1::","2001:db8:abcd:0012:3::");
toPrefixBlocks("1.1.1.111", "1.1.1.120");
static void toPrefixBlocks(String str1, String str2) {
IPAddressString string1 = new IPAddressString(str1),
string2 = new IPAddressString(str2);
IPAddress addr1 = string1.getAddress(),
addr2 = string2.getAddress();
IPAddressSeqRange range = addr1.spanWithRange(addr2);
IPAddress blocks[] = range.spanWithPrefixBlocks();
System.out.println("Starting with range " + range + ",\n" +
"CIDR prefix blocks are " + Arrays.toString(blocks) + "\n");
}
Output:
Starting with range 2001:db8:abcd:12:1:: -> 2001:db8:abcd:12:3::,
CIDR prefix blocks are [2001:db8:abcd:12:1::/80, 2001:db8:abcd:12:2::/80, 2001:db8:abcd:12:3::/128]
Starting with range 1.1.1.111 -> 1.1.1.120,
CIDR prefix blocks are [1.1.1.111/32, 1.1.1.112/29, 1.1.1.120/32]
In the example above, the prefix blocks span the range exactly. If you are looking for just a single prefix block including both addresses:
toPrefixBlock("2001:db8:abcd:0012:1::","2001:db8:abcd:0012:3::");
toPrefixBlock("1.1.1.111", "1.1.1.120");
static void toPrefixBlock(String str1, String str2) {
IPAddressString string1 = new IPAddressString(str1),
string2 = new IPAddressString(str2);
IPAddress addr1 = string1.getAddress(),
addr2 = string2.getAddress();
System.out.println("Starting with range " + addr1.toSequentialRange(addr2) +
",\nCIDR prefix block is " + addr1.coverWithPrefixBlock(addr2) +
"\n");
}
Output:
Starting with range 2001:db8:abcd:12:1:: -> 2001:db8:abcd:12:3::,
CIDR prefix block is 2001:db8:abcd:12::/78
Starting with range 1.1.1.111 -> 1.1.1.120,
CIDR prefix block is 1.1.1.96/27
print(merge("209.152.214.112/30", "209.152.214.116/31", "209.152.214.118/31"));
print(merge("209.152.214.112/30", "209.152.214.116/32", "209.152.214.118/31"));
print(merge("1:2:3:4:8000::/65", "1:2:3:4::/66", "1:2:3:4:4000::/66",
"1:2:3:5:4000::/66", "1:2:3:5::/66", "1:2:3:5:8000::/65"));
static void print(IPAddress addresses[]) {
System.out.println("blocks are " + Arrays.toString(addresses));
}
static IPAddress[] merge(String ...strs) {
IPAddress first = new IPAddressString(strs[0]).getAddress();
IPAddress remaining[] = Arrays.stream(strs, 1, strs.length).
map(str -> new IPAddressString(str).getAddress()).
toArray(IPAddress[]::new);
return first.mergeToPrefixBlocks(remaining);
}
Output:
blocks are [209.152.214.112/29]
blocks are [209.152.214.112/30, 209.152.214.116/32, 209.152.214.118/31]
blocks are [1:2:3:4::/63]
Starting with a prefix block subnet, you can increase the prefix length of the subnet, while not changing the address values themselves. Then the set of addresses have multiple different prefixes. Then you can iterate through those prefix blocks.
adjustBlock("192.168.0.0/28", 2);
static void adjustBlock(String original, int bitShift) {
IPAddress subnet = new IPAddressString(original).getAddress();
IPAddress newSubnets = subnet.setPrefixLength(subnet.getPrefixLength() +
bitShift, false);
TreeSet<IPAddress> subnetSet = new TreeSet<IPAddress>();
Iterator<? extends IPAddress> iterator = newSubnets.prefixBlockIterator();
iterator.forEachRemaining(subnetSet::add);
PrintStream out = System.out;
out.println("original block: " + subnet);
out.println("prefix length increased by " + bitShift + ": " + newSubnets);
out.println("subnets: " + subnetSet + "\n");
}
Output:
original block: 192.168.0.0/28
prefix length increased by 2: 192.168.0.0-12/30
subnets: [192.168.0.0/30, 192.168.0.4/30, 192.168.0.8/30, 192.168.0.12/30]
You can iterate through prefixes repeatedly, in different orders.
Iterate, ordered by block size (breadth-first traversal):
IPAddress subnet = new IPAddressString("192.168.0.0/29").getAddress();
listByDecreasingSize(subnet, 1);
static void listByDecreasingSize(IPAddress subnet, int bitShift) {
PrintStream out = System.out;
int subnetPrefixLength = subnet.getPrefixLength();
out.println(subnet + " size " + subnet.getCount());
String nextIndent = "";
while(subnetPrefixLength + bitShift <= subnet.getBitCount()) {
// adjust original subnet prefix length
subnetPrefixLength += bitShift;
IPAddress blocks = subnet.setPrefixLength(
subnetPrefixLength, false);
// iterate through all prefixes
Iterator<? extends IPAddress> blocksIterator =
blocks.prefixBlockIterator();
while(true) {
IPAddress next = blocksIterator.next();
String node = next + " size " + next.getCount();
out.print(nextIndent);
if(blocksIterator.hasNext()) {
out.print(LEFT_ELBOW);
out.println(node);
} else {
out.print(RIGHT_ELBOW);
out.println(node);
break;
}
}
nextIndent += BELOW_ELBOWS;
}
}
static String LEFT_ELBOW = "\u251C\u2500", // |-
BETWEEN_ELBOWS = "\u2502 ", // |
RIGHT_ELBOW = "\u2514\u2500", // '-
BELOW_ELBOWS = " ";
Output:
192.168.0.0/29 size 8
├─192.168.0.0/30 size 4
└─192.168.0.4/30 size 4
├─192.168.0.0/31 size 2
├─192.168.0.2/31 size 2
├─192.168.0.4/31 size 2
└─192.168.0.6/31 size 2
├─192.168.0.0/32 size 1
├─192.168.0.1/32 size 1
├─192.168.0.2/32 size 1
├─192.168.0.3/32 size 1
├─192.168.0.4/32 size 1
├─192.168.0.5/32 size 1
├─192.168.0.6/32 size 1
└─192.168.0.7/32 size 1
Iterate, ordered by block containment (depth-first traversal):
IPAddress subnet = new IPAddressString("192.168.0.0/29").getAddress();
listByContainment(subnet, 1);
static void listByContainment(IPAddress subnet, int bitShift) {
recurseBlocks(subnet, "", "", bitShift);
}
static void recurseBlocks(IPAddress subnet, String indent, String secondIndent,
int bitShift) {
System.out.println(indent + subnet + " size " + subnet.getCount());
int prefLength = subnet.getPrefixLength();
if(prefLength >= subnet.getBitCount()) {
return;
}
prefLength += bitShift;
// adjust the current block prefix length by the increment
IPAddress blocks = subnet.setPrefixLength(prefLength, false);
// iterate through those blocks
Iterator<? extends IPAddress> blocksIterator =
blocks.prefixBlockIterator();
while(true) {
IPAddress next = blocksIterator.next();
if(blocksIterator.hasNext()) {
recurseBlocks(next, secondIndent + LEFT_ELBOW,
secondIndent + BETWEEN_ELBOWS, bitShift);
} else {
recurseBlocks(next, secondIndent + RIGHT_ELBOW,
secondIndent + BELOW_ELBOWS, bitShift);
if(prefLength >= subnet.getBitCount()) {
System.out.println(secondIndent);
}
break;
}
}
}
Output:
192.168.0.0/29 size 8
├─192.168.0.0/30 size 4
│ ├─192.168.0.0/31 size 2
│ │ ├─192.168.0.0/32 size 1
│ │ └─192.168.0.1/32 size 1
│ │
│ └─192.168.0.2/31 size 2
│ ├─192.168.0.2/32 size 1
│ └─192.168.0.3/32 size 1
│
└─192.168.0.4/30 size 4
├─192.168.0.4/31 size 2
│ ├─192.168.0.4/32 size 1
│ └─192.168.0.5/32 size 1
│
└─192.168.0.6/31 size 2
├─192.168.0.6/32 size 1
└─192.168.0.7/32 size 1
The examples above iterate through prefixes, but if you want to derive a specific subnet rather than iterate through the list:
adjustSubnet("192.168.0.0/28", 2, "0.0.0.12");
adjustSubnet("2001:0db8:85a3::/64", 3, "0:0:0:0:e000::");
static void adjustSubnet(String original, int bitShift, String additionalPrefStr) {
IPAddress subnet = new IPAddressString(original).getAddress();
PrintStream out = System.out;
out.println("original block: " + subnet + " with count " +
subnet.getCount());
IPAddress additionalPrefix = new IPAddressString(additionalPrefStr).
getAddress();
IPAddress newSubnet = subnet.adjustPrefixLength(bitShift);
IPAddress specifiedSubnet = newSubnet.bitwiseOr(additionalPrefix, true);
out.println("prefix length increased by " + bitShift + ": " +
newSubnet + " with count " + newSubnet.getCount());
out.println("after inserting additional prefix: " + specifiedSubnet +
" with count " + specifiedSubnet.getCount());
IPAddress originalSubnet = specifiedSubnet.adjustPrefixLength(-bitShift);
out.println("back to original " + originalSubnet + " with count " +
originalSubnet.getCount() + "\n");
}
Output:
original block: 192.168.0.0/28 with count 16
prefix length increased by 2: 192.168.0.0/30 with count 4
after inserting additional prefix: 192.168.0.12/30 with count 4
back to original 192.168.0.0/28 with count 16
original block: 2001:db8:85a3::/64 with count 18446744073709551616
prefix length increased by 3: 2001:db8:85a3::/67 with count 2305843009213693952
after inserting additional prefix: 2001:db8:85a3:0:e000::/67 with count 2305843009213693952
back to original 2001:db8:85a3::/64 with count 18446744073709551616
String subnet = "192.0.10.0/28";
exclude(subnet, "192.0.10.1");
exclude(subnet, "192.0.10.2/31");
static void exclude(String addrStr, String sub) {
IPAddress one = new IPAddressString(addrStr).getAddress();
IPAddress two = new IPAddressString(sub).getAddress();
IPAddress result[] = one.subtract(two);
System.out.println("Removing " + two + " from " + one + " results in: " +
Arrays.toString(result));
ArrayList<IPAddress> blockList = new ArrayList<>();
for(IPAddress addr : result) {
blockList.addAll(Arrays.asList(addr.spanWithPrefixBlocks()));
}
System.out.println("converted to prefix blocks: " + blockList + "\n");
}
Output:
Removing 192.0.10.1 from 192.0.10.0/28 results in: [192.0.10.0, 192.0.10.2-14/31]
converted to prefix blocks: [192.0.10.0/32, 192.0.10.2/31, 192.0.10.4/30, 192.0.10.8/29]
Removing 192.0.10.2/31 from 192.0.10.0/28 results in: [192.0.10.0/31, 192.0.10.4-12/30]
converted to prefix blocks: [192.0.10.0/31, 192.0.10.4/30, 192.0.10.8/29]
In this example is code that can produce the subnetting shown in some online examples of subnetting, here and the calculator shown here.
For the quick and easy solution see the next example. This example shows a longer solution that can be customized.
We have a number of groups of hosts, each group to be allocated a subnet. We will represent each host group with the HostGroup
class, and each resulting subnet allocation with the Allocation
class:
class HostGroup {
String name;
int count;
HostGroup(String name, int count) {
this.name = name;
this.count = count;
}
@Override
public String toString() {
return name + " of size " + count;
}
}
class Allocation {
HostGroup hostGroup;
IPAddress subnet;
Allocation(HostGroup group, IPAddress subnet) {
this.hostGroup = group;
this.subnet = subnet;
}
@Override
public String toString() {
return hostGroup + " from " + subnet;
}
}
class InsufficientAddressSpaceException extends Exception {}
We start with one or more CIDR blocks of adequate size for the subnetting, the availableBlocks
list. Throughout, the availableBlocks
list will maintain the list of available CIDR prefix block subnets, sorted by block size from smallest to largest. We want the smallest in the list first because it is more efficient to try to allocate from smaller blocks first, using the smallest block that can fit the number of required hosts.
After we are done, each allocation will have an assigned CIDR block subnet for its host group, and the availableBlocks
list will have the unused CIDR blocks that can be used for additional host groups at a future time.
The host groups list is also sorted by size, but instead by largest groups first. We will allocate a subnet for each host group in turn, starting with the largest.
static void allocateHosts(
String availableBlockStrs[],
HostGroup toAllocate[],
int reservedPerBlock) {
try {
List<IPAddress> unusedBlocks = new ArrayList<>();
for(String blockStr : availableBlockStrs) {
IPAddress block = new IPAddressString(blockStr).toAddress();
if(!block.isSinglePrefixBlock()) {
throw new IncompatibleAddressException(
"not a single CIDR subnet");
}
unusedBlocks.add(block);
}
Allocation allocs[] = allocate(unusedBlocks, toAllocate, reservedPerBlock);
System.out.println("\nallocated subnets:\n" + Arrays.toString(allocs));
System.out.println("remaining unused blocks:\n" + unusedBlocks);
} catch(InsufficientAddressSpaceException e) {
System.out.println("unable to allocate: insufficient space");
} catch(AddressStringException |
IncompatibleAddressException |
IllegalArgumentException e) {
System.out.println("unable to allocate: " + e);
}
}
static Allocation[] allocate(
List<IPAddress> availableBlocks,
HostGroup toAllocate[],
int reservedPerBlock) throws InsufficientAddressSpaceException {
Arrays.sort(toAllocate, new Comparator<HostGroup>() {
@Override
public int compare(HostGroup one, HostGroup two) {
return two.count - one.count;
}
});
availableBlocks.sort(new Comparator<IPAddress>() {
@Override
public int compare(IPAddress one, IPAddress two) {
return two.getPrefixLength().compareTo(one.getPrefixLength());
}
});
Allocation allocated[] = new Allocation[toAllocate.length];
for(int i = 0; i < allocated.length; i++) { // allocate each group
HostGroup group = toAllocate[i];
allocated[i] = new Allocation(group,
assign(availableBlocks, group, reservedPerBlock));
}
return allocated;
}
We iterate through the available blocks until we find one large enough, a subnet whose address count matches or exceeds the required number. Each subnet may have a number of reserved addresses, which for IPv4 may include the network and broadcast addresses, so the subnet size must match or exceed the total of required hosts and reserved addresses. The selected block will be partitioned so that we do not waste address space.
static IPAddress assign(List<IPAddress> availableBlocks, HostGroup group, int reserved)
throws InsufficientAddressSpaceException {
int requiredSize = group.count + reserved;
if(group.count < 0 || reserved < 0 || requiredSize < 0) {
throw new IllegalArgumentException("invalid size");
}
for(int i = 0; i < availableBlocks.size(); i++) {
IPAddress candidate = availableBlocks.get(i);
if(candidate.getCount().compareTo(BigInteger.valueOf(requiredSize)) < 0) {
continue; // candidate not big enough
}
// remove the partitioned block from the list
availableBlocks.remove(i);
// use the candidate for the allocation
return partition(candidate, group, reserved, availableBlocks, i);
}
throw new InsufficientAddressSpaceException();
}
To partition the selected block, we calculate the prefix bit-length for the required block size. If that prefix length is larger than that of the selected block, we obtain an iterator from the selected block of that prefix length. We will use the first element of the iterator, while returning the remaining addresses back into the list. The remaining can be converted to a minimal list of prefix blocks using a sequential range that is spanned by prefix blocks.
static IPAddress partition(IPAddress block, int requiredSize,
List<IPAddress> availableBlocks, int listIndex) {
// bitsRequired is the ceiling of log base 2 of the required size
int bitsRequired = Integer.SIZE - Integer.numberOfLeadingZeros(requiredSize - 1);
int newPrefixLen = block.getBitCount() - bitsRequired;
if(block.getPrefixLength().intValue() < newPrefixLen) {
Iterator<? extends IPAddress> iterator =
block.prefixBlockIterator(newPrefixLen);
IPAddress allocated = iterator.next();
// insert unused blocks into list, maintaining sort order
// the unused range spans from the beginning of the next iterator block,
// to the end of the last iterator block
IPAddressSeqRange unused = iterator.next().getLower().
spanWithRange(block.getUpper());
insertUnused(availableBlocks, listIndex, unused);
return allocated;
}
return block;
}
static void insertUnused(List<IPAddress> availableBlocks, int listIndex,
IPAddressSeqRange unused) {
// convert to prefix blocks
IPAddress unusedBlocks[] = unused.spanWithPrefixBlocks();
// sort unused prefix blocks by size, smallest block first, same as availableBlocks
Arrays.sort(unusedBlocks, new Comparator<IPAddress>() {
@Override
public int compare(IPAddress one, IPAddress two) {
return two.getPrefixLength() - one.getPrefixLength();
}
});
// insert them, maintaining sorted order of availableBlocks
for(int i = unusedBlocks.length - 1; i >= 0 ; i--) {
IPAddress lastUnused = unusedBlocks[i];
for(; listIndex > 0; listIndex--) {
if(availableBlocks.get(listIndex-1).getPrefixLength() >=
lastUnused.getPrefixLength()) {
break;
}
}
availableBlocks.add(listIndex, lastUnused);
}
}
Now we are ready to run the code with a couple of examples. In the first example from Study-CCNA.com we have 5 host groups (of host group sizes 50, 30, 20, 2, 2, and 2) to assign from the single block 192.168.10.0/24
.
int adjustment = 2; // we do not use the network or broadcast address from each IPv4 subnet
allocateHosts(
new String[]{
"192.168.10.0/24"
},
new HostGroup[]{
new HostGroup("HQ LAN", 50),
new HostGroup("BRANCH 1", 30),
new HostGroup("BRANCH 2", 20),
new HostGroup("WAN 1", 2),
new HostGroup("WAN 2", 2),
new HostGroup("WAN 3", 2),
},
adjustment);
Output:
allocated subnets:
[HQ LAN of size 50 from 192.168.10.0/26, BRANCH 1 of size 30 from 192.168.10.64/27, BRANCH 2 of size 20 from 192.168.10.96/27, WAN 1 of size 2 from 192.168.10.128/30, WAN 2 of size 2 from 192.168.10.132/30, WAN 3 of size 2 from 192.168.10.136/30]
remaining unused blocks:
[192.168.10.140/30, 192.168.10.144/30, 192.168.10.148/30, 192.168.10.152/30, 192.168.10.156/30, 192.168.10.160/30, 192.168.10.164/30, 192.168.10.168/30, 192.168.10.172/30, 192.168.10.176/30, 192.168.10.180/30, 192.168.10.184/30, 192.168.10.188/30, 192.168.10.192/26]
In the second example we have 4 host groups (of host group sizes 60, 12, 12, and 28) to assign from the same example block 192.168.10.0/24
.
allocateHosts(
new String[]{
"192.168.10.0/24",
},
new HostGroup[]{
new HostGroup("Perth", 60),
new HostGroup("Sydney", 12),
new HostGroup("Singapore", 12),
new HostGroup("Kuala Lumpur", 28),
},
adjustment); // reserve the network and broadcast addresses
Output:
allocated subnets:
[Perth of size 60 from 192.168.10.0/26, Kuala Lumpur of size 28 from 192.168.10.64/27, Sydney of size 12 from 192.168.10.96/28, Singapore of size 12 from 192.168.10.112/28]
remaining unused blocks:
[192.168.10.128/26, 192.168.10.192/26]
Also note that this code works equally well with IPv6, although with IPv6 the address space is much larger, and thus splitting up the blocks efficiently may result in a large number of unused blocks. Using variable length prefixes for efficiency is typically not as important with IPv6.
The previous example illustrates variable-length subnetting that can be customized as desired.
However, the algorithm outlined in that example is frequently suitable without any customization. If no further customization is required, you can simply use the PrefixBlockAllocator, which follows the same algorithm.
AllocatedBlock<IPAddress> allocated[] = alloc(new String[]{"192.168.10.0/24"}, 30, 20, 2, 2, 2);
int i = 0;
for(AllocatedBlock<IPAddress> block : allocated) {
System.out.println(++i + " " + block);
}
static AllocatedBlock<IPAddress>[] alloc(String blockStrs[], long ...blockSizes) {
IPAddress blocks[] = new IPAddress[blockStrs.length];
for(int i = 0; i < blockStrs.length; i++) {
blocks[i] = new IPAddressString(blockStrs[i]).getAddress();
}
System.out.println("Allocating subnets from " + Arrays.toString(blocks) +
" for blocks of size " + Arrays.toString(blockSizes));
PrefixBlockAllocator<IPAddress> allocator = new PrefixBlockAllocator<>();
allocator.addAvailable(blocks);
allocator.setReserved(2);
return allocator.allocateSizes(blockSizes);
}
Output:
Allocating subnets from [192.168.10.0/24] for blocks of size [30, 20, 2, 2, 2]
1 192.168.10.0/27 for 30 hosts and 2 reserved addresses
2 192.168.10.32/27 for 20 hosts and 2 reserved addresses
3 192.168.10.64/30 for 2 hosts and 2 reserved addresses
4 192.168.10.68/30 for 2 hosts and 2 reserved addresses
5 192.168.10.72/30 for 2 hosts and 2 reserved addresses
In this example we assign address blocks to subnets in cloud availability zones.
Suppose we had a list of cloud accounts, and suppose we had the private blocks 10.0.0.0/8 and fd00::/64 available to use. For each account, we wish to allocate a subnet of about 2000 addresses for each availability zone in a list of cloud regions.
String accounts[] = new String[] {"Account A", "Account B"};
static class Region {
String name, availabilityZones[];
Region(String name, String availabilityZones[]) {
this.name = name;
this.availabilityZones = availabilityZones;
}
}
Region regions[] = new Region[] {
new Region("eu-west-1", new String[] {"a", "b", "c"}),
new Region("ap-southeast-1", new String[] {"a", "b"}),
};
We want to organize the addresses so there is a subnet for each region, from which we allocate subnets for each account, from which we allocate subnets for each availability zone.
Each subnet would be associated with an instance of AssignedBlock
.
static class AssignedBlock {
String account, region, availabilityZone;
AssignedBlock(String account, String region, String availabilityZone) {
this.account = account;
this.region = region;
this.availabilityZone = availabilityZone;
}
@Override
public String toString() {
String str = availabilityZone;
if (str == null) {
str = account;
if (str == null) {
str = region; // a regional subnet
} // else an account subnet
} else {
// an availability zone subnet
str = account + " " + region + availabilityZone;
}
return str;
}
}
To do this, we will map the allocated subnets to instances of AssignedBlock
. To store the mappings, we will use IPv4 and IPv6 tries, which will maintain the subnet containment structure. We will use the dual trie type DualIPv4v6AssociativeTries
, allowing the allocateSubnets
function to be IP-version agnostic. At each level, given a block to be used for allocation, it adjusts the prefix length, and then dishes out blocks with a prefix block iterator.
The initial bits in the subnets' prefix designate the region, the next 6 bits designate the account, the next 3 bits designate the availability zone, and the final 11 host bits form the availability zone subnet block.
void allocate() {
DualIPv4v6AssociativeTries<AssignedBlock> tries =
new DualIPv4v6AssociativeTries<>();
allocateSubnets("10.0.0.0/8", accounts, regions, tries);
allocateSubnets("fd00::/64", accounts, regions, tries);
System.out.println(tries);
}
void allocateSubnets(
String addressBlockStr,
String accounts[],
Region regions[],
DualIPv4v6AssociativeTries<AssignedBlock> tries) {
IPAddress block = new IPAddressString(addressBlockStr).getAddress();
// add the base block to the tries
tries.add(block);
// add the root ::0/0 or 0.0.0.0/0 to be a part of the hierarchy
tries.add(block.setPrefixLength(0).toPrefixBlock());
// 11-bit blocks for each availability zone,
// 3 preceding prefix bits for each availability zone,
// 6 preceding prefix bits for each account,
// the remaining initial bits for each region
int addrBlockSize = 11, avZoneBlockSize = 3, accountBlockSize = 6;
int totalBlockSize = addrBlockSize + avZoneBlockSize + accountBlockSize;
// set the prefix length for the regional subnets, then create the subnet iterator
int regionPrefixLen = block.getBitCount() - totalBlockSize;
IPAddress regionBlocks = block.setPrefixLength(regionPrefixLen, false);
Iterator<? extends IPAddress> regionIterator = regionBlocks.prefixBlockIterator();
for(Region region: regions) {
// each region gets a block
IPAddress regionBlock = regionIterator.next();
tries.put(regionBlock, new AssignedBlock(null, region.name, null));
int accountPrefixLen = regionPrefixLen + accountBlockSize;
IPAddress accountBlocks =
regionBlock.setPrefixLength(accountPrefixLen, false);
Iterator<? extends IPAddress> accountBlockIterator =
accountBlocks.prefixBlockIterator();
for(String account: accounts) {
// each account gets a block in each region
IPAddress accountBlock = accountBlockIterator.next();
tries.put(accountBlock, new AssignedBlock(account, null, null));
int avZonePrefixLen = accountPrefixLen + avZoneBlockSize;
IPAddress avZoneBlocks =
accountBlock.setPrefixLength(avZonePrefixLen, false);
Iterator<? extends IPAddress> avZoneBlockIterator =
avZoneBlocks.prefixBlockIterator();
for (String avZone: region.availabilityZones) {
// each availability zone gets a block in each account
tries.put(avZoneBlockIterator.next(),
new AssignedBlock(account, region.name, avZone));
}
}
}
}
The nested block allocations are clear when the trie is printed.
○ * (36)
├─● 0.0.0.0/0 (18)
│ └─● 10.0.0.0/8 (17)
│ └─○ 10.0.0.0/11 (16)
│ ├─● 10.0.0.0/12 = eu-west-1 (9)
│ │ └─○ 10.0.0.0/17 (8)
│ │ ├─● 10.0.0.0/18 = Account A (4)
│ │ │ └─○ 10.0.0.0/19 (3)
│ │ │ ├─○ 10.0.0.0/20 (2)
│ │ │ │ ├─● 10.0.0.0/21 = Account A eu-west-1a (1)
│ │ │ │ └─● 10.0.8.0/21 = Account A eu-west-1b (1)
│ │ │ └─● 10.0.16.0/21 = Account A eu-west-1c (1)
│ │ └─● 10.0.64.0/18 = Account B (4)
│ │ └─○ 10.0.64.0/19 (3)
│ │ ├─○ 10.0.64.0/20 (2)
│ │ │ ├─● 10.0.64.0/21 = Account B eu-west-1a (1)
│ │ │ └─● 10.0.72.0/21 = Account B eu-west-1b (1)
│ │ └─● 10.0.80.0/21 = Account B eu-west-1c (1)
│ └─● 10.16.0.0/12 = ap-southeast-1 (7)
│ └─○ 10.16.0.0/17 (6)
│ ├─● 10.16.0.0/18 = Account A (3)
│ │ └─○ 10.16.0.0/20 (2)
│ │ ├─● 10.16.0.0/21 = Account A ap-southeast-1a (1)
│ │ └─● 10.16.8.0/21 = Account A ap-southeast-1b (1)
│ └─● 10.16.64.0/18 = Account B (3)
│ └─○ 10.16.64.0/20 (2)
│ ├─● 10.16.64.0/21 = Account B ap-southeast-1a (1)
│ └─● 10.16.72.0/21 = Account B ap-southeast-1b (1)
└─● ::/0 (18)
└─● fd00::/64 (17)
└─○ fd00::/107 (16)
├─● fd00::/108 = eu-west-1 (9)
│ └─○ fd00::/113 (8)
│ ├─● fd00::/114 = Account A (4)
│ │ └─○ fd00::/115 (3)
│ │ ├─○ fd00::/116 (2)
│ │ │ ├─● fd00::/117 = Account A eu-west-1a (1)
│ │ │ └─● fd00::800/117 = Account A eu-west-1b (1)
│ │ └─● fd00::1000/117 = Account A eu-west-1c (1)
│ └─● fd00::4000/114 = Account B (4)
│ └─○ fd00::4000/115 (3)
│ ├─○ fd00::4000/116 (2)
│ │ ├─● fd00::4000/117 = Account B eu-west-1a (1)
│ │ └─● fd00::4800/117 = Account B eu-west-1b (1)
│ └─● fd00::5000/117 = Account B eu-west-1c (1)
└─● fd00::10:0/108 = ap-southeast-1 (7)
└─○ fd00::10:0/113 (6)
├─● fd00::10:0/114 = Account A (3)
│ └─○ fd00::10:0/116 (2)
│ ├─● fd00::10:0/117 = Account A ap-southeast-1a (1)
│ └─● fd00::10:800/117 = Account A ap-southeast-1b (1)
└─● fd00::10:4000/114 = Account B (3)
└─○ fd00::10:4000/116 (2)
├─● fd00::10:4000/117 = Account B ap-southeast-1a (1)
└─● fd00::10:4800/117 = Account B ap-southeast-1b (1)
From Start and End Address, Get Minimal List of Spanning Subnets with Single-Valued or Full-Range Segments
Given an address range, we can first split into a minimal list of sequential blocks. From there, we can split them further so that no segment in the final list is neither single-valued nor full-range. As in many examples, this code works for both IPv4 and IPv6.
static void example(String lowerStr, String upperStr) {
IPAddressString lower = new IPAddressString(lowerStr);
IPAddressString upper = new IPAddressString(upperStr);
IPAddressSeqRange range = lower.getAddress().spanWithRange(
upper.getAddress());
// first we split into sequential address blocks
IPAddress blocks[] = range.spanWithSequentialBlocks();
System.out.println("Starting with " + range +
", the minimal list of sequential blocks is: ");
System.out.println(Arrays.toString(blocks));
// now we split further
System.out.println("Splitting each so all segments" +
" are either single or full range:");
ArrayList<IPAddress> allSplits = splitBlocks(blocks);
System.out.println("Total block count: " + allSplits.size());
// we now go in the reverse direction
reverse(range, allSplits);
}
The code to split each block further finds the single segment that is neither single-valued nor full-range. We iterate over the segment values in that segment to produce the subnets for the final list. Earlier examples iterated over prefix blocks, which was iterating over the network bits, the initial part of a subnet. Here we iterate over the initial segments.
static ArrayList<IPAddress> splitBlocks(IPAddress[] blocks) {
// A sequential block can have at most one segment
// that is not single nor full size,
// so for each block we iterate over that one segment
ArrayList<IPAddress> allSplits = new ArrayList<IPAddress>();
for(IPAddress block : blocks) {
boolean isSplit = false;
if(block.isMultiple()) {
for(int i = 0; i < block.getSegmentCount(); i++) {
IPAddressSegment segment = block.getSegment(i);
if(segment.isMultiple()) {
if(segment.isFullRange()) {
break;
}
// segment is neither single nor full-range
split(block, i, allSplits);
isSplit = true;
break;
}
}
}
if(!isSplit) {
String label = block.isMultiple() ? "subnet" : "address";
System.out.println("\tNot splitting " + label + ' ' + block);
allSplits.add(block);
}
}
return allSplits;
}
static void split(IPAddress block, int index, ArrayList<IPAddress> allSplits) {
System.out.println("\tSplitting " + block);
Iterator<? extends IPAddress> iterator = block.blockIterator(index + 1);
ArrayList<IPAddress> addrs = new ArrayList<>(block.getMaxSegmentValue() + 1);
while(iterator.hasNext()) {
addrs.add(iterator.next());
}
boolean isLastSegment = index == block.getSegmentCount() - 1;
String label = isLastSegment ? "addresses" : "subnets";
System.out.println("\t\t" + addrs.size() + ' ' + label + ": " + addrs);
allSplits.addAll(addrs);
}
This additional code shows how to go in the reverse direction, first merging to a minimal list of blocks, then merging to a minimal list of ranges. Alternatively, the code could be shortened by switching the blocks to ranges right away, following with just a single merge of ranges.
static void reverse(IPAddressSeqRange range, ArrayList<IPAddress> allSplits) {
// merge to a minimal list of blocks
IPAddress merged[] = range.getLower().mergeToSequentialBlocks(
allSplits.toArray(new IPAddress[allSplits.size()]));
System.out.println("Merged back to minimal sequential blocks again:");
List<IPAddress> mergedList = Arrays.asList(merged);
System.out.println(mergedList);
// switch to ranges so we can merge to a minimal list of ranges
List<IPAddressSeqRange> ranges = new ArrayList<>(mergedList.size());
mergedList.forEach(addr -> ranges.add(addr.toSequentialRange()));
System.out.println("Merged back to range again:");
IPAddressSeqRange rngs[] = IPAddressSeqRange.join(
ranges.toArray(new IPAddressSeqRange[ranges.size()]));
System.out.println(Arrays.toString(rngs));
System.out.println("\n\n");
}
We show the output for a couple of sample ranges, the first a CIDR prefix block:
example("41.216.124.0", "41.216.127.255");
Output:
Starting with 41.216.124.0 -> 41.216.127.255, minimal sequential blocks are:
[41.216.124-127.*]
Splitting sequentials block so all segments are either single or full range:
Splitting 41.216.124-127.*
4 subnets: [41.216.124.*, 41.216.125.*, 41.216.126.*, 41.216.127.*]
Total block count: 4
Merged back to minimal sequential blocks again:
[41.216.124-127.*]
Merged back to range again:
[41.216.124.0 -> 41.216.127.255]
The second example is not a block, resulting in a much larger list:
example("128.1.0.1", "191.255.255.255");
Output:
Starting with 128.1.0.1 -> 191.255.255.255, minimal sequential blocks are:
[128.1.0.1-255, 128.1.1-255.*, 128.2-255.*.*, 129-191.*.*.*]
Splitting sequentials block so all segments are either single or full range:
Splitting 128.1.0.1-255
255 addresses: [128.1.0.1, 128.1.0.2, 128.1.0.3, 128.1.0.4, 128.1.0.5, 128.1.0.6, 128.1.0.7, 128.1.0.8, 128.1.0.9, 128.1.0.10, 128.1.0.11, 128.1.0.12, 128.1.0.13, 128.1.0.14, 128.1.0.15, 128.1.0.16, 128.1.0.17, 128.1.0.18, 128.1.0.19, 128.1.0.20, 128.1.0.21, 128.1.0.22, 128.1.0.23, 128.1.0.24, 128.1.0.25, 128.1.0.26, 128.1.0.27, 128.1.0.28, 128.1.0.29, 128.1.0.30, 128.1.0.31, 128.1.0.32, 128.1.0.33, 128.1.0.34, 128.1.0.35, 128.1.0.36, 128.1.0.37, 128.1.0.38, 128.1.0.39, 128.1.0.40, 128.1.0.41, 128.1.0.42, 128.1.0.43, 128.1.0.44, 128.1.0.45, 128.1.0.46, 128.1.0.47, 128.1.0.48, 128.1.0.49, 128.1.0.50, 128.1.0.51, 128.1.0.52, 128.1.0.53, 128.1.0.54, 128.1.0.55, 128.1.0.56, 128.1.0.57, 128.1.0.58, 128.1.0.59, 128.1.0.60, 128.1.0.61, 128.1.0.62, 128.1.0.63, 128.1.0.64, 128.1.0.65, 128.1.0.66, 128.1.0.67, 128.1.0.68, 128.1.0.69, 128.1.0.70, 128.1.0.71, 128.1.0.72, 128.1.0.73, 128.1.0.74, 128.1.0.75, 128.1.0.76, 128.1.0.77, 128.1.0.78, 128.1.0.79, 128.1.0.80, 128.1.0.81, 128.1.0.82, 128.1.0.83, 128.1.0.84, 128.1.0.85, 128.1.0.86, 128.1.0.87, 128.1.0.88, 128.1.0.89, 128.1.0.90, 128.1.0.91, 128.1.0.92, 128.1.0.93, 128.1.0.94, 128.1.0.95, 128.1.0.96, 128.1.0.97, 128.1.0.98, 128.1.0.99, 128.1.0.100, 128.1.0.101, 128.1.0.102, 128.1.0.103, 128.1.0.104, 128.1.0.105, 128.1.0.106, 128.1.0.107, 128.1.0.108, 128.1.0.109, 128.1.0.110, 128.1.0.111, 128.1.0.112, 128.1.0.113, 128.1.0.114, 128.1.0.115, 128.1.0.116, 128.1.0.117, 128.1.0.118, 128.1.0.119, 128.1.0.120, 128.1.0.121, 128.1.0.122, 128.1.0.123, 128.1.0.124, 128.1.0.125, 128.1.0.126, 128.1.0.127, 128.1.0.128, 128.1.0.129, 128.1.0.130, 128.1.0.131, 128.1.0.132, 128.1.0.133, 128.1.0.134, 128.1.0.135, 128.1.0.136, 128.1.0.137, 128.1.0.138, 128.1.0.139, 128.1.0.140, 128.1.0.141, 128.1.0.142, 128.1.0.143, 128.1.0.144, 128.1.0.145, 128.1.0.146, 128.1.0.147, 128.1.0.148, 128.1.0.149, 128.1.0.150, 128.1.0.151, 128.1.0.152, 128.1.0.153, 128.1.0.154, 128.1.0.155, 128.1.0.156, 128.1.0.157, 128.1.0.158, 128.1.0.159, 128.1.0.160, 128.1.0.161, 128.1.0.162, 128.1.0.163, 128.1.0.164, 128.1.0.165, 128.1.0.166, 128.1.0.167, 128.1.0.168, 128.1.0.169, 128.1.0.170, 128.1.0.171, 128.1.0.172, 128.1.0.173, 128.1.0.174, 128.1.0.175, 128.1.0.176, 128.1.0.177, 128.1.0.178, 128.1.0.179, 128.1.0.180, 128.1.0.181, 128.1.0.182, 128.1.0.183, 128.1.0.184, 128.1.0.185, 128.1.0.186, 128.1.0.187, 128.1.0.188, 128.1.0.189, 128.1.0.190, 128.1.0.191, 128.1.0.192, 128.1.0.193, 128.1.0.194, 128.1.0.195, 128.1.0.196, 128.1.0.197, 128.1.0.198, 128.1.0.199, 128.1.0.200, 128.1.0.201, 128.1.0.202, 128.1.0.203, 128.1.0.204, 128.1.0.205, 128.1.0.206, 128.1.0.207, 128.1.0.208, 128.1.0.209, 128.1.0.210, 128.1.0.211, 128.1.0.212, 128.1.0.213, 128.1.0.214, 128.1.0.215, 128.1.0.216, 128.1.0.217, 128.1.0.218, 128.1.0.219, 128.1.0.220, 128.1.0.221, 128.1.0.222, 128.1.0.223, 128.1.0.224, 128.1.0.225, 128.1.0.226, 128.1.0.227, 128.1.0.228, 128.1.0.229, 128.1.0.230, 128.1.0.231, 128.1.0.232, 128.1.0.233, 128.1.0.234, 128.1.0.235, 128.1.0.236, 128.1.0.237, 128.1.0.238, 128.1.0.239, 128.1.0.240, 128.1.0.241, 128.1.0.242, 128.1.0.243, 128.1.0.244, 128.1.0.245, 128.1.0.246, 128.1.0.247, 128.1.0.248, 128.1.0.249, 128.1.0.250, 128.1.0.251, 128.1.0.252, 128.1.0.253, 128.1.0.254, 128.1.0.255]
Splitting 128.1.1-255.*
255 subnets: [128.1.1.*, 128.1.2.*, 128.1.3.*, 128.1.4.*, 128.1.5.*, 128.1.6.*, 128.1.7.*, 128.1.8.*, 128.1.9.*, 128.1.10.*, 128.1.11.*, 128.1.12.*, 128.1.13.*, 128.1.14.*, 128.1.15.*, 128.1.16.*, 128.1.17.*, 128.1.18.*, 128.1.19.*, 128.1.20.*, 128.1.21.*, 128.1.22.*, 128.1.23.*, 128.1.24.*, 128.1.25.*, 128.1.26.*, 128.1.27.*, 128.1.28.*, 128.1.29.*, 128.1.30.*, 128.1.31.*, 128.1.32.*, 128.1.33.*, 128.1.34.*, 128.1.35.*, 128.1.36.*, 128.1.37.*, 128.1.38.*, 128.1.39.*, 128.1.40.*, 128.1.41.*, 128.1.42.*, 128.1.43.*, 128.1.44.*, 128.1.45.*, 128.1.46.*, 128.1.47.*, 128.1.48.*, 128.1.49.*, 128.1.50.*, 128.1.51.*, 128.1.52.*, 128.1.53.*, 128.1.54.*, 128.1.55.*, 128.1.56.*, 128.1.57.*, 128.1.58.*, 128.1.59.*, 128.1.60.*, 128.1.61.*, 128.1.62.*, 128.1.63.*, 128.1.64.*, 128.1.65.*, 128.1.66.*, 128.1.67.*, 128.1.68.*, 128.1.69.*, 128.1.70.*, 128.1.71.*, 128.1.72.*, 128.1.73.*, 128.1.74.*, 128.1.75.*, 128.1.76.*, 128.1.77.*, 128.1.78.*, 128.1.79.*, 128.1.80.*, 128.1.81.*, 128.1.82.*, 128.1.83.*, 128.1.84.*, 128.1.85.*, 128.1.86.*, 128.1.87.*, 128.1.88.*, 128.1.89.*, 128.1.90.*, 128.1.91.*, 128.1.92.*, 128.1.93.*, 128.1.94.*, 128.1.95.*, 128.1.96.*, 128.1.97.*, 128.1.98.*, 128.1.99.*, 128.1.100.*, 128.1.101.*, 128.1.102.*, 128.1.103.*, 128.1.104.*, 128.1.105.*, 128.1.106.*, 128.1.107.*, 128.1.108.*, 128.1.109.*, 128.1.110.*, 128.1.111.*, 128.1.112.*, 128.1.113.*, 128.1.114.*, 128.1.115.*, 128.1.116.*, 128.1.117.*, 128.1.118.*, 128.1.119.*, 128.1.120.*, 128.1.121.*, 128.1.122.*, 128.1.123.*, 128.1.124.*, 128.1.125.*, 128.1.126.*, 128.1.127.*, 128.1.128.*, 128.1.129.*, 128.1.130.*, 128.1.131.*, 128.1.132.*, 128.1.133.*, 128.1.134.*, 128.1.135.*, 128.1.136.*, 128.1.137.*, 128.1.138.*, 128.1.139.*, 128.1.140.*, 128.1.141.*, 128.1.142.*, 128.1.143.*, 128.1.144.*, 128.1.145.*, 128.1.146.*, 128.1.147.*, 128.1.148.*, 128.1.149.*, 128.1.150.*, 128.1.151.*, 128.1.152.*, 128.1.153.*, 128.1.154.*, 128.1.155.*, 128.1.156.*, 128.1.157.*, 128.1.158.*, 128.1.159.*, 128.1.160.*, 128.1.161.*, 128.1.162.*, 128.1.163.*, 128.1.164.*, 128.1.165.*, 128.1.166.*, 128.1.167.*, 128.1.168.*, 128.1.169.*, 128.1.170.*, 128.1.171.*, 128.1.172.*, 128.1.173.*, 128.1.174.*, 128.1.175.*, 128.1.176.*, 128.1.177.*, 128.1.178.*, 128.1.179.*, 128.1.180.*, 128.1.181.*, 128.1.182.*, 128.1.183.*, 128.1.184.*, 128.1.185.*, 128.1.186.*, 128.1.187.*, 128.1.188.*, 128.1.189.*, 128.1.190.*, 128.1.191.*, 128.1.192.*, 128.1.193.*, 128.1.194.*, 128.1.195.*, 128.1.196.*, 128.1.197.*, 128.1.198.*, 128.1.199.*, 128.1.200.*, 128.1.201.*, 128.1.202.*, 128.1.203.*, 128.1.204.*, 128.1.205.*, 128.1.206.*, 128.1.207.*, 128.1.208.*, 128.1.209.*, 128.1.210.*, 128.1.211.*, 128.1.212.*, 128.1.213.*, 128.1.214.*, 128.1.215.*, 128.1.216.*, 128.1.217.*, 128.1.218.*, 128.1.219.*, 128.1.220.*, 128.1.221.*, 128.1.222.*, 128.1.223.*, 128.1.224.*, 128.1.225.*, 128.1.226.*, 128.1.227.*, 128.1.228.*, 128.1.229.*, 128.1.230.*, 128.1.231.*, 128.1.232.*, 128.1.233.*, 128.1.234.*, 128.1.235.*, 128.1.236.*, 128.1.237.*, 128.1.238.*, 128.1.239.*, 128.1.240.*, 128.1.241.*, 128.1.242.*, 128.1.243.*, 128.1.244.*, 128.1.245.*, 128.1.246.*, 128.1.247.*, 128.1.248.*, 128.1.249.*, 128.1.250.*, 128.1.251.*, 128.1.252.*, 128.1.253.*, 128.1.254.*, 128.1.255.*]
Splitting 128.2-255.*.*
254 subnets: [128.2.*.*, 128.3.*.*, 128.4.*.*, 128.5.*.*, 128.6.*.*, 128.7.*.*, 128.8.*.*, 128.9.*.*, 128.10.*.*, 128.11.*.*, 128.12.*.*, 128.13.*.*, 128.14.*.*, 128.15.*.*, 128.16.*.*, 128.17.*.*, 128.18.*.*, 128.19.*.*, 128.20.*.*, 128.21.*.*, 128.22.*.*, 128.23.*.*, 128.24.*.*, 128.25.*.*, 128.26.*.*, 128.27.*.*, 128.28.*.*, 128.29.*.*, 128.30.*.*, 128.31.*.*, 128.32.*.*, 128.33.*.*, 128.34.*.*, 128.35.*.*, 128.36.*.*, 128.37.*.*, 128.38.*.*, 128.39.*.*, 128.40.*.*, 128.41.*.*, 128.42.*.*, 128.43.*.*, 128.44.*.*, 128.45.*.*, 128.46.*.*, 128.47.*.*, 128.48.*.*, 128.49.*.*, 128.50.*.*, 128.51.*.*, 128.52.*.*, 128.53.*.*, 128.54.*.*, 128.55.*.*, 128.56.*.*, 128.57.*.*, 128.58.*.*, 128.59.*.*, 128.60.*.*, 128.61.*.*, 128.62.*.*, 128.63.*.*, 128.64.*.*, 128.65.*.*, 128.66.*.*, 128.67.*.*, 128.68.*.*, 128.69.*.*, 128.70.*.*, 128.71.*.*, 128.72.*.*, 128.73.*.*, 128.74.*.*, 128.75.*.*, 128.76.*.*, 128.77.*.*, 128.78.*.*, 128.79.*.*, 128.80.*.*, 128.81.*.*, 128.82.*.*, 128.83.*.*, 128.84.*.*, 128.85.*.*, 128.86.*.*, 128.87.*.*, 128.88.*.*, 128.89.*.*, 128.90.*.*, 128.91.*.*, 128.92.*.*, 128.93.*.*, 128.94.*.*, 128.95.*.*, 128.96.*.*, 128.97.*.*, 128.98.*.*, 128.99.*.*, 128.100.*.*, 128.101.*.*, 128.102.*.*, 128.103.*.*, 128.104.*.*, 128.105.*.*, 128.106.*.*, 128.107.*.*, 128.108.*.*, 128.109.*.*, 128.110.*.*, 128.111.*.*, 128.112.*.*, 128.113.*.*, 128.114.*.*, 128.115.*.*, 128.116.*.*, 128.117.*.*, 128.118.*.*, 128.119.*.*, 128.120.*.*, 128.121.*.*, 128.122.*.*, 128.123.*.*, 128.124.*.*, 128.125.*.*, 128.126.*.*, 128.127.*.*, 128.128.*.*, 128.129.*.*, 128.130.*.*, 128.131.*.*, 128.132.*.*, 128.133.*.*, 128.134.*.*, 128.135.*.*, 128.136.*.*, 128.137.*.*, 128.138.*.*, 128.139.*.*, 128.140.*.*, 128.141.*.*, 128.142.*.*, 128.143.*.*, 128.144.*.*, 128.145.*.*, 128.146.*.*, 128.147.*.*, 128.148.*.*, 128.149.*.*, 128.150.*.*, 128.151.*.*, 128.152.*.*, 128.153.*.*, 128.154.*.*, 128.155.*.*, 128.156.*.*, 128.157.*.*, 128.158.*.*, 128.159.*.*, 128.160.*.*, 128.161.*.*, 128.162.*.*, 128.163.*.*, 128.164.*.*, 128.165.*.*, 128.166.*.*, 128.167.*.*, 128.168.*.*, 128.169.*.*, 128.170.*.*, 128.171.*.*, 128.172.*.*, 128.173.*.*, 128.174.*.*, 128.175.*.*, 128.176.*.*, 128.177.*.*, 128.178.*.*, 128.179.*.*, 128.180.*.*, 128.181.*.*, 128.182.*.*, 128.183.*.*, 128.184.*.*, 128.185.*.*, 128.186.*.*, 128.187.*.*, 128.188.*.*, 128.189.*.*, 128.190.*.*, 128.191.*.*, 128.192.*.*, 128.193.*.*, 128.194.*.*, 128.195.*.*, 128.196.*.*, 128.197.*.*, 128.198.*.*, 128.199.*.*, 128.200.*.*, 128.201.*.*, 128.202.*.*, 128.203.*.*, 128.204.*.*, 128.205.*.*, 128.206.*.*, 128.207.*.*, 128.208.*.*, 128.209.*.*, 128.210.*.*, 128.211.*.*, 128.212.*.*, 128.213.*.*, 128.214.*.*, 128.215.*.*, 128.216.*.*, 128.217.*.*, 128.218.*.*, 128.219.*.*, 128.220.*.*, 128.221.*.*, 128.222.*.*, 128.223.*.*, 128.224.*.*, 128.225.*.*, 128.226.*.*, 128.227.*.*, 128.228.*.*, 128.229.*.*, 128.230.*.*, 128.231.*.*, 128.232.*.*, 128.233.*.*, 128.234.*.*, 128.235.*.*, 128.236.*.*, 128.237.*.*, 128.238.*.*, 128.239.*.*, 128.240.*.*, 128.241.*.*, 128.242.*.*, 128.243.*.*, 128.244.*.*, 128.245.*.*, 128.246.*.*, 128.247.*.*, 128.248.*.*, 128.249.*.*, 128.250.*.*, 128.251.*.*, 128.252.*.*, 128.253.*.*, 128.254.*.*, 128.255.*.*]
Splitting 129-191.*.*.*
63 subnets: [129.*.*.*, 130.*.*.*, 131.*.*.*, 132.*.*.*, 133.*.*.*, 134.*.*.*, 135.*.*.*, 136.*.*.*, 137.*.*.*, 138.*.*.*, 139.*.*.*, 140.*.*.*, 141.*.*.*, 142.*.*.*, 143.*.*.*, 144.*.*.*, 145.*.*.*, 146.*.*.*, 147.*.*.*, 148.*.*.*, 149.*.*.*, 150.*.*.*, 151.*.*.*, 152.*.*.*, 153.*.*.*, 154.*.*.*, 155.*.*.*, 156.*.*.*, 157.*.*.*, 158.*.*.*, 159.*.*.*, 160.*.*.*, 161.*.*.*, 162.*.*.*, 163.*.*.*, 164.*.*.*, 165.*.*.*, 166.*.*.*, 167.*.*.*, 168.*.*.*, 169.*.*.*, 170.*.*.*, 171.*.*.*, 172.*.*.*, 173.*.*.*, 174.*.*.*, 175.*.*.*, 176.*.*.*, 177.*.*.*, 178.*.*.*, 179.*.*.*, 180.*.*.*, 181.*.*.*, 182.*.*.*, 183.*.*.*, 184.*.*.*, 185.*.*.*, 186.*.*.*, 187.*.*.*, 188.*.*.*, 189.*.*.*, 190.*.*.*, 191.*.*.*]
Total block count: 827
Merged back to minimal sequential blocks again:
[128.1.0.1-255, 128.1.1-255.*, 128.2-255.*.*, 129-191.*.*.*]
Merged back to range again:
[128.1.0.1 -> 191.255.255.255]