Skip to content

Code Examples 3: Subnetting and Other Subnet Operations

Sean C Foley edited this page Oct 6, 2023 · 49 revisions

Iterate through Subnet

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

Iterate through Subnet Boundaries

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

Sequential or Parallel Operations on Subnets using Streams

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]

From Start and End Address, Get a Minimal List of CIDR Blocks Spanning the Range

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]

From Start and End Address, Get a Single CIDR Block Covering Both

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

Merge a List of Addresses or Subnets into a Minimal List of CIDR Blocks

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]

Break a CIDR Prefix Block into Direct Component Blocks

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]

Iteratively Break a CIDR Prefix Block into Component Blocks

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

Derive New Subnet from Existing CIDR Subnet

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

Remove Address or Subnet from Subnet

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]

Variable Length Subnetting

In this example is code that can produce the subnetting shown in some online examples of subnetting, here and here as well as 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.

Automatic Subnetting

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

From Start and End Address, Get a Minimal List of Spanning Subnets, each with Segments either Single-Valued or Full-Range

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]