Skip to content

IP addressing (IPv4, IPv6)

Every device that sends or receives data on the internet needs an answer to one question: how does a packet know where to go? A MAC address only identifies a device on its own local network — it means nothing to a router three networks away. What does mean something everywhere is an IP address, the logical address that IP, the Internet layer protocol, uses to route a packet from any host to any other host, anywhere on the internet.

IPv4: 32 bits, four numbers

An IPv4 address is a 32-bit number. Written in binary it would be unreadable, so it's conventionally split into four 8-bit groups (called octets) and written in dotted-decimal notation — each octet converted to a decimal number from 0 to 255, separated by dots:

192.168.1.10

Each octet is 8 bits, and 8 bits can represent 2⁸ = 256 distinct values, numbered 0 through 255 — which is exactly why you'll never see a valid IPv4 octet above 255. The full 32-bit address space gives 2³² addresses, a little over 4.3 billion — a number that seemed enormous in the 1980s and turned out, decades later, to be nowhere near enough for a planet full of phones, servers, and IoT devices. What engineers did about that shortage is the subject of the IPv6 section below, and of Public vs Private IP later in this module.

Network portion and host portion

An IP address isn't one flat number — part of it identifies which network a host belongs to, and the rest identifies which specific host on that network. Which bits are "network" and which are "host" isn't fixed; it's determined by a companion value called a subnet mask or, more commonly today, expressed in CIDR notation as a suffix like /24.

192.168.1.10/24 means the first 24 bits (the first three octets, 192.168.1) identify the network, and the remaining 8 bits identify the specific host on it. This is the single most important idea in IP addressing, and it deserves proper treatment on its own — the next two articles work through it in full: CIDR explains the notation and how it defines a network's size, and Subnet mask calculation walks through computing it by hand.

Address classes — a legacy scheme worth knowing

Before CIDR existed, IPv4 addresses were divided into fixed-size classes — Class A, B, and C being the ones you'll actually encounter references to:

Class First octet range Default network size Typical use
A 1–126 /8 (16.7 million hosts) Very large networks
B 128–191 /16 (65,536 hosts) Mid-sized networks
C 192–223 /24 (256 hosts) Small networks

This scheme was rigid and wasteful — an organization needing 300 hosts had no choice but to take a whole Class B, wasting over 65,000 addresses — and it was formally replaced by CIDR in 1993 precisely because classful allocation was burning through the IPv4 address space far faster than it needed to. You won't configure a "Class C network" on any modern router, but the terminology persists in casual conversation and older documentation, so it's worth recognizing even though CIDR is what you'll actually use.

Reading an address's role at a glance

Not every IPv4 address behaves the same way. Most of the addresses you'll ever type or see in a config file are ordinary unicast addresses — one address, identifying one specific host, no special behavior attached. The other three categories below are special cases you'll recognize by their range rather than by anything marked on the address itself:

  • Private addresses, like 192.168.1.10, are reserved for use inside local networks and aren't routable on the public internet. Public vs Private IP covers this in full.
  • Loopback addresses, the 127.0.0.0/8 range, always refer back to the host itself. 127.0.0.1 means "this machine," and you'll use it constantly to test a local service without touching the network at all.
  • Broadcast addresses, typically the last address in a network's range, send one message to every host on that network simultaneously. CIDR, next in this module, explains exactly which address that is and why.

IPv6: why 32 bits wasn't enough

IPv4's 4.3 billion addresses sound like plenty until you account for how allocation actually happened: entire blocks handed out to organizations, universities, and early internet pioneers well before phones, smart TVs, and IoT sensors existed, with no way to reclaim the unused portions efficiently. By the 1990s it was clear the pool would run out, and the IETF designed a successor with a vastly larger address space: IPv6, standardized in 1998 and still being adopted today, uses 128-bit addresses — 2¹²⁸ possible addresses, a number so large that a commonly cited comparison is that it could assign a unique address to every grain of sand on Earth many times over.

An IPv6 address is written as eight groups of four hexadecimal digits, separated by colons:

2001:0db8:85a3:0000:0000:8a2e:0370:7334

Two shortening rules make these more manageable to write and read:

  1. Leading zeros within a group can be dropped: 0db8 becomes db8.
  2. One consecutive run of all-zero groups can be collapsed to ::, but only once per address, since using it twice would make the address ambiguous about how many zero groups were omitted from each spot.

Applying both rules, the address above becomes:

2001:db8:85a3::8a2e:370:7334

