FUN WITH LINUX

How to calculate ip-networks using bit-operators in C

16 January 2015

Sometimes we have to calculate networks. We are interested in stuff like: networkid, subnetmask, min. hostip, max. hostip, how many hosts can we address in this network aso.. There are many tools for calculating networks. And they are perfectly good. If you need it in a c programm, there are libraries for that too. But I think it’s not neccessary to use libraries for that. If you calculate it manually, it’s just a matter of working with bits in a 32bit address, so it might be an quite easy task…

I wrote this little programm to demonstrate how it works:

doctor@tardis> ./calciprog 192.168.10.0 255.255.255.0
Network: 192.168.10.0/24
Wildcard: 0.0.0.255
Netmask: 255.255.255.0
Hostmin: 192.168.10.1
Hostmax: 192.168.10.254
Broadcast: 192.168.10.255
Hosts: 254
NetID-Bytewise: 0 10 168 192 -> 698560

Structure for our 32bit-Address(IPV4)

On Linux, using the gcc and the gnu-standard-library, there is a header-file called “netinet/in.h”. In this file we find the following definition:

/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
  {
    in_addr_t s_addr;
  };

This is the struct we are looking for. We can use functions like the following to convert our IP-String into a binary address:

inet_pton(int af, const char *src, void *dst);

What exactly do we calculate?

Mostly we are interested in the following things:

  • Network ID
  • Subnetmask
  • Broadcast-Address
  • Wildcard-Address
  • Number of possible IPs
  • First Ip in our Network
  • Last Ip in our Network

For this I defined the following datatype:

typedef struct ipcalcsubnet{
        unsigned int hosts; /* max. possible hosts */
        struct in_addr netmask; /* subnetmask */
        struct in_addr netid; /* network id */
        struct in_addr hostmin; /* first possible ip of this network */
        struct in_addr hostmax; /* maximal possible ip of this network */
        struct in_addr broadcast; /* broadcast-ip of this network */
        struct in_addr wildcard; /* wildcard => inverse subnetmask */
} ipnet;

What do we need to calculate all the network stuff?

We need a Ip-Address of the network, or a Networkid and we need the Subnetmask of the network. Let’s assume we stored the netid(192.168.10.0) and the subnetmask(255.255.255.0) in our struct(above).

Let’s do the bit-stuff

/*
   let's say, that we filled in our netid the address 192.168.10.3
   and a subnetmask 255.255.255.0. then 192.168.10.3 is not the real network-id.
   it's just a ip of the network 192.168.10.0/24.
   We can easily calculate the netid:
                        just do a bitwise AND on the netmask and ip
 */
netdata->netid.s_addr = netdata->netid.s_addr & netdata->netmask.s_addr;
/*
   the wildcard-address is the inverse of our subnetmask.
   so if our subnetmask is 255.255.255.0, our wildcard would be 0.0.0.255
                        Just do a bitwise NOT on the netmask
 */
netdata->wildcard.s_addr = ~netdata->netmask.s_addr;
/*
   the broadcast-address is the highest possible ip-address of our network.
   it's easy to calculate too:
                        Just do a bitwise OR on net netid and the wildcard-address
 */
netdata->broadcast.s_addr = netdata->netid.s_addr | netdata->wildcard.s_addr;
/*
   the first useable hostip in our network is calculated by netid + 1
   if our netid is 192.168.10.0 and our subnetmask is 255.255.255.0
   then our first host would be 192.168.10.1
   But at this point we do have a problem. We have to take care about little-endian
   and big-endian.
   We do have a 32bit-Adress. Lets assume our netid is 192.168.10.0.
   so netdata->netid.s_addr would have the decimal value 698560.
   that's in binary: 00000000000010101010100011000000
   0 is in binary: 00000000
   10 is in binary: 00001010
   168 is in binary: 10101000
   192 is in binary: 11000000
   therefore if we just increase our netid.s_addr by one we would get:
                193.168.10.0
   instead of 192.168.10.1
   That's why we have to change the byte-order, increase the 32bit by 1
   and then change the byte-order back. our address is stored in host-byte-order,
   so we have to use the function htonl() to change it into network-byte-order. if it's in
   network-byte-order we can increase it and then we use ntohl() convert it back
   to host-byte-order:
 */
netdata->hostmin.s_addr = ntohl(htonl(netdata->netid.s_addr) +1);
/*
  the last ip of each network is the broadcast-address. so the
  last useable ip of our network is broadcast-address-1. like above
  we have to change our address to network-byte-order, decrease it and
  change back to host-byte-order again:
 */
netdata->hostmax.s_addr = ntohl(htonl(netdata->broadcast.s_addr) -1);
/*
   hosts will store the maximal number of useable ips in our network.
   it's calculated by wildcard -1. here we do not store a network-address!
   this is just an integer-value which stores the number of hosts. that's why
   we convert our wildcard-address to network-byteorder, decrease it and store
   this value into hosts.
   in our example the wildcard is 0.0.0.255 which is in decimal 255.
   255 decreased by one would be 254. so we can use 254 ips in our network
 */
netdata->hosts = htonl(netdata->wildcard.s_addr)-1;

What about the CIDR-Notation(192.168.10.0/24)

Now we calculated all the important things. But it would be neat to give out the netid/subnetmask in cidr-notation. To remember: in the cidr-notation we count all the 1-bits in the subnetmask. 255.255.255.0 would be 24 for example. The big question is, how access the bits in our 32bit address?

int cidr = 0;
struct in_addr netmask;
netmask.s_addr = nm->s_addr;

/*
   in CIDR-notation our address looks like:
        192.168.10.0/24
   So our subnetmask is just a short integer.
   this integer is easily calculated:

        just count all the 1-bit's of our
        subnetmask:

        00000000111111111111111111111111
                -here we have 24 1-bits-

   But how can we count all the 1-bits?
   with this tricky function:
   let's assume a netmask of 255.255.255.0
   stored in host-byte-order it would look like:
   00000000111111111111111111111111

   now let's do a bitwise AND with 0x01

   00000000111111111111111111111111
   00000000000000000000000000000001
   our result would be:
   00000000000000000000000000000001

   then right-shift all the bits by one digit:

   00000000011111111111111111111111

   We do this until our bitmask looks like:

   00000000000000000000000000000000

    perfect, because our loop stopps
    when netmask.s_addr is 0.

    so if we count all the loops of bitshifting
    we are able to count all the 1-bits in our
    netmask:

 */
while ( netmask.s_addr )
{
        cidr += ( netmask.s_addr & 0x01 );
        netmask.s_addr >>= 1;
}

How to easily split a 32bit-address in 4 bytes

/* this union splits 32bit-adresses into 4 bytes
   it's very important to use unsigned here. otherwise
   we would not be able to store values up to 255.
 */
typedef union {
        uint32_t val;
        unsigned char byte[4];
} uIp32;

uIp32 b;
b.val = netdata.netid.s_addr;
printf("NetID-Bytewise: %u %u %u %u -> %u \n",b.byte[3],b.byte[2],b.byte[1],b.byte[0],b.val);

Full Source-Code

Here is the download-link to the full soure-code

Final words

I need those things for my new project and I was very glad that it was easy like that..

[ Programming  C  Network  ]
Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 3.0 Unported License.

Copyright 2015-present Hoti