11

Here's the scenario... I have a table of subnets. (see below) I have an ip address. I would like to find out what subnet the ip address belongs to based on a lookup in the table. This association will then be used to determine what location the user is at. It's a private network space so the standard internet to location lookups wouldn't apply. What would be the best approach? Would I need to break the ip address into it's numeric parts and to a bitwise comparison against all the subnets.. or are there built-in tools in Java API that could make my life easier for comparing IP address to subnet mask?

I'm mainly looking for best way to compare ipaddress to a given subnetmask and determining yes this matches, or no it doesn't.. Optionally. Any tips on how to store the list and search with minimal operations would be appreciated also.

Ideally I'd be doing something similar to this:

List subnetInfo = null;

subnetInfo = findSubnet('192.168.0.1');  //value null if nothing found

....

//return null if nothing found
List findSubnet(String ipaddress) {
   List subnetDetails = null;
   .... code here ...
   return subnetDetails;
}

Table 1: Sample list of subnets

dk-ballerup-gen-off-v411        10.172.80.0/21  NANR-denmark-ballerup-metallbuen66-ground-first-floors-incl-dhcp-(sr14585203)
ae-dubai-ofssl-gen-off-v410  10.172.88.0/24  NANR-arab-emirates-ofssl-iflex-general-office-v410-(sr12781477)
ru-stpetersburg-gen-off-v411    10.172.89.0/24  NANR-russia-stpetersburg-general-office-incl-dhcp (bsteinba)
skaffman
  • 398,947
  • 96
  • 818
  • 769
Boolean
  • 1,001
  • 1
  • 8
  • 9

9 Answers9

3

Remember, that IP address is just an int value for historic reasons represented as 4 octets in decimal form.

For the same token, the subnet is really a range of consecutive ints from network address to a broadcast address.

Therefore if your IP address object has an int converter, you can simply check if that integer is in range of the subnet by doing simple int comparisons.

Alexander Pogrebnyak
  • 44,836
  • 10
  • 105
  • 121
3

Apache Commons Net has a SubnetUtils for this sort of thing, including determining if a given IP address is within a given subnet.

Trivial example:

    String[] subnetsMasks = { ... };
    Collection<SubnetInfo> subnets = new ArrayList<SubnetInfo>();
    for (String subnetMask : subnetsMasks) {
        subnets.add(new SubnetUtils(subnetMask).getInfo());
    }

    String ipAddress = ...;
    for (SubnetInfo subnet : subnets) {
        if (subnet.isInRange(ipAddress)) {
            System.out.println("IP Address " + ipAddress + " is in range " + subnet.getCidrSignature());
        }
    }
skaffman
  • 398,947
  • 96
  • 818
  • 769
  • Why would you need an API for something you can check with the & operator? ... which is why netmasks are *called* netmasks ... – user207421 Feb 17 '10 at 05:11
  • 3
    Because turning a CIDR mask into a number is the sort of task that you can easily get wrong. WHy re-implement it, when you can get it off the shelf? – skaffman Feb 17 '10 at 08:20
  • You're kidding, right? You don't even have to convert the bytes into an integer, you can just mask the bytes in a loop. About 4 lines of code. – user207421 Feb 17 '10 at 11:38
  • 8
    And if the CIDR mask crosses the byte boundary? More lines of code. And more bugs. This problem has been solved in libraries, there are better things to spend our energy on. – skaffman Feb 17 '10 at 15:29
1

The best way to do this is to use an address trie and check for containment in all addresses at the same time. The IPAddress Java library provides the means to do this for either IPv4 and IPv6. Disclaimer: I am the project manager.

String subnets[] = { "10.172.80.0/21", "10.172.88.0/24", "10.172.89.0/24" };
IPv4AddressTrie blocksTrie = new IPv4AddressTrie();
for(String str : subnets) {
    blocksTrie.add(new IPAddressString(str).getAddress().toIPv4());
}
IPAddressString toFindAddrStr = new IPAddressString("10.172.88.3");
IPv4Address toFindBlockFor = toFindAddrStr.getAddress().toIPv4();
IPv4TrieNode containingNode = blocksTrie.elementsContaining(toFindBlockFor);
System.out.println("For address " + toFindBlockFor + " containing block is " +
    containingNode.toTreeString(false, false) + "\n");

Output:

For address 10.172.88.3 containing block is 
● 10.172.88.0/24

The address also provides the means to check containment one by one, as in this answer: How to check if an IP address is from a particular network/netmask in Java?

Sean F
  • 4,344
  • 16
  • 30
0

