Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Canonigo
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Canonigo ( @mcanonigo )

Using Commons IP Math To Check If An IP Address Exists In An IPv4 Or IPv6 CIDR Range In ColdFusion

By on
Tags:

Last week, I discovered that ColdFusion's underlying Java resources expose a Jakarta Commons Net utility called SubnetUtils. This SubnetUtils class makes it super simple to see if a given IP address exists within a given IPv4 CIDR range. Unfortunately, SubnetUtils has no support for IPv6. As such, I wanted to revisit the concept using Commons IP Math - a Java library maintained by Yannis Gonianakis. With Commons IP Math, we can easily inspect both IPv4 and IPv6 CIDR ranges in ColdFusion.

In order to load the Commons IP Math Java library into my ColdFusion demo, I'm going to use the popular JavaLoader and JavaLoaderFactory projects. These projects allow me to manage the Java dependencies in a clean and isolated way, ensuring that I don't run into any conflicts with the existing Java resources that come bundled with the ColdFusion runtime.

And, as with most Java libraries that I consume in ColdFusion, I'm going to create a ColdFusion component wrapper - IPAddressUtils.cfc - for the Commons IP Math API. This way, the calling context doesn't have to understand class relationships or deal with casting method arguments using JavaCast().

Before we look at the ColdFusion component wrapper, however, let's take a quick look at the ColdFusion application framework to see how the dependencies are managed:

component
	output = false
	hint = "I provide the application settings and event handlers."
	{

	// Application settings.
	this.name = hash( getCurrentTemplatePath() );
	this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 );
	this.sessionManagement = false;

	// Setup path mappings.
	this.mappings[ "/" ] = getDirectoryFromPath( getCurrentTemplatePath() );
	this.mappings[ "/javaloader" ] = ( this.mappings[ "/" ] & "vendor/javaloader" );
	this.mappings[ "/vendor" ] = ( this.mappings[ "/" ] & "vendor" );

	// ---
	// PUBLIC METHODS.
	// ---

	/**
	* I initialize the application.
	*
	* @output false
	*/
	public void function onApplicationStart() {

		var javaLoaderFactory = new vendor.javaloaderfactory.JavaLoaderFactory();

		// In order to safely consume the Commons IP Math Java library, we have to give
		// it its own class loader. This way, we know it won't conflict with anything
		// else in the ColdFusion world.
		var commonsIpMathClassLoader = javaLoaderFactory.getJavaLoader(
			loadPaths = [
				expandPath( "/vendor/commons-ip-math/v1.32/commons-ip-math-1.32.jar" )
			]
		);

		application.ipAddressUtils = new IPAddressUtils( commonsIpMathClassLoader );

	}

}

As you can see, I'm using the JavaLoader to create a class loader for the Commons IP Math library. I then inject that class loader into my IPAddressUtils.cfc constructor.

Now, let's take a look at how my IPAddressUtils.cfc consumes the Commons IP Math class loader. I am exposing two public methods:

  • getRange( ipRange ) - Returns the high and low IP addresses for the given range.
  • isInRange( ipRange, ipAddress ) - Determines if the given IP address is contained within the given IP range.

Thanks to the Commons IP Math library, these methods can handle both IPv4 and IPv6 addresses; however, I do have to branch some of the internal logic based on which class of IP is being provided. The Commons IP Math library purposefully provides different classes for Type safety guarantees, deferring the more general abstraction to the consumer (which is me).

