IPv4 Addressing
How IPv4 addresses are built, what the subnet mask does, which ranges are private or special, and what a default gateway is for.
What an IP address is
An IP address (Internet Protocol address) is the logical address of a network interface in an IP network. Where a MAC address is burned into a network card and only matters on the local link, an IP address is assigned by whoever runs the network and says where the interface is: which network it belongs to, and which host it is within that network. Routers use that structure to move packets across many networks, the way a postal service uses a country, city and street.
IPv4 (Internet Protocol version 4) is the version of IP defined in RFC 791 in 1981, and still carries most traffic inside company networks. Its addresses are 32 bits long, which gives 232, about 4.3 billion, addresses. That turned out to be too few for the internet, which is why private addresses, network address translation (NAT) and eventually IPv6 exist.
Dotted-decimal notation
Thirty-two ones and zeros are unreadable, so an IPv4 address is
written as four octets (groups of 8 bits), each
shown as a decimal number from 0 to 255, separated by dots. Python's
ipaddress module shows what is behind the notation (see
Number Systems for
binary itself):
import ipaddress
address = ipaddress.IPv4Address("192.168.1.10")
print(" ".join(f"{byte:08b}" for byte in address.packed))
print(int(address))
print(ipaddress.IPv4Address(3232235786))
11000000 10101000 00000001 00001010 3232235786 192.168.1.10
address.packed gives the four bytes; each is printed as
8 binary digits. To the computer the address is just one 32-bit
number, 3232235786, and converting that number back gives the
familiar notation again.
Try it: an octet in binary
Convert one octet between decimal and its 8 bits, and see which place values (128 down to 1) add up to the number.
The network part and the host part
Every IPv4 address consists of two parts. The leading bits, the network part (or prefix), are the same for all hosts on one network. The remaining bits, the host part, are different for each host on it. Where the boundary lies is not visible in the address itself; it is configured together with the address.
The subnet mask
A subnet mask is a 32-bit value that marks which bits of an IPv4 address belong to the network part: ones for the network bits, then zeros for the host bits. It is written in dotted decimal like an address. The mask 255.255.255.0 is 24 ones followed by 8 zeros, so with 192.168.1.10 the first three octets (192.168.1) are the network and the last octet (10) is the host.
Prefix notation and CIDR
Because a mask is always a run of ones followed by zeros, it is enough to say how many ones there are. That number is the prefix length, written after a slash: 192.168.1.10/24 means "mask of 24 ones", which is 255.255.255.0. This notation comes from CIDR (Classless Inter-Domain Routing, RFC 1519 in 1993, now RFC 4632): the system that lets the boundary between network and host lie at any bit, instead of only at the fixed class boundaries described below.
import ipaddress
interface = ipaddress.IPv4Interface("192.168.1.10/24")
print(interface.ip)
print(interface.network)
print(interface.netmask)
print(interface.with_netmask)
print(f"{int(interface.netmask):032b}")
for prefix in (8, 16, 24, 26, 30):
net = ipaddress.IPv4Network(f"0.0.0.0/{prefix}")
print(f"/{prefix:<2} = {net.netmask}")
192.168.1.10 192.168.1.0/24 255.255.255.0 192.168.1.10/255.255.255.0 11111111111111111111111100000000 /8 = 255.0.0.0 /16 = 255.255.0.0 /24 = 255.255.255.0 /26 = 255.255.255.192 /30 = 255.255.255.252
An IPv4Interface is an address together with its
prefix, as configured on a network card. From it Python derives the
network the host belongs to (192.168.1.0/24) and the mask. The loop
shows common prefix lengths and their masks. With 8 host bits, a /24
has 28 = 256 addresses, of which 254 can be given to
hosts: the all-zeros host part is the network address and the
all-ones host part is the broadcast address. Calculating these
values for any prefix is the subject of
Subnetting.
Classful addressing (history)
Before CIDR, the first bits of an address fixed where the network part ended. You may still see these classes in older documentation, and exams may ask you to recognize them by the first octet:
| Class | First octet | Default mask | Purpose |
|---|---|---|---|
| A | 1–126 | /8 (255.0.0.0) | Very large networks (127 is loopback) |
| B | 128–191 | /16 (255.255.0.0) | Medium networks |
| C | 192–223 | /24 (255.255.255.0) | Small networks |
| D | 224–239 | none | Multicast |
| E | 240–255 | none | Reserved (experimental) |
The classes were abandoned because they wasted addresses and made routing tables explode. An organization with 2,000 hosts was too big for a class C (254 hosts) and received a class B with 65,534 host addresses, most of which stayed unused. With CIDR, any prefix length is allowed, so that organization gets a /21 (2,046 hosts), and providers can summarize many customer networks into one route. The words live on only as nicknames: people still say "a class C" when they mean a /24.
Private addresses
A private IP address is an address from one of three ranges that RFC 1918 reserved in 1996 for use inside organizations. Anyone may use them without asking a registry, and the same ranges are reused in millions of networks. Internet routers do not route them, so a private address only has meaning inside its own network. To reach the internet, a router or firewall at the edge translates private addresses to a public one with NAT.
They exist because there are not enough public IPv4 addresses for every device. Your laptop at home, your phone and the office printer all have private addresses; only the edge of the network has a public one.
import ipaddress
RFC1918 = [
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
]
for net in RFC1918:
print(f"{str(net):<15} {net[0]} - {net[-1]} ({net.num_addresses:,} addresses)")
print()
for text in ["10.20.30.40", "172.31.255.254", "172.32.0.1", "192.168.1.10", "203.0.113.7"]:
address = ipaddress.ip_address(text)
private = any(address in net for net in RFC1918)
print(f"{text:<15} private: {private}")
10.0.0.0/8 10.0.0.0 - 10.255.255.255 (16,777,216 addresses) 172.16.0.0/12 172.16.0.0 - 172.31.255.255 (1,048,576 addresses) 192.168.0.0/16 192.168.0.0 - 192.168.255.255 (65,536 addresses) 10.20.30.40 private: True 172.31.255.254 private: True 172.32.0.1 private: False 192.168.1.10 private: True 203.0.113.7 private: False
Special addresses
Several other ranges have a fixed meaning. The ones you will meet in practice:
| Range | Name | What it is for |
|---|---|---|
0.0.0.0 |
Unspecified | "No address yet", as the source of a Dynamic Host Configuration Protocol (DHCP) request, or "all local addresses" when a server listens; 0.0.0.0/0 is the default route |
127.0.0.0/8 |
Loopback | The host itself; never leaves the machine |
169.254.0.0/16 |
Link-local (APIPA, Automatic Private IP Addressing) | Self-assigned when no DHCP server answers |
100.64.0.0/10 |
Shared address space | Between a provider's carrier-grade NAT (CGNAT) and its customers (RFC 6598) |
192.0.2.0/24, 198.51.100.0/24,
203.0.113.0/24
|
Documentation | For examples in books and manuals (RFC 5737); never used on real networks |
224.0.0.0/4 |
Multicast | Group addresses (the old class D) |
240.0.0.0/4 |
Reserved | The old class E; not usable |
255.255.255.255 |
Limited broadcast | All hosts on the local network; never routed |
Loopback
A loopback address is an
address in 127.0.0.0/8, most often 127.0.0.1, that refers to the
host itself. Traffic to it never reaches a network card; the
operating system hands it straight back. It lets programs on one
machine talk to each other over IP (a web app to its local database,
for example), and ping 127.0.0.1 tests the host's own
IP stack. The name localhost points to it. Routers have
a different "loopback": a virtual interface, such as
interface Loopback0, with a normal address that stays
up as long as the router does, used as a stable management address.
APIPA
APIPA (Automatic Private IP Addressing) is the mechanism by which a host that gets no answer from a DHCP server gives itself an address from 169.254.0.0/16 (strictly 169.254.1.0 to 169.254.254.255, RFC 3927), after checking with the Address Resolution Protocol (ARP) that no other host uses it. The name comes from Microsoft; the standard calls these IPv4 link-local addresses. They only work between hosts on the same link and have no gateway, so in practice a 169.254.x.x address on a user's PC is a symptom: "DHCP did not work". Check the cable, the VLAN of the switch port and the DHCP server or relay.
A small classifier shows the special ranges side by side. It checks a list of networks in order, so the /32 for the limited broadcast comes before the reserved /4 that contains it:
import ipaddress
SPECIAL = {
"255.255.255.255/32": "limited broadcast",
"0.0.0.0/8": "this network",
"10.0.0.0/8": "private (RFC 1918)",
"100.64.0.0/10": "shared address space (CGNAT)",
"127.0.0.0/8": "loopback",
"169.254.0.0/16": "link-local (APIPA)",
"172.16.0.0/12": "private (RFC 1918)",
"192.0.2.0/24": "documentation",
"192.168.0.0/16": "private (RFC 1918)",
"198.51.100.0/24": "documentation",
"203.0.113.0/24": "documentation",
"224.0.0.0/4": "multicast",
"240.0.0.0/4": "reserved",
}
def classify(text):
address = ipaddress.ip_address(text)
for network, kind in SPECIAL.items():
if address in ipaddress.ip_network(network):
return kind
return "public unicast"
for text in ["127.0.0.1", "169.254.12.34", "100.72.1.9", "224.0.0.5",
"255.255.255.255", "172.20.1.1", "198.51.100.20"]:
print(f"{text:<16} {classify(text)}")
127.0.0.1 loopback 169.254.12.34 link-local (APIPA) 100.72.1.9 shared address space (CGNAT) 224.0.0.5 multicast 255.255.255.255 limited broadcast 172.20.1.1 private (RFC 1918) 198.51.100.20 documentation
Try it: IPv4 calculator
Enter an address with its prefix length or mask to see its network and broadcast address, the usable range, the number of hosts, its historical class and whether it is private, public or special.
Unicast, broadcast and multicast
An IPv4 packet can be addressed to one, all or some hosts:
- Unicast: one sender, one receiver. Almost all traffic, such as web browsing or a file copy, is unicast.
- Broadcast: to every host on a network. The limited broadcast 255.255.255.255 reaches all hosts on the local network, for example a DHCP discover from a host that has no address yet. A directed broadcast is the last address of a subnet, such as 192.168.1.255 for 192.168.1.0/24. Routers never forward limited broadcasts, and by default do not forward directed broadcasts either, because they were abused for attacks.
- Multicast: to a group of hosts that asked to receive it, using an address from 224.0.0.0/4. Routing protocols such as Open Shortest Path First (OSPF) use it (OSPF talks to 224.0.0.5), and so do video streaming and some service discovery protocols.
IPv4 has no separate anycast address type; IPv6 replaces broadcast with multicast altogether.
The default gateway
A host can only deliver a packet directly to a destination on its own subnet: it looks up the destination's MAC address with ARP and sends the frame. For anything else it needs a router. The default gateway is the address of the router on the host's own subnet to which the host sends every packet whose destination is not on that subnet. It is usually the first or last usable address of the subnet, such as 192.168.1.1.
Local or remote: how a host decides
Before sending, the host applies its own subnet mask to the destination address with a bitwise AND, which keeps the network bits and sets the host bits to zero. If the result equals its own network address, the destination is local. Otherwise it is remote, and the packet goes to the gateway: the host uses ARP to find the gateway's MAC address, and sends the frame there with the final destination still in the IP header.
import ipaddress
host = ipaddress.IPv4Interface("192.168.1.10/24")
gateway = ipaddress.IPv4Address("192.168.1.1")
mask = int(host.netmask)
for text in ["192.168.1.25", "192.168.2.25", "203.0.113.80"]:
destination = ipaddress.IPv4Address(text)
network = ipaddress.IPv4Address(int(destination) & mask)
print(f"{destination} AND {host.netmask} = {network}")
if network == host.network.network_address:
print(f" local: send directly to {destination}")
else:
print(f" remote: send to the default gateway {gateway}")
192.168.1.25 AND 255.255.255.0 = 192.168.1.0 local: send directly to 192.168.1.25 192.168.2.25 AND 255.255.255.0 = 192.168.2.0 remote: send to the default gateway 192.168.1.1 203.0.113.80 AND 255.255.255.0 = 203.0.113.0 remote: send to the default gateway 192.168.1.1
Note that 192.168.2.25 is remote even though it is also a private
address in the same building: what counts is the subnet, not whether
the address "looks close". In Python you can write the same test
shorter as destination in host.network.
Try it: network address with bitwise AND
Enter an address and a mask to see the AND bit by bit, and the broadcast address that OR with the inverted mask gives.
How a host gets its address
A host can get its IPv4 settings in two ways:
- Static: an administrator types the address, mask, gateway and Domain Name System (DNS) servers into the device. Used for devices that others must find at a fixed address: routers, switches, servers, printers.
- Dynamic: the host asks a DHCP server, which leases it an address with all the other settings. Used for almost all clients. See DHCP.
Checking the address
On Linux, the ip command from iproute2 shows the
address and the default route:
ip -4 addr show dev eth0
ip route show default
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
inet 192.168.1.10/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0
valid_lft 85833sec preferred_lft 85833sec
default via 192.168.1.1 dev eth0 proto dhcp src 192.168.1.10 metric 100
inet 192.168.1.10/24 is the address with its prefix,
brd the broadcast address, and
dynamic with a valid_lft means it came
from DHCP with a lease. The default route,
default via 192.168.1.1, is the default gateway. On
Windows, ipconfig shows the same (ipconfig /all
adds the MAC address, DHCP server and DNS servers):
ipconfig
Windows IP Configuration Ethernet adapter Ethernet: Connection-specific DNS Suffix . : example.com Link-local IPv6 Address . . . . . : fe80::1c4a:7e2f:9b3d:2a61%12 IPv4 Address. . . . . . . . . . . : 192.168.1.20 Subnet Mask . . . . . . . . . . . : 255.255.255.0 Default Gateway . . . . . . . . . : 192.168.1.1
On a Cisco router, addresses are configured statically per interface, and one command gives an overview of all interfaces:
R1(config)# interface GigabitEthernet0/0
R1(config-if)# ip address 192.168.1.1 255.255.255.0
R1(config-if)# no shutdown
R1(config-if)# end
R1# show ip interface brief
Interface IP-Address OK? Method Status Protocol
GigabitEthernet0/0 192.168.1.1 YES manual up up
GigabitEthernet0/1 203.0.113.2 YES manual up up
GigabitEthernet0/2 unassigned YES unset administratively down down
ip address takes the address and a dotted-decimal mask;
no shutdown enables the interface, because router
interfaces are shut down by default. In
show ip interface brief, Status is layer 1 (is
there a link?) and Protocol is layer 2 (does the data link
work?). administratively down means someone, or the
default configuration, shut the interface down. This is often the
first command run when a ticket reports a site as unreachable.