# IP address

## What it is

An IP address is the number the Internet Protocol writes into every packet to
say where the packet is going, next to a second address that says where it came
from. RFC 791, the 1981 specification of IP version 4, calls it an internet
address and draws the line in one sentence: "An address indicates where it is."
A name, such as a domain, says what you are looking for and has to be turned
into an address first, which is the job of [DNS](/glossary/#dns). The route, the
path between the two addresses, is left to the routers in between.

In IPv4 the address is 32 bits, four octets, written as four decimal numbers
from 0 to 255 separated by dots: 192.0.2.10. RFC 791 split those bits into a
network number and a local part, with three fixed classes deciding where the
split fell. The classes fitted most organisations badly: a class C network held
at most 254 hosts, a class B up to 65,534, so class B numbers ran short while
routing tables grew. In September 1993 RFC 1519 replaced the classes with CIDR,
which lets the split fall after any bit and writes its position after a slash.
192.0.2.0/24 is the block whose first 24 bits are fixed: 256 addresses.

IPv6 widens the address to 128 bits. It is written as eight groups of up to four
hexadecimal digits separated by colons, and RFC 4291 lets one run of zero groups
collapse to `::`, so 2001:db8:0:0:0:0:2:1 can also be written 2001:db8::2:1.
Because the same address could then be spelt many ways, RFC 5952 later fixed one
recommended form: lower case, no leading zeros in a group, and `::` used for the
longest run of zero groups.

An address belongs to a network interface, not to a machine. RFC 791 already
said that a single host must be able to act as several, with several internet
addresses, and that some hosts have several physical interfaces. RFC 4291
requires every IPv6 interface to have at least one link-local address, valid
only on its own network segment, whatever other addresses it holds. A host on a
network that runs both versions therefore carries several addresses at once,
besides the loopback addresses it uses to reach itself: the 127.0.0.0/8 block in
IPv4 and `::1` in IPv6.

Not every address is reachable from everywhere. RFC 1918 sets aside three IPv4
blocks for private networks, 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16, and
says that routing information about them shall not be passed between
organisations. A device holding one therefore reaches the internet only through
something that stands in for it, such as NAT, which RFC 3022 describes as
translating the private address into a public one on the way out. Three further
blocks, 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24, are reserved by RFC
5737 for documentation, and RFC 3849 does the same for 2001:db8::/32 in IPv6.
Every example address in this article comes from those documentation ranges.

## Why it matters

The address is the one fact about a client that a server always has, because
without it the reply would have nowhere to go. That makes it the default key for
many decisions: [firewall](/glossary/#firewall) rules allow or block by address,
a rate limit counts requests per address, an access log writes it on every line,
and an abuse report names it. Each of these works only as well as the address
identifies what you think it identifies.

It identifies less than it seems to. Behind NAT, every device in an office or a
household can reach the internet from the same public IPv4 address, so a block
or a limit aimed at one of them lands on all of them. Behind a proxy or a
[CDN](/glossary/#cdn), the server sees the proxy's address on every request, and
the client's own address arrives, if at all, in a header that someone else
wrote. A rule written against an address is a rule about a network position,
never about a person.

At the same time it reveals more than people expect. RFC 7239, which
standardised the `Forwarded` header that proxies use to pass the client's
address on, notes in its privacy section that an IP address may identify a
client uniquely, show which operator it uses, and give a rough idea of where it
is. A log full of addresses is therefore a liability as well as a tool, and a
good reason to decide what you keep before you start keeping it.

And there are now two kinds. Anything that parses, stores or compares addresses
has to accept IPv4 and IPv6 alike, because a client on a network that offers
both may arrive over either. Code written when every address fit in fifteen
characters of digits and dots breaks on the first IPv6 visitor, usually
somewhere far from where the address was read.

## What it looks like in practice

Python's standard library includes an `ipaddress` module that parses both
versions and does the arithmetic, so you never have to split strings by hand. In
an interactive session:

```text
>>> import ipaddress
>>> ipaddress.ip_address("203.0.113.57")
IPv4Address('203.0.113.57')
>>> ipaddress.ip_address("2001:0DB8:0000:0000:0000:0000:0002:0001")
IPv6Address('2001:db8::2:1')
>>> ipaddress.ip_network("203.0.113.57/24", strict=False)
IPv4Network('203.0.113.0/24')
>>> ipaddress.ip_network("2001:db8:42:7::5/48", strict=False)
IPv6Network('2001:db8:42::/48')
```

The second call shows the normalisation that RFC 5952 asks for: the long
upper-case spelling and the short lower-case one are the same address, and the
module prints the canonical form. A string such as 192.0.2.300 is refused with a
`ValueError`, because 300 does not fit in an octet. The last two calls turn an
address into the network around it. Without `strict=False` the module would
refuse those as well, because 203.0.113.57/24 has host bits set; with it, the
host part is set to zero and what remains is the network alone.

That zeroing is how this site treats its visitors' addresses. Its
[privacy page](/privacy/) says the IP address is never stored in full: it is
shortened at the moment the log line is written, with the last part replaced by
zeroes before anything touches the disk, so what is kept identifies a rough
network, in which a whole office or a whole street can look identical. The page
adds that the server records the address under two names, masks both, and drops
the headers that can carry an address of their own, the ones a company proxy or
a content delivery network puts in front of the visitor, before the line is
written. It does not say how many bits survive; the /24 and /48 above are this
article's illustration, not the site's settings. The design point holds either
way. An address that is never written in full cannot leak in full, and
shortening it on the way in does not depend on a clean-up job that might one day
stop running.

## Where it goes wrong

**Trusting a header for the client's address.** `X-Forwarded-For` and the
standard `Forwarded` header are plain request headers, and RFC 7239 warns that
any hop between the client and the server, and the client itself, may rewrite
the `Forwarded` header, by mistake or on purpose. Code that takes the first
address in the list lets any client choose its own address, and with it its own
rate limit and its own place on an allow-list. Use the address of the TCP
connection unless the request came through a proxy you run, and then read only
what your own proxies appended, counting from the right.

**Treating an address as a person.** Because many users can share one address, a
ban on an address may lock out an entire office, and a limit sized for one
person throttles a whole school. Use addresses for coarse decisions, set limits
with shared addresses in mind, and tie anything personal to an authenticated
account instead.

**Comparing addresses as strings.** 2001:db8::2:1 and 2001:DB8:0:0:0:0:2:1 are
one address and two different strings, so an allow-list matched by text accepts
one and rejects the other. Parse both sides before comparing, or store only the
RFC 5952 form.

**Parsing for IPv4 only.** A regular expression for four dotted numbers, a
column sized for fifteen characters, or code that splits host and port at the
last colon will all fail on IPv6. Use a real parser and a column type or length
meant for both versions. When a port is written next to an IPv6 address, RFC
5952 recommends brackets, as in [2001:db8::1]:443, because without them the last
group could be read as part of the address.

**Keeping full addresses longer than needed.** Logs, analytics tables and
backups collect addresses for years if nothing stops them. Decide what precision
you need, shorten the address when it is written rather than afterwards, and
give the remainder an expiry date.

**Using real addresses in examples.** A sample configuration, a test fixture or
a screenshot that uses someone's real address sends copied traffic to a
stranger. RFC 5737 reserves its three blocks precisely to reduce that kind of
conflict and confusion; use them, and 2001:db8::/32 for IPv6.

## Further reading

- [RFC 791: Internet Protocol](https://www.rfc-editor.org/rfc/rfc791.html) — the
  1981 specification of IPv4, with its 32-bit address and the distinction
  between names, addresses and routes.
- [RFC 4291: IP Version 6 Addressing Architecture](https://www.rfc-editor.org/rfc/rfc4291.html)
  — the IPv6 address types, their text forms and the link-local requirement.
- [RFC 5952: A Recommendation for IPv6 Address Text Representation](https://www.rfc-editor.org/rfc/rfc5952.html)
  — one canonical way to write an IPv6 address, and the trouble that flexible
  spellings cause.
- [RFC 7239: Forwarded HTTP Extension](https://www.rfc-editor.org/rfc/rfc7239.html)
  — how proxies pass a client's address on, why the result cannot simply be
  trusted, and its privacy cost.
- [RFC 5737: IPv4 Address Blocks Reserved for Documentation](https://www.rfc-editor.org/rfc/rfc5737.html)
  — the three ranges meant for examples.
- [ipaddress: IPv4/IPv6 manipulation library](https://docs.python.org/3/library/ipaddress.html)
  — Python's standard module for parsing, comparing and grouping addresses.

<https://engineer.company/glossary/ip-address/>
