Conversion from CIDR notation to IP address/subnet mask (dot-decimal)

3
 /*
 * RFC 1518, 1519 - Classless Inter-Domain Routing (CIDR)
 * This converts from "prefix + prefix-length" format to
 * "address + mask" format, e.g. from xxx.xxx.xxx.xxx/yy
 * to xxx.xxx.xxx.xxx/yyy.yyy.yyy.yyy.
 */
static private String normalizeFromCIDR(final String netspec)
{
    final int bits = 32 - Integer.parseInt(netspec.substring(netspec.indexOf('/')+1));
    final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits)-1); 

    return netspec.substring(0, netspec.indexOf('/') + 1) +
            Integer.toString(mask >> 24 & 0xFF, 10) + "." +
            Integer.toString(mask >> 16 & 0xFF, 10) + "." +
            Integer.toString(mask >>  8 & 0xFF, 10) + "." +
            Integer.toString(mask >>  0 & 0xFF, 10);
}

This is a function in apache james to convert the ip to the specified format. Can you please explain what's happening inside the function. Confused with this bit shifting and conversion. Thanks in Advance.

java
ip
subnet
james
cidr
asked on Stack Overflow Apr 30, 2014 by Jobin • edited Feb 2, 2015 by pajaja

2 Answers

2

Bit-wise operations maybe are not most intuitive at first glance but once you got it you'll see that they're quite easy to understand. I'll try to explain what this code does on a example of 172.16.0.1/23 as a netspec string.

Part1 - CIDR to binary

The goal is to make a binary representation of a subnet mask from a given CIDR prefix length. CIDR prefix length is just a number of 1 bits in a subnet mask. The first line

final int bits = 32 - Integer.parseInt(netspec.substring(netspec.indexOf('/')+1));

finds CIDR prefix length by getting the index of / and parsing the integer that succeeds it (23 in my example). This number is subtracted from 32 to get a number of 0 in a subnet mask — those bits are also called host bits.

In this example we know that we're dealing with /23 prefix and that it's subnet mask should look like:

n represents network (16 bits for class B network), s represents subnet, h represents host. For us network and subnet bits are functionally the same, but I made a distinction just to be precise. Our interest is just in host bits (number of it).

nnnnnnnn nnnnnnnn sssssssh hhhhhhhh
11111111 11111111 11111110 00000000

The easiest way to make this is to have a 32bit binary number of all 1s and 'fill' the last 9 bits with 0. This is where the second line comes in:

You can ignore the bits == 32 check since it is not that relevant and probably is there just as a optimization.

//final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits)-1); 
final int mask = 0xFFFFFFFF - ((1 << 9)-1); 

0xFFFFFFFF will give you 32bit binary number of all 1s. 1 shifted left 9 bits (1 << bits) will give you 512 and 512 - 1 in binary is 111111111:

  1 << 9                               10 00000000
-      1                                         1
--------------------------------------------------
                                        1 11111111

When you subtract those values you will get the subnet mask in binary:

  0xFFFFFFFF = 11111111 11111111 11111111 11111111
- (1 << 9)-1 =                          1 11111111
--------------------------------------------------
               11111111 11111111 11111110 00000000

Which is exactly the network mask we wanted.

Note: This is maybe not the most intuitive way of calculating the binary value. I like to start with a binary number of all ones and that number in signed int has a decimal value of -1. Then i just shift it the number of host bits to the left and that's it. (Additionally if you're dealing with integers that are larger than 32bits you can mask it with 0xFFFFFFFF):

(-1 << 9) & 0xFFFFFFFF

Part2 - Binary to dotted-decimal

The rest of the code converts the binary value to a dotted-decimal representation — 255.255.254.0.

return netspec.substring(0, netspec.indexOf('/') + 1) +  // part of the netspec string before '/' -> IP address
        Integer.toString(mask >> 24 & 0xFF, 10) + "." +  //                         11111111 & 0xFF = 0xFF
        Integer.toString(mask >> 16 & 0xFF, 10) + "." +  //                 1111111111111111 & 0xFF = 0xFF
        Integer.toString(mask >>  8 & 0xFF, 10) + "." +  //         111111111111111111111110 & 0xFF = 0xFE
        Integer.toString(mask >>  0 & 0xFF, 10);         // 11111111111111111111111000000000 & 0xFF = 0x00

The return statement is composed of several concatenated strings, starting with IP address and following by decimal representation of each octet. Binary mask is shifted right for (4-n)*8 bits (where n is octet number) and using binary AND with 0xFF you get only the last 8bits which are then parsed by Integer.toString.

The result is 172.16.0.1/255.255.254.0.

answered on Stack Overflow Feb 2, 2015 by pajaja • edited Feb 2, 2015 by pajaja
0

another way to get cidr notation to binary:

input = '1.2.3.4/5'
cidr = input.split('/') 

bin_mask = '1' * cidr + '0' * (32 - cidr)
answered on Stack Overflow Sep 15, 2019 by tipkopf • edited Sep 15, 2019 by caco3

User contributions licensed under CC BY-SA 3.0