The Network Layer & IP Routing
The network layer is the global addressing and routing fabric that makes the internet one logical network out of millions of heterogeneous physical ones.
Overview
Every time you load a webpage, your browser sends a request that traverses dozens of routers across continents — each one making an independent forwarding decision in microseconds — to reach a server it has never directly touched. This coordination miracle is the network layer's job. Where the link layer gets a frame from one machine to its immediate neighbor, and the transport layer manages end-to-end reliability, the network layer provides the global glue: how does a packet get from any source to any destination on the planet, across arbitrary networks that may use completely different technologies?
The answer sits on two interlocking pillars. The first is IP addressing — a globally agreed-upon scheme for naming every interface on every device, structured so that addresses reveal topology. The second is routing — a distributed, asynchronous computation running simultaneously on every router, building the maps that make forwarding decisions possible. Understanding these pillars means understanding why the internet scales to billions of devices while remaining decentralized, why it is resilient to failures, and why it has the particular failure modes it does.
This module covers both IPv4 and IPv6, subnet and CIDR notation, the distinction between forwarding (the data plane, microsecond scale) and routing (the control plane, second scale), the two canonical routing algorithm families (link-state and distance-vector), and the global inter-domain routing protocol BGP that stitches autonomous systems into the internet. Along the way we will build genuine intuition for longest-prefix matching, Dijkstra's algorithm, the Bellman-Ford recurrence, the count-to-infinity problem, and the policy-driven world of interdomain routing.
By the end of this module you will be able to subnet a network from scratch, trace a packet's forwarding path, understand why BGP convergence is slow and why that matters for internet stability, and appreciate the engineering tradeoffs that shaped the design of the protocols billions of people rely on daily.
The network layer is the reason the internet exists as a single logical network rather than a collection of isolated islands. Every cloud service, every API call, every video stream, every SSH session you will ever write code for depends on IP routing working correctly. As a software engineer, understanding this layer lets you diagnose latency, reason about failure domains, design services for global distribution, and understand the security properties (and vulnerabilities) of the systems you build. It is also the substrate on which containers, virtual machines, Kubernetes networking, VPNs, CDNs, and load balancers are built — getting network-layer intuition wrong means you will be perpetually confused by the infrastructure around you.
Beyond pragmatics, the network layer is a stunning example of distributed algorithm design under real-world constraints. The routing algorithms here — link-state and distance-vector — are not just computer-science curriculum; they are running right now on routers that collectively maintain a consistent, loop-free view of a network that changes constantly. BGP is arguably the most consequential distributed system ever deployed: it is the protocol that holds the internet together, and its failure modes (route leaks, BGP hijacks) make global headline news. Understanding the layer at this depth gives you a lens on distributed systems, algorithm design, and systems engineering that transfers broadly.
BGP Inter-AS Routing and Packet Forwarding Path
A packet from Host 10.0.1.5 (AS 64501) to Google's 8.8.8.8 (AS 15169) traverses three autonomous systems. Within each AS, an interior gateway protocol (OSPF or IS-IS) handles forwarding. At AS boundaries, eBGP sessions exchange route announcements with AS_PATH prepended at each crossing. The data plane (numbered arrows) follows the forwarding table populated by the control plane (BGP/IGP).
The concepts
01IP Addressing: Hierarchical Names for a Flat World
An IP address is a 32-bit (IPv4) or 128-bit (IPv6) number assigned to a network interface, not a machine. A laptop with Wi-Fi and Ethernet has two interfaces, each with its own IP address. The address is not a random identifier — it encodes topology: addresses are allocated in contiguous blocks to organizations, and those blocks are allocated in larger blocks to regions, ISPs, and continents. This hierarchy is what makes routing tables feasible.
An IPv4 address like 192.168.1.42 is written in dotted-decimal notation: four 8-bit octets in base 10 separated by dots. In binary, it is 11000000.10101000.00000001.00101010. The key insight is that the address is split into a network prefix (the high-order bits, shared by all addresses in the same subnet) and a host part (the low-order bits, identifying the specific interface within that subnet).
A subnet mask or prefix length specifies where the split is. The notation /24 means the first 24 bits are the network prefix. The network 192.168.1.0/24 contains 2^8 = 256 addresses (192.168.1.0 through 192.168.1.255). Two interfaces on the same /24 subnet can communicate at layer 2 (Ethernet/ARP) without involving a router. Communication to any other prefix must go through the default gateway (a router).
The brilliant structural property: because addresses are hierarchically allocated, a router in Tokyo does not need a separate entry for every one of the ~4 billion IPv4 addresses. It only needs to know which next hop covers large prefixes — and then let routers closer to the destination worry about the details. This aggregation is what makes global routing tables manageable (around 900,000 prefixes today for IPv4, not 4 billion individual entries).
# Python: parse and inspect an IPv4 address
import ipaddress
net = ipaddress.ip_network('192.168.1.0/24')
print(net.network_address) # 192.168.1.0
print(net.broadcast_address) # 192.168.1.255
print(net.prefixlen) # 24
print(net.num_addresses) # 256
# Check if a host is in the subnet
host = ipaddress.ip_address('192.168.1.42')
print(host in net) # True
# The subnet mask in dotted form
print(net.netmask) # 255.255.255.0
02Subnets, CIDR, and Longest-Prefix Matching
Classless Inter-Domain Routing (CIDR), introduced in 1993, replaced the old class-based system (Class A/B/C) with a flexible prefix-length scheme. Before CIDR, the world was divided into a small number of fixed-size blocks: Class A (8-bit prefix, 16 million hosts), Class B (16-bit prefix, 65,536 hosts), Class C (24-bit prefix, 256 hosts). This was catastrophically wasteful — a company needing 300 addresses had to get a Class B (65,536 addresses) and waste almost all of them. CIDR allows any prefix length, so you can allocate exactly a /23 (512 addresses) or a /28 (16 addresses).
Subnetting is the practice of dividing a larger allocated block into smaller pieces for internal organization. An ISP might receive 10.0.0.0/8 and subdivide it into /16 blocks for customers, who further subdivide into /24 blocks for departments. The hierarchy mirrors organizational structure.
Longest-prefix matching (LPM) is the fundamental forwarding rule: when a router looks up a destination address, it matches it against all prefixes in its forwarding table and picks the most specific (longest prefix) match. This is what makes hierarchical allocation work: a router can have both 0.0.0.0/0 (the default route, matches everything) and 192.168.1.0/24 (a specific subnet). A packet to 192.168.1.5 matches both, but the /24 is longer, so it wins. This allows more-specific exceptions to coexist with aggregate routes.
In hardware, LPM is implemented with specialized data structures (TCAM — ternary content-addressable memory) that can search a million-entry forwarding table in a single clock cycle. This is why your home router can forward packets at wire speed despite having a full routing table.
# Pseudocode: longest-prefix match in a forwarding table
# Table: list of (prefix, prefix_len, next_hop)
forwarding_table = [
('0.0.0.0', 0, 'gateway'), # default route
('10.0.0.0', 8, 'isp-link'),
('10.1.0.0', 16, 'datacenter'),
('10.1.2.0', 24, 'rack-42'),
]
def lookup(dest_ip, table):
best_prefix_len = -1
best_next_hop = None
dest_int = ip_to_int(dest_ip) # convert to 32-bit integer
for (prefix, plen, next_hop) in table:
prefix_int = ip_to_int(prefix)
mask = (0xFFFFFFFF << (32 - plen)) & 0xFFFFFFFF
if (dest_int & mask) == (prefix_int & mask):
if plen > best_prefix_len: # longest match wins
best_prefix_len = plen
best_next_hop = next_hop
return best_next_hop
# lookup('10.1.2.55') -> 'rack-42' (matches /24, /16, /8, /0 but /24 wins)
# lookup('10.2.3.4') -> 'isp-link' (matches /8 and /0, /8 wins)
03IPv4 vs IPv6: Why the Internet is Running Out of Addresses
IPv4's 32-bit address space yields ~4.3 billion unique addresses. In the 1980s this seemed infinite. By the early 1990s it was clear it was not — the explosive growth of the internet, combined with classful allocation waste, meant exhaustion was coming. The Internet Assigned Numbers Authority (IANA) allocated the last IPv4 /8 blocks to regional registries in 2011.
Stopgaps delayed the crisis: Network Address Translation (NAT) allows an entire home or office network to share a single public IP address, with the NAT device rewriting source addresses and tracking sessions. This breaks the end-to-end principle (a remote host cannot initiate a connection to a NATted host), but it bought a decade of breathing room. Private address spaces (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) are used internally and never appear on the global internet.
IPv6 was standardized in 1998 (RFC 2460) to solve the problem permanently. Its 128-bit address space contains 2^128 ≈ 3.4 × 10^38 addresses — enough to assign billions of addresses to every atom on Earth's surface. IPv6 addresses are written as eight groups of four hex digits separated by colons: 2001:0db8:85a3:0000:0000:8a2e:0370:7334. Consecutive all-zero groups can be compressed with :: once per address: 2001:db8::1.
IPv6 also simplifies the packet header (fixed 40-byte header, no checksum — left to upper layers, no fragmentation at routers), adds mandatory IPsec support hooks, and includes built-in autoconfiguration (SLAAC — Stateless Address Autoconfiguration). The transition has been painfully slow — as of 2024, roughly 40-45% of Google traffic is over IPv6 — because it requires upgrading every router, firewall, and application that inspects IP addresses.
Key structural differences: IPv6 has no broadcast (replaced by multicast and anycast). The link-local prefix fe80::/10 is used for neighbor discovery (replacing ARP). The loopback is ::1. Global unicast addresses start with 2000::/3.
# IPv4 header (simplified) — 20 bytes minimum
# 0 1 2 3
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# |Version| IHL |Type of Service| Total Length |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Identification |Flags| Fragment Offset |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Time to Live | Protocol | Header Checksum |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Source Address |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Destination Address |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# IPv6 header — exactly 40 bytes, simpler
# Version(4) | Traffic Class(8) | Flow Label(20)
# Payload Length(16) | Next Header(8) | Hop Limit(8)
# Source Address (128 bits)
# Destination Address (128 bits)
# No checksum, no fragmentation fields, no options in base header
04Forwarding vs Routing: Data Plane vs Control Plane
This is one of the most important conceptual distinctions in networking, and it maps directly onto how modern routers are built.
Forwarding (the data plane) is the per-packet, real-time action: given an incoming packet, look up the destination IP in the forwarding table (FIB — Forwarding Information Base), and send the packet out the correct interface toward the next hop. This happens at line rate — tens to hundreds of millions of packets per second on a modern router — in specialized hardware (ASICs, NPUs, TCAM). The forwarding table is essentially a read-only lookup structure; the data plane does not think, it just executes.
Routing (the control plane) is the distributed computation that builds the forwarding table. Routing protocols (OSPF, RIP, BGP) run as software processes on the router's general-purpose CPU. They exchange messages with neighboring routers, run graph algorithms, and periodically install updated routes into the FIB. This happens on a timescale of seconds to minutes, not microseconds.
The analogy: forwarding is like a postal clerk reading the address on a letter and dropping it in the right bin (takes milliseconds, done a million times a day). Routing is like the process that decided which bin corresponds to which region — done once, updated when the post office network changes.
In Software-Defined Networking (SDN), this separation is made explicit and radical: the control plane is physically separated from the data plane and moved to a centralized controller (like OpenFlow). Individual switches become simple forwarding elements that execute rules pushed by the controller. This allows network-wide optimization that is impossible when every router computes its own local view.
The TTL (Time-to-Live) field in the IP header is a forwarding-level loop prevention mechanism: every router decrements TTL by 1, and if it reaches 0, the packet is dropped and an ICMP Time Exceeded message is sent back. traceroute exploits this by sending packets with TTL=1, 2, 3, ... to elicit ICMP responses from successive hops.
# Simplified forwarding logic in pseudocode (what a router does per packet)
function forward_packet(packet):
if packet.ttl <= 0:
send ICMP(TIME_EXCEEDED) to packet.src
drop packet
return
packet.ttl -= 1
recalculate_checksum(packet) # IPv4 only; IPv6 has no header checksum
next_hop = longest_prefix_match(packet.dst, forwarding_table)
if next_hop is None:
send ICMP(DEST_UNREACHABLE) to packet.src
return
out_interface = next_hop.interface
next_hop_mac = arp_lookup(next_hop.ip, out_interface) # layer 2
encapsulate_and_send(packet, next_hop_mac, out_interface)
05Link-State Routing: Dijkstra with Global Knowledge
Link-state routing is based on a seductively simple idea: if every router knows the complete topology of the network, every router can independently compute the shortest paths to all destinations using a classical graph algorithm.
Here is how it works in practice (as in OSPF — Open Shortest Path First):
- Neighbor discovery: each router uses Hello packets to discover its directly connected neighbors and measure the cost (usually bandwidth-derived) of each link.
- LSA flooding: each router creates a Link-State Advertisement (LSA) describing its local connectivity ("I am Router A, my neighbors are B (cost 2) and C (cost 5)") and floods it to the entire network. Every router forwards each LSA it receives out all interfaces except the one it arrived on, exactly once.
- Topology database: each router accumulates LSAs until it has a complete picture of the entire network as a weighted graph.
- Dijkstra's algorithm: each router runs Dijkstra independently on its local topology database to compute shortest-path trees rooted at itself, then installs the results as forwarding entries.
Dijkstra's algorithm (for those needing a refresher): maintain a set of "settled" nodes (shortest path known) and a priority queue of candidates. Initialize: dist[source] = 0, dist[all others] = ∞. Repeatedly extract the minimum-distance unsettled node u, add it to settled, and relax its neighbors: if dist[u] + cost(u,v) < dist[v], update dist[v] and record that the best predecessor of v is u. Time complexity: O((V + E) log V) with a binary heap.
Convergence: when a link fails, the routers on either end flood a new LSA with an infinite-cost entry. All routers receive this within seconds (flooding is fast), rerun Dijkstra, and install new routes. Convergence is fast and loop-free because all routers compute from the same consistent topology snapshot.
Drawback: flooding O(N^2) messages in a large network is expensive. LSPs (link-state packets) are suppressed with sequence numbers to prevent loops and reduplication. OSPF uses areas to limit flooding scope — routers within an area have full topology knowledge; inter-area routing is summarized.
import heapq
def dijkstra(graph, source):
"""
graph: dict of {node: [(neighbor, cost), ...]}
Returns: (dist dict, prev dict for path reconstruction)
"""
dist = {node: float('inf') for node in graph}
prev = {node: None for node in graph}
dist[source] = 0
pq = [(0, source)] # (distance, node)
while pq:
d, u = heapq.heappop(pq)
if d > dist[u]: # stale entry in heap
continue
for (v, cost) in graph[u]:
alt = dist[u] + cost
if alt < dist[v]:
dist[v] = alt
prev[v] = u
heapq.heappush(pq, (alt, v))
return dist, prev
# Example: small network
graph = {
'A': [('B', 2), ('C', 5)],
'B': [('A', 2), ('C', 1), ('D', 4)],
'C': [('A', 5), ('B', 1), ('D', 1)],
'D': [('B', 4), ('C', 1)],
}
dist, prev = dijkstra(graph, 'A')
# dist = {'A': 0, 'B': 2, 'C': 3, 'D': 4}
# Next hop from A to D: A -> B -> C -> D
06Distance-Vector Routing: Bellman-Ford with Local Knowledge
Distance-vector routing takes the opposite philosophy: each router knows only its immediate neighbors, and routers iteratively share their current best-distance vectors with each other until the whole network converges. The classical protocol is RIP (Routing Information Protocol); the algorithm is distributed Bellman-Ford.
The core Bellman-Ford recurrence: the shortest distance from node v to destination d is:
dist[v][d] = min over all neighbors u of (cost(v,u) + dist[u][d])
In the protocol: each router periodically sends its entire distance vector (the table of distances it currently believes to all destinations) to each neighbor. Upon receiving a neighbor's vector, a router updates its own table using the recurrence above. This continues until no more updates propagate — which is guaranteed in a finite network without negative cycles.
Convergence is slow for bad news — the infamous count-to-infinity problem. Suppose A-B-C are in a line. C fails. B sets its distance to C as ∞. But before B tells A, A sends its vector to B: "I can reach C in 2 hops via B." B thinks: "Great, A can reach C in 2, so I can reach it in 3!" and updates its table. A then learns from B: "B says C is 3 hops away, so I can reach C in 4!" This loop continues until the distance counts to infinity (typically 16 in RIP, where 16 = unreachable).
Solutions:
- Split horizon: never advertise a route back on the interface you learned it from.
- Poison reverse: actively advertise infinity for routes learned on the same interface.
- Triggered updates: send updates immediately when something changes, not just on schedule.
- Hold-down timers: after learning a route is gone, ignore better-looking routes for a while.
DV routing has lower overhead than link-state (each router only talks to its neighbors), but converges more slowly and has worse failure behavior. RIP still runs in some small networks. EIGRP (Cisco's Enhanced IGRP) is a hybrid that avoids count-to-infinity with a diffusing computation.
# Distributed Bellman-Ford (one router's update step)
# Each router maintains: dist[dest] = (cost, next_hop)
# On receiving neighbor n's distance vector dv_n:
def receive_distance_vector(self, neighbor, dv_n, link_cost_to_neighbor):
updated = False
for dest, neighbor_cost in dv_n.items():
candidate = link_cost_to_neighbor + neighbor_cost
if candidate < self.dist.get(dest, float('inf')):
self.dist[dest] = candidate
self.next_hop[dest] = neighbor
updated = True
# Split horizon: don't advertise back what we learned from neighbor
if updated:
self.send_dv_to_all_neighbors(exclude=neighbor)
# Count-to-infinity illustrated:
# t=0: A->C=1, B->C=2 (B routes through A)
# t=1: C fails. A sets dist[C]=inf.
# t=2: B still has dist[C]=2. A hears B->C=2: updates dist[C]=3 (via B).
# t=3: B hears A->C=3: updates dist[C]=4 (via A). Loop!
07Hierarchical Routing and Autonomous Systems
A flat routing scheme — one routing protocol for all ~70,000 networks on the internet — would be infeasible. The scale problem is obvious: no router could store routes to every subnet, and the messaging overhead of global link-state flooding or DV exchange would overwhelm every link. But there is also a policy problem: the internet is not owned by one entity. Companies, universities, governments, and ISPs each own and operate their own networks, and they have complex commercial and political relationships that affect which routes they are willing to carry traffic on.
The solution is hierarchical routing organized around Autonomous Systems (ASes). An AS is a network (or collection of networks) under a single administrative authority, identified by a globally unique AS Number (ASN) (originally 16-bit, now 32-bit, allocated by IANA/regional registries). Google is AS15169. Comcast is AS7922. Each AS runs whatever internal routing protocol it likes (OSPF, EIGRP, RIP, IS-IS — called Interior Gateway Protocols, IGP). Between ASes, a single standard protocol is used: BGP.
The division of routing into intra-AS (IGP) and inter-AS (BGP) is fundamental:
- IGP: optimize for shortest path, minimal convergence time, full topology knowledge within the AS.
- BGP: propagate reachability announcements ("AS 15169 can reach prefix 8.8.8.0/24") and enforce policy (business agreements, peering vs transit, traffic engineering).
The physical topology of an AS is invisible outside it. From the outside, an AS is a black box that announces prefixes. This encapsulation is what allows the internet's routing system to scale — the global BGP table carries ~900,000 prefixes (aggregated blocks), not entries for every individual host.
# No code needed here — the key structure is conceptual.
# Typical AS topology:
#
# [Stub AS] --customer--> [Transit ISP AS] <--peer--> [Peer ISP AS]
# | | |
# (end-user) (carries transit) (carries transit)
#
# Routing policy example:
# - Transit provider: accept all your routes, carry traffic to/from internet.
# - Peering: exchange routes for each other's customers only, no transit.
# - Customer: pay for transit, your routes are accepted and propagated.
#
# A BGP route announcement carries:
# NLRI (prefix), AS_PATH, NEXT_HOP, LOCAL_PREF, MED, communities
#
# A router receiving prefix P via two paths:
# Path 1: AS_PATH = [701, 3356, 15169] (3 hops)
# Path 2: AS_PATH = [7922, 15169] (2 hops)
# Prefers Path 2 (shorter AS_PATH) — all else equal.
08BGP: The Protocol That Holds the Internet Together
Border Gateway Protocol (BGP), currently at version 4 (RFC 4271), is the inter-domain routing protocol of the internet. It is an Exterior Gateway Protocol (EGP) designed around policy rather than optimal paths. Every AS boundary router runs BGP and maintains persistent TCP connections (port 179) to its BGP peers (neighboring AS routers).
BGP is a path-vector protocol: each route announcement carries not just the destination prefix and a cost, but the full AS_PATH — the sequence of AS numbers the announcement has traversed. This has two critical properties:
- Loop prevention: if a router sees its own AS number in the AS_PATH of an incoming announcement, it rejects it.
- Policy expression: operators can inspect the AS_PATH (and other attributes) to implement complex routing policies.
BGP route selection is a long decision process applied when multiple paths exist for the same prefix:
- Highest LOCAL_PREF (set locally to prefer certain exit paths)
- Shortest AS_PATH
- Lowest origin type (IGP < EGP < incomplete)
- Lowest MED (Multi-Exit Discriminator, a hint from the neighboring AS)
- eBGP over iBGP paths
- Lowest IGP cost to next hop
- Oldest route (stability), then lowest router ID as tiebreaker
iBGP vs eBGP: eBGP (external BGP) runs between routers in different ASes. iBGP (internal BGP) runs between routers within the same AS to distribute externally learned routes. iBGP has a critical rule: routes learned via iBGP are not re-advertised to other iBGP peers (to prevent loops). This requires a full mesh of iBGP sessions within an AS (or a route reflector hierarchy).
BGP convergence is slow — minutes in the worst case — because it relies on TCP (for reliability) and incremental updates. Path exploration after a failure can create transient routing loops. This is why internet outages (a major AS going down, a BGP misconfiguration) can last minutes.
BGP security is a deep concern: the protocol itself has no origin authentication. A malicious or misconfigured AS can announce prefixes it does not own, hijacking traffic. The 2010 China Telecom incident (AS4134 announcing ~37,000 foreign prefixes) redirected global traffic through China for 18 minutes. RPKI (Resource Public Key Infrastructure) is the modern fix: cryptographic certificates tie prefixes to authorized origin ASes.
# BGP UPDATE message structure (simplified)
# Sent over TCP to announce or withdraw routes
bgp_update = {
'withdrawn_routes': ['203.0.113.0/24'], # prefixes to remove
'path_attributes': {
'ORIGIN': 'IGP', # how the route was learned
'AS_PATH': [64501, 64502, 15169], # ASes traversed
'NEXT_HOP': '198.51.100.1', # where to forward to
'LOCAL_PREF': 100, # preference (higher = better)
'MED': 50, # metric for neighbor
'COMMUNITIES': ['64501:100'], # policy tags
},
'nlri': ['8.8.8.0/24', '8.8.4.0/24'], # prefixes being announced
}
# BGP state machine (simplified)
# IDLE -> CONNECT -> ACTIVE -> OPENSENT -> OPENCONFIRM -> ESTABLISHED
# In ESTABLISHED state: exchange KEEPALIVE (every 60s) and UPDATE messages
# Missing 3 KAs = HOLD_TIMER expires = session reset
09ICMP and Network Diagnostics
Internet Control Message Protocol (ICMP) is the network layer's error-reporting and diagnostic protocol (RFC 792 for v4, RFC 4443 for v6). ICMP messages are carried inside IP packets but are conceptually part of the network layer (not the transport layer). They are generated by routers and hosts to report conditions that the IP layer cannot handle silently.
Key ICMP message types:
- Echo Request / Echo Reply (type 8/0): the basis of
ping. A host sends an Echo Request; the destination replies with Echo Reply. Round-trip time gives network latency. - Destination Unreachable (type 3): generated by a router that cannot forward a packet, or a host that has no application listening on the destination port. The code field gives the reason (network unreachable, host unreachable, port unreachable, fragmentation needed but DF bit set).
- Time Exceeded (type 11): generated when TTL reaches 0. This is the mechanism
tracerouteexploits. - Redirect (type 5): a router tells a host to use a better gateway for a particular destination.
traceroute works by sending a series of UDP packets (or ICMP Echo Requests on Windows) with TTL=1, 2, 3, ... The router at hop k decrements TTL to 0, drops the packet, and sends back an ICMP Time Exceeded. By recording the source IP and timestamp of each ICMP response, traceroute reconstructs the path. Some routers are configured to not generate ICMP Time Exceeded messages (rate-limited or firewalled), producing * * * lines.
# Python: send a ping (ICMP Echo Request) using raw sockets
# (requires root/admin privileges)
import socket, struct, time, os
def checksum(data):
s = 0
for i in range(0, len(data), 2):
w = (data[i] << 8) + (data[i+1] if i+1 < len(data) else 0)
s = (s + w) & 0xFFFF
return ~s & 0xFFFF
def ping(dest, seq=1):
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
# ICMP Echo Request: type=8, code=0, id=PID, seq=seq
pid = os.getpid() & 0xFFFF
header = struct.pack('bbHHh', 8, 0, 0, pid, seq) # checksum=0 for now
payload = struct.pack('d', time.time())
chk = checksum(header + payload)
header = struct.pack('bbHHh', 8, 0, socket.htons(chk), pid, seq)
sock.sendto(header + payload, (dest, 0))
recv, addr = sock.recvfrom(1024)
rtt = (time.time() - struct.unpack('d', recv[28:36])[0]) * 1000
print(f'Reply from {addr[0]}: time={rtt:.2f}ms')
Key insights
- ✦IP addresses encode topology (hierarchy, not just identity) — this is what makes routing table aggregation possible and is the single most important structural property of the addressing scheme.
- ✦Longest-prefix matching is the forwarding rule that makes CIDR work: more-specific prefixes always beat less-specific ones, enabling hierarchical delegation without losing the ability to make exceptions.
- ✦Forwarding (data plane, microseconds, hardware) and routing (control plane, seconds, software) are fundamentally different operations that happen at different timescales — conflating them causes confusion about router architecture and SDN.
- ✦Link-state routing achieves fast, loop-free convergence by giving every router global knowledge; distance-vector routing uses only local knowledge but suffers count-to-infinity on failure, an inherent consequence of the distributed Bellman-Ford algorithm.
- ✦BGP is a policy protocol, not a shortest-path protocol — the shortest AS_PATH is only one criterion, and LOCAL_PREF (set by local policy) overrides it. Understanding this explains why internet traffic often takes seemingly suboptimal paths.
- ✦The Autonomous System abstraction is what allows the internet to scale: by hiding internal topology and using prefix aggregation at AS boundaries, the global routing table stays manageable despite billions of end hosts.
- ✦BGP's lack of origin authentication (historically) made it vulnerable to prefix hijacking — RPKI is the deployed fix, but adoption is still partial as of 2024.
- ✦IPv4 exhaustion was a real engineering crisis: the transition to IPv6 has taken 25+ years, not because the protocol is technically hard, but because of the installed base problem — a vivid lesson in the inertia of deployed infrastructure.
Common pitfalls
- ⚠Confusing a subnet's network address (first address, all-zeros host part) and broadcast address (last address, all-ones host part) with usable host addresses — a /24 has 256 addresses but only 254 usable hosts.
- ⚠Forgetting that IP addresses are assigned to *interfaces*, not to machines — a router with 4 interfaces has 4 IP addresses, and a multihomed server also has multiple addresses.
- ⚠Thinking that routing protocols find 'the shortest path' in the internet — BGP explicitly does not; it finds a *policy-compliant* path, which may be far from geometrically or latency-optimal.
- ⚠Confusing iBGP and eBGP: iBGP peers are within the same AS (no AS_PATH prepend), eBGP peers are across AS boundaries (AS_PATH is extended). The iBGP no-readvertise rule is a common source of routing black holes.
- ⚠Assuming that link-state and distance-vector routing converge instantly — both have convergence times ranging from milliseconds to minutes depending on topology, timer settings, and failure patterns.
- ⚠Treating the TTL field as a time value — it is a *hop count*, decremented by 1 at each router, with no relationship to wall-clock time despite its name.
- ⚠Thinking that NAT is part of the IP standard — it is a workaround that violates the end-to-end principle and causes significant problems for P2P applications, VoIP, and certain security protocols.
Practice
- 01medium
Subnet the block `172.16.0.0/20` into exactly 8 equal subnets. For each subnet: write its network address, broadcast address, and the range of usable host addresses in dotted-decimal notation. Then: if a router has one entry for the original /20 and specific entries for subnets 3 and 7, show how longest-prefix matching resolves a lookup for a host in subnet 3, a host in subnet 6, and a host outside the /20.
- 02hard
Implement distributed Bellman-Ford in Python: represent a network as a graph, give each 'router' only its local link costs, and simulate the iterative DV exchange until convergence. Then demonstrate the count-to-infinity problem by removing a link and observing the loop. Finally, implement split-horizon and show that it prevents the loop in this case (but find a topology where split-horizon alone is insufficient).
- 03medium
Trace through Dijkstra's algorithm by hand on this network: A-B(cost 1), A-C(cost 4), B-C(cost 2), B-D(cost 5), C-D(cost 1). Compute the shortest path tree from A. Then: if the link B-D fails, which LSAs are flooded and which routers need to rerun Dijkstra? Compare this to what would happen with distance-vector routing on the same failure.
- 04hard
Write a Python script that reads a BGP routing table dump (use a sample from CAIDA's RouteViews project or construct a synthetic one) and: (1) finds the top 10 most frequently occurring ASes in AS_PATH, (2) counts the distribution of AS_PATH lengths, (3) identifies any prefixes more specific than a /24 (which are often considered filtering noise). What does the AS_PATH length distribution tell you about internet diameter?
- 05hard
Prove that Bellman-Ford correctly computes shortest paths in a graph with no negative cycles. Then explain why the count-to-infinity problem is not a 'bug' in the implementation but an inherent consequence of the asynchronous distributed version of the algorithm, and formalize the condition under which it occurs.
Resources
MIT OCW archive; lecture notes on routing algorithms and BGP are rigorous and freely available
The original IPv4 specification; the header diagram and addressing sections are essential primary sources
The definitive BGP specification; the decision process section (Section 9) is the authoritative source on route selection
The IPv6 specification; header format and addressing architecture are in RFC 4291
Annual analysis of BGP table growth, prefix counts, AS path lengths, and routing security — grounding for the real-world scale numbers
Stanford's undergraduate networking course with labs including implementing a router; lectures on IP and routing are publicly available
Accurate and comprehensive treatment of CIDR notation, subnetting arithmetic, and historical context
Checkpoints — you can now…
- ✓You can take any IP address and prefix length (e.g., 10.200.48.0/21) and correctly compute: the network address, broadcast address, number of usable hosts, and whether two given addresses are in the same subnet — without a calculator.
- ✓You can explain, in plain language, why longest-prefix matching is necessary and trace through a small forwarding table to determine the correct next hop for any destination, including cases where multiple prefixes match.
- ✓You can articulate the difference between the data plane and the control plane, explain why this separation matters, and describe how SDN makes this separation explicit.
- ✓You can trace Dijkstra's algorithm step by step on a small weighted graph, producing the correct shortest-path tree and explaining when and why link-state routing re-converges after a failure.
- ✓You can explain the count-to-infinity problem, give a concrete example with a specific network topology, and explain why split-horizon mitigates but does not fully solve it.
- ✓You can describe what an Autonomous System is, why the internet is organized around them, and explain the difference between IGP and BGP in terms of their goals and mechanisms.
- ✓You can walk through the BGP decision process for a prefix reachable via two paths with different attributes, explain why LOCAL_PREF beats AS_PATH length, and explain what a BGP hijack is and how RPKI addresses it.