Skip to content

Code Examples 2: Subnet Containment, Matching, Comparing

Sean C Foley edited this page Mar 19, 2024 · 31 revisions

Check if Subnet contains Address or Subnet

Containment and equality checks work the same regardless of whether something is a subnet or address. Generally, you can use IPAddress.contains for any and all containment checks:

boolean containing = new IPAddressString("1::3-4:5-6").getAddress().contains(
    new IPAddressString("1::4:5").getAddress()); //true

But there are other options and other considerations.

Containment within a CIDR prefix block

The most typical scenario is checking for containment within a CIDR prefix block like 10.10.20.0/30. The address 10.10.20.0/30 is parsed as a CIDR prefix block subnet because the host, the last two bits, are zero. Because such addresses are parsed directly to CIDR block subnets, IPAddressString can be used to immediately check for containment, which is a bit quicker and avoids IPAddress instance creation.

contains("10.10.20.0/30", "10.10.20.3");
contains("10.10.20.0/30", "10.10.20.5");
contains("10.10.20.0/30", "10.10.20.0/31");
contains("1::/64", "1::1");
contains("1::/64", "2::1");
contains("1::/64", "1::/32");
contains("1::/64", "1::/112");
contains("1::3-4:5-6", "1::4:5");   	
contains("1-2::/64", "2::");
contains("bla", "foo");

static void contains(String network, String address) {
	IPAddressString one = new IPAddressString(network);
	IPAddressString two = new IPAddressString(address);
	System.out.println(one +  " contains " + two + " " + one.contains(two));
}

Output:

10.10.20.0/30 contains 10.10.20.3 true
10.10.20.0/30 contains 10.10.20.5 false
10.10.20.0/30 contains 10.10.20.0/31 true
1::/64 contains 1::1 true
1::/64 contains 2::1 false
1::/64 contains 1::/32 false
1::/64 contains 1::/112 true
1::3-4:5-6 contains 1::4:5 true
1-2::/64 contains 2:: true
bla contains foo false

Containment within the enclosing CIDR prefix block

Suppose you had the address 10.10.20.1/30 instead. This is not parsed as a subnet, because the host, the last two bits, are not zero. This is parsed as the individual address 10.10.20.1.

If you wish to check the associated prefix block (the block of addresses sharing the same prefix) for containment of another subnet or address, you must get the associated prefix block, and then check that block for containment. Use toPrefixBlock() to convert any address or subnet to the associated prefix block subnet first. Of course, this also works for subnets that are already CIDR prefix blocks. If you want to get an exact look at the network and host bits, use toBinaryString().

String prefixedAddrStr = "10.10.20.1/30";
String addrStr = "10.10.20.3";
contains(prefixedAddrStr, addrStr);
enclosingBlockContains(prefixedAddrStr, addrStr);
		
prefixedAddrStr = "1::f/64";
addrStr = "1::1";
contains(prefixedAddrStr, addrStr);
enclosingBlockContains(prefixedAddrStr, addrStr);

static void enclosingBlockContains(String network, String address) {
	IPAddressString one = new IPAddressString(network);
	IPAddress oneAddr = one.getAddress().toPrefixBlock();
	IPAddressString two = new IPAddressString(address);
	IPAddress twoAddr = two.getAddress();
	System.out.println(one + " block " + oneAddr + " contains " + 
		twoAddr + " " + oneAddr.contains(twoAddr));
}

Output:

10.10.20.1/30 contains 10.10.20.3 false
10.10.20.1/30 block 10.10.20.0/30 contains 10.10.20.3 true
1::f/64 contains 1::1 false
1::f/64 block 1::/64 contains 1::1 true

Comparing just the prefix

Alternatively, you can simply check just the CIDR prefixes for containment using prefixContains or equality using prefixEquals of IPAddressString. The latter is what many libraries do in all cases, since those other libraries do not support the additional formats supported by this library. These two options ignore the CIDR host entirely.

String prefixedAddrStr = "10.10.20.1/30";
String addrStr = "10.10.20.3";
contains(prefixedAddrStr, addrStr);
prefixContains(prefixedAddrStr, addrStr);
		
prefixedAddrStr = "1::f/64";
addrStr = "1::1";
contains(prefixedAddrStr, addrStr);
prefixContains(prefixedAddrStr, addrStr);

static void prefixContains(String network, String address) {
	IPAddressString one = new IPAddressString(network);
	IPAddressString two = new IPAddressString(address);
	System.out.println(one + " prefix contains " + two + " " + 
		one.prefixContains(two));
}

Output:

10.10.20.1/30 contains 10.10.20.3 false
10.10.20.1/30 prefix contains 10.10.20.3 true
1::f/64 contains 1::1 false
1::f/64 prefix contains 1::1 true

Get Position of Address in Subnet

To find the position of an address relative to a subnet, we use the enumerate method. The method is the inverse of the increment method, as shown with this pseudo-code:

subnet.increment(subnet.enumerate(address)) -> address
subnet.enumerate(subnet.increment(value)) -> value

Applying the enumerate method returns the position as a BigInteger. The argument to enumerate must be an individual address and not multi-valued, otherwise null is returned.

static PositionResult findPosition(String subnetStr, String addressStr) {
	IPAddress subnet = new IPAddressString(subnetStr).getAddress();
	IPAddress address = new IPAddressString(addressStr).getAddress();
	BigInteger position = subnet.enumerate(address);
	return new PositionResult(subnet, address, position);
}

static void printPosition(String subnetStr, String addressStr) {
	System.out.println(findPosition(subnetStr, addressStr));
}

Simply for the purposes of this example, we provide a helper class PositionResult to illustrate interpreting the values returned from enumerate.

static class PositionResult {
	final IPAddress subnet, address;
	final BigInteger position;
	final String description;
	
	PositionResult(IPAddress subnet, IPAddress address, 
			BigInteger position) {
		this.subnet = subnet;
		this.address = address;
		this.position = position;
		if(subnet.isMultiple()) {
			if(position == null) {
				description = "The address " + address + 
					" is within the bounds but is not " +
					"contained by the subnet " + subnet;
			} else if(position.signum() < 0) {
				description = "The address " + address + 
					diff(position) + " the smallest element " + 
					subnet.getLower() + " of subnet "  + subnet;
			} else {
				BigInteger count = subnet.getCount();
				if(position.compareTo(count) < 0) {
					description = "The address " + address +
						" is at index " + position + 
						" in the subnet " + subnet;
				} else {
					description = "The address " + address + 
						diff(position.subtract(count)) + 
						" the largest element " + 
						subnet.getUpper() + 
						" of subnet " + subnet;
				}
			}
		} else {
			description = "The address " + address + diff(position) +
				" the address " + subnet;
		}
	}

	private static String diff(BigInteger diff) {
		if(diff.signum() == 0) {
			return " matches";
		} else if(diff.compareTo(BigInteger.ONE) > 0) {
			return " is " + diff + " addresses above";
		} else if(diff.compareTo(BigInteger.ONE.negate()) < 0) {
			return " is " + diff.negate() + " addresses below";
		} else if(diff.signum() == 1) {
			return " is 1 address above";
		}
		return " is 1 address below";
	}

	public String toString() {
		return description;
	}
		
	public boolean contains() {
		return position.signum() >= 0 && 
			position.compareTo(subnet.getCount()) < 0;
	}
}

Here are some example results. Here are a couple of cases in which the address lies above the subnet bounds.

