%% fig-alt: Sequence diagram showing route discovery process with RREQ flooding from source through intermediate nodes to destination, followed by RREP returning along discovered path to establish the route.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#FFFFFF', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#FFFFFF'}}}%%
sequenceDiagram
participant S as Source
participant A as Node A
participant B as Node B
participant D as Destination
Note over S,D: Route Request (RREQ) Flooding
S->>A: RREQ (src=S, dest=D, seq=1)
S->>B: RREQ (src=S, dest=D, seq=1)
A->>B: RREQ (path: S->A)
A->>D: RREQ (path: S->A)
B->>D: RREQ (path: S->B)
Note over S,D: Route Reply (RREP) - Unicast
D->>A: RREP (path: S->A->D)
A->>S: RREP (route established)
Note over S,D: Data Transmission
S->>A: Data packet
A->>D: Data packet
260 Ad-Hoc Routing Visualizer
Explore Dynamic Routing Protocols
260.1 Ad-Hoc Network Routing Protocols
This interactive visualizer demonstrates how different ad-hoc routing protocols discover and maintain routes in mobile networks. Compare proactive (table-driven), reactive (on-demand), and hybrid approaches to understand their trade-offs in control overhead, latency, and adaptability.
Show code
viewof adhocRoutingVisualizer = {
// ===========================================================================
// AD-HOC ROUTING PROTOCOL VISUALIZER
// ===========================================================================
// Demonstrates key routing protocols:
// - DSDV (Destination-Sequenced Distance-Vector) - Proactive
// - DSR (Dynamic Source Routing) - Reactive
// - AODV (Ad-hoc On-demand Distance Vector) - Reactive
// - ZRP (Zone Routing Protocol) - Hybrid
//
// IEEE Color Palette:
// Navy: #2C3E50 (primary, headers)
// Teal: #16A085 (secondary, success)
// Orange: #E67E22 (highlights, RREQ)
// Gray: #7F8C8D (neutral, inactive)
// LtGray: #ECF0F1 (backgrounds)
// Green: #27AE60 (RREP, established routes)
// Red: #E74C3C (broken links)
// Purple: #9B59B6 (zone boundary)
// ===========================================================================
// ---------------------------------------------------------------------------
// CONFIGURATION
// ---------------------------------------------------------------------------
const config = {
width: 950,
height: 850,
colors: {
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
green: "#27AE60",
red: "#E74C3C",
yellow: "#F1C40F",
purple: "#9B59B6",
blue: "#3498DB"
},
nodeRadius: 25,
linkWidth: 3,
protocols: {
dsdv: {
name: "DSDV",
fullName: "Destination-Sequenced Distance-Vector",
type: "Proactive",
color: "#3498DB",
description: "Maintains routing tables at all times through periodic updates"
},
dsr: {
name: "DSR",
fullName: "Dynamic Source Routing",
type: "Reactive",
color: "#E67E22",
description: "Discovers routes on-demand using source routing"
},
aodv: {
name: "AODV",
fullName: "Ad-hoc On-demand Distance Vector",
type: "Reactive",
color: "#27AE60",
description: "Discovers routes on-demand with hop-by-hop routing"
},
zrp: {
name: "ZRP",
fullName: "Zone Routing Protocol",
type: "Hybrid",
color: "#9B59B6",
description: "Combines proactive (intra-zone) and reactive (inter-zone) routing"
}
},
timing: {
rreqDelay: 300,
rrepDelay: 200,
updateInterval: 50
}
};
// ---------------------------------------------------------------------------
// NETWORK TOPOLOGY - 10 nodes with positions
// ---------------------------------------------------------------------------
const initialNodes = [
{ id: 0, label: "A", x: 100, y: 200, zone: 0 },
{ id: 1, label: "B", x: 220, y: 120, zone: 0 },
{ id: 2, label: "C", x: 220, y: 280, zone: 0 },
{ id: 3, label: "D", x: 350, y: 200, zone: 1 },
{ id: 4, label: "E", x: 350, y: 350, zone: 1 },
{ id: 5, label: "F", x: 480, y: 120, zone: 1 },
{ id: 6, label: "G", x: 480, y: 280, zone: 2 },
{ id: 7, label: "H", x: 610, y: 200, zone: 2 },
{ id: 8, label: "I", x: 610, y: 350, zone: 2 },
{ id: 9, label: "J", x: 740, y: 250, zone: 2 }
];
// Links define connectivity (bidirectional)
const initialLinks = [
{ source: 0, target: 1 },
{ source: 0, target: 2 },
{ source: 1, target: 2 },
{ source: 1, target: 3 },
{ source: 2, target: 3 },
{ source: 2, target: 4 },
{ source: 3, target: 5 },
{ source: 3, target: 6 },
{ source: 4, target: 6 },
{ source: 4, target: 8 },
{ source: 5, target: 7 },
{ source: 6, target: 7 },
{ source: 6, target: 8 },
{ source: 7, target: 9 },
{ source: 8, target: 9 }
];
// ---------------------------------------------------------------------------
// STATE MANAGEMENT
// ---------------------------------------------------------------------------
let state = {
protocol: "dsdv",
sourceNode: 0,
destNode: 9,
selectedNode: null,
isAnimating: false,
mobilityEnabled: false,
animationFrame: null,
nodes: JSON.parse(JSON.stringify(initialNodes)),
links: JSON.parse(JSON.stringify(initialLinks)),
routingTables: {},
discoveredRoute: [],
rreqPackets: [],
rrepPackets: [],
brokenLinks: [],
metrics: {
controlPackets: 0,
discoveryTime: 0,
pathLength: 0,
pathOptimal: true
}
};
// Initialize routing tables for all nodes
function initRoutingTables() {
state.routingTables = {};
state.nodes.forEach(node => {
state.routingTables[node.id] = {};
// For DSDV, pre-populate with known routes
if (state.protocol === "dsdv") {
state.nodes.forEach(dest => {
if (dest.id !== node.id) {
const path = findShortestPath(node.id, dest.id);
if (path.length > 0) {
state.routingTables[node.id][dest.id] = {
nextHop: path.length > 1 ? path[1] : dest.id,
distance: path.length - 1,
seqNum: 1
};
}
}
});
}
});
}
// Find shortest path using BFS
function findShortestPath(start, end) {
const visited = new Set();
const queue = [[start]];
visited.add(start);
while (queue.length > 0) {
const path = queue.shift();
const node = path[path.length - 1];
if (node === end) return path;
getNeighbors(node).forEach(neighbor => {
if (!visited.has(neighbor) && !isLinkBroken(node, neighbor)) {
visited.add(neighbor);
queue.push([...path, neighbor]);
}
});
}
return [];
}
// Get neighbors of a node
function getNeighbors(nodeId) {
const neighbors = [];
state.links.forEach(link => {
if (link.source === nodeId) neighbors.push(link.target);
if (link.target === nodeId) neighbors.push(link.source);
});
return neighbors;
}
// Check if link is broken
function isLinkBroken(a, b) {
return state.brokenLinks.some(bl =>
(bl.source === a && bl.target === b) ||
(bl.source === b && bl.target === a)
);
}
// ---------------------------------------------------------------------------
// CREATE CONTAINER
// ---------------------------------------------------------------------------
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif")
.style("max-width", `${config.width}px`)
.style("margin", "0 auto");
// ---------------------------------------------------------------------------
// CONTROL PANEL
// ---------------------------------------------------------------------------
const controlPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "20px")
.style("margin-bottom", "20px")
.style("border", `2px solid ${config.colors.gray}`);
controlPanel.append("h3")
.style("margin", "0 0 15px 0")
.style("color", config.colors.navy)
.style("font-size", "16px")
.text("Protocol Configuration");
const controlGrid = controlPanel.append("div")
.style("display", "grid")
.style("grid-template-columns", "repeat(auto-fit, minmax(180px, 1fr))")
.style("gap", "15px");
// Protocol Selector
const protocolGroup = controlGrid.append("div");
protocolGroup.append("label")
.style("display", "block")
.style("margin-bottom", "5px")
.style("color", config.colors.navy)
.style("font-weight", "600")
.style("font-size", "13px")
.text("Routing Protocol");
const protocolSelect = protocolGroup.append("select")
.style("width", "100%")
.style("padding", "8px")
.style("border-radius", "6px")
.style("border", `1px solid ${config.colors.gray}`)
.style("font-size", "13px")
.style("cursor", "pointer");
Object.entries(config.protocols).forEach(([key, proto]) => {
protocolSelect.append("option")
.attr("value", key)
.attr("selected", key === state.protocol ? true : null)
.text(`${proto.name} (${proto.type})`);
});
protocolSelect.on("change", function() {
state.protocol = this.value;
resetVisualization();
updateProtocolInfo();
});
// Source Node Selector
const sourceGroup = controlGrid.append("div");
sourceGroup.append("label")
.style("display", "block")
.style("margin-bottom", "5px")
.style("color", config.colors.navy)
.style("font-weight", "600")
.style("font-size", "13px")
.text("Source Node");
const sourceSelect = sourceGroup.append("select")
.style("width", "100%")
.style("padding", "8px")
.style("border-radius", "6px")
.style("border", `1px solid ${config.colors.gray}`)
.style("font-size", "13px")
.style("cursor", "pointer");
state.nodes.forEach(node => {
sourceSelect.append("option")
.attr("value", node.id)
.attr("selected", node.id === state.sourceNode ? true : null)
.text(`Node ${node.label}`);
});
sourceSelect.on("change", function() {
state.sourceNode = +this.value;
resetVisualization();
});
// Destination Node Selector
const destGroup = controlGrid.append("div");
destGroup.append("label")
.style("display", "block")
.style("margin-bottom", "5px")
.style("color", config.colors.navy)
.style("font-weight", "600")
.style("font-size", "13px")
.text("Destination Node");
const destSelect = destGroup.append("select")
.style("width", "100%")
.style("padding", "8px")
.style("border-radius", "6px")
.style("border", `1px solid ${config.colors.gray}`)
.style("font-size", "13px")
.style("cursor", "pointer");
state.nodes.forEach(node => {
destSelect.append("option")
.attr("value", node.id)
.attr("selected", node.id === state.destNode ? true : null)
.text(`Node ${node.label}`);
});
destSelect.on("change", function() {
state.destNode = +this.value;
resetVisualization();
});
// View Node Tables
const viewGroup = controlGrid.append("div");
viewGroup.append("label")
.style("display", "block")
.style("margin-bottom", "5px")
.style("color", config.colors.navy)
.style("font-weight", "600")
.style("font-size", "13px")
.text("View Routing Table");
const viewSelect = viewGroup.append("select")
.style("width", "100%")
.style("padding", "8px")
.style("border-radius", "6px")
.style("border", `1px solid ${config.colors.gray}`)
.style("font-size", "13px")
.style("cursor", "pointer");
viewSelect.append("option").attr("value", "").text("-- Select Node --");
state.nodes.forEach(node => {
viewSelect.append("option")
.attr("value", node.id)
.text(`Node ${node.label}`);
});
viewSelect.on("change", function() {
state.selectedNode = this.value !== "" ? +this.value : null;
updateRoutingTableDisplay();
});
// Button Row
const buttonRow = controlPanel.append("div")
.style("display", "flex")
.style("gap", "10px")
.style("margin-top", "15px")
.style("flex-wrap", "wrap")
.style("align-items", "center");
const discoverBtn = buttonRow.append("button")
.style("padding", "12px 24px")
.style("background", config.colors.teal)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("font-size", "14px")
.style("font-weight", "bold")
.style("cursor", "pointer")
.text("Discover Route")
.on("click", startRouteDiscovery)
.on("mouseenter", function() {
d3.select(this).style("background", config.colors.navy);
})
.on("mouseleave", function() {
d3.select(this).style("background", config.colors.teal);
});
const resetBtn = buttonRow.append("button")
.style("padding", "12px 24px")
.style("background", config.colors.gray)
.style("color", config.colors.white)
.style("border", "none")
.style("border-radius", "8px")
.style("font-size", "14px")
.style("font-weight", "bold")
.style("cursor", "pointer")
.text("Reset")
.on("click", resetVisualization)
.on("mouseenter", function() {
d3.select(this).style("background", config.colors.red);
})
.on("mouseleave", function() {
d3.select(this).style("background", config.colors.gray);
});
// Mobility Toggle
const mobilityLabel = buttonRow.append("label")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "8px")
.style("margin-left", "auto")
.style("cursor", "pointer");
const mobilityCheck = mobilityLabel.append("input")
.attr("type", "checkbox")
.style("width", "18px")
.style("height", "18px")
.style("cursor", "pointer")
.on("change", function() {
state.mobilityEnabled = this.checked;
if (state.mobilityEnabled) {
startMobility();
}
});
mobilityLabel.append("span")
.style("color", config.colors.navy)
.style("font-size", "13px")
.style("font-weight", "600")
.text("Node Mobility");
// Protocol Info Display
const protocolInfo = controlPanel.append("div")
.attr("class", "protocol-info")
.style("margin-top", "15px")
.style("padding", "12px")
.style("background", config.colors.white)
.style("border-radius", "8px")
.style("border-left", `4px solid ${config.protocols.dsdv.color}`);
function updateProtocolInfo() {
const proto = config.protocols[state.protocol];
protocolInfo
.style("border-left-color", proto.color)
.html(`<strong style="color: ${proto.color}">${proto.fullName}</strong>
<span style="background: ${proto.color}; color: white; padding: 2px 8px; border-radius: 4px; margin-left: 10px; font-size: 11px;">${proto.type}</span>
<br><span style="color: ${config.colors.gray}; font-size: 13px; margin-top: 5px; display: block;">${proto.description}</span>`);
}
updateProtocolInfo();
// ---------------------------------------------------------------------------
// SVG CANVAS
// ---------------------------------------------------------------------------
const svgHeight = 500;
const svg = container.append("svg")
.attr("viewBox", `0 0 ${config.width} ${svgHeight}`)
.attr("width", "100%")
.style("background", config.colors.white)
.style("border-radius", "12px")
.style("border", `2px solid ${config.colors.gray}`);
// Definitions
const defs = svg.append("defs");
// Arrow marker for RREQ
defs.append("marker")
.attr("id", "rreqArrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", config.colors.orange);
// Arrow marker for RREP
defs.append("marker")
.attr("id", "rrepArrow")
.attr("viewBox", "0 -5 10 10")
.attr("refX", 8)
.attr("refY", 0)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", config.colors.green);
// Drop shadow filter
const filter = defs.append("filter")
.attr("id", "dropShadow")
.attr("x", "-20%")
.attr("y", "-20%")
.attr("width", "140%")
.attr("height", "140%");
filter.append("feDropShadow")
.attr("dx", "2")
.attr("dy", "2")
.attr("stdDeviation", "3")
.attr("flood-opacity", "0.3");
// Title
svg.append("text")
.attr("x", config.width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.attr("fill", config.colors.navy)
.attr("font-size", "18px")
.attr("font-weight", "bold")
.text("Ad-Hoc Network Topology");
// Network offset
const networkOffset = { x: 50, y: 50 };
// Link group (rendered first, behind nodes)
const linkGroup = svg.append("g").attr("class", "links");
// Packet animation group
const packetGroup = svg.append("g").attr("class", "packets");
// Node group
const nodeGroup = svg.append("g").attr("class", "nodes");
// Zone boundaries for ZRP
const zoneGroup = svg.append("g").attr("class", "zones");
// ---------------------------------------------------------------------------
// RENDER FUNCTIONS
// ---------------------------------------------------------------------------
function renderNetwork() {
// Clear existing
linkGroup.selectAll("*").remove();
nodeGroup.selectAll("*").remove();
zoneGroup.selectAll("*").remove();
// Draw zone boundaries for ZRP
if (state.protocol === "zrp") {
const zones = [
{ nodes: [0, 1, 2], color: config.colors.blue },
{ nodes: [3, 4, 5], color: config.colors.orange },
{ nodes: [6, 7, 8, 9], color: config.colors.purple }
];
zones.forEach((zone, i) => {
const zoneNodes = zone.nodes.map(id => state.nodes[id]);
const centerX = zoneNodes.reduce((sum, n) => sum + n.x, 0) / zoneNodes.length + networkOffset.x;
const centerY = zoneNodes.reduce((sum, n) => sum + n.y, 0) / zoneNodes.length + networkOffset.y;
const radius = 120;
zoneGroup.append("circle")
.attr("cx", centerX)
.attr("cy", centerY)
.attr("r", radius)
.attr("fill", zone.color)
.attr("fill-opacity", 0.1)
.attr("stroke", zone.color)
.attr("stroke-width", 2)
.attr("stroke-dasharray", "5,5");
zoneGroup.append("text")
.attr("x", centerX)
.attr("y", centerY - radius - 10)
.attr("text-anchor", "middle")
.attr("fill", zone.color)
.attr("font-size", "12px")
.attr("font-weight", "bold")
.text(`Zone ${i + 1}`);
});
}
// Draw links
state.links.forEach(link => {
const source = state.nodes[link.source];
const target = state.nodes[link.target];
const isBroken = isLinkBroken(link.source, link.target);
const isOnRoute = state.discoveredRoute.length > 1 &&
state.discoveredRoute.some((n, i) =>
i < state.discoveredRoute.length - 1 &&
((state.discoveredRoute[i] === link.source && state.discoveredRoute[i + 1] === link.target) ||
(state.discoveredRoute[i] === link.target && state.discoveredRoute[i + 1] === link.source))
);
linkGroup.append("line")
.attr("class", `link link-${link.source}-${link.target}`)
.attr("x1", source.x + networkOffset.x)
.attr("y1", source.y + networkOffset.y)
.attr("x2", target.x + networkOffset.x)
.attr("y2", target.y + networkOffset.y)
.attr("stroke", isBroken ? config.colors.red : (isOnRoute ? config.colors.green : config.colors.gray))
.attr("stroke-width", isOnRoute ? 4 : config.linkWidth)
.attr("stroke-dasharray", isBroken ? "5,5" : "none")
.style("opacity", isBroken ? 0.5 : (isOnRoute ? 1 : 0.6));
});
// Draw nodes
state.nodes.forEach(node => {
const isSource = node.id === state.sourceNode;
const isDest = node.id === state.destNode;
const isOnRoute = state.discoveredRoute.includes(node.id);
const g = nodeGroup.append("g")
.attr("class", `node node-${node.id}`)
.attr("transform", `translate(${node.x + networkOffset.x}, ${node.y + networkOffset.y})`)
.style("cursor", "pointer")
.on("click", () => {
state.selectedNode = node.id;
viewSelect.property("value", node.id);
updateRoutingTableDisplay();
});
// Node circle
g.append("circle")
.attr("r", config.nodeRadius)
.attr("fill", isSource ? config.colors.teal : (isDest ? config.colors.orange : config.colors.navy))
.attr("stroke", isOnRoute ? config.colors.green : config.colors.white)
.attr("stroke-width", isOnRoute ? 4 : 2)
.attr("filter", "url(#dropShadow)");
// Node label
g.append("text")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("fill", config.colors.white)
.attr("font-size", "16px")
.attr("font-weight", "bold")
.text(node.label);
// Source/Dest indicator
if (isSource) {
g.append("text")
.attr("y", config.nodeRadius + 18)
.attr("text-anchor", "middle")
.attr("fill", config.colors.teal)
.attr("font-size", "11px")
.attr("font-weight", "bold")
.text("SOURCE");
}
if (isDest) {
g.append("text")
.attr("y", config.nodeRadius + 18)
.attr("text-anchor", "middle")
.attr("fill", config.colors.orange)
.attr("font-size", "11px")
.attr("font-weight", "bold")
.text("DEST");
}
});
}
// ---------------------------------------------------------------------------
// ROUTE DISCOVERY ANIMATIONS
// ---------------------------------------------------------------------------
async function startRouteDiscovery() {
if (state.isAnimating) return;
if (state.sourceNode === state.destNode) {
alert("Source and destination must be different nodes.");
return;
}
state.isAnimating = true;
state.discoveredRoute = [];
state.rreqPackets = [];
state.rrepPackets = [];
state.metrics = { controlPackets: 0, discoveryTime: 0, pathLength: 0, pathOptimal: true };
discoverBtn.text("Discovering...").style("background", config.colors.orange);
const startTime = performance.now();
switch (state.protocol) {
case "dsdv":
await animateDSDV();
break;
case "dsr":
await animateDSR();
break;
case "aodv":
await animateAODV();
break;
case "zrp":
await animateZRP();
break;
}
state.metrics.discoveryTime = Math.round(performance.now() - startTime);
state.metrics.pathLength = state.discoveredRoute.length - 1;
// Check if path is optimal
const optimalPath = findShortestPath(state.sourceNode, state.destNode);
state.metrics.pathOptimal = state.discoveredRoute.length === optimalPath.length;
updateMetricsDisplay();
renderNetwork();
state.isAnimating = false;
discoverBtn.text("Discover Route").style("background", config.colors.teal);
}
// DSDV Animation - Proactive, just lookup routing table
async function animateDSDV() {
initRoutingTables();
// Show routing table lookups at each hop
const path = [];
let current = state.sourceNode;
const visited = new Set();
while (current !== state.destNode && !visited.has(current)) {
visited.add(current);
path.push(current);
// Animate table lookup
await highlightNode(current, config.colors.blue, 300);
const route = state.routingTables[current][state.destNode];
if (!route) break;
// Show route info
state.metrics.controlPackets = state.nodes.length * (state.nodes.length - 1); // Periodic updates
current = route.nextHop;
}
if (current === state.destNode) {
path.push(state.destNode);
await highlightNode(state.destNode, config.colors.green, 300);
}
state.discoveredRoute = path;
}
// DSR Animation - Source Routing with RREQ flood
async function animateDSR() {
// RREQ flooding
const visited = new Set([state.sourceNode]);
const queue = [{ node: state.sourceNode, path: [state.sourceNode] }];
let foundPath = null;
while (queue.length > 0 && !foundPath) {
const batch = [...queue];
queue.length = 0;
for (const item of batch) {
const neighbors = getNeighbors(item.node);
for (const neighbor of neighbors) {
if (!visited.has(neighbor) && !isLinkBroken(item.node, neighbor)) {
visited.add(neighbor);
const newPath = [...item.path, neighbor];
// Animate RREQ packet
await animatePacket(item.node, neighbor, config.colors.orange, "RREQ");
state.metrics.controlPackets++;
if (neighbor === state.destNode) {
foundPath = newPath;
break;
}
queue.push({ node: neighbor, path: newPath });
}
}
if (foundPath) break;
}
}
// RREP back along the path
if (foundPath) {
for (let i = foundPath.length - 1; i > 0; i--) {
await animatePacket(foundPath[i], foundPath[i - 1], config.colors.green, "RREP");
state.metrics.controlPackets++;
}
state.discoveredRoute = foundPath;
}
}
// AODV Animation - Similar to DSR but hop-by-hop
async function animateAODV() {
// RREQ flooding with reverse path setup
const visited = new Set([state.sourceNode]);
const reverseRoutes = {};
const queue = [state.sourceNode];
let foundDest = false;
while (queue.length > 0 && !foundDest) {
const batch = [...queue];
queue.length = 0;
for (const current of batch) {
const neighbors = getNeighbors(current);
for (const neighbor of neighbors) {
if (!visited.has(neighbor) && !isLinkBroken(current, neighbor)) {
visited.add(neighbor);
reverseRoutes[neighbor] = current;
// Animate RREQ
await animatePacket(current, neighbor, config.colors.orange, "RREQ");
state.metrics.controlPackets++;
if (neighbor === state.destNode) {
foundDest = true;
break;
}
queue.push(neighbor);
}
}
if (foundDest) break;
}
}
// RREP - build path from reverse routes
if (foundDest) {
const path = [state.destNode];
let current = state.destNode;
while (current !== state.sourceNode) {
const prev = reverseRoutes[current];
path.unshift(prev);
// Animate RREP and setup forward route
await animatePacket(current, prev, config.colors.green, "RREP");
state.metrics.controlPackets++;
// Update routing tables
state.routingTables[prev] = state.routingTables[prev] || {};
state.routingTables[prev][state.destNode] = { nextHop: current, distance: 1, seqNum: 1 };
current = prev;
}
state.discoveredRoute = path;
}
}
// ZRP Animation - Hybrid approach
async function animateZRP() {
const sourceZone = state.nodes[state.sourceNode].zone;
const destZone = state.nodes[state.destNode].zone;
if (sourceZone === destZone) {
// Intra-zone: Use proactive routing (like DSDV)
await highlightNode(state.sourceNode, config.colors.purple, 200);
const path = findShortestPath(state.sourceNode, state.destNode);
state.metrics.controlPackets = 5; // Proactive table maintenance
for (let i = 0; i < path.length - 1; i++) {
await animatePacket(path[i], path[i + 1], config.colors.purple, "IARP");
state.metrics.controlPackets++;
}
state.discoveredRoute = path;
} else {
// Inter-zone: Use reactive routing at zone borders
// First, route to zone border
const borderNodes = state.nodes.filter(n =>
n.zone === sourceZone && getNeighbors(n.id).some(nb =>
state.nodes[nb].zone !== sourceZone
)
).map(n => n.id);
// Find nearest border node
let bestBorder = null;
let bestPath = null;
for (const border of borderNodes) {
const pathToBorder = findShortestPath(state.sourceNode, border);
if (!bestPath || pathToBorder.length < bestPath.length) {
bestPath = pathToBorder;
bestBorder = border;
}
}
// Proactive to border
if (bestPath) {
for (let i = 0; i < bestPath.length - 1; i++) {
await animatePacket(bestPath[i], bestPath[i + 1], config.colors.purple, "IARP");
state.metrics.controlPackets++;
}
}
// Reactive from border to destination (IERP)
const visited = new Set([bestBorder]);
const queue = [{ node: bestBorder, path: bestPath }];
let foundPath = null;
while (queue.length > 0 && !foundPath) {
const item = queue.shift();
const neighbors = getNeighbors(item.node);
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
const newPath = [...item.path, neighbor];
await animatePacket(item.node, neighbor, config.colors.orange, "IERP");
state.metrics.controlPackets++;
if (neighbor === state.destNode) {
foundPath = newPath;
break;
}
queue.push({ node: neighbor, path: newPath });
}
}
}
// RREP back
if (foundPath) {
for (let i = foundPath.length - 1; i > 0; i--) {
await animatePacket(foundPath[i], foundPath[i - 1], config.colors.green, "RREP");
state.metrics.controlPackets++;
}
state.discoveredRoute = foundPath;
}
}
}
// Helper: Animate packet movement
function animatePacket(fromId, toId, color, label) {
return new Promise(resolve => {
const from = state.nodes[fromId];
const to = state.nodes[toId];
const packet = packetGroup.append("g");
packet.append("circle")
.attr("r", 12)
.attr("fill", color);
packet.append("text")
.attr("text-anchor", "middle")
.attr("dy", "0.35em")
.attr("fill", config.colors.white)
.attr("font-size", "8px")
.attr("font-weight", "bold")
.text(label.substring(0, 4));
packet
.attr("transform", `translate(${from.x + networkOffset.x}, ${from.y + networkOffset.y})`)
.transition()
.duration(config.timing.rreqDelay)
.attr("transform", `translate(${to.x + networkOffset.x}, ${to.y + networkOffset.y})`)
.on("end", () => {
packet.remove();
resolve();
});
});
}
// Helper: Highlight node
function highlightNode(nodeId, color, duration) {
return new Promise(resolve => {
const node = state.nodes[nodeId];
const highlight = nodeGroup.append("circle")
.attr("cx", node.x + networkOffset.x)
.attr("cy", node.y + networkOffset.y)
.attr("r", config.nodeRadius + 10)
.attr("fill", "none")
.attr("stroke", color)
.attr("stroke-width", 3)
.attr("opacity", 0.8);
highlight.transition()
.duration(duration)
.attr("r", config.nodeRadius + 20)
.attr("opacity", 0)
.on("end", () => {
highlight.remove();
resolve();
});
});
}
// ---------------------------------------------------------------------------
// NODE MOBILITY
// ---------------------------------------------------------------------------
function startMobility() {
if (!state.mobilityEnabled) return;
// Move a random node
const movableNodes = state.nodes.filter(n => n.id !== state.sourceNode && n.id !== state.destNode);
const nodeToMove = movableNodes[Math.floor(Math.random() * movableNodes.length)];
const originalX = nodeToMove.x;
const originalY = nodeToMove.y;
// Random movement
nodeToMove.x += (Math.random() - 0.5) * 80;
nodeToMove.y += (Math.random() - 0.5) * 80;
// Keep within bounds
nodeToMove.x = Math.max(50, Math.min(700, nodeToMove.x));
nodeToMove.y = Math.max(80, Math.min(380, nodeToMove.y));
// Check if any links break (too far)
state.links.forEach(link => {
const source = state.nodes[link.source];
const target = state.nodes[link.target];
const distance = Math.sqrt(Math.pow(source.x - target.x, 2) + Math.pow(source.y - target.y, 2));
if (distance > 200) {
// Link breaks
if (!isLinkBroken(link.source, link.target)) {
state.brokenLinks.push({ source: link.source, target: link.target });
// Check if route is affected
if (state.discoveredRoute.length > 1) {
const routeAffected = state.discoveredRoute.some((n, i) =>
i < state.discoveredRoute.length - 1 &&
((state.discoveredRoute[i] === link.source && state.discoveredRoute[i + 1] === link.target) ||
(state.discoveredRoute[i] === link.target && state.discoveredRoute[i + 1] === link.source))
);
if (routeAffected) {
// Route repair needed
state.discoveredRoute = [];
state.metrics.controlPackets += 10; // Route repair overhead
}
}
}
}
});
renderNetwork();
// Continue mobility
if (state.mobilityEnabled) {
setTimeout(startMobility, 2000);
}
}
// ---------------------------------------------------------------------------
// ROUTING TABLE DISPLAY
// ---------------------------------------------------------------------------
const tableContainer = container.append("div")
.style("display", "grid")
.style("grid-template-columns", "1fr 1fr")
.style("gap", "20px")
.style("margin-top", "20px");
// Routing Table Panel
const tablePanel = tableContainer.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "15px")
.style("border", `2px solid ${config.colors.gray}`);
tablePanel.append("h4")
.style("margin", "0 0 10px 0")
.style("color", config.colors.navy)
.style("font-size", "14px")
.text("Routing Table");
const tableDisplay = tablePanel.append("div")
.attr("class", "routing-table-display")
.style("font-family", "monospace")
.style("font-size", "12px")
.style("background", config.colors.white)
.style("padding", "10px")
.style("border-radius", "6px")
.style("max-height", "200px")
.style("overflow-y", "auto");
function updateRoutingTableDisplay() {
if (state.selectedNode === null) {
tableDisplay.html('<em style="color: ' + config.colors.gray + '">Select a node to view its routing table</em>');
return;
}
const node = state.nodes[state.selectedNode];
const table = state.routingTables[state.selectedNode] || {};
let html = `<div style="font-weight: bold; margin-bottom: 8px; color: ${config.colors.navy}">Node ${node.label} Routing Table</div>`;
if (Object.keys(table).length === 0) {
html += `<em style="color: ${config.colors.gray}">No routes discovered yet</em>`;
} else {
html += `<table style="width: 100%; border-collapse: collapse;">
<tr style="background: ${config.colors.lightGray}">
<th style="padding: 4px; text-align: left; border-bottom: 1px solid ${config.colors.gray}">Dest</th>
<th style="padding: 4px; text-align: left; border-bottom: 1px solid ${config.colors.gray}">Next Hop</th>
<th style="padding: 4px; text-align: left; border-bottom: 1px solid ${config.colors.gray}">Hops</th>
<th style="padding: 4px; text-align: left; border-bottom: 1px solid ${config.colors.gray}">Seq#</th>
</tr>`;
Object.entries(table).forEach(([destId, route]) => {
const destNode = state.nodes[destId];
const nextNode = state.nodes[route.nextHop];
html += `<tr>
<td style="padding: 4px; border-bottom: 1px solid ${config.colors.lightGray}">${destNode.label}</td>
<td style="padding: 4px; border-bottom: 1px solid ${config.colors.lightGray}">${nextNode.label}</td>
<td style="padding: 4px; border-bottom: 1px solid ${config.colors.lightGray}">${route.distance}</td>
<td style="padding: 4px; border-bottom: 1px solid ${config.colors.lightGray}">${route.seqNum}</td>
</tr>`;
});
html += '</table>';
}
tableDisplay.html(html);
}
// ---------------------------------------------------------------------------
// METRICS PANEL
// ---------------------------------------------------------------------------
const metricsPanel = tableContainer.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "15px")
.style("border", `2px solid ${config.colors.gray}`);
metricsPanel.append("h4")
.style("margin", "0 0 10px 0")
.style("color", config.colors.navy)
.style("font-size", "14px")
.text("Protocol Metrics");
const metricsDisplay = metricsPanel.append("div")
.attr("class", "metrics-display");
function updateMetricsDisplay() {
metricsDisplay.html(`
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
<div style="background: ${config.colors.white}; padding: 10px; border-radius: 6px; text-align: center;">
<div style="color: ${config.colors.gray}; font-size: 11px;">Control Overhead</div>
<div style="color: ${config.colors.orange}; font-size: 20px; font-weight: bold;">${state.metrics.controlPackets}</div>
<div style="color: ${config.colors.gray}; font-size: 10px;">packets</div>
</div>
<div style="background: ${config.colors.white}; padding: 10px; border-radius: 6px; text-align: center;">
<div style="color: ${config.colors.gray}; font-size: 11px;">Discovery Time</div>
<div style="color: ${config.colors.teal}; font-size: 20px; font-weight: bold;">${state.metrics.discoveryTime}</div>
<div style="color: ${config.colors.gray}; font-size: 10px;">ms</div>
</div>
<div style="background: ${config.colors.white}; padding: 10px; border-radius: 6px; text-align: center;">
<div style="color: ${config.colors.gray}; font-size: 11px;">Path Length</div>
<div style="color: ${config.colors.navy}; font-size: 20px; font-weight: bold;">${state.metrics.pathLength}</div>
<div style="color: ${config.colors.gray}; font-size: 10px;">hops</div>
</div>
<div style="background: ${config.colors.white}; padding: 10px; border-radius: 6px; text-align: center;">
<div style="color: ${config.colors.gray}; font-size: 11px;">Path Optimality</div>
<div style="color: ${state.metrics.pathOptimal ? config.colors.green : config.colors.red}; font-size: 20px; font-weight: bold;">
${state.metrics.pathOptimal ? "Optimal" : "Suboptimal"}
</div>
</div>
</div>
${state.discoveredRoute.length > 0 ? `
<div style="margin-top: 10px; background: ${config.colors.white}; padding: 10px; border-radius: 6px;">
<div style="color: ${config.colors.gray}; font-size: 11px; margin-bottom: 5px;">Discovered Route:</div>
<div style="color: ${config.colors.green}; font-size: 14px; font-weight: bold;">
${state.discoveredRoute.map(id => state.nodes[id].label).join(" -> ")}
</div>
</div>
` : ''}
`);
}
// ---------------------------------------------------------------------------
// PROTOCOL COMPARISON
// ---------------------------------------------------------------------------
const comparisonPanel = container.append("div")
.style("background", config.colors.lightGray)
.style("border-radius", "12px")
.style("padding", "15px")
.style("margin-top", "20px")
.style("border", `2px solid ${config.colors.gray}`);
comparisonPanel.append("h4")
.style("margin", "0 0 10px 0")
.style("color", config.colors.navy)
.style("font-size", "14px")
.text("Protocol Comparison");
comparisonPanel.append("div").html(`
<table style="width: 100%; border-collapse: collapse; font-size: 12px;">
<tr style="background: ${config.colors.navy}; color: white;">
<th style="padding: 8px; text-align: left;">Protocol</th>
<th style="padding: 8px; text-align: center;">Type</th>
<th style="padding: 8px; text-align: center;">Control Overhead</th>
<th style="padding: 8px; text-align: center;">Route Discovery</th>
<th style="padding: 8px; text-align: center;">Mobility Support</th>
<th style="padding: 8px; text-align: left;">Best For</th>
</tr>
<tr style="background: white;">
<td style="padding: 8px; font-weight: bold; color: ${config.protocols.dsdv.color};">DSDV</td>
<td style="padding: 8px; text-align: center;">Proactive</td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.red};">High</span></td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.green};">Instant</span></td>
<td style="padding: 8px; text-align: center;">Slow updates</td>
<td style="padding: 8px;">Low mobility, frequent routes</td>
</tr>
<tr style="background: ${config.colors.lightGray};">
<td style="padding: 8px; font-weight: bold; color: ${config.protocols.dsr.color};">DSR</td>
<td style="padding: 8px; text-align: center;">Reactive</td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.green};">Low</span></td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.orange};">On-demand</span></td>
<td style="padding: 8px; text-align: center;">Good (source route)</td>
<td style="padding: 8px;">Infrequent communication</td>
</tr>
<tr style="background: white;">
<td style="padding: 8px; font-weight: bold; color: ${config.protocols.aodv.color};">AODV</td>
<td style="padding: 8px; text-align: center;">Reactive</td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.green};">Low</span></td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.orange};">On-demand</span></td>
<td style="padding: 8px; text-align: center;">Good (local repair)</td>
<td style="padding: 8px;">Mobile networks</td>
</tr>
<tr style="background: ${config.colors.lightGray};">
<td style="padding: 8px; font-weight: bold; color: ${config.protocols.zrp.color};">ZRP</td>
<td style="padding: 8px; text-align: center;">Hybrid</td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.orange};">Medium</span></td>
<td style="padding: 8px; text-align: center;"><span style="color: ${config.colors.teal};">Fast local</span></td>
<td style="padding: 8px; text-align: center;">Best (zone-aware)</td>
<td style="padding: 8px;">Large networks, varied traffic</td>
</tr>
</table>
`);
// ---------------------------------------------------------------------------
// RESET FUNCTION
// ---------------------------------------------------------------------------
function resetVisualization() {
state.discoveredRoute = [];
state.rreqPackets = [];
state.rrepPackets = [];
state.brokenLinks = [];
state.metrics = { controlPackets: 0, discoveryTime: 0, pathLength: 0, pathOptimal: true };
state.nodes = JSON.parse(JSON.stringify(initialNodes));
initRoutingTables();
renderNetwork();
updateMetricsDisplay();
updateRoutingTableDisplay();
packetGroup.selectAll("*").remove();
}
// ---------------------------------------------------------------------------
// INITIALIZE
// ---------------------------------------------------------------------------
initRoutingTables();
renderNetwork();
updateMetricsDisplay();
updateRoutingTableDisplay();
return container.node();
}260.2 Understanding Ad-Hoc Routing
Ad-hoc networks are self-organizing wireless networks where devices communicate directly without fixed infrastructure. Each node can act as both a host and a router, forwarding packets for other nodes. This is essential for:
- Disaster recovery when infrastructure is damaged
- Military communications in the field
- Sensor networks in remote areas
- Vehicle-to-vehicle communication
- IoT mesh networks for extended coverage
260.3 Protocol Categories
260.3.1 Proactive (Table-Driven) Protocols
Proactive protocols maintain routing tables continuously through periodic updates.
DSDV (Destination-Sequenced Distance-Vector): - Every node maintains a routing table with all destinations - Periodic updates broadcast routing information - Sequence numbers prevent routing loops - Low latency for route lookup - High overhead in large/mobile networks
260.3.2 Reactive (On-Demand) Protocols
Reactive protocols discover routes only when needed.
DSR (Dynamic Source Routing): - Source determines complete route - Route stored in packet header (source routing) - Route cache reduces rediscovery - No periodic updates needed - Header overhead grows with path length
AODV (Ad-hoc On-demand Distance Vector): - Combines DSR and DSDV features - On-demand discovery like DSR - Hop-by-hop routing like DSDV - Sequence numbers for freshness - Local route repair capability
260.3.3 Hybrid Protocols
ZRP (Zone Routing Protocol): - Divides network into overlapping zones - Proactive routing within zones (IARP) - Reactive routing between zones (IERP) - Zone radius is configurable - Balances overhead and latency
260.4 Route Discovery Process
%% fig-alt: Quadrant chart comparing four ad-hoc routing protocols on axes of control overhead versus route discovery latency, showing DSDV in high overhead low latency quadrant, DSR and AODV in low overhead high latency quadrant, and ZRP balanced in the middle.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#FFFFFF', 'primaryBorderColor': '#16A085', 'lineColor': '#7F8C8D', 'secondaryColor': '#ECF0F1', 'tertiaryColor': '#FFFFFF'}}}%%
graph TB
subgraph OVERHEAD["Control Overhead"]
direction LR
HIGH_OH["HIGH<br/>Constant updates"]
MED_OH["MEDIUM<br/>Zone-based"]
LOW_OH["LOW<br/>On-demand only"]
end
subgraph PROTOCOLS["Protocol Comparison"]
DSDV["<b>DSDV</b><br/>━━━━━━━━<br/>Proactive<br/>━━━━━━━━<br/>Overhead: HIGH<br/>Latency: INSTANT<br/>━━━━━━━━<br/>Best: Static networks"]
ZRP["<b>ZRP</b><br/>━━━━━━━━<br/>Hybrid<br/>━━━━━━━━<br/>Overhead: MEDIUM<br/>Latency: FAST local<br/>━━━━━━━━<br/>Best: Large networks"]
AODV["<b>AODV</b><br/>━━━━━━━━<br/>Reactive<br/>━━━━━━━━<br/>Overhead: LOW<br/>Latency: ON-DEMAND<br/>━━━━━━━━<br/>Best: Mobile nodes"]
DSR["<b>DSR</b><br/>━━━━━━━━<br/>Reactive<br/>━━━━━━━━<br/>Overhead: LOW<br/>Latency: ON-DEMAND<br/>━━━━━━━━<br/>Best: Sparse traffic"]
end
HIGH_OH -.-> DSDV
MED_OH -.-> ZRP
LOW_OH -.-> AODV
LOW_OH -.-> DSR
style DSDV fill:#3498DB,stroke:#2C3E50,stroke-width:2px,color:#fff
style ZRP fill:#9B59B6,stroke:#2C3E50,stroke-width:2px,color:#fff
style AODV fill:#27AE60,stroke:#2C3E50,stroke-width:2px,color:#fff
style DSR fill:#E67E22,stroke:#2C3E50,stroke-width:2px,color:#fff
style HIGH_OH fill:#E74C3C,stroke:#2C3E50,stroke-width:1px,color:#fff
style MED_OH fill:#F1C40F,stroke:#2C3E50,stroke-width:1px,color:#000
style LOW_OH fill:#27AE60,stroke:#2C3E50,stroke-width:1px,color:#fff
This comparison helps select the right protocol based on network characteristics. DSDV trades high bandwidth overhead for instant route availability. DSR and AODV minimize overhead but require route discovery time. ZRP provides a balanced approach with fast local routing and on-demand inter-zone discovery.
260.5 Key Metrics Comparison
| Metric | DSDV | DSR | AODV | ZRP |
|---|---|---|---|---|
| Route Discovery Latency | None (instant) | High (flooding) | Medium | Low (local) / Medium (remote) |
| Control Overhead | High (periodic) | Low (on-demand) | Low | Medium (zone-based) |
| Storage Requirements | High (all routes) | Medium (cache) | Medium | Medium |
| Mobility Handling | Slow (full update) | Good (route repair) | Good (local repair) | Best (zone-aware) |
| Scalability | Poor | Good | Good | Best |
| Loop Freedom | Yes (seq nums) | Yes (source route) | Yes (seq nums) | Yes |
260.6 Implementation Considerations
- Small, static networks: DSDV provides instant routing
- Large, sparse traffic: DSR minimizes overhead
- Mobile networks: AODV handles mobility well
- Large, varied traffic: ZRP offers best balance
- IoT sensor networks: Consider traffic patterns and node density
- Broadcast storms: RREQ flooding can overwhelm network
- Stale routes: Cached routes may become invalid
- Energy consumption: Frequent updates drain batteries
- Scalability: Table size grows with network
- Hidden terminal: Nodes may not hear each other
260.7 What’s Next
Now that you understand ad-hoc routing protocols, explore these related topics:
- Ad-Hoc Network Fundamentals - Core concepts and terminology
- Multi-Hop Networking - Extended range communication
- Wireless Sensor Networks - WSN-specific considerations
- Wi-Fi Architecture and Mesh - Self-healing network topologies
Animation created for the IoT Class Textbook - ADHOC-001