component
	output = false
	hint = "I provide utility methods for inspecting IPv4 and IPv6 addresses and ranges."
	{

	/**
	* I initialize the IPAddressUtils with the given dependencies.
	*
	* @commonsIpMathClassLoader I provide IP address parsing and checking for IPv4 and IPv6 addresses.
	* @output false
	*/
	public any function init( required any commonsIpMathClassLoader ) {

		// Store Java class references so we can use their static methods.
		Ipv4Class = commonsIpMathClassLoader.create( "com.github.jgonian.ipmath.Ipv4" );
		Ipv4RangeClass = commonsIpMathClassLoader.create( "com.github.jgonian.ipmath.Ipv4Range" );
		Ipv6Class = commonsIpMathClassLoader.create( "com.github.jgonian.ipmath.Ipv6" );
		Ipv6RangeClass = commonsIpMathClassLoader.create( "com.github.jgonian.ipmath.Ipv6Range" );

	}

	// ---
	// PUBLIC METHODS.
	// ---

	/**
	* I return the "high" and "low" addresses in the given IP address range.
	*
	* NOTE: Throws error if address cannot be parsed.
	*
	* @ipRange I am the IP address range being parsed.
	* @output false
	*/
	public struct function getRange( required string ipRange ) {

		var xRange = parseIpRange( ipRange );
		var range = {
			high: xRange.end().toString(),
			low: xRange.start().toString()
		};

		return( range );

	}


	/**
	* I determine if the given IP address is contained within the given IP range.
	*
	* NOTE: Throws error if address(es) cannot be parsed.
	*
	* @ipRange I am the IP range.
	* @ipAddress I am the IP address being tested.
	* @output false
	*/
	public boolean function isInRange(
		required string ipRange,
		required string ipAddress
		) {

		// If the ipRange doesn't appear to use CIDR range notation, reject the test.
		if ( ! looksLikeCidrRange( ipRange ) ) {

			return( false );

		}

		// We can't compare IPv6 and IPv4 values. As such, if the range and the address
		// aren't in the same class, reject the test.
		if ( ! isSameIpClass( ipRange, ipAddress ) ) {

			return( false );

		}

		var xRange = parseIpRange( ipRange );
		var xAddress = parseIpAddress( ipAddress );

		return( xRange.contains( xAddress ) );

	}

	// ---
	// PRIVATE METHODS.
	// ---

	/**
	* I determine if the given IP address and range values are in the same class.
	*
	* @ipRange I am the IP address range being compared.
	* @ipAddress I am the IP address being compared.
	* @output false
	*/
	private boolean function isSameIpClass(
		required string ipRange,
		required string ipAddress
		) {

		return( looksLikeIpv6( ipRange ) == looksLikeIpv6( ipAddress ) );

	}


	/**
	* I perform a QUICK CHECK on the given IP address range to see if it represents what
	* is LIKELY TO BE a CIDR range. This does not perform a full validation!
	*
	* @ipRange I am the IP address range being checked.
	* @output false
	*/
	private boolean function looksLikeCidrRange( required string ipRange ) {

		return( find( "/", ipRange ) );

	}


	/**
	* I perform a QUICK CHECK on the given IP address range to see if it represents what
	* is LIKELY TO BE an IPv6 range. This does not perform a full validation!
	*
	* @ipValue I am the IP address OR range being checked.
	* @output false
	*/
	private boolean function looksLikeIpv6( required string ipValue ) {

		return( find( ":", ipValue ) );

	}


	/**
	* I parse the given IP address into the appropriate Ipv4 or Ipv6 Java instance.
	*
	* @ipAddress I am the IP address being parsed.
	*/
	private any function parseIpAddress( required string ipAddress ) {

		if ( looksLikeIpv6( ipAddress ) ) {

			return( Ipv6Class.parse( javaCast( "string", ipAddress ) ) );

		} else {

			return( Ipv4Class.parse( javaCast( "string", ipAddress ) ) );

		}

	}


	/**
	* I parse the given IP address range into the appropriate Ipv4Range or Ipv6Range
	* Java instance.
	*
	* @ipAddress I am the IP address range being parsed.
	*/
	private any function parseIpRange( required string ipRange ) {

		if ( looksLikeIpv6( ipRange ) ) {

			return( Ipv6RangeClass.parse( javaCast( "string", ipRange ) ) );

		} else {

			return( Ipv4RangeClass.parse( javaCast( "string", ipRange ) ) );

		}

	}

}