printPosition("1000::/125", "1000::15ff");
printPosition("1000-2000::/125", "2000::15ff");

Output:

The address 1000::15ff is 5623 addresses above the largest element 1000::7/125 of subnet 1000::/125
The address 2000::15ff is 5623 addresses above the largest element 2000::7/125 of subnet 1000-2000::/125

Here are a couple of cases in which the address is within the subnet.

printPosition("1000::/125", "1000::7");
printPosition("1000-2000::/125", "2000::7");

Output:

The address 1000::7 is at index 7 in the subnet 1000::/125
The address 2000::7 is at index 32775 in the subnet 1000-2000::/125

Here is a case in which the address is neither above, nor below, nor within the subnet.

printPosition("1000-2000::/125", "1a00::8");

Output:

The address 1a00::8 is within the bounds but is not contained by the subnet 1000-2000::/125

Switching to IPv4, a few example results:

printPosition("0.0.0.0/16", "0.0.2.2");
printPosition("0.0.1-3.1-3", "0.0.2.2");
printPosition("0.0.2.0/24", "0.0.2.2");

Output:

The address 0.0.2.2 is at index 514 in the subnet 0.0.0.0/16
The address 0.0.2.2 is at index 4 in the subnet 0.0.1-3.1-3
The address 0.0.2.2 is at index 2 in the subnet 0.0.2.0/24

The enumerate method provides one more way of checking containment, although not the most efficient. You can check if the index of an address in a subnet is non-negative and less than the subnet element count.

printContains("10.10.20.0/30", "10.10.20.3");
printContains("10.10.20.0/30", "10.10.20.5");

static void printContains(String subnetStr, String addressStr) {
	PositionResult result = findPosition(subnetStr, addressStr);
	System.out.println(result.subnet +  " contains " + result.address + " " + 
			result.contains());
}

Output:

10.10.20.0/30 contains 10.10.20.3 true
10.10.20.0/30 contains 10.10.20.5 false

Get Distance between Two Addresses

An address can be considered a subnet with a single element. In this library the same type is used for both.

As indicated in the previous example, if an address is above the bounds of a subnet, then the subnet's enumerate method returns the distance from the address to the subnet's upper bound added to 1 less than the subnet size. If an address is below the bounds of a subnet, then the enumerate method returns the distance from the address to the subnet's lower bound.

Translating this to a one-element subnet, the enumerate method returns the distance from the address to the one-element subnet, or put more simply, the distance between the two addresses.

Reusing code from the previous example:

printPosition("1a00::ffff", "1a00::ffff");
printPosition("1a00::ffff", "1a00::1:0");
printPosition("1a00::8", "1a00::ffff");

Output:

The address 1a00::ffff matches the address 1a00::ffff
The address 1a00::1:0 is 1 address above the address 1a00::ffff
The address 1a00::ffff is 65527 addresses above the address 1a00::8

Switching the two address args gives the negative:

printPosition("1a00::ffff", "1a00::8");

Output:

The address 1a00::8 is 65527 addresses below the address 1a00::ffff

Here is the full IPv4 address space size, and the same for IPv6:

printPosition("0.0.0.0", "255.255.255.255");
printPosition("::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");

Output:

The address 255.255.255.255 is 4294967295 addresses above the address 0.0.0.0
The address ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff is 340282366920938463463374607431768211455 addresses above the address ::

The size of the IPv4 address space can fit into a long. The enumerateIPv4 method in IPv4Address returns a long.

IPv4Address subnet = new IPAddressString("0.0.0.0").getAddress().toIPv4();
IPv4Address address = new IPAddressString("255.255.255.255").getAddress().toIPv4();
long position = subnet.enumerateIPv4(address);
System.out.println("255.255.255.255 position in 0.0.0.0 is " + position);

Output:

255.255.255.255 position in 0.0.0.0 is 4294967295

Check if Address or Subnet Falls within Address Range

static void range(String lowerStr, String upperStr, String str)
		throws AddressStringException  {
	IPAddress lower = new IPAddressString(lowerStr).toAddress();
	IPAddress upper = new IPAddressString(upperStr).toAddress();
	IPAddress addr = new IPAddressString(str).toAddress();
	IPAddressSeqRange range = lower.toSequentialRange(upper);
	System.out.println(range + " contains " + addr + " " + 
		range.contains(addr));
}

range("192.200.0.0", "192.255.0.0", "192.200.3.0");
range("2001:0db8:85a3::8a2e:0370:7334", "2001:0db8:85a3::8a00:ff:ffff",
	"2001:0db8:85a3::8a03:a:b");
range("192.200.0.0", "192.255.0.0", "191.200.3.0");
range("2001:0db8:85a3::8a2e:0370:7334", "2001:0db8:85a3::8a00:ff:ffff",
	"2002:0db8:85a3::8a03:a:b");

Output:

192.200.0.0 -> 192.255.0.0 contains 192.200.3.0 true
2001:db8:85a3::8a00:ff:ffff -> 2001:db8:85a3::8a2e:370:7334 contains 2001:db8:85a3::8a03:a:b true
192.200.0.0 -> 192.255.0.0 contains 191.200.3.0 false
2001:db8:85a3::8a00:ff:ffff -> 2001:db8:85a3::8a2e:370:7334 contains 2002:db8:85a3::8a03:a:b false

Select CIDR Prefix Block Subnets Containing an Address

Suppose we had some CIDR prefix blocks, and we wanted to know which ones contained a specific address. The address trie data structure allows for containment or equality checks with many prefix block subnets or addresses at once in constant time.

Starting with an array of address strings, we convert to a list of address objects.

String addrStrs[] = {
	"62.109.3.240", "91.87.64.89", "209.97.191.87", "195.231.4.132",
	"212.253.90.111", "192.250.200.250", "26.154.36.255", "82.200.127.52",
	"103.192.63.244", "212.253.90.11", "109.197.13.33", "141.101.231.203",
	"212.74.202.30", "88.250.248.235", "188.18.253.203", "122.114.118.63",
	"186.169.171.32", "46.56.236.163", "195.231.4.217", "109.197.13.33",
	"109.197.13.149", "212.253.90.11", "194.59.250.237", "37.99.32.144",
	"185.148.82.122", "141.101.231.182", "37.99.32.76", "62.192.232.40",
	"62.109.11.226"};
ArrayList<IPv4Address> ipv4Addresses = new ArrayList<>();
for(String str : addrStrs) {
	ipv4Addresses.add(new IPAddressString(str).getAddress().toIPv4());
}

For this example, the CIDR prefix blocks will be /24 block of every address. We construct a trie, adding those blocks to it:

int prefixBlockLen = 24;
IPv4AddressTrie blocksTrie = new IPv4AddressTrie();
for(IPv4Address addr : ipv4Addresses) {
	blocksTrie.add(addr.toPrefixBlock(prefixBlockLen));
}
System.out.println("From the list of " + addrStrs.length +
	" addresses,\nthere are " + blocksTrie.size() +  
	" unique address blocks of prefix length " +
	prefixBlockLen + ":\n" + blocksTrie);

Output:

From the list of 29 addresses,
there are 22 unique address blocks of prefix length 24:

