Loading content...
In normal IP routing, packets find their own way through the network—each router makes an independent forwarding decision based on its routing table. But what if you need absolute control over the exact path a packet takes? What if you need to ensure traffic flows through specific routers for monitoring, testing, or policy compliance?
Strict Source Routing (SSR) answers this need by allowing the source host to specify the complete, exact sequence of routers the packet must traverse. No deviations are permitted—if any specified router cannot be reached directly from the previous hop, the packet is dropped. It's the ultimate expression of sender-controlled routing.
By the end of this page, you will understand how Strict Source Routing works, its option structure and pointer mechanics, how routers process source-routed packets, error handling when routes fail, and why this option is almost universally blocked on modern networks.
Source routing inverts the traditional routing paradigm. Instead of routers determining paths, the source host specifies the route.
Traditional Routing:
Source Routing:
Two Flavors of Source Routing:
IPv4 provides two source routing options with different strictness levels:
| Option | Type Value | Behavior |
|---|---|---|
| Strict Source Routing | 137 (0x89) | Must go directly through each specified router—no intermediate hops allowed |
| Loose Source Routing | 131 (0x83) | Must visit each specified router, but normal routing allowed between them |
This page focuses on Strict Source Routing (SSR). The next page covers Loose Source Routing (LSR).
The Strict Source Route (SSR) option, also called Strict Source and Record Route (SSRR), uses the same format as Record Route with similar semantics but different purpose.
+--------+--------+--------+--------+
| Type | Length | Pointer| |
| (137) | | | IP #1 |
+--------+--------+--------+--------+
| IP Address #1 |
+-----------------------------------+
| IP Address #2 |
+-----------------------------------+
| ... |
+-----------------------------------+
| IP Address #N |
+-----------------------------------+
Option Identification:
| Field | Value | Binary | Description |
|---|---|---|---|
| Type | 137 | 10001001 | SSR identifier |
| Copy Flag | 1 | bit 0 | Copied to all fragments |
| Class | 0 | bits 1-2 | Control option |
| Number | 9 | bits 3-7 | Option number within class |
Unlike Record Route (copy=0), SSR has copy flag set to 1. This is critical: every fragment needs routing instructions. If fragments could take different paths without routing info, they'd never reach the destination correctly.
| Field | Size | Offset | Purpose |
|---|---|---|---|
| Type | 1 byte | 0 | Value 137 (0x89) - identifies SSR |
| Length | 1 byte | 1 | Total option length (3 + 4×N) |
| Pointer | 1 byte | 2 | Points to next route address to use |
| Route Data | 4×N bytes | 3+ | Sequence of router IP addresses |
The "strict" in Strict Source Routing means no intermediate hops are allowed between specified routers. Each router in the list must be directly reachable from the previous router.
Direct Reachability Requirement:
Example Scenarios:
When a router cannot reach the next specified address directly, it generates an ICMP Destination Unreachable message with code 5 (Source Route Failed) and drops the packet. The source learns that the strict route is impossible.
When a router receives a packet with Strict Source Routing, it follows a specific algorithm that differs significantly from normal forwarding.
Processing Steps:
The Address Swap Mechanism:
This is the clever part of source routing. The packet's destination address changes at each hop:
This mechanism allows the packet to "walk" through the specified route while simultaneously recording the path taken.
Let's trace a packet through strict source routing step by step.
Scenario:
| Stage | IP Destination | Pointer | Route Data[0] | Route Data[1] | Route Data[2] |
|---|---|---|---|---|---|
| Host A sends | 10.0.1.1 (R1) | 4 | 10.0.2.1 | 10.0.3.1 | 10.20.30.40 |
| R1 processes | 10.0.2.1 (R2) | 8 | 10.0.1.1* | 10.0.3.1 | 10.20.30.40 |
| R2 processes | 10.0.3.1 (R3) | 12 | 10.0.1.1 | 10.0.2.1* | 10.20.30.40 |
| R3 processes | 10.20.30.40 (B) | 16 | 10.0.1.1 | 10.0.2.1 | 10.0.3.1* |
| Host B receives | 10.20.30.40 | 16 | 10.0.1.1 | 10.0.2.1 | 10.0.3.1 |
Note: *asterisk indicates the address just recorded by that router
Observations:
Step-by-step SSR packet transformation: ═══════════════════════════════════════════════════════════════STAGE 1: Host A creates packet═══════════════════════════════════════════════════════════════Source IP: 192.168.1.10 (Host A)Destination IP: 10.0.1.1 (R1) ← First hop goes in destinationSSR Option: Type=137, Length=15, Pointer=4Route Data: [10.0.2.1] [10.0.3.1] [10.20.30.40] ↑ Pointer (next to use) ═══════════════════════════════════════════════════════════════STAGE 2: R1 receives packet (destination = R1's address)═══════════════════════════════════════════════════════════════R1 actions:1. "Destination is me, but pointer (4) ≤ length (15)"2. "Read next address at pointer position: 10.0.2.1"3. "Is 10.0.2.1 directly reachable? YES (it's my neighbor)"4. "Swap: destination ← 10.0.2.1, record my IP (10.0.1.1)"5. "Advance pointer: 4 → 8" After R1:Destination IP: 10.0.2.1 (R2)Route Data: [10.0.1.1] [10.0.3.1] [10.20.30.40] ↑recorded ↑ Pointer ═══════════════════════════════════════════════════════════════STAGE 3: R2 receives packet═══════════════════════════════════════════════════════════════R2 actions:1. "Destination is me, pointer (8) ≤ length (15)"2. "Read next address at pointer position: 10.0.3.1"3. "Is 10.0.3.1 directly reachable? YES"4. "Swap: destination ← 10.0.3.1, record my IP (10.0.2.1)"5. "Advance pointer: 8 → 12" After R2:Destination IP: 10.0.3.1 (R3)Route Data: [10.0.1.1] [10.0.2.1] [10.20.30.40] ↑recorded ↑ Pointer ═══════════════════════════════════════════════════════════════STAGE 4: R3 receives packet═══════════════════════════════════════════════════════════════R3 actions:1. "Destination is me, pointer (12) ≤ length (15)"2. "Read next address at pointer position: 10.20.30.40"3. "Is 10.20.30.40 directly reachable? YES (on my subnet)"4. "Swap: destination ← 10.20.30.40, record my IP"5. "Advance pointer: 12 → 16" After R3:Destination IP: 10.20.30.40 (Host B) ← Final destination!Route Data: [10.0.1.1] [10.0.2.1] [10.0.3.1]Pointer: 16 > Length(15) → Route exhausted ═══════════════════════════════════════════════════════════════STAGE 5: Host B receives packet═══════════════════════════════════════════════════════════════- Destination matches Host B's IP- Pointer (16) > Length (15) → Final destination confirmed- Route data now contains: R1 → R2 → R3 (reverse path for reply)- Deliver to upper layer protocolImplementing strict source routing correctly requires careful attention to pointer arithmetic, direct reachability checking, and proper address swapping.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
#include <stdint.h>#include <stdbool.h>#include <string.h> #define OPT_SSR 137 // Strict Source Route type typedef struct { uint32_t src_ip; uint32_t dst_ip; uint8_t options[40]; uint8_t options_len;} IPv4Packet; typedef struct { uint32_t ip; uint32_t subnet_mask; uint32_t connected_networks[8]; // Directly connected network IPs int num_connected;} RouterInterface; /** * Check if an IP is directly reachable (STRICT requirement) * * For Strict Source Routing, the next hop must be: * 1. On a directly connected network * 2. Reachable without going through another router */bool is_directly_reachable(RouterInterface *interfaces, int num_if, uint32_t target_ip) { for (int i = 0; i < num_if; i++) { // Check if target is on same subnet as this interface uint32_t our_network = interfaces[i].ip & interfaces[i].subnet_mask; uint32_t target_network = target_ip & interfaces[i].subnet_mask; if (our_network == target_network) { // Target is on our directly connected network // (We can ARP for it directly) return true; } } return false; // Would need to route through another router} /** * Process Strict Source Route option * * @param packet The IPv4 packet * @param my_ip Our IP address (for recording) * @param interfaces Our network interfaces * @param num_if Number of interfaces * @return 0 on success, -1 on error (should send ICMP and drop) */int process_ssr(IPv4Packet *packet, uint32_t my_ip, RouterInterface *interfaces, int num_if) { // Find SSR option in options area int offset = 0; while (offset < packet->options_len) { uint8_t type = packet->options[offset]; if (type == 0) break; // EOL if (type == 1) { // NOP offset++; continue; } if (type == OPT_SSR) { // Found Strict Source Route option uint8_t length = packet->options[offset + 1]; uint8_t pointer = packet->options[offset + 2]; // Is this packet addressed to us? if (packet->dst_ip != my_ip) { // Not our concern yet - forward normally // (SSR only triggers when we're the destination) offset += length; continue; } // We're the destination - check if there's more route if (pointer > length) { // Route exhausted - we're the final destination return 0; // Success - deliver to upper layer } // More route to go - get next hop int route_offset = offset + pointer - 1; // Convert 1-indexed to 0-indexed if (route_offset + 4 > offset + length) { // Malformed option return -1; } // Extract next hop IP uint32_t next_hop; memcpy(&next_hop, &packet->options[route_offset], 4); next_hop = ntohl(next_hop); // Network to host byte order // STRICT CHECK: Is next hop directly reachable? if (!is_directly_reachable(interfaces, num_if, next_hop)) { // STRICT ROUTE FAILED! // Should send ICMP Dest Unreachable, Code 5 return -1; } // Good - perform the swap // 1. Record our IP where we read the next hop uint32_t my_ip_network = htonl(my_ip); memcpy(&packet->options[route_offset], &my_ip_network, 4); // 2. Set new destination packet->dst_ip = next_hop; // 3. Advance pointer packet->options[offset + 2] = pointer + 4; return 0; // Success - forward to new destination } // Skip this option by its length uint8_t length = packet->options[offset + 1]; offset += length; } return 0; // No SSR found, normal processing}When Strict Source Routing fails, routers generate ICMP error messages to inform the source. Understanding these errors is crucial for troubleshooting.
ICMP Destination Unreachable - Code 5: Source Route Failed
This specific code indicates the source route could not be followed. The ICMP message includes:
| ICMP Type | Code | Name | Cause |
|---|---|---|---|
| 3 (Dest Unreachable) | 5 | Source Route Failed | Next hop not directly reachable |
| 11 (Time Exceeded) | 0 | TTL Expired in Transit | Routing loop in source route |
| 12 (Parameter Problem) | 0 | Pointer indicates error | Malformed SSR option |
123456789101112131415161718192021222324252627282930313233343536
#include <stdint.h> // ICMP Type 3: Destination Unreachable#define ICMP_DEST_UNREACHABLE 3 // Code 5: Source Route Failed#define ICMP_CODE_SOURCE_ROUTE_FAILED 5 typedef struct __attribute__((packed)) { uint8_t type; uint8_t code; uint16_t checksum; uint32_t unused; // Must be zero uint8_t original_header[]; // Variable: IP header + 8 bytes of original data} ICMPDestUnreachable; /** * Generate ICMP Source Route Failed message * * Called when strict source routing cannot find direct path */void send_source_route_failed(IPv4Packet *original, uint32_t our_ip) { ICMPDestUnreachable icmp; icmp.type = ICMP_DEST_UNREACHABLE; icmp.code = ICMP_CODE_SOURCE_ROUTE_FAILED; icmp.checksum = 0; // Calculated later icmp.unused = 0; // Copy original IP header + first 8 bytes of data // This helps the source identify which packet failed // (Limited to 8 bytes per original RFC 792) // Build and send ICMP packet back to source... // (Implementation details omitted)}When you receive 'Source Route Failed', the ICMP message tells you which router couldn't continue. Check: (1) Are the specified routers directly connected? (2) Is an intervening router missing from the route? (3) Has network topology changed since the route was planned?
Understanding when Strict Source Routing might be used (historically) and why it's largely obsolete helps contextualize this option.
Historical Use Cases:
Why SSR Requires Complete Topology Knowledge:
To use SSR, you must know:
This makes SSR impractical for most real-world scenarios—you'd need network documentation that's rarely available and always current.
Strict Source Routing presents severe security risks that have led to its near-universal blocking on modern networks.
Security Vulnerabilities:
RFC 7126 (2014) and numerous security best practices recommend BLOCKING all source-routed packets at network borders. Most enterprise firewalls, ISPs, and cloud providers drop packets with SSR or LSR options by default. Never enable source routing processing unless you have an extremely specific, controlled use case.
# Cisco IOS - Disable IP source routing (recommended)Router(config)# no ip source-route # Linux - Disable source routing at kernel levelecho 0 > /proc/sys/net/ipv4/accept_source_route # Or permanently in /etc/sysctl.conf:net.ipv4.accept_source_route = 0 # iptables - Drop source-routed packetsiptables -A INPUT -m ipv4options --ssrr -j DROPiptables -A INPUT -m ipv4options --lsrr -j DROPiptables -A FORWARD -m ipv4options --ssrr -j DROPiptables -A FORWARD -m ipv4options --lsrr -j DROP # Windows (registry)HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\ParametersDisableIPSourceRouting = 2 (drop all source-routed packets)We've comprehensively covered Strict Source Routing. Let's consolidate the key points:
What's Next:
Now that you understand the strictest form of source routing, we'll explore Loose Source Routing—a more flexible variant that allows normal routing between specified waypoints. This relaxation makes LSR more practical but carries the same security concerns.
You now have complete knowledge of Strict Source Routing—its structure, processing algorithm, error handling, and security implications. While rarely used in practice, understanding SSR is essential for protocol analysis, security assessment, and historical context of IP design decisions.