691  Routing Labs: Advanced

691.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Implement packet forwarding logic on ESP32 with TTL decrement and route lookup
  • Simulate routing table decisions using embedded C code with multiple route types
  • Visualize network convergence using Python and matplotlib to understand route propagation
  • Analyze packet drop scenarios including TTL expiration and missing routes
  • Debug routing issues by understanding forwarding statistics and error conditions

Lab Series: - Routing Labs: Overview - Lab series introduction and quiz - Routing Labs: Fundamentals - Basic routing table commands and traceroute - Routing Labs: Algorithms - Python implementations of routing algorithms

Core Concepts: - Routing Fundamentals - Core routing theory and concepts - Routing Comprehensive Review - Advanced routing strategies

IoT Routing: - RPL Fundamentals - Low-power routing protocol - RPL Labs - Hands-on RPL implementation

Learning: - Simulations Hub - Routing simulators and tools

691.2 Prerequisites

Before working through these advanced labs, ensure you have completed:

Software Requirements:

  • Arduino IDE with ESP32 board support
  • Python 3.x with matplotlib and networkx libraries

691.3 Lab 4: ESP32 Packet Forwarding Simulator

Time: ~30 min | Difficulty: Advanced | Code: P07.C20.U01

Objective: Simulate packet forwarding with TTL decrement on ESP32.

Hardware Required:

  • ESP32 development board
  • USB cable
  • Computer with Arduino IDE

Software Setup:

  1. Install ESP32 board support in Arduino IDE
  2. No additional libraries needed

691.3.1 Understanding the Simulation

This lab simulates how a router processes packets:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
flowchart TD
    PKT["Packet Arrives"]

    PKT --> TTL{"TTL = 0?"}
    TTL -->|"Yes"| DROP1["DROP<br/>ICMP Time Exceeded"]
    TTL -->|"No"| DEC["Decrement TTL"]

    DEC --> LOOKUP["Route Lookup<br/>(Longest Prefix Match)"]

    LOOKUP --> FOUND{"Route<br/>Found?"}
    FOUND -->|"No"| DROP2["DROP<br/>ICMP Dest Unreachable"]
    FOUND -->|"Yes"| TYPE{"Route<br/>Type?"}

    TYPE -->|"Connected"| DIRECT["Direct Delivery<br/>(ARP → Send)"]
    TYPE -->|"Static/Default"| FORWARD["Forward to<br/>Next Hop"]

    style PKT fill:#E67E22,stroke:#2C3E50,color:#fff
    style DROP1 fill:#E74C3C,stroke:#2C3E50,color:#fff
    style DROP2 fill:#E74C3C,stroke:#2C3E50,color:#fff
    style DIRECT fill:#16A085,stroke:#2C3E50,color:#fff
    style FORWARD fill:#16A085,stroke:#2C3E50,color:#fff

Figure 691.1: Packet Forwarding Process: A router first checks TTL - if zero, the packet is dropped. Otherwise, TTL is decremented and the routing table is consulted. Connected routes result in direct delivery while static/default routes forward to the next hop.

691.3.2 Complete ESP32 Code

#include <Wi-Fi.h>

// Simulated packet structure
struct IPPacket {
  char source_ip[16];
  char dest_ip[16];
  uint8_t ttl;
  uint8_t protocol;
  char payload[64];
  uint32_t packet_id;
};

// Simulated routing table entry
struct RouteEntry {
  char destination[16];
  char next_hop[16];
  char interface[10];
  int metric;
};

// Simulated routing table
RouteEntry routing_table[] = {
  {"192.168.1.0/24", "0.0.0.0", "eth0", 0},      // Connected
  {"10.0.0.0/8", "0.0.0.0", "eth1", 0},          // Connected
  {"172.16.0.0/16", "10.0.0.2", "eth1", 10},     // Static
  {"0.0.0.0/0", "192.168.1.1", "eth0", 1}        // Default
};

const int ROUTING_TABLE_SIZE = 4;
uint32_t packets_forwarded = 0;
uint32_t packets_dropped = 0;
uint32_t ttl_expired = 0;

void setup() {
  Serial.begin(115200);
  delay(2000);

  Serial.println("\n========================================");
  Serial.println("  ESP32 Packet Forwarding Simulator");
  Serial.println("  Routing & TTL Management Demo");
  Serial.println("========================================\n");

  displayRoutingTable();

  Serial.println("\n=======================================");
  Serial.println("Starting Packet Forwarding Simulation");
  Serial.println("=======================================\n");

  // Simulate various packet scenarios
  simulatePacketScenarios();

  displayStatistics();
}