○ 0.0.0.0/0 (22)
├─○ 0.0.0.0/1 (12)
│ ├─○ 0.0.0.0/2 (6)
│ │ ├─● 26.154.36.0/24 (1)
│ │ └─○ 32.0.0.0/3 (5)
│ │   ├─○ 32.0.0.0/4 (2)
│ │   │ ├─● 37.99.32.0/24 (1)
│ │   │ └─● 46.56.236.0/24 (1)
│ │   └─○ 62.0.0.0/8 (3)
│ │     ├─○ 62.109.0.0/20 (2)
│ │     │ ├─● 62.109.3.0/24 (1)
│ │     │ └─● 62.109.11.0/24 (1)
│ │     └─● 62.192.232.0/24 (1)
│ └─○ 64.0.0.0/2 (6)
│   ├─○ 80.0.0.0/4 (3)
│   │ ├─● 82.200.127.0/24 (1)
│   │ └─○ 88.0.0.0/6 (2)
│   │   ├─● 88.250.248.0/24 (1)
│   │   └─● 91.87.64.0/24 (1)
│   └─○ 96.0.0.0/3 (3)
│     ├─○ 96.0.0.0/4 (2)
│     │ ├─● 103.192.63.0/24 (1)
│     │ └─● 109.197.13.0/24 (1)
│     └─● 122.114.118.0/24 (1)
└─○ 128.0.0.0/1 (10)
  ├─○ 128.0.0.0/2 (4)
  │ ├─● 141.101.231.0/24 (1)
  │ └─○ 184.0.0.0/5 (3)
  │   ├─○ 184.0.0.0/6 (2)
  │   │ ├─● 185.148.82.0/24 (1)
  │   │ └─● 186.169.171.0/24 (1)
  │   └─● 188.18.253.0/24 (1)
  └─○ 192.0.0.0/3 (6)
    ├─○ 192.0.0.0/6 (3)
    │ ├─● 192.250.200.0/24 (1)
    │ └─○ 194.0.0.0/7 (2)
    │   ├─● 194.59.250.0/24 (1)
    │   └─● 195.231.4.0/24 (1)
    └─○ 208.0.0.0/5 (3)
      ├─● 209.97.191.0/24 (1)
      └─○ 212.0.0.0/8 (2)
        ├─● 212.74.202.0/24 (1)
        └─● 212.253.90.0/24 (1)

With a single operation we determine whether a block contains address 188.18.253.203:

IPAddressString toFindAddrStr = new IPAddressString("188.18.253.203");
IPv4Address toFindBlockFor = toFindAddrStr.getAddress().toIPv4();
IPv4TrieNode containingNode = blocksTrie.elementsContaining(toFindBlockFor);
if(containingNode != null) {
	System.out.println("For address " + toFindBlockFor + 
		" containing block is " + containingNode.getKey() + "\n");
}

Output:

For address 188.18.253.203 containing block is 188.18.253.0/24

Select Addresses Contained by a CIDR Prefix Block Subnet

Using the addresses from the previous example, we construct a trie from those addresses:

IPv4AddressTrie addressTrie = new IPv4AddressTrie();
for(IPv4Address addr : ipv4Addresses) {
	addressTrie.add(addr);
}
System.out.println("There are " + addressTrie.size() + 
	" unique addresses from the list of " + addrStrs.length + ":\n" + 
	addressTrie);

Output:

There are 27 unique addresses from the list of 29:

○ 0.0.0.0/0 (27)
├─○ 0.0.0.0/1 (14)
│ ├─○ 0.0.0.0/2 (7)
│ │ ├─● 26.154.36.255 (1)
│ │ └─○ 32.0.0.0/3 (6)
│ │   ├─○ 32.0.0.0/4 (3)
│ │   │ ├─○ 37.99.32.0/24 (2)
│ │   │ │ ├─● 37.99.32.76 (1)
│ │   │ │ └─● 37.99.32.144 (1)
│ │   │ └─● 46.56.236.163 (1)
│ │   └─○ 62.0.0.0/8 (3)
│ │     ├─○ 62.109.0.0/20 (2)
│ │     │ ├─● 62.109.3.240 (1)
│ │     │ └─● 62.109.11.226 (1)
│ │     └─● 62.192.232.40 (1)
│ └─○ 64.0.0.0/2 (7)
│   ├─○ 80.0.0.0/4 (3)
│   │ ├─● 82.200.127.52 (1)
│   │ └─○ 88.0.0.0/6 (2)
│   │   ├─● 88.250.248.235 (1)
│   │   └─● 91.87.64.89 (1)
│   └─○ 96.0.0.0/3 (4)
│     ├─○ 96.0.0.0/4 (3)
│     │ ├─● 103.192.63.244 (1)
│     │ └─○ 109.197.13.0/24 (2)
│     │   ├─● 109.197.13.33 (1)
│     │   └─● 109.197.13.149 (1)
│     └─● 122.114.118.63 (1)
└─○ 128.0.0.0/1 (13)
  ├─○ 128.0.0.0/2 (5)
  │ ├─○ 141.101.231.128/25 (2)
  │ │ ├─● 141.101.231.182 (1)
  │ │ └─● 141.101.231.203 (1)
  │ └─○ 184.0.0.0/5 (3)
  │   ├─○ 184.0.0.0/6 (2)
  │   │ ├─● 185.148.82.122 (1)
  │   │ └─● 186.169.171.32 (1)
  │   └─● 188.18.253.203 (1)
  └─○ 192.0.0.0/3 (8)
    ├─○ 192.0.0.0/6 (4)
    │ ├─● 192.250.200.250 (1)
    │ └─○ 194.0.0.0/7 (3)
    │   ├─● 194.59.250.237 (1)
    │   └─○ 195.231.4.128/25 (2)
    │     ├─● 195.231.4.132 (1)
    │     └─● 195.231.4.217 (1)
    └─○ 208.0.0.0/5 (4)
      ├─● 209.97.191.87 (1)
      └─○ 212.0.0.0/8 (3)
        ├─● 212.74.202.30 (1)
        └─○ 212.253.90.0/25 (2)
          ├─● 212.253.90.11 (1)
          └─● 212.253.90.111 (1)

With a single operation we determine what addresses are contained in 62.109.0.0/16:

IPAddressString toFindAddrsInStr = new IPAddressString("62.109.0.0/16");
IPv4Address toFindAddrsIn = toFindAddrsInStr.getAddress().toIPv4();
IPv4TrieNode containedNode = addressTrie.elementsContainedBy(toFindAddrsIn);
if(containedNode != null) {
	System.out.println("For block " + toFindAddrsIn + 
		" contained addresses are:\n" + 
		containedNode.toTreeString(true, true));
}	

Output:

For block 62.109.0.0/16 contained addresses are:

○ 62.109.0.0/20 (2)
├─● 62.109.3.240 (1)
└─● 62.109.11.226 (1)

Trying the larger /8 block and printing the results in sorted order:

IPv4Address largerBlock = toFindAddrsIn.adjustPrefixBySegment(false);
containedNode = addressTrie.elementsContainedBy(largerBlock);
if(containedNode != null) {
	System.out.println("For block " + largerBlock + " 
		contained addresses are:\n" + 
		containedNode.toTreeString(true, true));
	System.out.print("In order they are:");
	for(IPv4Address addr : containedNode) {
		System.out.print("\n" + addr);
	}
}

Output:

For block 62.0.0.0/8 contained addresses are:

○ 62.0.0.0/8 (3)
├─○ 62.109.0.0/20 (2)
│ ├─● 62.109.3.240 (1)
│ └─● 62.109.11.226 (1)
└─● 62.192.232.40 (1)

In order they are:
62.109.3.240
62.109.11.226
62.192.232.40

