127.257 and other fun legacy IP addresses

I think I'm taking up a coat of arms. Source.

IPv6, the addressing scheme of the future, has been lacking adoption for decades now. Its complexity is an often cited reason: What's with the different kinds of addresses? What's fe80 supposed to be? And you can't even compare them properly, what with those pesky :: contractions!

Though … did you know that 127.257 is interpreted as a valid IPv4 address by programs such as ping and your browser? Let's look into this.

The surprise

Why is this surprising? Well, IPv4 addresses are most often seen, described, and taught as being four numbers, separated by dots. A typical address would be the localhost address 127.0.0.1. The four parts of an IPv4 address in its dotted representation are called “octets” – that's because each of these numbers is really eight bits long, so this IP address really looks like this:

├────────────── 32 bit ─────────────┤
 01111111.00000000.00000000.00000001
         ├ 8  bit ┤

So really, IP addresses are 32-bit numbers, and the dotted four-octet display is just one of many possible ways of displaying them. Granted, it's the one everybody uses and that is firmly associated with IPv4 addresses.

So the first surprise in 127.257 is that it has only two parts, not four. The other surprise is that it contains a number above 255 (which is the largest number you can usually encode in a binary eight-bit number). Why is that?

The reason

Let's look at a less unusual address first: 127.1 is treated as a valid address, too! If you ping 127.1, you'll see that a ping to 127.0.0.1 is sent out. So if not all octets are given, remaining octets are filled with zeroes.

This behaviour will be familiar if you have looked into IPv6 – in an IPv6 address, you can indicate parts that are filled with zeroes by writing ::. This shorthand will be expanded until the correct IP address length has been reached. This also means you can only use this expansion shorthand once per IP address, because otherwise it wouldn't be clear how much to expand!

But what happens when we ping 127.257? Let's see:

$ ping 127.257

PING 127.257 (127.0.1.1) 56(84) bytes of data.
64 bytes from 127.0.1.1: icmp_seq=1 ttl=64 time=0.072 ms

We're pinging 127.0.1.1 – which isn't that surprising if we look at the representation of the IP address when filled with zeroes – the wider number expands into the next byte to the left, since it's omitted and can be filled however we like:

 ├ 127 ─┤ ├─── filled ───┤├── 257 ─┤
 01111111.00000000.00000001.00000001
 ├ 127 ─┤ ├── 0 ─┤ ├── 1 ─┤ ├── 1 ─┤ 

If we think this through, we should even be able to ping the number represented by 11111110000000000000000000000001 – which is 2130706433, and indeed we can:

$ ping 2130706433

PING 2130706433 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.042 ms

Valid IP addresses

Things that will ping 127.0.0.1:

  • ping 127.0.0.1
  • ping 127.0.1
  • ping 127.1
  • ping 0x7f.1
  • ping 2130706433

Caveats

This blog post describes fun behaviour that is decidedly not standardized. That means you cannot and should not rely on it! This behaviour is implemented in low level systems functions on Unix-like systems, so you'll find it on Linux, BSD, and MacOS at least. If you want to look into detail, man 3 inet should prove helpful, including the source code of the functions in that man page.

You should be especially aware that this behaviour will only be present in software that uses systems libraries to parse IP addresses. Other software will either not support IP addresses without their standard four-octet form, or may even interpret them differently. For instance, the Python standard library doesn't want to play with us:

>>> import ipaddress
>>> ipaddress.IPv4Address('127.1')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/ipaddress.py", line 1301, in __init__
    self._ip = self._ip_int_from_string(addr_str)
  File "/usr/lib/python3.7/ipaddress.py", line 1135, in _ip_int_from_string
    raise AddressValueError("Expected 4 octets in %r" % ip_str)
ipaddress.AddressValueError: Expected 4 octets in '127.1'

And as another similar example, Postgres (at least versions 10 and 11) is also reasonably upset if we try to insert something without four octets (including whole integers!):

postgres=# insert into test_inet values ('127.1');
ERROR:  invalid input syntax for type inet: "127.1"

postgres=# insert into test_inet values (cast('127.1' as inet));
ERROR:  invalid input syntax for type inet: "127.1"

postgres=# insert into test_inet values (cast(2130706432 as inet));
ERROR:  cannot cast type integer to inet

On the other hand, knowing that your system is willing to resolve IP addresses that many applications will not recognize can be a handy way around less-than-carefully implemented firewalls or network filters. Just saying.


If this post was helpful to you, consider clicking the Funding link.