void loop() {
  // Simulation runs once in setup()
}

void displayRoutingTable() {
  Serial.println("Routing Table:");
  Serial.println("-----------------------------------------------------");
  Serial.println("Destination       Next Hop        Interface  Metric");
  Serial.println("-----------------------------------------------------");

  for (int i = 0; i < ROUTING_TABLE_SIZE; i++) {
    char line[100];
    sprintf(line, "%-17s %-15s %-10s %d",
            routing_table[i].destination,
            routing_table[i].next_hop,
            routing_table[i].interface,
            routing_table[i].metric);
    Serial.println(line);
  }
  Serial.println("-----------------------------------------------------");
}

RouteEntry* lookupRoute(const char* dest_ip) {
  // Simplified lookup - in reality would do longest prefix matching

  // Check for local network matches (simplified)
  if (strncmp(dest_ip, "192.168.1.", 10) == 0) {
    return &routing_table[0];  // 192.168.1.0/24
  } else if (strncmp(dest_ip, "10.", 3) == 0) {
    return &routing_table[1];  // 10.0.0.0/8
  } else if (strncmp(dest_ip, "172.16.", 7) == 0) {
    return &routing_table[2];  // 172.16.0.0/16
  } else {
    return &routing_table[3];  // Default route
  }
}

enum ForwardResult {
  FORWARDED,
  TTL_EXPIRED,
  NO_ROUTE
};

ForwardResult forwardPacket(IPPacket* packet) {
  Serial.println("\n-----------------------------------------");
  Serial.printf("| Packet ID: %lu\n", packet->packet_id);
  Serial.println("-----------------------------------------");
  Serial.printf("  Source: %s -> Destination: %s\n",
                packet->source_ip, packet->dest_ip);
  Serial.printf("  TTL: %d  Protocol: %d\n", packet->ttl, packet->protocol);
  Serial.printf("  Payload: %s\n", packet->payload);

  // Step 1: Check TTL
  if (packet->ttl == 0) {
    Serial.println("  X Action: DROP (TTL expired)");
    Serial.println("  -> Would send ICMP Time Exceeded to source");
    ttl_expired++;
    packets_dropped++;
    return TTL_EXPIRED;
  }

  // Step 2: Decrement TTL
  packet->ttl--;
  Serial.printf("  -> TTL decremented to %d\n", packet->ttl);

  // Step 3: Route lookup
  RouteEntry* route = lookupRoute(packet->dest_ip);

  if (route == NULL) {
    Serial.println("  X Action: DROP (No route to destination)");
    Serial.println("  -> Would send ICMP Destination Unreachable");
    packets_dropped++;
    return NO_ROUTE;
  }

  // Step 4: Forward packet
  Serial.printf("  OK Route found: %s\n", route->destination);

  if (strcmp(route->next_hop, "0.0.0.0") == 0) {
    Serial.printf("  -> Direct delivery on %s\n", route->interface);
  } else {
    Serial.printf("  -> Forward to next hop: %s via %s\n",
                  route->next_hop, route->interface);
  }

  Serial.printf("  OK Packet forwarded (metric: %d)\n", route->metric);
  packets_forwarded++;
  return FORWARDED;
}