Get Prefix Blocks Common to List of Addresses

This is precisely how the address trie works, it organizes by common prefix. Using the address trie from the previous example, we iterate by block size to go from largest blocks (smallest prefix) to smallest blocks, and then skip the addresses.

System.out.println(
	"\nThe common prefixes, in order from largest block size to smallest:");
Iterator<IPv4TrieNode> nodeIterator = addressTrie.blockSizeAllNodeIterator(true);
while(nodeIterator.hasNext()) {
	IPv4TrieNode next = nodeIterator.next();
	IPv4Address nextAddr = next.getKey();
	if(nextAddr.isPrefixed()) {
		System.out.println(nextAddr);
	} else break;
}

Output:

The common prefixes, in order from largest block size to smallest:
0.0.0.0/0
0.0.0.0/1
128.0.0.0/1
0.0.0.0/2
64.0.0.0/2
128.0.0.0/2
32.0.0.0/3
96.0.0.0/3
192.0.0.0/3
32.0.0.0/4
80.0.0.0/4
96.0.0.0/4
184.0.0.0/5
208.0.0.0/5
88.0.0.0/6
184.0.0.0/6
192.0.0.0/6
194.0.0.0/7
62.0.0.0/8
212.0.0.0/8
62.109.0.0/20
37.99.32.0/24
109.197.13.0/24
141.101.231.128/25
195.231.4.128/25
212.253.90.0/25

Longest Prefix Match

We find the subnet with the longest matching prefix, which is the the smallest subnet containing an address. First we construct a trie from the list of subnets to be matched against:

static void add(IPv4AddressTrie trie, String addrStr) {
	trie.add(new IPAddressString(addrStr).getAddress().toIPv4());
}

IPv4AddressTrie trie = new IPv4AddressTrie();
trie.getRoot().setAdded(); // makes 0.0.0.0/0 an added node
add(trie, "127.0.0.1");
add(trie, "10.0.2.15");
add(trie, "8.9.8.12/31");
add(trie, "8.9.8.12/30");
add(trie, "8.9.8.9");
add(trie, "8.9.8.10");
add(trie, "8.9.8.8/29");
add(trie, "8.9.8.0/29");
add(trie, "8.9.8.0/24");
		
System.out.println(trie);

Here is the trie:

● 0.0.0.0/0 (10)
└─○ 0.0.0.0/1 (9)
  ├─○ 8.0.0.0/6 (8)
  │ ├─● 8.9.8.0/24 (7)
  │ │ └─○ 8.9.8.0/28 (6)
  │ │   ├─● 8.9.8.0/29 (1)
  │ │   └─● 8.9.8.8/29 (5)
  │ │     ├─○ 8.9.8.8/30 (2)
  │ │     │ ├─● 8.9.8.9 (1)
  │ │     │ └─● 8.9.8.10 (1)
  │ │     └─● 8.9.8.12/30 (2)
  │ │       └─● 8.9.8.12/31 (1)
  │ └─● 10.0.2.15 (1)
  └─● 127.0.0.1 (1)

We can use the longestPrefixMatch method to get the desired matching element. We can also use the elementsContaining method to get a list of all containing subnets (all matching prefixes, not just the longest).

longPrefMatch(trie, "8.9.8.10");
longPrefMatch(trie, "8.9.8.11");
longPrefMatch(trie, "8.9.8.12");
longPrefMatch(trie, "8.9.8.13");
longPrefMatch(trie, "8.9.8.14");

static void longPrefMatch(IPv4AddressTrie trie, String addrStr) {
	IPv4Address addr = new IPAddressString(addrStr).getAddress().toIPv4();
	IPv4Address result = trie.longestPrefixMatch(addr);
	System.out.println("Longest prefix match for " + addr + " is " + result);
		
	IPv4TrieNode node = trie.elementsContaining(addr);
	IPv4AddressTrie listTrie = node.asNewTrie();
	System.out.print("All matching prefixes: " + listTrie + "\n");
}

Output:

Longest prefix match for 8.9.8.10 is 8.9.8.10
All matching prefixes: 
● 0.0.0.0/0 (4)
└─● 8.9.8.0/24 (3)
  └─● 8.9.8.8/29 (2)
    └─● 8.9.8.10 (1)

Longest prefix match for 8.9.8.11 is 8.9.8.8/29
All matching prefixes: 
● 0.0.0.0/0 (3)
└─● 8.9.8.0/24 (2)
  └─● 8.9.8.8/29 (1)

Longest prefix match for 8.9.8.12 is 8.9.8.12/31
All matching prefixes: 
● 0.0.0.0/0 (5)
└─● 8.9.8.0/24 (4)
  └─● 8.9.8.8/29 (3)
    └─● 8.9.8.12/30 (2)
      └─● 8.9.8.12/31 (1)

Longest prefix match for 8.9.8.13 is 8.9.8.12/31
All matching prefixes: 
● 0.0.0.0/0 (5)
└─● 8.9.8.0/24 (4)
  └─● 8.9.8.8/29 (3)
    └─● 8.9.8.12/30 (2)
      └─● 8.9.8.12/31 (1)

Longest prefix match for 8.9.8.14 is 8.9.8.12/30
All matching prefixes: 
● 0.0.0.0/0 (4)
└─● 8.9.8.0/24 (3)
  └─● 8.9.8.8/29 (2)
    └─● 8.9.8.12/30 (1)

Select Addresses and Subnets within Arbitrary Address Range

Starting with a list of address strings, we convert to addresses. They will be the addresses used for the example.

String ipv6AddrStrs [] = {
	"2001:4860:4860::8888", "2001:4860:4860::8844", "2620:fe::fe", 
	"2620:fe::9", "2620:119:35::35", "2620:119:53::53",
	"2606:4700:4700::1111", "2606:4700:4700::1001", "2a0d:2a00:1::2",
	"2a0d:2a00:2::2", "2620:74:1b::1:1", "2620:74:1c::2:2",
	"2001:4800:780e:510:a8cf:392e:ff04:8982",
	"2001:4801:7825:103:be76:4eff:fe10:2e49",
	"2a00:5a60::ad1:0ff", "2a00:5a60::ad2:0ff",
};
List<IPv6Address> ipv6Addresses = new ArrayList<>();
for(String str : ipv6AddrStrs) {
	ipv6Addresses.add(new IPAddressString(str).getAddress().toIPv6());
}

The simplest way is to sort the list, here we could have sorted the list directly but we convert to an array instead. The library offers multiple comparators based on subnet size and address values, but when comparing individual addresses they are generally equivalent, they compare by address value. So here we choose the default comparator. Once sorted, we can iterate through the array first comparing with the lower bound of our range, and then the upper, which are 2610:: and 2a08:: in this example.

IPv6Address lowerBound = new IPAddressString("2610::").getAddress().toIPv6();
IPv6Address upperBound = new IPAddressString("2a08::").getAddress().toIPv6();
IPv6Address arr[] = ipv6Addresses.toArray(new IPv6Address[ipv6Addresses.size()]);
Arrays.sort(arr, Address.DEFAULT_ADDRESS_COMPARATOR);
System.out.println("The selected addresses are:");
for(int i = 0; i < arr.length; i++)  { 
	if(arr[i].compareTo(lowerBound) >= 0) {
		for(int j = i; j < arr.length; j++)  { 
			IPv6Address addr = (arr[j]);
			if(addr.compareTo(upperBound) > 0) {
				break;
			}
			System.out.println(addr);
		}
		break;
	}
}

