CI Location Field Script
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 weeks ago
I have a question regarding the location field for some of my company's Configuration Items (CIs) under the `cmdb_ci_list`. I posted about this issue on the ServiceNow Developer Forum [here](https://www.servicenow.com/community/developer-forum/the-location-field-for-some-of-our-cis-is-wrong...). Specifically, I discovered a scheduled job that updates the CI location based on the IP address (see "Location Issue-14"). In this job, there is a script (see "Location Issue-15").
I am considering updating the scheduled job with a new script. I would appreciate any thoughts on whether this new script will accomplish the task. Here’s the script:
// Helper function to safely convert IP to an unsigned 32-bit integer
function ipToInt(ip) {
if (!ip) return -1;
var parts = ip.trim().split('.');
if (parts.length !== 4) return -1;
// Using >>> 0 forces JavaScript to treat the result as an unsigned 32-bit integer
return ((parseInt(parts[0], 10) << 24) |
(parseInt(parts[1], 10) << 16) |
(parseInt(parts[2], 10) << 8) |
parseInt(parts[3], 10)) >>> 0;
}
var updatedCount = 0;
// 1. Caching ranges in memory avoids running a nested loop query for every single CI
var ranges = [];
var rangeGR = new GlideRecord('ip_address_range');
rangeGR.query();
while (rangeGR.next()) {
var startInt = ipToInt(rangeGR.start_ip + '');
var endInt = ipToInt(rangeGR.end_ip + '');
if (startInt !== -1 && endInt !== -1) {
ranges.push({
start: startInt,
end: endInt,
location: rangeGR.getValue('u_location_info') // Use getValue for references
});
}
}
// 2. Query Configuration Items needing evaluation
var ciGR = new GlideRecord('cmdb_ci');
ciGR.addNotNullQuery('ip_address');
// Optional: restrict query to only look at 10.14.x.x addresses for testing
// ciGR.addQuery('ip_address', 'STARTSWITH', '10.14.');
ciGR.query();
while (ciGR.next()) {
var ciIp = ciGR.ip_address + '';
var ciIpInt = ipToInt(ciIp);
if (ciIpInt === -1) {
gs.warn('[CI Location Update] Invalid IP on CI: ' + ciGR.getValue('name') + ' IP: [' + ciIp + ']');
continue;
}
var matched = false;
// Evaluate against the pre-loaded memory array
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
if (ciIpInt >= range.start && ciIpInt <= range.end) {
matched = true;
var newLocation = range.location;
if (ciGR.getValue('location') != newLocation) {
ciGR.setValue('location', newLocation);
ciGR.update();
updatedCount++;
gs.info('[CI Location Update] CI "' + ciGR.getValue('name') + '" updated to location reference: ' + newLocation);
}
break; // Exit the loop once the first matching range is found
}
}
if (!matched) {
gs.info('[CI Location Update] No range match found for CI: ' + ciGR.getValue('name') + ' IP: ' + ciIp);
}
}
gs.info('[CI Location Update] Execution complete. Total CIs updated: ' + updatedCount);
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 weeks ago - last edited 2 weeks ago
Hey @BenFan,
Good news first: for the specific 10.14.6.x vs 10.14.106.x collision you started with, your ipToInt function does fix it. I ran the numbers, 10.14.6.0 converts to 168,691,200 and 10.14.106.0 converts to 168,716,800, both positive integers, both correctly ordered with a real gap between them. That confusion is gone.
But before you close this out, there's a landmine hiding in exactly how you built that conversion, and it's going to bite you the moment you onboard a location on a more common private range.
You're using the bitwise shift operators (<<) to build the integer. In JavaScript those operators work on signed 32-bit integers, not arbitrary numbers. Bit 31 is the sign bit, and parts[0] << 24 is exactly the shift that lands the first octet's high bit right on top of it. That means any IP whose first octet is 128 or higher flips the whole result negative. Concretely:
- 10.x.x.x and 127.x.x.x and below stay positive, no problem, which is why your 10.14 test case looked clean.
- 172.16.x.x through 172.31.x.x (the other big RFC1918 private range) comes out negative.
- 192.168.x.x, one of the most common private ranges in the world, also comes out negative.
- Anything 128.x.x.x and above, same story.
So the day you add a location whose range is 192.168.1.0 to 192.168.1.255, that range's startInt and endInt come out as large negative numbers, while your existing 10.14.x ranges stay positive. Your comparison, ciIpInt >= startInt && ciIpInt <= endInt, will now treat a negative range boundary as "less than" every positive 10.x address, which reintroduces a version of the exact same false-overlap symptom you just fixed, just triggered by mixing ranges above and below that 128 threshold instead of by string comparison.
Two ways to fix it, pick whichever fits your style better:
- Keep the bitwise version but force it back to unsigned by appending >>> 0 to the final result. That coerces the value through ToUint32 instead of leaving it as a signed Int32, so 192.168.1.1 comes out as 3,232,235,777 instead of a negative number.
- Or switch to the plain arithmetic version I mentioned in my last reply, octet1 * 16777216 + octet2 * 65536 + octet3 * 256 + octet4, which never touches a bitwise operator and never overflows 32 bits since JS numbers are doubles under the hood.
function ipToInt(ip) {
if (!ip) return -1;
var parts = ip.trim().split('.');
if (parts.length !== 4) return -1;
return ((parseInt(parts[0]) << 24) +
(parseInt(parts[1]) << 16) +
(parseInt(parts[2]) << 8 +
parseInt(parts[3])) >>> 0;
}Two smaller things worth a look while you're in there:
- The image you attached (iprange-0) didn't come through on my side, attachments don't render for me, so if that's a screenshot of actual rows in ip_address_range, paste the start_ip/end_ip values as plain text and I'll check them against the logic directly. If it's the MID Server's own IP range field instead, that's a separate mechanism used for MID Server selection during discovery/orchestration, it has nothing to do with your CI location lookup, so don't let the two get cross-wired as the same "source of truth."
- Your excludedClasses NOT IN check on sys_class_name only matches the literal class value stored on the record. It won't catch anything extended further down from cmdb_ci_win_server, cmdb_ci_linux_server, etc. if your instance has custom subclasses under those. Worth a quick check of sys_db_object to confirm nothing important inherits from those six and slips through.
Once you've got the sign-overflow fix in and you're comfortable the numeric conversion is solid end to end, that ordering-by-specificity approach I mentioned last time still stands for the day you have genuinely overlapping ranges by design.
Thank you,
Vikram Karety
Octigo Solutions INC
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 weeks ago
So if I were to choose the 2nd option: "switch to the plain arithmetic version", does that mean I would be swapping out:
var ciIpInt = ipToInt(ciIp)with:
ipInt = (octet1 * 16777216) + (octet2 * 65536) + (octet3 * 256) + octet4but should I leave:
var ciIp = ciGR.ip_address + '';
Also, the IP Range in my Mid Server looks like this:
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 weeks ago
So if I were to choose the 2nd option: "switch to the plain arithmetic version", does that mean I would be swapping out: var ciIpInt = ipToInt(ciIp) with: ipInt = (octet1 * 16777216) + (octet2 * 65536) + (octet3 * 256) + octet4 on line 29?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 weeks ago
Hey @BenFan,
I see you're still running the ipToInt function exactly as shown in your screenshot, with the bitwise << 24 / << 16 / << 8 shifts, and that's what's producing the bad matches for anything in the 192.168.x.x or 172.16-31.x.x ranges. If that's the case, don't just drop the arithmetic line into line 29 as-is: octet1 through octet4 aren't defined anywhere in that scope, so as written it'll throw a reference error the moment the script runs. You'd have to split ciIp into its four parts first, the same way ipToInt already does internally, before you could multiply them.
The cleaner move is to change the arithmetic inside the ipToInt function itself rather than inlining it just for ciIp. A few things worth checking before you commit to one approach:
- ipToInt is called three separate times, once for ciIp and twice for rangeGR.start_ip and rangeGR.end_ip, so a fix applied only to the ciIp line leaves the range comparisons still broken.
- The << operator coerces both operands to a 32-bit signed integer, so once the first octet hits 128 or higher the shifted value flips negative instead of getting larger.
- Plain multiplication avoids that coercion entirely, the largest possible value (255.255.255.255) is 4294967295, comfortably under JavaScript's 2^53 safe-integer ceiling, so no precision is lost.
So rather than touching line 29, swap the body of the function:
function ipToInt(ip) {
if (!ip) return -1;
var parts = ip.trim().split('.');
if (parts.length !== 4) return -1;
return (parseInt(parts[0]) * 16777216) +
(parseInt(parts[1]) * 65536) +
(parseInt(parts[2]) * 256) +
parseInt(parts[3]);
}Then leave line 29 as var ciIpInt = ipToInt(ciIp); untouched. The function now does the safe math for every caller, ciIp, start_ip, and end_ip alike, so the >= / <= range comparison at line 47 is comparing apples to apples again.
If it's still not matching after that, paste the gs.warn or gs.info output for one of the CIs that isn't getting picked up and I'll take a closer look.
Thank you,
Vikram Karety
Octigo Solutions INC
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
2 weeks ago
Okay, I've changed line 29 back to ipToInt(clip) and updated the ipToInt(ip) function.