Every device on a network needs an address — a unique identifier that allows other devices to find it and send data to it. At Layer 3 (the Network layer), this identifier is the IP address. IP addresses serve two purposes: identification (which device?) and location (which network?).
Two versions of the Internet Protocol coexist today: IPv4, which has powered the Internet since 1983, and IPv6, designed to replace it as the world runs out of IPv4 addresses.
An IPv4 address is a 32-bit number, typically written in dotted-decimal notation — four octets (bytes) separated by dots:
192.168.1.42
Each octet ranges from 0 to 255. In binary, the address above is:
11000000.10101000.00000001.00101010
This gives IPv4 a total address space of $2^{32} = 4{,}294{,}967{,}296$ addresses — roughly 4.3 billion. This seemed enormous in 1981 but is now exhausted.
Every IP address is divided into two parts:
The division is determined by the subnet mask.
A subnet mask is a 32-bit value where the network bits are set to 1 and the host bits are set to 0:
| CIDR Notation | Subnet Mask | Network Bits | Host Bits | Usable Hosts |
|---|---|---|---|---|
| /8 | 255.0.0.0 | 8 | 24 | 16,777,214 |
| /16 | 255.255.0.0 | 16 | 16 | 65,534 |
| /24 | 255.255.255.0 | 24 | 8 | 254 |
| /28 | 255.255.255.240 | 28 | 4 | 14 |
| /30 | 255.255.255.252 | 30 | 2 | 2 |
| /32 | 255.255.255.255 | 32 | 0 | 1 (single host) |
CIDR (Classless Inter-Domain Routing) notation appends the prefix length to the address: 192.168.1.0/24 means “the first 24 bits are the network portion.”
The number of usable hosts is $2^{(32 - \text{prefix})} - 2$ (subtracting the network address and broadcast address).
Suppose you have the network 10.0.0.0/8 and need to create 4 subnets:
Original: 10.0.0.0/8 → 16,777,214 hosts
Split into /10 subnets (borrow 2 bits → 2² = 4 subnets):
10.0.0.0/10 → 10.0.0.1 to 10.63.255.254 (4,194,302 hosts)
10.64.0.0/10 → 10.64.0.1 to 10.127.255.254 (4,194,302 hosts)
10.128.0.0/10 → 10.128.0.1 to 10.191.255.254 (4,194,302 hosts)
10.192.0.0/10 → 10.192.0.1 to 10.255.255.254 (4,194,302 hosts)
| Address / Range | Purpose |
|---|---|
0.0.0.0 |
Default route / “any” address |
127.0.0.0/8 |
Loopback (localhost) |
10.0.0.0/8 |
Private network (Class A) |
172.16.0.0/12 |
Private network (Class B) |
192.168.0.0/16 |
Private network (Class C) |
169.254.0.0/16 |
Link-local (auto-configuration) |
224.0.0.0/4 |
Multicast |
255.255.255.255 |
Broadcast |
Private addresses (RFC 1918) are not routable on the public Internet. They’re used inside LANs and translated to public addresses via NAT (covered in Chapter 5).
Python’s ipaddress module makes IP calculations straightforward:
import ipaddress
# Parse an address and network
addr = ipaddress.ip_address("192.168.1.42")
net = ipaddress.ip_network("192.168.1.0/24")
print(addr.is_private) # True
print(net.num_addresses) # 256
print(addr in net) # True
print(list(net.hosts())[:3]) # First 3 usable hosts
Full example: code/ip_address_calculator.py
IPv4’s 4.3 billion addresses are exhausted. While NAT has extended IPv4’s life by allowing private networks to share a single public address, this is a workaround — not a solution. NAT breaks end-to-end connectivity, complicates peer-to-peer applications, and adds latency.
IPv6 was designed to solve the address exhaustion problem permanently, while also improving routing efficiency, security, and auto-configuration.
An IPv6 address is 128 bits — four times longer than IPv4. It’s written as eight groups of four hexadecimal digits, separated by colons:
2001:0db8:85a3:0000:0000:8a2e:0370:7334
The total address space is $2^{128} \approx 3.4 \times 10^{38}$ — enough to assign trillions of addresses to every grain of sand on Earth.
IPv6 addresses can be shortened using two rules:
0db8 → db8, 0000 → 0:: (only once per address)Full: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
Simplified: 2001:db8:85a3::8a2e:370:7334
| Type | Prefix | Description |
|---|---|---|
| Global Unicast | 2000::/3 |
Publicly routable (like IPv4 public addresses) |
| Link-Local | fe80::/10 |
Auto-configured, valid only on local link |
| Unique Local | fc00::/7 |
Private (like IPv4 RFC 1918 addresses) |
| Multicast | ff00::/8 |
One-to-many delivery |
| Loopback | ::1 |
Equivalent to 127.0.0.1 |
| Unspecified | :: |
Equivalent to 0.0.0.0 |
Every IPv6 interface automatically gets a link-local address (starting with fe80::) derived from its MAC address or randomly generated. This enables communication on the local network without any configuration.
IPv6 subnetting follows the same principles as IPv4 but with much larger address blocks. The standard allocation for a site is a /48 prefix, which is then subnetted into /64 networks:
ISP assigns: 2001:db8:abcd::/48
↓
Site creates subnets:
2001:db8:abcd:0001::/64 → Office LAN
2001:db8:abcd:0002::/64 → Server VLAN
2001:db8:abcd:0003::/64 → IoT network
...
(65,536 possible /64 subnets)
Within each /64 network, the host portion is 64 bits — $2^{64}$ possible addresses per subnet.
IPv6 replaces ARP (used in IPv4 to map IP → MAC addresses) with Neighbor Discovery Protocol (NDP), which uses ICMPv6 messages:
The same ipaddress module handles IPv6 seamlessly:
import ipaddress
addr6 = ipaddress.ip_address("2001:db8:85a3::8a2e:370:7334")
net6 = ipaddress.ip_network("2001:db8:abcd::/48")
print(addr6.is_global) # True (in real allocation)
print(addr6.exploded) # Full form with all zeros
print(net6.num_addresses) # 1,208,925,819,614,629,174,706,176
print(addr6 in net6) # False (different prefix)
The Internet cannot switch to IPv6 overnight. Several transition mechanisms allow IPv4 and IPv6 to coexist:
Devices run both IPv4 and IPv6 simultaneously. The OS chooses which protocol to use based on destination address availability. This is the most common approach today — most modern operating systems and routers support dual stack.
# Check dual-stack configuration on Linux
ip -4 addr show # IPv4 addresses
ip -6 addr show # IPv6 addresses
IPv6 packets are encapsulated inside IPv4 packets to traverse IPv4-only networks. Common tunneling methods include:
NAT64 translates between IPv6 and IPv4 at a gateway, allowing IPv6-only clients to reach IPv4 servers. DNS64 synthesizes AAAA records (IPv6) from A records (IPv4) so that IPv6 clients can discover IPv4-only services.
# Show all IP addresses (IPv4 and IPv6)
ip addr show
# Show only IPv4
ip -4 addr show
# Show only IPv6
ip -6 addr show
# Resolve a hostname to IP
dig example.com A # IPv4
dig example.com AAAA # IPv6
host example.com
# Ping using IPv4
ping -c 4 8.8.8.8
# Ping using IPv6
ping6 -c 4 2001:4860:4860::8888
# Check if IPv6 is working
curl -6 https://ifconfig.co
/24) specifies the prefix lengthipaddress module provides powerful tools for working with both IPv4 and IPv6 addresses| ← Previous: The OSI Model and TCP/IP Stack | Table of Contents | Next: Network Configuration and Interfaces → |