If your table is a SQL table AND your database is PostgreSql, you can use PostgreSQL types and functions.

Well that's a lot of conditions ...

chburd
  • 4,131
  • 28
  • 33
0

Turn the IP address and the netmask into ints and AND them together. If the result equals the IP address it is an address in that netmask. Note that you may get more than one match if your netmasks aren't distinct.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • 1
    This is only true for IPv4. The same technique works for IPv6, but the addresses are not ints. They're not even longs. They're 128-bit numbers, which Java represents as a byte array. – Ian McLaird Aug 11 '10 at 14:34
0

I built on top of skaffman solution one method. For those who are interested in copy paste solution:

    /**
     * Checks if client ip equals or is in range of ip networks provided by
     * semicolon separated list
     *
     * @param clientIp in numeric form like "192.168.0.10"
     * @param ipList   like "127.0.0.1;192.168.0.0/24;10.0.0.0/8"
     * @return true if client ip from the list os ips and networks
     */
    protected boolean evaluateIp(String clientIp, String ipList) {
        BusinessLogger log = getLog();
        log.debug("Evaluate ip ");
        if (ipList == null) {
            log.info("No ip address provided in ApiPrivilege");
            return false;
        }
        String[] subnetMasks = ipList.split(";");
        for (String subnetMask : subnetMasks) {
            subnetMask = subnetMask.trim();
            if (eq(subnetMask, clientIp))
                return true;
            if (subnetMask.contains("/")) {
                if (new SubnetUtils(subnetMask).getInfo().isInRange(clientIp))
                    return true;
            }
        }
        return false;
    }
Pavel Niedoba
  • 1,554
  • 2
  • 19
  • 36
0

It's an old question, but I thought it worth saving somebody else the pain it caused me.

Steer clear of the Apache Commons Net SubnetUtils class, it's buggy:

http://en.newinstance.it/2009/03/25/checking-if-an-ip-is-in-a-subnet-range-subnetutilssubnetinfo-isinrange-bug/

Clive Evans
  • 658
  • 8
  • 12
  • Your link is broken, please could you update it? What is wrong with Apache Commons? – Rich Mar 21 '18 at 16:59
  • Good question. This is 6 years old now, I've no idea what the specifics were. I suspect I had clearly incorrect case, looked at the code and found a mistake. Given it's pretty easy to apply a mask yourself, I'll have done that instead. – Clive Evans Mar 22 '18 at 19:42
  • If it's so easy, how did Apache get it wrong? ;-) It looks like IpAddressMatcher in `spring-web-security` can do this these days. – Rich Mar 23 '18 at 13:16
  • Right. I've looked back and have vague memories that match what I've found. If you look at: https://issues.apache.org/jira/browse/NET-260 you can see they've not considered that integers might be negative, so they're not checking the ranges correctly. It looks like they'd fixed it in SVN by the point I was running into it, which I hadn't discovered at the time, but I'm fairly sure there was no release version. Certainly I couldn't find a version that didn't have this bug. – Clive Evans Mar 24 '18 at 19:30
  • 1
    So, in fairness, they've fixed this now, if you want to use it now, knock yourself out. – Clive Evans Mar 24 '18 at 19:33
0

I'm facing the same question than you but limited to private networks (192.168.X.X, 10.x.x.x, ... & IPv6).

If you're limited to this case too you should use the built in InetAddress.isSiteLocalAddress()

Cerber
  • 2,889
  • 4
  • 27
  • 43
-3

Include the following jar your in pom.xml

*<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>3.0.7.RELEASE</version>
</dependency>*

Spring provides the above library to support the ipv4 and ipv6.

Below is the code snippet to check if ip falls under given subnet.

System.out.println(matches("192.168.2.1", "192.168.2.1/11"));
System.out.println(  matches("192.168.2.1", "192.168.2.0/32")); 
System.out.println(  matches("192.168.2.5", "192.168.2.0/24"));
System.out.println(  matches("fe80:0:0:0:0:0:c0a8:11", 
  "fe80:0:0:0:0:0:c0a8:1/120")); 
System.out.println(  matches("fe80:0:0:0:0:0:c0a8:11", 
  "fe80:0:0:0:0:0:c0a8:1/128")); 
System.out.println( matches("2401:4900:1780:0:0:0:0:11", 
   "2401:4900:1780:0:0:0:0:0/44")); 


private static boolean matches(String ip, String subnet) {
    IpAddressMatcher ipAddressMatcher = new IpAddressMatcher(subnet);
    return ipAddressMatcher.matches(ip);
}
Mohit Garg
  • 145
  • 1
  • 9