127.257 and other fun legacy IP addresses
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.