Output:

The selected addresses are:
2620:74:1b::1:1
2620:74:1c::2:2
2620:fe::9
2620:fe::fe
2620:119:35::35
2620:119:53::53
2a00:5a60::ad1:ff
2a00:5a60::ad2:ff

Another way is to use an address trie. Create an address trie and add the addresses to it.

IPv6AddressTrie addressTrie = new IPv6AddressTrie();
for(IPv6Address addr : ipv6Addresses) {
	addressTrie.add(addr);
}
System.out.println("There are " + addressTrie.size() +
	" unique addresses in the trie:\n" + addressTrie);
		

Output:

There are 16 unique addresses in the trie:

○ ::/0 (16)
└─○ 2000::/4 (16)
  ├─○ 2000::/5 (12)
  │ ├─○ 2001:4800::/25 (4)
  │ │ ├─○ 2001:4800::/31 (2)
  │ │ │ ├─● 2001:4800:780e:510:a8cf:392e:ff04:8982 (1)
  │ │ │ └─● 2001:4801:7825:103:be76:4eff:fe10:2e49 (1)
  │ │ └─○ 2001:4860:4860::8800/120 (2)
  │ │   ├─● 2001:4860:4860::8844 (1)
  │ │   └─● 2001:4860:4860::8888 (1)
  │ └─○ 2600::/10 (8)
  │   ├─○ 2606:4700:4700::1000/119 (2)
  │   │ ├─● 2606:4700:4700::1001 (1)
  │   │ └─● 2606:4700:4700::1111 (1)
  │   └─○ 2620::/23 (6)
  │     ├─○ 2620::/24 (4)
  │     │ ├─○ 2620:74:18::/45 (2)
  │     │ │ ├─● 2620:74:1b::1:1 (1)
  │     │ │ └─● 2620:74:1c::2:2 (1)
  │     │ └─○ 2620:fe::/120 (2)
  │     │   ├─● 2620:fe::9 (1)
  │     │   └─● 2620:fe::fe (1)
  │     └─○ 2620:119::/41 (2)
  │       ├─● 2620:119:35::35 (1)
  │       └─● 2620:119:53::53 (1)
  └─○ 2a00::/12 (4)
    ├─○ 2a00:5a60::ad0:0/110 (2)
    │ ├─● 2a00:5a60::ad1:ff (1)
    │ └─● 2a00:5a60::ad2:ff (1)
    └─○ 2a0d:2a00::/46 (2)
      ├─● 2a0d:2a00:1::2 (1)
      └─● 2a0d:2a00:2::2 (1)

The arbitrary address range we chose for the example is 2610:: to 2a08::, so we will now show how to find the addresses within that range, using the trie. We convert the trie to a sorted set so that we can obtain a subset using the range:

AddressTrieSet<IPv6Address> set = addressTrie.asSet();
AddressTrieSet<IPv6Address> reducedSet = set.subSet(lowerBound, upperBound);

The subset has the addresses we are looking for. It is backed by the original trie. We can create a new trie so that we can alter it without affecting the original trie.

System.out.println("The reduced sorted set of size " + reducedSet.size() + 
	" has elements\n" + reducedSet);
		
AddressTrie<IPv6Address> reducedTrie = reducedSet.asTrie();
System.out.println("\nThe trimmed trie is:" + reducedTrie);
System.out.println("The compact trie view is:" + 
	reducedTrie.toAddedNodesTreeString());

Output:

The reduced sorted set of size 8 has elements
[2620:74:1b::1:1, 2620:74:1c::2:2, 2620:fe::9, 2620:fe::fe, 2620:119:35::35, 2620:119:53::53, 2a00:5a60::ad1:ff, 2a00:5a60::ad2:ff]

The trimmed trie is:
○ ::/0 (8)
└─○ 2000::/4 (8)
  ├─○ 2620::/23 (6)
  │ ├─○ 2620::/24 (4)
  │ │ ├─○ 2620:74:18::/45 (2)
  │ │ │ ├─● 2620:74:1b::1:1 (1)
  │ │ │ └─● 2620:74:1c::2:2 (1)
  │ │ └─○ 2620:fe::/120 (2)
  │ │   ├─● 2620:fe::9 (1)
  │ │   └─● 2620:fe::fe (1)
  │ └─○ 2620:119::/41 (2)
  │   ├─● 2620:119:35::35 (1)
  │   └─● 2620:119:53::53 (1)
  └─○ 2a00:5a60::ad0:0/110 (2)
    ├─● 2a00:5a60::ad1:ff (1)
    └─● 2a00:5a60::ad2:ff (1)

The compact trie view is 
○ ::/0
├─● 2620:74:1b::1:1
├─● 2620:74:1c::2:2
├─● 2620:fe::9
├─● 2620:fe::fe
├─● 2620:119:35::35
├─● 2620:119:53::53
├─● 2a00:5a60::ad1:ff
└─● 2a00:5a60::ad2:ff

Select Address Ranges Containing Arbitrary Address or Subnet

We start with 28 random address ranges.

String ipRangeStrs[][] = new String[][]{
	{"26.154.36.255", "82.200.127.52"}, {"26.154.36.255", "192.250.200.250"},
	{"37.99.32.76", "62.192.232.40"}, {"37.99.32.76", "141.101.231.182"},
	{"37.99.32.144", "185.148.82.122"}, {"37.99.32.144", "194.59.250.237"},
	{"46.56.236.163", "186.169.171.32"}, {"46.56.236.163", "195.231.4.217"},
	{"62.109.3.240", "91.87.64.89"}, {"62.109.11.226", "62.192.232.40"},
	{"82.200.127.52", "103.192.63.244"}, {"88.250.248.235", "188.18.253.203"},
	{"88.250.248.235", "212.74.202.30"}, {"91.87.64.89", "209.97.191.87"},
	{"103.192.63.244", "212.253.90.11"}, {"109.197.13.33", "109.197.13.149"},
	{"109.197.13.33", "141.101.231.203"}, {"109.197.13.33", "195.231.4.217"},
	{"109.197.13.33", "212.253.90.11"}, {"109.197.13.149", "212.253.90.11"},
	{"122.114.118.63", "186.169.171.32"}, {"122.114.118.63", "188.18.253.203"},
	{"141.101.231.182", "185.148.82.122"}, {"141.101.231.203", "212.74.202.30"},
	{"192.250.200.250", "212.253.90.111"}, {"194.59.250.237", "212.253.90.11"},
	{"195.231.4.132", "209.97.191.87"}, {"195.231.4.132", "212.253.90.111"},
};

We will select those ranges which contain the address 88.255.1.1.

One way is to use sequential ranges, iterating through each. This is O(n), where n is the number of ranges.

IPAddressSeqRange ipRanges[] = new IPAddressSeqRange[ipRangeStrs.length];
int i = 0;
for(String strs[] : ipRangeStrs) {
	IPAddress addr1 = new IPAddressString(strs[0]).getAddress();
	IPAddress addr2 = new IPAddressString(strs[1]).getAddress();
	IPAddressSeqRange rng = addr1.spanWithRange(addr2);
	ipRanges[i++] = rng;
}

IPAddress addrToCheck = new IPAddressString("88.255.1.1").getAddress();
int counter = 0;
for(IPAddressSeqRange range : ipRanges) {
	if(range.contains(addrToCheck)) {
		System.out.println("range " + ++counter + " " + range +
 			" contains " + addrToCheck);
	}
}