void simulatePacketScenarios() {
  IPPacket packet;
  packet.protocol = 6;  // TCP

  // Scenario 1: Local network packet
  Serial.println("\n=== Scenario 1: Local Network ===");
  strcpy(packet.source_ip, "192.168.1.100");
  strcpy(packet.dest_ip, "192.168.1.50");
  packet.ttl = 64;
  packet.packet_id = 1001;
  strcpy(packet.payload, "HTTP Request");
  forwardPacket(&packet);

  // Scenario 2: Remote network via static route
  Serial.println("\n\n=== Scenario 2: Static Route ===");
  strcpy(packet.source_ip, "192.168.1.100");
  strcpy(packet.dest_ip, "172.16.50.100");
  packet.ttl = 64;
  packet.packet_id = 1002;
  strcpy(packet.payload, "IoT Sensor Data");
  forwardPacket(&packet);

  // Scenario 3: Internet via default route
  Serial.println("\n\n=== Scenario 3: Default Route (Internet) ===");
  strcpy(packet.source_ip, "192.168.1.100");
  strcpy(packet.dest_ip, "8.8.8.8");
  packet.ttl = 64;
  packet.packet_id = 1003;
  strcpy(packet.payload, "DNS Query");
  forwardPacket(&packet);

  // Scenario 4: TTL expiration
  Serial.println("\n\n=== Scenario 4: TTL Expiration ===");
  strcpy(packet.source_ip, "192.168.1.100");
  strcpy(packet.dest_ip, "10.0.0.50");
  packet.ttl = 1;  // Will expire after decrement
  packet.packet_id = 1004;
  strcpy(packet.payload, "Data packet");
  forwardPacket(&packet);
  forwardPacket(&packet);  // Second hop - TTL will be 0

  // Scenario 5: Multiple hops
  Serial.println("\n\n=== Scenario 5: Multiple Hops (TTL tracking) ===");
  strcpy(packet.source_ip, "192.168.1.100");
  strcpy(packet.dest_ip, "10.0.0.50");
  packet.ttl = 5;
  packet.packet_id = 1005;
  strcpy(packet.payload, "Multi-hop packet");

  Serial.println("Simulating packet traverse across multiple routers:");
  for (int hop = 1; hop <= 5; hop++) {
    Serial.printf("\n--- Hop %d ---\n", hop);
    ForwardResult result = forwardPacket(&packet);
    if (result != FORWARDED) {
      break;
    }
  }
}

void displayStatistics() {
  Serial.println("\n\n========================================");
  Serial.println("       Forwarding Statistics");
  Serial.println("========================================");
  Serial.printf("  Packets Forwarded: %lu\n", packets_forwarded);
  Serial.printf("  Packets Dropped:   %lu\n", packets_dropped);
  Serial.printf("  TTL Expired:       %lu\n", ttl_expired);
  Serial.printf("  Total Processed:   %lu\n",
                packets_forwarded + packets_dropped);

  if (packets_forwarded + packets_dropped > 0) {
    float success_rate = (float)packets_forwarded /
                         (packets_forwarded + packets_dropped) * 100.0;
    Serial.printf("  Success Rate:      %.1f%%\n", success_rate);
  }

  Serial.println("\n=======================================");
  Serial.println("Simulation Complete");
  Serial.println("=======================================\n");
}

691.3.3 Expected Serial Monitor Output

========================================
  ESP32 Packet Forwarding Simulator
  Routing & TTL Management Demo
========================================

Routing Table:
-----------------------------------------------------
Destination       Next Hop        Interface  Metric
-----------------------------------------------------
192.168.1.0/24    0.0.0.0         eth0       0
10.0.0.0/8        0.0.0.0         eth1       0
172.16.0.0/16     10.0.0.2        eth1       10
0.0.0.0/0         192.168.1.1     eth0       1
-----------------------------------------------------

=======================================
Starting Packet Forwarding Simulation
=======================================


=== Scenario 1: Local Network ===

-----------------------------------------
| Packet ID: 1001
-----------------------------------------
  Source: 192.168.1.100 -> Destination: 192.168.1.50
  TTL: 64  Protocol: 6
  Payload: HTTP Request
  -> TTL decremented to 63
  OK Route found: 192.168.1.0/24
  -> Direct delivery on eth0
  OK Packet forwarded (metric: 0)


=== Scenario 2: Static Route ===

-----------------------------------------
| Packet ID: 1002
-----------------------------------------
  Source: 192.168.1.100 -> Destination: 172.16.50.100
  TTL: 64  Protocol: 6
  Payload: IoT Sensor Data
  -> TTL decremented to 63
  OK Route found: 172.16.0.0/16
  -> Forward to next hop: 10.0.0.2 via eth1
  OK Packet forwarded (metric: 10)

[... more scenarios ...]

691.3.4 Learning Outcomes

  • See TTL decrement in action
  • Understand routing table lookup process
  • Observe different forwarding decisions (connected, static, default)
  • Visualize packet drop scenarios

691.4 Lab 5: Python Network Convergence Simulator

Time: ~25 min | Difficulty: Advanced | Code: P07.C20.U02

Objective: Visualize routing protocol convergence and link failure recovery.

691.4.1 Setup

pip install matplotlib networkx

