TL;DR

In most cases, if you enter 0 for an IP address it will expand to 0.0.0.0. Likewise, 127.1 will expand to 127.0.0.1. Why? Magic.

Not Magic

But really, why do these shortcuts exist and how do they work? It can’t be as simple as adding zeros, if it was 127.1 would more logically expand to 127.1.0.0.

Turns out it’s a historic quirk in the inet_aton() function which is used, directly or indirectly, by most software to parse IP addresses in dotted-quad format into integers.

The familiar dotted-quad notation (AKA “dot-decimal notation”) is purely a human readable convenience. Each quad represents an octet, an 8 bit number. The four octets can then be combined into a 32 bit number which is the actual IP address.

Under the hood, integer IP addresses are always used. They are space efficient and reduce calculating networks from netmasks to applying a bitmask. If you’ve every entered a subnet mask in hex form (0xffffff00), you might have a sense of this.

The Math

To convert a dotted-quad into an integer, we need to put each octet in to the correct position in the integer. The first octet needs to go in bits 25 through 32, the second in 17 through 24 and so on. This is done with the Left Bitwise Shift Operator << in most languages.

Let’s take Google well known 8.8.8.8 DNS server address:

1
(8<<24) + (8<<16) + (8<<8) + 8 => 134744072

134744072 looks like nothing and yet:

1
2
% PING 134744072 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=50 time=18.016 ms

It’s not that simple

The conversion explains why 0 works, the integer version of 0.0.0.0 is 0:

1
(0<<24) + (0<<16) + (0<<8) + 0 => 0

But what about 127.1? How does it know where to put the zeros?

Turns out inet_aton() treats addresses differently depending on the how many dot separated numbers there are.

As we have seen, one number is the full integer IP address.

If there are two numbers, as in 127.1, the first number is the first octet and the second is a integer that fills in the remaining 24 bits. In the case of 127.1 it’s:

1
(127<<24) + 1 => 2130706433

Which is the same as:

1
(127<<24) + (0<<16) + (0<<8) + 1 => 2130706433

(And aren’t you glad you aren’t typing 2130706433 to get to localhost?)

A variation of this logic is applied in the case were three numbers are provided, the first two are octets and the third is the remaining 16 bits. Google’s 8.8.8.8 can be written as 8.8.2056

1
2
(8<<8) + 8 => 2056
(8<<24) + (8<<16) + 2056 => 134744072

Why does this exist?

Why on Earth would inet_aton() take such on construction? It’s not actually clear from the documentation, but I have a theory.

First consider that an IP address has two parts, the network and the host. In the dotted-quad format, given 192.168.0.1, the first three octets are the network, “192.168.0” and the last the host, “1”.

At the time inet_aton() was written, it was very common to receive Class B (/16) network allocations. Given the Class B network 10.10.0.0/16, your first 255 addresses would be 10.10.0.1 to 10.10.0.255 (.0.255 is not a broadcast address) with the 256th host being 10.10.1.0 (.1.0 is not a network address). However, if you used a dotted-triplet you could start at 10.10.1, continue through 10.10.256 all the way up to 10.10.65534. As with the dotted-quad, the last octet is the host and the rest is the network.

I don’t think this form was ever common and stopped making sense when subnetting became the norm. Yet, it exists and, if nothing else, can save you a little typing.

Added Bonus!

Here’s a C program to explore the various notations. Most modern languages provide inet_aton() either direct or indirectly, as in Ruby’s IPAddr#to_i. Still, there is something right about doing this in C.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdlib.h>
#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char **argv) {
  struct in_addr pin;

  if (argc != 2) {
    printf("%d\n",argc);
    fprintf(stderr,"Usage: inet_aton dotted-quad\n");
    exit(1);
  }

  int valid = inet_aton(argv[1],&pin);

  if (!valid) {
    fprintf(stderr,"inet_aton could not parse \"%s\"\n",argv[1]);
    exit(1);
  }

  /* pin.s_addr is in network by order, convert to host byte order */
  unsigned int address = ntohl(pin.s_addr);

  unsigned int octet1 = (0xff000000 & address)>>24;
  unsigned int octet2 = (0x00ff0000 & address)>>16;
  unsigned int octet3 = (0x0000ff00 & address)>>8;
  unsigned int octet4 = 0x000000ff & address;

  printf("ping %u.%u.%u.%u\n",octet1,octet2,octet3,octet4);
  printf("ping %u.%u.%u\n",octet1,octet2,(octet3<<8) + octet4);
  printf("ping %u.%u\n",octet1,(octet2<<16) + (octet3<<8) + octet4);
  printf("ping %u\n",address);
}

To use:

1
2
cc -o inet_aton inet_aton.c
./inet_aton 8.8.8.8

Now you can amaze and confuse your friends with inscrutable integer IP addresses.

Comments