Output:

range 1 26.154.36.255 -> 192.250.200.250 contains 88.255.1.1
range 2 37.99.32.76 -> 141.101.231.182 contains 88.255.1.1
range 3 37.99.32.144 -> 185.148.82.122 contains 88.255.1.1
range 4 37.99.32.144 -> 194.59.250.237 contains 88.255.1.1
range 5 46.56.236.163 -> 186.169.171.32 contains 88.255.1.1
range 6 46.56.236.163 -> 195.231.4.217 contains 88.255.1.1
range 7 62.109.3.240 -> 91.87.64.89 contains 88.255.1.1
range 8 82.200.127.52 -> 103.192.63.244 contains 88.255.1.1
range 9 88.250.248.235 -> 188.18.253.203 contains 88.255.1.1
range 10 88.250.248.235 -> 212.74.202.30 contains 88.255.1.1

The following is an alternative algorithm, using a trie. Each search is a constant time operation O(1). Building the trie is O(n). So this is best for cases where you may be doing multiple searches to find the containing ranges, and the list of ranges is fixed, or is not changing much.

We convert each range to spanning blocks that can be added to an associative address trie, and map each block to the original range.

AssociativeAddressTrie<? extends IPAddress, IPAddressSeqRange[]> ipv4Trie = 
	new IPv4AddressAssociativeTrie<IPAddressSeqRange[]>();
@SuppressWarnings("unchecked")
AssociativeAddressTrie<IPAddress, IPAddressSeqRange[]> trie = 
	(AssociativeAddressTrie<IPAddress, IPAddressSeqRange[]>) ipv4Trie;
for(IPAddressSeqRange range : ipRanges) {
	IPAddress blocks[] = range.spanWithPrefixBlocks();
	for(IPAddress block : blocks) {
		trie.remap(block, 
			existing -> {
				// make the node point to the original range(s)
				if(existing == null) {
					return new IPAddressSeqRange[]{range};
				} else {
					int length = existing.length;
					IPAddressSeqRange newRanges[] = 
						new IPAddressSeqRange[length + 1];
					System.arraycopy(existing, 0, 
						newRanges, 0, existing.length);
					newRanges[length] = range;
					return newRanges;
				}
			});
	}
}
System.out.println("The trie has " + trie.size() + " blocks");
//print the trie with: System.out.println(trie)

We look up the trie elements containing the address 88.255.1.1. This is a constant time operation that requires at most 32 comparisons because IPv4 address bit-size is 32. On average, it will do log(n) comparisons where n is the number of items in the trie. So in this example, the average is 9 comparisons (trie size is 421), less than the 28 comparisons required with the previous code.

From each containing block trie element, we get the associated ranges:

AssociativeTrieNode<IPAddress, IPAddressSeqRange[]> trieNode = 
	trie.elementsContaining(addrToCheck);

// ElementsContaining returns a linked list in trie form of containing blocks.
// The mapped values of each in the list are the containing ranges.
ArrayList<IPAddressSeqRange> result = new ArrayList<>();
while(trieNode != null) {
	IPAddressSeqRange ranges[] = trieNode.getValue();
	for(IPAddressSeqRange range : ranges) {
		result.add(range);
	}
	if(trieNode.getUpperSubNode() != null) {
		trieNode = trieNode.getUpperSubNode();
	} else {
		trieNode = trieNode.getLowerSubNode();
	}
}
System.out.println("The " + result.size() + " containing ranges are: " + result);

Output:

The trie has 421 blocks
The 10 containing ranges are: [26.154.36.255 -> 192.250.200.250, 37.99.32.76 -> 141.101.231.182, 37.99.32.144 -> 185.148.82.122, 37.99.32.144 -> 194.59.250.237, 46.56.236.163 -> 186.169.171.32, 46.56.236.163 -> 195.231.4.217, 82.200.127.52 -> 103.192.63.244, 62.109.3.240 -> 91.87.64.89, 88.250.248.235 -> 188.18.253.203, 88.250.248.235 -> 212.74.202.30]

Select CIDR Prefix Block Subnets Intersecting with Arbitrary Block

Given a list of CIDR blocks and addresses, we find which ones intersect with a given subnet. With CIDR blocks, intersection means either containment or equality. In other words, if two blocks intersect, then they are either the same, or one is contained in the other.

We start with some blocks and addresses:

String ipv6BlockStrs[] = {
	"2001:4860:4860::8888", 
	"2001:4860:4860::/64", 
	"2001:4860:4860::/96", 
	"2001:4860:4860:0:1::/96", 
	"2001:4860:4860::8844", 
			
	"2001:4801:7825:103:be76:4eff::/96",
	"2001:4801:7825:103:be76:4efe::/96",
	"2001:4801:7825:103:be76::/80",
	"2001:4801:7825:103:be75::/80",
	"2001:4801:7825:103:be77::/80",
	"2001:4801:7825:103:be76:4eff:fe10:2e49",
			
	"2001:4800:780e:510:a8cf:392e:ff04:8982",
	"2001:4800:7825:103:be76:4eff:fe10:2e49",
	"2001:4800:7825:103:be76:4eff:fe10:2e48",
	"2001:4800:7800::/40",
	"2001:4800:7825::/40",
	"2001:4800:7825:103::/64",
	"2001:4800:780e:510::/64",
	"2001:4800:780e:510:a8cf::/80",
	"2001:4800:780e:510:a8ff::/80",
	"2001:4800:7825:103:be76:4eff::/96",
	"2001:4800:7825:103:be76:4efe::/96",
	"2001:4800:7825:103:be76::/80",
	"2001:4800:7825:103:ce76::/80",
			
	"2620:fe::fe", 
	"2620:fe::9", 
	"2620:119:35::/64", 
	"2620:119:35::35", 
	"2620:119:35::37", 
	"2620:119:53::53",
};
		
List<IPAddress> blocks = new ArrayList<>();
for(String str : ipv6BlockStrs) {
	blocks.add(new IPAddressString(str).getAddress());
}

Our example block to check for intersection is 2001:4800:7825:103:be76:4000::/84

IPv6Address candidate = new IPAddressString("2001:4800:7825:103:be76:4000::/84").
	getAddress().toIPv6();		

One way is to simply iterate through the ranges:

List<IPAddress> containing = new ArrayList<>();
List<IPAddress> contained = new ArrayList<>();
for(IPAddress block : blocks) {
	if(block.contains(candidate)) {
		containing.add(block);
	} else if(candidate.contains(block)) {
		contained.add(block);
	}
}
System.out.println("contained by " + containing);
System.out.println("contains " + contained);

Output:

contained by [2001:4800:7800::/40, 2001:4800:7825:103::/64, 2001:4800:7825:103:be76::/80]
contains [2001:4800:7825:103:be76:4eff:fe10:2e49, 2001:4800:7825:103:be76:4eff:fe10:2e48, 2001:4800:7825:103:be76:4eff::/96, 2001:4800:7825:103:be76:4efe::/96]

Suppose you wanted to repeat this operation many times against the same set of blocks. Then comparing every block in the list, every time, is expensive, especially if the list is large or the check is repeated many times. In such cases a more efficient option is to use a trie. Create a trie with all the addresses, and use that to check for containment.

IPv6AddressTrie ipv6AddressTrie = new IPv6AddressTrie();
for(IPAddress block : blocks) {
	ipv6AddressTrie.add(block.toIPv6());
}