691.4.2 Understanding Convergence

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
graph TD
    subgraph Round0["Round 0: Initial State"]
        R0A["R1<br/>Knows: R2, R3"]
        R0B["R2<br/>Knows: R1, R4"]
        R0C["R3<br/>Knows: R1, R4"]
        R0D["R4<br/>Knows: R2, R3"]
    end

    subgraph Round1["Round 1: Exchange"]
        R1A["R1<br/>Knows: R2, R3, R4"]
        R1B["R2<br/>Knows: R1, R3, R4"]
        R1C["R3<br/>Knows: R1, R2, R4"]
        R1D["R4<br/>Knows: R1, R2, R3"]
    end

    subgraph Round2["Round 2: Converged"]
        R2A["R1<br/>All routes optimal"]
        R2B["R2<br/>All routes optimal"]
        R2C["R3<br/>All routes optimal"]
        R2D["R4<br/>All routes optimal"]
    end

    Round0 -->|"Tables exchanged"| Round1
    Round1 -->|"No changes"| Round2

    style R0A fill:#E67E22,stroke:#16A085,color:#fff
    style R1A fill:#16A085,stroke:#16A085,color:#fff
    style R2A fill:#2C3E50,stroke:#16A085,color:#fff

Figure 691.2: Convergence Process: Distance vector protocols converge by exchanging routing tables. Each round, routers learn about destinations one hop further away. Convergence is complete when no routing table changes occur between rounds.

691.4.3 Python Simulation Code

"""
Network Convergence Simulator
Demonstrates distance vector routing protocol convergence
"""

import matplotlib.pyplot as plt
import networkx as nx
from collections import defaultdict

class DistanceVectorRouter:
    """Simulates a router running distance vector protocol"""

    def __init__(self, name):
        self.name = name
        self.neighbors = {}  # neighbor_name: cost
        self.routing_table = {}  # destination: (cost, next_hop)

    def add_neighbor(self, neighbor_name, cost):
        """Add a directly connected neighbor"""
        self.neighbors[neighbor_name] = cost
        self.routing_table[neighbor_name] = (cost, neighbor_name)

    def receive_update(self, from_neighbor, neighbor_table):
        """
        Process routing update from a neighbor
        Returns True if routing table changed
        """
        changed = False
        cost_to_neighbor = self.neighbors.get(from_neighbor, float('inf'))

        for destination, (dest_cost, _) in neighbor_table.items():
            if destination == self.name:
                continue

            new_cost = cost_to_neighbor + dest_cost
            current = self.routing_table.get(destination)

            if current is None or new_cost < current[0]:
                self.routing_table[destination] = (new_cost, from_neighbor)
                changed = True

        return changed

    def get_routing_table(self):
        """Return copy of routing table for sharing"""
        return dict(self.routing_table)