As you can see, I'm caching a few references to the Commons IP Math classes (using the JavaLoader) so that I can reuse various static methods geared towards address parsing. Ultimately, I'm using the Ipv4, Ipv4Range, Ipv6, and Ipv6Range Java classes to do all the heavy lifting; my ColdFusion code does little more than figuring out which classes to instantiate.

With the IPAddressUtils.cfc cached in the Application scope, I can then go about testing some IP address ranges. Here's an IPv4 example:

<cfscript>

	ipAddressUtils = application.ipAddressUtils;

	testRange = "192.168.0.0/16";
	testAddresses = [
		"192.167.255.255",
		"192.168.0.0",
		"192.168.100.100",
		"192.168.255.255",
		"192.169.0.0",
		"2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"
	];

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	range = ipAddressUtils.getRange( testRange );

	writeOutput( "<strong>Range: #testRange#</strong> <br />" );
	writeOutput( "<br />" );
	writeOutput( "<strong>Low Address</strong>: #range.low# <br />" );
	writeOutput( "<strong>High Address</strong>: #range.high# <br />" );
	writeOutput( "<br />" );

	for ( testAddress in testAddresses ) {

		writeOutput( "#testAddress# in range: #ipAddressUtils.isInRange( testRange, testAddress )# <br />" );

	}

</cfscript>

Running this gives us the following ColdFusion page output:

Range: 192.168.0.0/16

Low Address: 192.168.0.0
High Address: 192.168.255.255

192.167.255.255 in range: NO
192.168.0.0 in range: YES
192.168.100.100 in range: YES
192.168.255.255 in range: YES
192.169.0.0 in range: NO
2001:db8:ffff:ffff:ffff:ffff:ffff:ffff in range: false

And, here's an IPv6 example:

<cfscript>

	ipAddressUtils = application.ipAddressUtils;

	testRange = "2001:db8::/32";
	testAddresses = [
		"2001:db7:ffff:ffff:ffff:ffff:ffff:ffff",
		"2001:db8::",
		"2001:db8:0:0:0:0:0:0",
		"2001:db8:ffff:ffff:ffff:ffff:ffff:ffff",
		"2001:db9::",
		"192.168.0.0"
	];

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	range = ipAddressUtils.getRange( testRange );

	writeOutput( "<strong>Range: #testRange#</strong> <br />" );
	writeOutput( "<br />" );
	writeOutput( "<strong>Low Address</strong>: #range.low# <br />" );
	writeOutput( "<strong>High Address</strong>: #range.high# <br />" );
	writeOutput( "<br />" );

	for ( testAddress in testAddresses ) {

		writeOutput( "#testAddress# in range: #ipAddressUtils.isInRange( testRange, testAddress )# <br />" );

	}

</cfscript>

Running this gives us the following ColdFusion page output:

Range: 2001:db8::/32

Low Address: 2001:db8::
High Address: 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff

2001:db7:ffff:ffff:ffff:ffff:ffff:ffff in range: NO
2001:db8:: in range: YES
2001:db8:0:0:0:0:0:0 in range: YES
2001:db8:ffff:ffff:ffff:ffff:ffff:ffff in range: YES
2001:db9:: in range: NO
192.168.0.0 in range: false

As you can see, the API in both examples is the same. The IPAddressUtils.cfc wrapper doesn't care if you pass-in IPv4 or IPv6 addresses - it simply instantiates the right Java classes and performs the desired IP operation.

One of the best parts of using ColdFusion is that you have access to the ocean of Java libraries that are maintained by the Java community. In this case, you can see how simple it is to load the Commons IP Math library using the JavaLoader; then, check to see if a given IP address exists in a given CIDR range. And, by wrapping the Java library in a ColdFusion abstraction, I can always change the internal implementation or swap out the Java library in the future.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel