Skip to content

Code Examples 2: Subnet Containment, Matching, Comparing

Sean C Foley edited this page Aug 14, 2024 · 38 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, 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);
}

The helper class PositionResult illustrates how to interpret the values returned from enumerate. There are four possibilities. The first three possibilities are: below the subnet, above the subnet, or within the subnet. The last possibility is neither above, nor below, nor within the subnet.

static class PositionResult {
	final IPAddress subnet, address;
	final BigInteger position;

	PositionResult(IPAddress subnet, IPAddress address, BigInteger position) {
		this.subnet = subnet;
		this.address = address;
		this.position = position;
	}

	@Override
	public String toString() {
		if(subnet.isMultiple()) {
			if(position == null) {
				return address + " is within the bounds but " + 
					"is not contained by " + subnet;
			} else if(position.signum() < 0) {
				return address + " is " + position.negate() + 
					" below the smallest element " + 
					subnet.getLower() + " of "  + subnet;
			}
			BigInteger count = subnet.getCount();
			if(position.compareTo(count) < 0) {
				return address + " is at index " + position +
					" in " + subnet;
			} else {
				return address + " is " +
					position.subtract(count) + 
					" above the largest element " + 
					subnet.getUpper() + " of "  + subnet;
			}
		} 
		if(position.signum() > 0) {
			return address + " is " + position + " above " + subnet;
		} else if(position.signum() < 0) {
			return address + " is " + position.negate() + " below "
				+ subnet;
		}
		return address + " matches " + subnet;
	}
}

Here are two cases where the address lies above the subnet bounds, followed by two cases where the address is within the subnet, followed by a case where the address is neither above, nor below, nor within the subnet.

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

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

Output:

1000::15ff is 5623 above the largest element 1000::7/125 of 1000::/125
2000::15ff is 5623 above the largest element 2000::7/125 of 1000-2000::/125
1000::7 is at index 7 in 1000::/125
2000::7 is at index 32775 in 1000-2000::/125
1a00::8 is within the bounds but is not contained by 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:

0.0.2.2 is at index 514 in 0.0.0.0/16
0.0.2.2 is at index 4 in 0.0.1-3.1-3
0.0.2.2 is at index 2 in 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);
	BigInteger position = result.position;
	boolean contains = position != null && position.signum() >= 0 && 
			position.compareTo(result.subnet.getCount()) < 0;
	System.out.println(result.subnet +  " contains " + result.address + 
			" " + 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.

When the enumerate method is called on a subnet that is a single element, then all the possibilities, whether above, below, or within the subnet, translate to the same thing. The result is the distance from the argument address to the one-element subnet, or put more simply, the distance between the two addresses.

Reusing the printPosition function from the previous example:

printPosition("1a00::ffff", "1a00::ffff");
printPosition("1a00::ffff", "1a00::1:0");
printPosition("1a00::8", "1a00::ffff");
printPosition("1a00::ffff", "1a00::8"); // flip the args

Output:

1a00::ffff matches 1a00::ffff
1a00::1:0 is 1 above 1a00::ffff
1a00::ffff is 65527 above 1a00::8
1a00::8 is 65527 below 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:

255.255.255.255 is 4294967295 above 0.0.0.0
ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff is 340282366920938463463374607431768211455 above ::

We can instead use a 64-bit long integer for IPv4, since the size of the IPv4 address space can fit into a long. The enumerateIPv4 method of 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("The distance of 255.255.255.255 from 0.0.0.0 is " + position);

Output:

The distance of 255.255.255.255 from 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.%.%