class NetworkSimulator:
    """Simulates a network of distance vector routers"""

    def __init__(self):
        self.routers = {}
        self.history = []  # Track convergence history

    def add_router(self, name):
        """Add a router to the network"""
        self.routers[name] = DistanceVectorRouter(name)
        self.routers[name].routing_table[name] = (0, name)

    def add_link(self, router1, router2, cost):
        """Add a bidirectional link between routers"""
        self.routers[router1].add_neighbor(router2, cost)
        self.routers[router2].add_neighbor(router1, cost)

    def run_convergence(self, max_rounds=10):
        """
        Run distance vector convergence
        Returns number of rounds to converge
        """
        print("\nRunning distance vector convergence...")
        print("=" * 60)

        for round_num in range(max_rounds):
            print(f"\n{'=' * 60}")
            print(f"Round {round_num}")
            print(f"{'=' * 60}")

            # Snapshot current state
            self.history.append(self._get_network_state())

            # Each router sends updates to neighbors
            any_change = False
            updates = {}

            # Collect all updates first
            for name, router in self.routers.items():
                updates[name] = router.get_routing_table()

            # Apply updates
            for name, router in self.routers.items():
                for neighbor in router.neighbors:
                    if router.receive_update(neighbor, updates[neighbor]):
                        any_change = True

            # Print current routing tables
            self._print_routing_tables()

            if not any_change and round_num > 0:
                print(f"\nConverged in {round_num} rounds")
                return round_num

        print(f"\nDid not converge in {max_rounds} rounds")
        return max_rounds

    def _get_network_state(self):
        """Capture current network state for visualization"""
        state = {}
        for name, router in self.routers.items():
            state[name] = dict(router.routing_table)
        return state

    def _print_routing_tables(self):
        """Print all routing tables"""
        for name, router in self.routers.items():
            print(f"\nRouting Table for {name}:")
            print("Destination     Cost   Next Hop")
            print("-" * 40)
            for dest, (cost, next_hop) in sorted(router.routing_table.items()):
                print(f"{dest:<15} {cost:<6} {next_hop}")

    def simulate_link_failure(self, router1, router2):
        """Simulate a link failure between two routers"""
        print(f"\n{'=' * 60}")
        print(f"Link Failure: {router1}-{router2} link goes down")
        print(f"{'=' * 60}")

        # Remove neighbor relationship
        if router2 in self.routers[router1].neighbors:
            del self.routers[router1].neighbors[router2]
        if router1 in self.routers[router2].neighbors:
            del self.routers[router2].neighbors[router1]

        # Mark routes via failed link as invalid
        for name, router in self.routers.items():
            invalid_dests = []
            for dest, (cost, next_hop) in router.routing_table.items():
                if next_hop == router2 and name == router1:
                    invalid_dests.append(dest)
                elif next_hop == router1 and name == router2:
                    invalid_dests.append(dest)

            for dest in invalid_dests:
                del router.routing_table[dest]

    def visualize_topology(self):
        """Create network topology visualization"""
        G = nx.Graph()

        # Add nodes and edges
        for name, router in self.routers.items():
            G.add_node(name)
            for neighbor, cost in router.neighbors.items():
                G.add_edge(name, neighbor, weight=cost)

        # Draw network
        plt.figure(figsize=(10, 8))
        pos = nx.spring_layout(G, seed=42)

        nx.draw_networkx_nodes(G, pos, node_size=700,
                               node_color='#2C3E50')
        nx.draw_networkx_labels(G, pos, font_size=12,
                                font_color='white')
        nx.draw_networkx_edges(G, pos, width=2,
                               edge_color='#16A085')

        # Add edge labels (costs)
        edge_labels = nx.get_edge_attributes(G, 'weight')
        nx.draw_networkx_edge_labels(G, pos, edge_labels,
                                     font_size=10)

        plt.title("Network Topology", fontsize=14)
        plt.axis('off')
        plt.tight_layout()
        plt.savefig('network_topology.png', dpi=150)
        plt.show()
        print("Saved: network_topology.png")


def main():
    """Run the network convergence simulation"""
    print("Network Convergence Simulator")
    print("=" * 60)

    # Create network
    sim = NetworkSimulator()

    # Add routers
    for name in ['R1', 'R2', 'R3', 'R4', 'R5']:
        sim.add_router(name)

    # Add links (creating a mesh topology)
    sim.add_link('R1', 'R2', 1)
    sim.add_link('R1', 'R3', 5)
    sim.add_link('R2', 'R4', 2)
    sim.add_link('R3', 'R4', 1)
    sim.add_link('R4', 'R5', 3)

    print("\nInitial Network Topology:")
    print("""
    R1 ---- 1 ---- R2
    |              |
    5              2
    |              |
    R3 ---- 1 ---- R4 ---- 3 ---- R5
    """)

    # Run convergence
    rounds = sim.run_convergence()

    # Simulate link failure
    sim.simulate_link_failure('R1', 'R2')
    print("\nReconverging after link failure...")
    sim.run_convergence()

    # Visualize (optional - requires display)
    try:
        sim.visualize_topology()
    except Exception as e:
        print(f"Visualization skipped: {e}")


if __name__ == "__main__":
    main()

691.4.4 Expected Output

Network Convergence Simulator
======================================================================

Initial Network Topology:

    R1 ---- 1 ---- R2
    |              |
    5              2
    |              |
    R3 ---- 1 ---- R4 ---- 3 ---- R5


Running distance vector convergence...
============================================================

============================================================
Round 0
============================================================

Routing Table for R1:
Destination     Cost   Next Hop
----------------------------------------
R1              0      R1
R2              1      R2
R3              5      R3

Routing Table for R2:
Destination     Cost   Next Hop
----------------------------------------
R1              1      R1
R2              0      R2
R4              2      R4

[... continues ...]

============================================================
Round 3
============================================================

Routing Table for R1:
Destination     Cost   Next Hop
----------------------------------------
R1              0      R1
R2              1      R2
R3              4      R2
R4              3      R2
R5              6      R2

Converged in 3 rounds

============================================================
Link Failure: R1-R2 link goes down
============================================================

Reconverging after link failure...

[... reconvergence output ...]