TrieNode<? extends IPAddress> containingBlocks, containedBlocks;
containingBlocks = ipv6AddressTrie.elementsContaining(candidate);
containedBlocks = ipv6AddressTrie.elementsContainedBy(candidate);
		
System.out.println("contained by " + containingBlocks.asNewTrie().asSet());
System.out.println("contains " + containedBlocks.asNewTrie().asSet());

Output:

contained by [2001:4800:7825:103::/64, 2001:4800:7825:103:be76::/80, 2001:4800:7800::/40]
contains [2001:4800:7825:103:be76:4efe::/96, 2001:4800:7825:103:be76:4eff::/96, 2001:4800:7825:103:be76:4eff:fe10:2e48, 2001:4800:7825:103:be76:4eff:fe10:2e49]

Select Address Ranges Intersecting with Arbitrary Range

Given a list of sequential address ranges, we find which ones intersect with a given range. This is similar to the previous example, although range intersection is not always containment, as is the case with CIDR blocks.

Much like the previous example for CIDR blocks, one way is to simply iterate through the ranges.

We reuse the array of 28 ranges in the variable ipRanges from the second-previous example. The range from 37.97.0.0 to 88.0.0.0 is this example's arbitrary range to check for intersection.

IPv4AddressSeqRange candidateRange = new IPAddressString("37.97.0.0").getAddress().toIPv4().
	spanWithRange(new IPAddressString("88.0.0.0").getAddress().toIPv4());
		
HashSet<IPAddressSeqRange> intersectingRanges = new HashSet<>();
for(IPAddressSeqRange range : ipRanges) {
	if(range.intersect(candidateRange) != null) {
		intersectingRanges.add(range);
	}
}
		
System.out.println("intersects: " + intersectingRanges);

Output:

intersects: [26.154.36.255 -> 82.200.127.52, 37.99.32.76 -> 62.192.232.40, 62.109.3.240 -> 91.87.64.89, 62.109.11.226 -> 62.192.232.40, 26.154.36.255 -> 192.250.200.250, 37.99.32.144 -> 185.148.82.122, 37.99.32.76 -> 141.101.231.182, 82.200.127.52 -> 103.192.63.244, 46.56.236.163 -> 186.169.171.32, 46.56.236.163 -> 195.231.4.217, 37.99.32.144 -> 194.59.250.237]

Much like the previous example, when you need to repeat this operation many times against the same list of ranges, and that list of ranges may be large, then comparing with each and every range in the list is expensive. Once again, a more efficient option is to use a trie.

This time, in order to use a trie, we must first convert each range to its associated spanning CIDR blocks. We can recover the original range from each by using an associative trie that maps each trie element to its originating ranges.

We created such a trie in the second-previous example. It was populated from our example ranges using the trie's remap operation and stored in the variable ipv4Trie. We can reuse that same trie here.

To use the trie we must also split the candidate range into spanning blocks:

IPv4Address blocks[] = candidateRange.spanWithPrefixBlocks();

For each such block we check for intersecting blocks in the trie.

HashSet<IPAddressSeqRange> intersectingRanges = new HashSet<>();
for(IPv4Address block : blocks) {
	AssociativeTrieNode<?, IPAddressSeqRange[]> containingBlocks, containedBlocks;
 	containingBlocks = ipv4Trie.elementsContaining(block);
	containedBlocks = ipv4Trie.elementsContainedBy(block);
			
	// We must map these intersecting blocks back to their original ranges.
	Iterator<? extends AssociativeTrieNode<?, IPAddressSeqRange[]>> nodeIterator;
	if(containingBlocks != null) {
		nodeIterator = containingBlocks.containingFirstIterator(true);
		while(nodeIterator.hasNext()) {
			IPAddressSeqRange[] ranges = nodeIterator.next().getValue();
			for(IPAddressSeqRange range : ranges) {
				intersectingRanges.add(range);
			}
		}
	}

	if(containedBlocks != null) {
		nodeIterator = containedBlocks.containingFirstIterator(true);
		while(nodeIterator.hasNext()) {
			IPAddressSeqRange[] ranges = nodeIterator.next().getValue();
			for(IPAddressSeqRange range : ranges) {
				intersectingRanges.add(range);
			}
		}
	}
}

System.out.println("intersects: " + intersectingRanges);

Output:

intersects: [26.154.36.255 -> 82.200.127.52, 37.99.32.76 -> 62.192.232.40, 62.109.3.240 -> 91.87.64.89, 62.109.11.226 -> 62.192.232.40, 26.154.36.255 -> 192.250.200.250, 37.99.32.144 -> 185.148.82.122, 37.99.32.76 -> 141.101.231.182, 82.200.127.52 -> 103.192.63.244, 46.56.236.163 -> 186.169.171.32, 46.56.236.163 -> 195.231.4.217, 37.99.32.144 -> 194.59.250.237]

Select Address Closest to Arbitrary Address

We reuse the IPv4 addresses in the variable addrStrs in a previous example and its corresponding list ArrayList<IPv4Address> ipv4Addresses. We will show different ways to find the address in the list closest to the 190.0.0.0 address.

IPv4Address candidate = new IPAddressString("190.0.0.0").getAddress().toIPv4();

One way is to check the distance to each and every address to find the closest.

printAddr(eachNear(ipv4Addresses, candidate));

static <E extends Address> E eachNear(ArrayList<E> addresses, E candidate) {
	E currentClosest = addresses.get(0);
	BigInteger currentDistance = candidate.enumerate(currentClosest).abs();
	for(int i = 1; i < addresses.size(); i++) {
		E addr = addresses.get(i);
		BigInteger distance = candidate.enumerate(addr).abs();
		if(distance.compareTo(currentDistance) < 0) {
			currentClosest = addr;
			currentDistance = distance;
		}
	}
	return currentClosest;
}

static void printAddr(Address addr) {
	System.out.println(addr);
}

Output:

188.18.253.203

If you were to repeat this operation many times with different candidates, you might want to do it more efficiently. The following options use binary search to avoid comparisons with each and every address in the list.

First we show a binary list search.

printAddr(listNear(ipv4Addresses, candidate));

static <E extends Address> E listNear(ArrayList<E> addresses, E candidate) {
	addresses = (ArrayList<E>) addresses.clone(); // don't alter original list
	Collections.sort(addresses);
	int index = Collections.binarySearch(addresses, candidate);
	if(index >= 0) {
		// exact match
		return addresses.get(index);
	}
	index = -1 - index;
	int size = addresses.size();
	if(index == 0) {
		return addresses.get(0);
	} else if(index == size) {
		return addresses.get(size - 1);
	}

	// We get the address closest from below, and the address closest from above
	E lower = addresses.get(index - 1), higher = addresses.get(index);

	// Find the closest of the two
	return closest(candidate, lower, higher);
}

static <E extends Address> E closest(E candidate, E lower, E higher) {
	// We use the enumerate method to find which one is closest
	BigInteger lowerDistance = lower.enumerate(candidate);
	BigInteger higherDistance = candidate.enumerate(higher);
	int comp = lowerDistance.compareTo(higherDistance);
	if(comp <= 0) {
		return lower;
	}
	return higher;
}

Output:

188.18.253.203

Another way is to do a binary trie search.

IPv4AddressTrie addrTrie = new IPv4AddressTrie();
for(IPv4Address addr : ipv4Addresses) {
	addrTrie.add(addr);
}
printAddr(trieNear(addrTrie, candidate));

