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
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.
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 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?
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
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
├ 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
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
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.