691.4.5 Key Concepts Demonstrated

  • Distance Vector Propagation: Each round, knowledge spreads one hop further
  • Bellman-Ford Algorithm: Cost via neighbor = cost_to_neighbor + neighbor’s_cost
  • Convergence Detection: No changes between rounds indicates convergence
  • Link Failure Recovery: Routes via failed link are removed, new paths discovered

691.5 Routing Metrics in IoT Networks

Different routing protocols use different metrics to determine the best path. Understanding these metrics helps you choose the right protocol for your IoT deployment.

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
graph TD
    subgraph Metric1["Hop Count Metric"]
        R1A["Router A"]
        R1B["Router B"]
        R1C["Router C"]
        R1D["Destination"]

        R1A -->|"1 hop"| R1B
        R1B -->|"1 hop"| R1C
        R1C -->|"1 hop"| R1D
        R1A -.->|"Cost: 3 hops"| R1D
    end

    subgraph Metric2["ETX Metric (Expected Transmission Count)"]
        R2A["Router A"]
        R2B["Router B<br/>PRR 80%<br/>ETX 1.25"]
        R2C["Router C<br/>PRR 90%<br/>ETX 1.11"]
        R2D["Destination"]

        R2A -->|"ETX 1.25"| R2B
        R2B -->|"ETX 1.11"| R2C
        R2C -->|"ETX 1.0"| R2D
        R2A -.->|"Total ETX: 3.36"| R2D
    end

    subgraph Metric3["Latency Metric"]
        R3A["Router A"]
        R3B["Router B<br/>10ms"]
        R3C["Router C<br/>15ms"]
        R3D["Destination"]

        R3A -->|"10ms"| R3B
        R3B -->|"15ms"| R3C
        R3C -->|"5ms"| R3D
        R3A -.->|"Total: 30ms"| R3D
    end

    style R1A fill:#2C3E50,stroke:#16A085,color:#fff
    style R1B fill:#16A085,stroke:#16A085,color:#fff
    style R1C fill:#16A085,stroke:#16A085,color:#fff
    style R1D fill:#E67E22,stroke:#16A085,color:#fff
    style R2A fill:#2C3E50,stroke:#16A085,color:#fff
    style R2B fill:#16A085,stroke:#16A085,color:#fff
    style R2C fill:#16A085,stroke:#16A085,color:#fff
    style R2D fill:#E67E22,stroke:#16A085,color:#fff
    style R3A fill:#2C3E50,stroke:#16A085,color:#fff
    style R3B fill:#16A085,stroke:#16A085,color:#fff
    style R3C fill:#16A085,stroke:#16A085,color:#fff
    style R3D fill:#E67E22,stroke:#16A085,color:#fff

Figure 691.3: Routing Metrics Comparison: Three common routing metrics used in IoT networks. Hop Count (top) counts the number of routers traversed. ETX (middle) measures expected transmission attempts accounting for packet loss. Latency (bottom) measures end-to-end delay.
NoteChoosing the Right Metric

Use Hop Count when:

  • Network links are reliable (wired connections)
  • Minimizing routing overhead is critical
  • Simple routing decisions are sufficient

Use ETX when:

  • Wireless links with variable quality
  • Retransmissions impact performance
  • Link reliability varies significantly (typical in IoT)

Use Latency when:

  • Real-time applications (video, voice, industrial control)
  • Delay-sensitive IoT use cases
  • Network congestion is a concern

For IoT sensor networks, ETX is often the best choice because wireless links experience packet loss, and minimizing retransmissions saves battery power.


691.6 Summary

  • ESP32 packet forwarding simulation demonstrates TTL decrement, route lookup, and forwarding decisions in embedded systems
  • Routing table structures include destination network, next hop, interface, and metric for making forwarding decisions
  • Python convergence simulation visualizes how distance vector protocols propagate routing information across a network
  • Link failure recovery shows how routers automatically discover new paths when existing routes become unavailable
  • Routing metrics (hop count, ETX, latency) serve different purposes depending on network characteristics and application requirements
  • Forwarding statistics help diagnose routing issues by tracking successful forwards, drops, and TTL expirations

691.7 What’s Next

Continue to Routing Labs: Algorithms for Python implementations of:

  • Routing table simulator with longest prefix matching
  • Dijkstra’s shortest path algorithm (link-state routing)
  • Distance vector routing protocol (RIP simulation)

Or return to Routing Labs: Overview for quiz questions testing your understanding.