static <E extends Address> E trieNear(AddressTrie<E> trie, E candidate) {
	// We find the address closest from below, and the address closest from above
	E lower = trie.lower(candidate), higher = trie.higher(candidate);;
	if(lower == null) {
		return higher;
	} else if(higher == null) {
		return lower;
	}
	// Find the closest of the two
	return closest(candidate, lower, higher);
}

Output:

188.18.253.203

Another way is to do a binary tree search with the java.util.TreeSet standard library type. The code is not much different than the code above using AddressTrie.

TreeSet<IPv4Address> addrTree = new TreeSet<>();
for(IPv4Address addr : ipv4Addresses) {
	addrTree.add(addr);
}
printAddr(setNear(addrTree, candidate));

static <E extends Address> E setNear(NavigableSet<E> tree, E candidate) {
	// We find the address closest from below, and the address closest from above
	E lower = tree.lower(candidate), higher = tree.higher(candidate);
	if(lower == null) {
		return higher;
	} else if(higher == null) {
		return lower;
	}
	// Find the closest of the two
	return closest(candidate, lower, higher);
}

Output:

188.18.253.203

In fact, we chose NavigableSet instead of TreeSet as the first parameter for a reason. We can actually reuse the above method with the trie, since it takes NavigableSet as an argument, and a trie can also be viewed as a NavigableSet.

printAddr(setNear(addrTrie.asSet(), candidate));

Output:

188.18.253.203

Look up Associated IP Address Data by Containing Subnet or Matching Address

Suppose we had a number of regional buildings, departments, and employees in an organization. The subnets are allocated according to these subdivisions, as shown.

String mappings[] = {
	"region R1: 1.0.0.0/8",
				
	"building A: 1.1.0.0/16",
	"building B: 1.2.0.0/16",
	"building C: 1.3.0.0/16",
			
	"department A1: 1.1.1.0/24",
	"department A2: 1.1.2.0/24",
	"department A3: 1.1.3.0/24",
			
	"employee 1: 1.1.1.1", // department A1
			
	"employee 2: 1.1.2.1", // department A2
	"employee 3: 1.1.2.2", 
	"employee 4: 1.1.2.3",
			
	"employee 5: 1.1.3.1", // department A3
	"employee 6: 1.1.3.2",
};

We start with a trie of the associated subnets.

static class Info {
	String str;
		
	Info(String str) {
		this.str = str;
	}
		
	@Override
	public String toString() {
		return str;
	}
}

static IPv4Address getAddr(String addrStr) {
	return new IPAddressString(addrStr).getAddress().toIPv4();
}

static void addToTrie(IPv4AddressAssociativeTrie<Info> trie, String entry) {
	int index = entry.indexOf(':');
	trie.put(getAddr(entry.substring(index + 1)),
		new Info(entry.substring(0, index)));
}

IPv4AddressAssociativeTrie<Info> trie = new IPv4AddressAssociativeTrie<>();
for(String entry : mappings) {
	addToTrie(trie, entry);
}
System.out.println("The trie is:");
System.out.println(trie);

Output:

The trie is:

○ 0.0.0.0/0 = null (13)
└─● 1.0.0.0/8 = region R1 (13)
  └─○ 1.0.0.0/14 = null (12)
    ├─● 1.1.0.0/16 = building A (10)
    │ └─○ 1.1.0.0/22 = null (9)
    │   ├─● 1.1.1.0/24 = department A1 (2)
    │   │ └─● 1.1.1.1 = employee 1 (1)
    │   └─○ 1.1.2.0/23 = null (7)
    │     ├─● 1.1.2.0/24 = department A2 (4)
    │     │ └─○ 1.1.2.0/30 = null (3)
    │     │   ├─● 1.1.2.1 = employee 2 (1)
    │     │   └─○ 1.1.2.2/31 = null (2)
    │     │     ├─● 1.1.2.2 = employee 3 (1)
    │     │     └─● 1.1.2.3 = employee 4 (1)
    │     └─● 1.1.3.0/24 = department A3 (3)
    │       └─○ 1.1.3.0/30 = null (2)
    │         ├─● 1.1.3.1 = employee 5 (1)
    │         └─● 1.1.3.2 = employee 6 (1)
    └─○ 1.2.0.0/15 = null (2)
      ├─● 1.2.0.0/16 = building B (1)
      └─● 1.3.0.0/16 = building C (1)

Given an address in the network, whether an employee or from some other origin, we can use the trie to identify the details for that address.

static void printDetails(IPv4AddressAssociativeTrie<Info> trie, String addrStr) {
	IPv4Address addr = getAddr(addrStr);
	IPv4AssociativeTrieNode<Info> subnetNode = trie.longestPrefixMatchNode(addr);
	Info info = subnetNode.getValue();
	System.out.print("details for " + addr + ":\n" + info);
	subnetNode = subnetNode.getParent();
	while(subnetNode != null) {
		if(subnetNode.isAdded()) {
			info = subnetNode.getValue();
			System.out.print(", " + info);
		}
		subnetNode = subnetNode.getParent();
	}
	System.out.println();
}

printDetails(trie, "1.1.2.2"); // known employee
System.out.println();
printDetails(trie, "1.1.3.11"); // unknown IP

Output:

details for 1.1.2.2:
employee 3, department A2, building A, region R1

details for 1.1.3.11:
department A3, building A, region R1

Search Text of Database for Addresses in a CIDR Prefix Block Subnet

Suppose you wanted to search for all addresses from a subnet in a large amount of text data. Suppose the subnet was a:​b:0:0::/64. Starting with a representation of just the network prefix section of the address, you can get all strings representing that prefix.

IPAddressSection prefix = new IPAddressString("a:b::").getAddress().
  getNetworkSection(64, false);
String strings[] = prefix.toStandardStringCollection().toStrings();
for(String str : strings) {
  System.out.println(str);
}

Output:

a:b:0:0  
000a:000b:0000:0000  
A:B:0:0  
000A:000B:0000:0000  
a:b::  
000a:000b::  
A:B::  
000A:000B::

If you need to be more or less stringent about the address formats you wish to search, then you can use toStringCollection(IPStringBuilderOptions options) with an instance of IPv6StringBuilderOptions.

Searching for those strings will find the subnet addresses. However, you may get a few false positives, like "a:​b::d:e:f:​a:​b". To eliminate the false positives, you need to account for the number of segments. The library can produce the SQL query you need for a MySQL database search:

public static void main(String[] args) {
  IPAddressSection prefix = new IPAddressString("a:b::").  
    getAddress().getNetworkSection(64, false);  
  StringBuilder sql = new StringBuilder("Select rows from table where");  
  prefix.getStartsWithSQLClause(sql, "column1");  
  System.out.println(sql);
}

Output:

Select rows from table where ((substring_index(column1,':',4) =
'a:b:0:0') OR ((substring_index(column1,':',3) = 'a:b:') AND (LENGTH
(column1) - LENGTH(REPLACE(column1, ':', '')) <= 6)))

For IPv4, another way to search for a subnet like 1.2.0.0/16 would be to do an SQL SELECT with the SQL wildcard string:

public static void main(String[] args) {  
  String wildcardString = new IPAddressString("1.2.0.0/16").  
    getAddress().toSQLWildcardString();  
  System.out.println(wildcardString);
}

Output:

1.2.%.%

Then your SQL search string would be like:

Select rows from table where column1 like 1.2.%.%