IPv6 also has its own loopback address, ::1 (equivalent to IPv4's 127.0.0.1). It drops broadcast entirely in favour of two more targeted schemes: multicast, where one packet is delivered to every host that has chosen to subscribe to a particular group, and anycast, where one address is advertised from many locations at once and the network delivers each packet to whichever copy is closest. Both get proper treatment later in the course; for now it's enough to know that "send to everyone on this network" is not how IPv6 works.

IPv4 vs. IPv6 at a glance

IPv4 IPv6
Address length 32 bits 128 bits
Notation Dotted decimal (192.168.1.10) Hexadecimal, colon-separated (2001:db8::1)
Address space ~4.3 billion ~340 undecillion
Loopback 127.0.0.1 ::1
Header design Includes fields IPv6 removed for simplicity Simplified, fixed-size header

IPv6 adoption has been gradual rather than a hard cutover — most networks today run dual stack, meaning hosts have both an IPv4 and an IPv6 address and can use either, which is why you'll see both notations in real command output for years to come.

What an IP packet actually carries

Everything above describes the address itself, but the address is only one field in the header IP actually attaches to your data. A simplified view of building one, from the application down:

flowchart TD
    A["Application data"] --> B["Transport header added<br/>(TCP or UDP: source port, destination port)"]
    B --> C["IP header added<br/>source IP, destination IP, TTL, protocol number, MTU-related flags"]
    C --> D["Link-layer frame added<br/>(source MAC, destination MAC)"]

Two IP header fields matter far more in practice than their obscurity suggests. TTL (Time To Live) starts at a value set by the sending host and gets decremented by one at every router that forwards the packet; if it hits zero, the packet is dropped and the router that dropped it sends back an ICMP "time exceeded" message. That single mechanism is what traceroute/tracepath actually exploits — sending packets with deliberately low TTLs to make each router along the path reveal itself. The protocol field says what's inside the packet (6 for TCP, 17 for UDP, 1 for ICMP), which is how the receiving host's IP stack knows which transport-layer handler should even look at the payload.

It's worth being precise about what IP itself does and doesn't do with this packet once it's built: IP does not retransmit anything and does not reorder anything. Those are the accuracy traps worth avoiding out loud in an interview — reliability and ordering are TCP's job, layered on top; plain IP (and UDP riding on it) will silently let a packet vanish or arrive out of sequence and never notice. What IP does do, on its own, is decide whether a packet needs to be split up to fit through a link with a smaller frame size than the packet — which is the one piece of "handling" that belongs to IP alone.

MTU and fragmentation

Every link has a MTU (Maximum Transmission Unit) — the largest frame it can carry in one piece. Standard Ethernet's is 1500 bytes. If an IP packet is larger than the MTU of the next link it needs to cross, one of two things happens: IPv4 either fragments it into smaller pieces that get reassembled at the destination, or — if the packet's Don't Fragment (DF) bit is set — the router refuses to fragment it, drops the whole packet, and sends back an ICMP "Destination Unreachable, fragmentation needed" message telling the sender what MTU it should have used instead. That feedback loop is what modern systems use to run Path MTU Discovery (PMTUD): send with DF set, shrink the packet size whenever that ICMP message comes back, and settle on the largest size the whole path actually supports.

You can watch this from a Linux host directly:

ping -M do -s 1472 -c 2 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 1472(1500) bytes of data.
1480 bytes from 192.168.1.1: icmp_seq=1 ttl=64 time=0.612 ms
1480 bytes from 192.168.1.1: icmp_seq=2 ttl=64 time=0.598 ms

-M do sets the Don't Fragment bit and -s 1472 requests a 1472-byte payload — with the 8-byte ICMP header and 20-byte IP header, that lands exactly at a 1500-byte frame, the standard Ethernet MTU. It succeeds because the whole path supports 1500. Push past it and the failure looks distinctive:

ping -M do -s 1473 -c 2 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 1473(1501) bytes of data.
ping: local error: message too long, mtu=1500

Note that this particular failure was caught locally, before the packet even left the host — the kernel already knew the outgoing interface's MTU. A failure further out along the path (a smaller-MTU link a few hops away, common over some VPNs and tunnels) instead shows up as packets that vanish silently rather than an immediate local error, which is a much harder symptom to diagnose: everything looks fine for small requests and mysteriously hangs for large ones. tracepath is built specifically to hunt for that:

tracepath 192.168.1.1
 1?: [LOCALHOST]                      pmtu 1500
 1:  192.168.1.1                                      0.416ms reached
     Resumed: pmtu 1500 hops 1 back 1

The pmtu 1500 in the output is tracepath actively probing the path for the smallest MTU any hop reports, which is exactly the number a mismatched tunnel or VPN interface would drop.

Reading IP addresses in real command output

On a Linux host, ip addr show lists the addresses assigned to each network interface. (You may still see ifconfig in older guides; it's deprecated on modern distributions and can report incomplete information about interfaces with multiple addresses, so prefer ip.)

ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:4e:66:a1 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.10/24 brd 192.168.1.255 scope global eth0
    inet6 fe80::a00:27ff:fe4e:66a1/64 scope link

Reading this line by line: link/ether is the interface's MAC address (Layer 2, covered in MAC address); inet 192.168.1.10/24 is its IPv4 address with the CIDR prefix; brd 192.168.1.255 is the broadcast address for that network; and inet6 fe80::... is an automatically assigned IPv6 link-local address, valid only on the local network segment. The actual numbers on your own machine will differ — the MAC address, IP address, and interface name are all specific to your hardware and network.

Practical scenario: the same wire, two different ideas about the network

Two servers sit on the same office switch. One can reach the other; the other can't reach back. Nothing is unplugged, both addresses look correct, and both are obviously "in 192.168.1.x."

# on host A
ip addr show eth0 | grep inet
    inet 192.168.1.10/24 scope global eth0
# on host B
ip addr show eth0 | grep inet
    inet 192.168.1.200/25 scope global eth0

Those two prefixes disagree about what "the local network" means, and that's the whole bug. Host A's /24 covers 192.168.1.0 through 192.168.1.255, so A considers B a local neighbour and sends frames to it directly. Host B's /25 covers only 192.168.1.128 through 192.168.1.255 — and 192.168.1.10 falls outside it. So when B replies, it decides the destination is on some other network and hands the packet to its default gateway instead of putting it straight onto the wire.

Confirming it takes one more command:

# on host B
ip route show
default via 192.168.1.129 dev eth0
192.168.1.128/25 dev eth0 proto kernel scope link src 192.168.1.200

There it is in the second line: B's own idea of its directly connected network stops at .128. Traffic to .10 follows the default route instead, and whether it ever comes back depends on whether that router is willing to send a packet back out of the interface it arrived on. Often it isn't, and you get exactly this signature — one direction works, the other silently fails.

The fix is to make the prefixes agree, not to change either address. And the lesson generalizes: 192.168.1.200 is not an address, it's half of one. Without the prefix it doesn't specify which network the host belongs to, which means it doesn't specify who the host can talk to directly.

Common mistakes

  • Writing an IPv4 octet above 255 (like 192.168.1.999) — an easy typo that's invalid because 8 bits can't represent a value that high.
  • Assuming an IP address alone tells you the network size. 192.168.1.10 could belong to a /24 network of 256 addresses or a /16 network of 65,536 — the prefix length is not optional information, it's part of the address's meaning, as the scenario above shows the hard way.
  • Treating IPv6's :: shorthand as usable more than once per address. 2001::db8::1 is invalid and ambiguous — there's no way to know how many zero groups each :: was supposed to represent.
  • Confusing a private IP address with a public one just because it "looks like a normal IP." 192.168.1.10 and 8.8.8.8 are both syntactically valid IPv4 addresses, but only one of them is reachable from the public internet — see Public vs Private IP for the exact ranges that distinguish them.

Practice exercises

  1. Convert the IPv4 address 10.0.0.5 to its 32-bit binary representation, one octet at a time, and verify each octet is a valid 8-bit value.
  2. Expand the shortened IPv6 address fe80::1 back to its full eight-group, four-hex-digit form.
  3. Run ip addr show on a machine you have access to, and identify the IPv4 address, its CIDR prefix, and the MAC address of at least one interface. Then run ip route show and find the line that corresponds to the network that address belongs to.
  4. Using the scenario above, work out which of these pairs would have the same problem and which would work fine: 10.0.0.5/8 talking to 10.99.0.7/8; 172.16.4.10/16 talking to 172.16.200.9/24; 192.168.0.50/23 talking to 192.168.1.50/23. For each, say whether both hosts agree that the other is a local neighbour.

Exercise 4 is impossible to answer reliably by eye, and that's the point — you have to be able to turn a prefix length into an actual range of addresses. That skill is entirely mechanical once you know the arithmetic, and it's what the next two articles build: CIDR for what the number after the slash means and how it sizes a network, then Subnet mask calculation for computing ranges and boundaries by hand.

Sources