What's in an IP address?

What is an IP address? To many people, it’s just a sequence of four digits separated by dots1. But really it’s just a 32 bit integer. So, my IP address right now is 192.168.1.52. As a 32 bit integer, that’s:

  • 0xC0A80134 (hexadecimal)
  • 11000000 10101000 00000001 00110100 (binary)

An IP address on it’s own isn’t terribly useful. It’s normally part of a network. That means that a certain part of the IP address designates the network and a certain part designates the “host” (your computer). Conventionally, this is represented through a network “mask” — a series of bits stating which bits identify the network and which bits don’t. My netmask is presently displayed as 255.255.255.0. Again, as an integer that is:

  • 0xffffff00 (hexadecimal)
  • 11111111 11111111 11111111 00000000 (binary)

Combining an IP address and a netmask, you can find out just the network. You have to use a “binary AND” to work it out.

  11000000 10101000 00000001 00110100 (192.168.1.52)
& 11111111 11111111 11111111 00000000 (255.255.255.0)
= 11000000 10101000 00000001 00000000 (192.168.1.0)

Effectively, that’s the lowest address in the network. Likewise, it’s fairly simple to work out the highest address in the network. You use a “binary OR” with the inverse of the netmask. i.e.

  11111111 11111111 11111111 00000000 (255.255.255.0)
~ 00000000 00000000 00000000 11111111 (0.0.0.255)
| 11000000 10101000 00000001 00110100 (192.168.1.52)
= 11000000 10101000 00000001 11111111 (192.168.1.255)

Thankfully, these are both far more expressible in high level programming languages.

  lowest  = address &  mask;
  highest = address | ~mask;

Now you may have noticed that the netmask is a contiguous range of bits. It’s common to express the netmask as just the number of bits in the netmask. So my IP address and netmask are expressible together as 192.168.1.52/24. This is known as CIDR format (and the number of bits is known as a prefix length).

Here’s some sample Java code for representing an IP address2. Doing this in Java is particularly entertaining because Java only has signed integers, unlike C. So there are some shenanigans involved in making the netmask.

  public class IP implements Comparable<ip> {
 
    private static final int MAX_MASK = 32;
    private static final int MIN_MASK = 0;
 
    private final int addr;
    private final int prefixLength;
 
    public IP(int addr) {
      this(addr, MAX_MASK);
    }
 
    public IP(int addr, int prefixLength) {
      this.addr = addr;
      if (prefixLength < MIN_MASK || prefixLength > MAX_MASK)
        throw new IllegalArgumentException("mask must be >= 0 && <= 32");
      this.prefixLength = prefixLength;
    }
 
    public int compareTo(IP o) {
      if (o == null)
        throw new NullPointerException("can't compare to null");
      if (addr < o.addr)
        return -1;
      if (addr > o.addr)
        return 1;
      return prefixLength - o.prefixLength;
    }
 
    public boolean equals(Object obj) {
      if (!(obj instanceof IP)) {
        return false;
      }
      IP other = (IP) obj;
      return prefixLength == other.prefixLength
          && (addr & getMask()) == (other.addr & other.getMask());
    }
 
    public IP getLowerBound() {
      return new IP(addr & getMask());
    }
 
    public int getMask() {
      // note: the >>> operator on 32-bit ints only considers the
      // five lowest bits of the shift count, so 32 shifts would
      // actually perform 0 shift!
      return prefixLength == 32 ? -1 : ~(-1 >>> prefixLength);
    }
 
    public IP getUpperBound() {
      return new IP(addr | ~getMask());
    }
 
    public int hashCode() {
      return addr ^ getMask();
    }
 
    private String intToString(int i) {
      return (i >> 24 & 0xFF) + "."
           + (i >> 16 & 0xFF) + "."
           + (i >> 8  & 0xFF) + "."
           + (i & 0xFF);
    }
 
    public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(intToString(addr));
      if (prefixLength != MAX_MASK) {
        sb.append('/');
        sb.append(prefixLength);
      }
      return sb.toString();
    }
  }

This is fairly basic (it could use a contains() method, for example). But it’s been quite useful for me over the last couple of weeks for representing CIDR addresses. By treating an IP address as a CIDR range of /32, it’s handy for the general case.

I hope that this is of use to somebody who is tempted to use a String to store an IP address in future.

Of course, whilst it’s useful, you also need a parser of sorts to go from the String form to an instance of the IP class. This is what I’ve been using, which I “borrowed” from limewire’s IP.java.

  public class IpParser {
    private int parseMask(String maskStr) {
      return Integer.parseInt(maskStr);
    }
 
    public IP parseCidr(String addr) {
      int slash = addr.indexOf('/');
      if (slash == -1) {
        return parseSingleAddr(addr);
      } else if (addr.lastIndexOf('/') == slash) {
        return parseCidrOnly(addr);
      } else {
        throw new IllegalArgumentException("Can't parse " + addr);
      }
    }
 
    private IP parseCidrOnly(String addr) {
      int slash = addr.indexOf('/');
      String maskStr = addr.substring(slash + 1);
      String addrStr = addr.substring(0, slash);
      return new IP(stringToInt(addrStr), parseMask(maskStr));
    }
 
    private IP parseSingleAddr(String addr) {
      return new IP(stringToInt(addr));
    }
 
    private int stringToInt(final String ipStr) {
      int ip = 0;
      int numOctets = 0;
      int length = ipStr.length();
      // loop over each octet
      for (int i = 0; i < length; i++, numOctets++) {
        int octet = 0;
        // loop over each character making the octet
        for (int j = 0; i < length; i++, j++) {
          char c = ipStr.charAt(i);
          if (c == '.') { // finished octet?
            // can't be first in octet, and not ending 4th octet.
            if (j != 0 && numOctets < 3) {
              break; // loop to next octet
            }
          } else if (c >= '0' && c <= '9' && j <= 2) {
            octet = octet * 10 + c - '0';
            // check it's not a faulty addr.
            if (octet <= 255) {
              continue;
            }
          }
          throw new IllegalArgumentException("Could not parse " + ipStr);
        }
        ip = ip << 8 | octet;
      }
      // if the address had less than 4 octets, push the ip over suitably.
      if (numOctets < 4) {
        ip <<= (4 - numOctets) * 8;
      }
      return ip;
    }
  }

1 Ignoring IPv6 for now, as I don’t have any need for it.

2 Don’t get me started on how non-useful Java’s InetAddress class is.

One Comment to What's in an IP address?

  1. John says:

    nice code, just appears int for stringToInt will cause overflow