logo

NJP

How to determine whether an IP address belongs to a CIDR-masked subnet

Import · Mar 07, 2022 · article

If you have a little bit of familiarity with networking, you've probably seen network ranges displayed using what's called CIDR notation. For example, a subnet might be described as 192.168.1.0/24. You'll commonly see /8, /16, and /24 covering an IP range and those are easy to determine. If your network is 192.168.1.0/24, you can quickly check if a valid IP address belongs to that range by checking if it starts with "192.168.1." Easy peasy!

But, if you have larger ranges like 10.0.0.0/14, whose range goes from 10.0.0.0 to 10.3.255.255, a startsWith() will no longer be helpful. Instead you need to enter the confusing depths of bit-wise operations.

I've created the the following script include to take CIDR masked IP ranges from a system property and determine if the input belongs to one of those ranges. If it matches, it will also let you know the range that it belongs to.

Sample System Property: ip_tools.network_ranges

10.3.0.0/21
10.10.32.0/20
10.10.64.0/18
10.10.128.0/17
10.12.0.0/15
10.30.0.0/21
10.32.0.0/15
10.34.0.0/22
10.36.0.0/16

Script Include: IPRangeUtil

var IPRangeUtil = Class.create();
IPRangeUtil .prototype = Object.extendsObject(AbstractAjaxProcessor, { checkIP: function(ip) { ip = ip ? ip : this.getParameter('sysparm_ip'); ip = String(ip); var range_obj = { within_range: false, range: '' }; rangeArray = gs.getProperty('ip_tools.network_ranges') .split('\r\n') .filter(function(x) { return x; }); for (var i = 0; i < rangeArray.length; i++) { if (this._isIp4InCidr(ip, rangeArray[i])) { range_obj.within_range = true; range_obj.range = rangeArray[i]; break; } } return JSON.stringify(range_obj); }, _ip4ToInt: function(ip) { return ip.split('.').reduce(function(integer, oct) { return (integer <<  + parseInt(oct, 10);
        }, 0) >>> 0;
    },
    _isIp4InCidr: function(ip, cidr) {
        var split = cidr.split('/');
        var range = split[0];
        var bits = split[1];
        var mask = -1 << (32 - bits);

        // check if the masked IP is the same as the range
        return (this._ip4ToInt(ip) & mask) == (this._ip4ToInt(range));
    },
    type: 'IPRangeUtil '
});

You can then use this script include on catalog items that might require an IP address to fall within a specific range or subnet.

For example, an onChange Client Catalog Script might contain the following:

function onChange(control, oldValue, newValue, isLoading) {
    if (isLoading || newValue == '') {
        return;
    }

    var ga = new GlideAjax('IPRangeUtil');
    ga.addParam('sysparm_name', 'checkIP');
    ga.addParam('sysparm_ip', newValue);
    ga.getXMLAnswer(validateIP);

    function validateIP(answer) {
        var response = JSON.parse(answer);
        if (response.within_range == false) {
            g_form.clearValue('ip_address');
            g_form.showFieldMsg('ip_address', newValue + ' does not fall within one of the allowed subnets.', 'error');
        } else {
            g_form.hideFieldMsg('ip_address');
        }
    }
}

You can also do the inverse and protect specific IP ranges, displaying the response.range value to show which range an IP address falls within.

View original source

https://www.servicenow.com/community/developer-articles/how-to-determine-whether-an-ip-address-belongs-to-a-cidr-masked/ta-p/2322697