# 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 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
```

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

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

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.