Computer Networks Lesson 3 of 6 ⏱️ 5 min read

IP Addressing, Subnetting & Routing

IPv4 and IPv6 addresses, CIDR subnetting with worked examples, private addresses, NAT and how routers forward packets.

Updated Oct 30, 2026

IP addresses

An IP address identifies a network interface so packets can be routed to it.

  • IPv4 — 32 bits, written as four numbers 0–255: 192.168.1.25. About 4.3 billion addresses — not enough for today's internet.
  • IPv6 — 128 bits, written in hexadecimal groups: 2001:db8:85a3::8a2e:370:7334. Practically unlimited addresses; :: compresses runs of zeros.
Plaintext
192     .168     .1       .25
11000000 10101000 00000001 00011001   ← 32 bits

CIDR notation and subnets

An address is split into a network part and a host part. CIDR notation states how many leading bits are the network: 192.168.1.0/24 means the first 24 bits identify the network, leaving 8 bits (256 addresses) for hosts.

CIDR Subnet mask Total addresses Usable hosts (IPv4)
/16 255.255.0.0 65,536 65,534
/24 255.255.255.0 256 254
/26 255.255.255.192 64 62
/28 255.255.255.240 16 14
/32 255.255.255.255 1 a single host

Two addresses in each classic IPv4 subnet are reserved: the network address (all host bits 0) and the broadcast address (all host bits 1). Formula: total = 2^(32 − prefix).

Python's ipaddress module does the arithmetic for you:

Python
import ipaddress

net = ipaddress.ip_network("192.168.10.0/26")
hosts = list(net.hosts())

print("netmask:  ", net.netmask)
print("addresses:", net.num_addresses)
print("network:  ", net.network_address)
print("broadcast:", net.broadcast_address)
print("usable:   ", hosts[0], "-", hosts[-1])
print("192.168.10.70 inside?", ipaddress.ip_address("192.168.10.70") in net)

Output:

Plaintext
netmask:   255.255.255.192
addresses: 64
network:   192.168.10.0
broadcast: 192.168.10.63
usable:    192.168.10.1 - 192.168.10.62
192.168.10.70 inside? False

Worked example: splitting a network

A college has 10.20.0.0/22 and wants four equal subnets for labs, staff, students and servers. Borrow 2 bits (/22 → /24):

Python
import ipaddress

for name, subnet in zip(["labs", "staff", "students", "servers"],
                        ipaddress.ip_network("10.20.0.0/22").subnets(new_prefix=24)):
    print(f"{name:<9} {subnet}  ({subnet.num_addresses - 2} hosts)")

Output:

Plaintext
labs      10.20.0.0/24  (254 hosts)
staff     10.20.1.0/24  (254 hosts)
students  10.20.2.0/24  (254 hosts)
servers   10.20.3.0/24  (254 hosts)

The same skill is used when designing cloud VPCs: a VPC like 10.0.0.0/16 is carved into public and private subnets per availability zone.

Private addresses and NAT

Some ranges are reserved for private networks and are not routed on the public internet:

Range CIDR
10.0.0.0 – 10.255.255.255 10.0.0.0/8
172.16.0.0 – 172.31.255.255 172.16.0.0/12
192.168.0.0 – 192.168.255.255 192.168.0.0/16

Also special: 127.0.0.0/8 (loopback, "this machine") and 169.254.0.0/16 (link-local).

NAT (Network Address Translation) lets many private devices share one public IP. Your home router rewrites outgoing packets' source address and port to its public IP, remembers the mapping, and translates replies back. NAT conserved IPv4 addresses for decades, but it means devices behind it can't easily accept incoming connections — which is why video-call apps need tricks like STUN/TURN.

How routing works

A router connects networks and forwards each packet toward its destination using a routing table. For each packet it chooses the most specific matching route — the longest prefix match:

Python
import ipaddress

routing_table = [
    ("10.0.0.0/8",  "core-router"),
    ("10.1.0.0/16", "campus-router"),
    ("10.1.5.0/24", "lab-switch"),
    ("0.0.0.0/0",   "isp-gateway"),       # default route: everything else
]

def next_hop(destination: str) -> str:
    ip = ipaddress.ip_address(destination)
    matches = [(ipaddress.ip_network(prefix), hop) for prefix, hop in routing_table
               if ip in ipaddress.ip_network(prefix)]
    return max(matches, key=lambda m: m[0].prefixlen)[1]     # longest prefix wins

for dst in ("10.1.5.20", "10.1.9.9", "10.200.0.1", "8.8.8.8"):
    print(f"{dst:<11} → {next_hop(dst)}")

Output:

Plaintext
10.1.5.20   → lab-switch
10.1.9.9    → campus-router
10.200.0.1  → core-router
8.8.8.8     → isp-gateway

Each router only decides the next hop; the packet hops router to router until it arrives. Each IP packet has a TTL (hop limit) that decreases at every router, so packets caught in loops eventually die — traceroute uses this to reveal the path.

Routers learn routes from routing protocols: OSPF inside an organisation, and BGP between the networks (autonomous systems) that make up the internet.

ARP: from IP to MAC

On a local network, frames are delivered by MAC address. ARP asks "who has 192.168.1.1?" and the owner replies with its MAC. Your computer sends internet-bound packets to the default gateway's MAC, and the gateway routes them onward.

Common mistakes

  • Forgetting the network and broadcast addresses when counting usable hosts.
  • Overlapping CIDR ranges between VPCs or offices, which makes peering/VPN impossible later.
  • Binding a server to 127.0.0.1 inside a container and wondering why it's unreachable — bind to 0.0.0.0.
  • Treating NAT as a firewall — use real firewall rules.

Key takeaways

  • IPv4 is 32 bits, IPv6 is 128 bits; CIDR /n gives 2^(32 − n) IPv4 addresses.
  • Private ranges (10/8, 172.16/12, 192.168/16) reach the internet through NAT.
  • Routers forward by longest prefix match; the default route 0.0.0.0/0 catches the rest.
  • ARP maps IPs to MAC addresses on the local link.
Finished this lesson?
Tick it off to track your Computer Networks progress.
Next lesson