%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#7F8C8D'}}}%%
graph TB
subgraph Internet["Internet / Cloud"]
Cloud[Cloud Server]
end
subgraph LowPAN["6LoWPAN Mesh Network"]
BR[Border Router<br/>NODE_TYPE=0<br/>Rank: 0]
R1[Router 1<br/>NODE_TYPE=1<br/>Rank: 256]
R2[Router 2<br/>NODE_TYPE=2<br/>Rank: 256]
Leaf[Leaf Node<br/>NODE_TYPE=3<br/>Rank: 512]
end
Cloud <--> BR
BR <--> R1
BR <--> R2
R1 <--> Leaf
R2 -.-> Leaf
style BR fill:#2C3E50,stroke:#16A085,color:#fff
style R1 fill:#16A085,stroke:#2C3E50,color:#fff
style R2 fill:#16A085,stroke:#2C3E50,color:#fff
style Leaf fill:#E67E22,stroke:#2C3E50,color:#fff
style Cloud fill:#7F8C8D,stroke:#2C3E50,color:#fff
964 6LoWPAN Lab Simulation
964.1 Learning Objectives
By the end of this lab, you will be able to:
- Understand IPHC Header Compression: See how 40-byte IPv6 headers compress to 2-7 bytes using context-based elision
- Visualize Fragmentation: Watch how 1280-byte IPv6 packets split into 102-byte IEEE 802.15.4 frames and reassemble
- Implement Mesh Addressing: Configure mesh-under and route-over forwarding modes
- Observe RPL Routing: See DODAG construction with DIO/DAO messages and rank calculation
- Configure Border Router: Bridge 6LoWPAN mesh to external IPv6 networks
964.2 Prerequisites
Before starting this lab, you should be familiar with:
- 6LoWPAN Overview: Basic understanding of 6LoWPAN
- 6LoWPAN Header Compression: IPHC compression mechanics
- 6LoWPAN Fragmentation: Fragmentation and reassembly
- 6LoWPAN Routing with RPL: RPL DODAG concepts
964.3 Lab Introduction
This hands-on lab uses the Wokwi ESP32 simulator to demonstrate 6LoWPAN protocol concepts. Since ESP32 does not natively support IEEE 802.15.4 radio, we simulate the 6LoWPAN adaptation layer using ESP-NOW as the transport, while implementing authentic 6LoWPAN packet structures including IPHC header compression, fragmentation/reassembly, mesh addressing, and RPL routing concepts.
Real 6LoWPAN requires IEEE 802.15.4 hardware (CC2538, nRF52840) and complex protocol stacks (Contiki-NG, RIOT OS). This simulation lets you:
- Learn protocol mechanics without specialized hardware
- Visualize header compression that would be invisible in binary packets
- Observe RPL topology formation in real-time
- Experiment with fragmentation without packet sniffers
- Test border router concepts in a controlled environment
The simulation accurately models 6LoWPAN packet structures, IPHC encoding rules, and RPL state machines—the same concepts you will apply with real 6LoWPAN implementations.
964.3.1 Concepts Demonstrated
| Concept | Real 6LoWPAN | This Simulation |
|---|---|---|
| Physical Layer | IEEE 802.15.4 (2.4 GHz) | ESP-NOW (2.4 GHz) |
| IPHC Compression | RFC 6282 encoding | Identical compression rules |
| Fragmentation | FRAG1/FRAGN headers | Full implementation |
| Mesh Addressing | Mesh header with hops left | Simulated multi-hop |
| RPL Routing | DODAG with DIO/DAO/DIS | Simplified construction |
| Border Router | IPv6 prefix delegation | Simulated prefix management |
| Link MTU | 127 bytes (102 usable) | Simulated 102-byte limit |
964.4 Lab Architecture
The simulation creates a 6LoWPAN mesh network with four ESP32 devices:
964.5 Embedded Wokwi Simulator
- Click the green Play button to start the simulation
- Watch the Serial Monitor for detailed 6LoWPAN packet logs
- Copy the code below into the simulator to run the full demonstration
- Modify compression settings and observe header size changes
- Trigger fragmentation by sending large payloads
Since Wokwi does not persist embedded projects, copy the complete 6LoWPAN simulation code below into the simulator. For a multi-node mesh simulation, open multiple browser tabs with separate Wokwi instances.
Quick Start: Copy the code, set NODE_TYPE to match the role (0 = Border Router, 1 = Router 1, 2 = Router 2, 3 = Leaf Node), and run the simulation.
964.6 Complete Simulation Code
This comprehensive code implements 6LoWPAN protocol concepts including IPHC header compression, fragmentation/reassembly, mesh addressing, and RPL routing:
// ============================================================
// 6LoWPAN Protocol Simulator for ESP32
// Demonstrates: IPHC compression, fragmentation, mesh addressing, RPL
// Uses ESP-NOW as transport layer to simulate IEEE 802.15.4 radio
// ============================================================
#include <WiFi.h>
#include <esp_now.h>
#include <esp_wifi.h>
// ============ NODE CONFIGURATION ============
// Change this for each ESP32 in the simulation:
// 0 = Border Router (DODAG Root, Rank 0)
// 1 = Router Node 1 (Rank 256)
// 2 = Router Node 2 (Rank 256)
// 3 = Leaf Node (Sensor, Rank 512)
#define NODE_TYPE 0
// ============ 6LoWPAN CONSTANTS ============
// IEEE 802.15.4 frame constraints
#define IEEE802154_MTU 127
#define IEEE802154_HEADER_SIZE 25 // MAC header overhead
#define SIXLOWPAN_MTU 102 // 127 - 25 = 102 bytes available
// IPv6 constants
#define IPV6_HEADER_SIZE 40
#define IPV6_MIN_MTU 1280
#define UDP_HEADER_SIZE 8
// 6LoWPAN dispatch bytes (RFC 4944, RFC 6282)
#define DISPATCH_NALP 0x00 // Not a LoWPAN frame (00xxxxxx)
#define DISPATCH_IPV6 0x41 // Uncompressed IPv6 (01000001)
#define DISPATCH_LOWPAN_HC1 0x42 // HC1 compressed (deprecated)
#define DISPATCH_IPHC 0x60 // IPHC compressed (011xxxxx)
#define DISPATCH_MESH 0x80 // Mesh header (10xxxxxx)
#define DISPATCH_FRAG1 0xC0 // First fragment (11000xxx)
#define DISPATCH_FRAGN 0xE0 // Subsequent fragment (11100xxx)
// RPL constants (RFC 6550)
#define RPL_INFINITE_RANK 0xFFFF
#define RPL_DEFAULT_MIN_HOP_RANK_INCREASE 256
#define RPL_DIO_INTERVAL_MIN 12 // 2^12 = 4096 ms
#define RPL_DIO_INTERVAL_DOUBLINGS 8
#define RPL_DEFAULT_PATH_CONTROL_SIZE 0
// Message types for simulation
#define MSG_TYPE_DIO 0x01 // DODAG Information Object
#define MSG_TYPE_DAO 0x02 // Destination Advertisement Object
#define MSG_TYPE_DIS 0x03 // DODAG Information Solicitation
#define MSG_TYPE_DATA 0x04 // Data packet
#define MSG_TYPE_FRAG 0x05 // Fragment
#define MSG_TYPE_ACK 0x06 // Acknowledgment
// ============ IPv6 ADDRESS STRUCTURES ============
typedef struct {
uint8_t bytes[16];
} IPv6Address_t;
// Link-local prefix (fe80::/10)
const uint8_t LINK_LOCAL_PREFIX[8] = {0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// Global prefix assigned by border router
const uint8_t GLOBAL_PREFIX[8] = {0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00};
// ============ IPHC COMPRESSION STRUCTURES ============
typedef struct __attribute__((packed)) {
uint8_t dispatch_tf_nh_hlim; // dispatch(3) + TF(2) + NH(1) + HLIM(2)
uint8_t cid_sac_sam_m_dac_dam; // CID(1) + SAC(1) + SAM(2) + M(1) + DAC(1) + DAM(2)
} IPHCHeader_t;
// IPHC field definitions
#define IPHC_TF_MASK 0x18
#define IPHC_TF_SHIFT 3
#define IPHC_NH_MASK 0x04
#define IPHC_NH_SHIFT 2
#define IPHC_HLIM_MASK 0x03
#define IPHC_HLIM_SHIFT 0
#define IPHC_CID_MASK 0x80
#define IPHC_SAC_MASK 0x40
#define IPHC_SAM_MASK 0x30
#define IPHC_SAM_SHIFT 4
#define IPHC_M_MASK 0x08
#define IPHC_DAC_MASK 0x04
#define IPHC_DAM_MASK 0x03
// SAM/DAM modes
#define IPHC_ADDR_128BIT 0x00
#define IPHC_ADDR_64BIT 0x01
#define IPHC_ADDR_16BIT 0x02
#define IPHC_ADDR_ELIDED 0x03
// Hop Limit compression
#define IPHC_HLIM_INLINE 0x00
#define IPHC_HLIM_1 0x01
#define IPHC_HLIM_64 0x02
#define IPHC_HLIM_255 0x03
// ============ NHC (Next Header Compression) ============
#define NHC_UDP 0xF0
#define NHC_UDP_CHECKSUM 0x04
#define NHC_UDP_PORTS_MASK 0x03
#define NHC_PORTS_FULL 0x00
#define NHC_PORTS_SRC_8 0x01
#define NHC_PORTS_DST_8 0x02
#define NHC_PORTS_BOTH_4 0x03
// ============ FRAGMENTATION STRUCTURES ============
typedef struct __attribute__((packed)) {
uint8_t dispatch_size_hi;
uint8_t size_lo;
uint16_t tag;
} Frag1Header_t;
typedef struct __attribute__((packed)) {
uint8_t dispatch_size_hi;
uint8_t size_lo;
uint16_t tag;
uint8_t offset;
} FragNHeader_t;
// ============ MESH ADDRESSING HEADER ============
typedef struct __attribute__((packed)) {
uint8_t dispatch_hops;
uint8_t originator[2];
uint8_t finalDest[2];
} MeshHeader_t;
#define MESH_V_FLAG 0x20
#define MESH_F_FLAG 0x10
#define MESH_HOPS_MASK 0x0F
// ============ RPL CONTROL MESSAGES ============
typedef struct __attribute__((packed)) {
uint8_t instanceId;
uint8_t version;
uint16_t rank;
uint8_t mop_prf;
uint8_t dtsn;
uint8_t dodagId[16];
} RPL_DIO_t;
typedef struct __attribute__((packed)) {
uint8_t instanceId;
uint8_t flags;
uint8_t reserved;
uint8_t daoSequence;
uint8_t targetPrefix[16];
uint8_t targetPrefixLen;
uint8_t parentAddr[16];
} RPL_DAO_t;
typedef struct __attribute__((packed)) {
uint8_t flags;
uint8_t reserved;
} RPL_DIS_t;
// ============ DEVICE STATE ============
struct DeviceState {
uint8_t nodeType;
uint8_t shortAddr[2];
IPv6Address_t linkLocalAddr;
IPv6Address_t globalAddr;
// RPL state
uint16_t rank;
uint8_t parentAddr[2];
bool hasParent;
uint8_t instanceId;
uint8_t daoSequence;
uint32_t lastDioSent;
uint32_t lastDaoSent;
uint32_t dioInterval;
// Fragmentation state
uint16_t fragTag;
uint8_t reassemblyBuffer[IPV6_MIN_MTU];
uint16_t reassemblySize;
uint16_t reassemblyReceived;
uint16_t reassemblyTag;
uint32_t reassemblyTimeout;
// Statistics
uint32_t packetsCompressed;
uint32_t bytesBeforeCompression;
uint32_t bytesAfterCompression;
uint32_t fragmentsSent;
uint32_t fragmentsReceived;
uint32_t diosSent;
uint32_t daosSent;
uint32_t dataPacketsSent;
} device;
// Context table for stateful compression
struct CompressionContext {
uint8_t prefix[8];
uint8_t prefixLen;
bool valid;
} contexts[4];
// Routing table for mesh forwarding
struct RoutingEntry {
uint8_t destAddr[2];
uint8_t nextHop[2];
uint16_t rank;
bool valid;
} routingTable[16];
uint8_t routingTableSize = 0;
// ============ ESP-NOW TRANSPORT ============
uint8_t broadcastMAC[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// ============ UTILITY FUNCTIONS ============
void printHex(const uint8_t* data, size_t len) {
for (size_t i = 0; i < len; i++) {
Serial.printf("%02X ", data[i]);
}
}
void printIPv6(const IPv6Address_t* addr) {
for (int i = 0; i < 16; i += 2) {
if (i > 0) Serial.print(":");
Serial.printf("%02x%02x", addr->bytes[i], addr->bytes[i+1]);
}
}
String getNodeTypeName(uint8_t type) {
switch (type) {
case 0: return "Border Router (DODAG Root)";
case 1: return "Router Node 1";
case 2: return "Router Node 2";
case 3: return "Leaf Node (Sensor)";
default: return "Unknown";
}
}
// Generate link-local address from short address
void generateLinkLocalAddr(IPv6Address_t* addr, const uint8_t* shortAddr) {
memcpy(addr->bytes, LINK_LOCAL_PREFIX, 8);
addr->bytes[8] = 0x00;
addr->bytes[9] = 0x00;
addr->bytes[10] = 0x00;
addr->bytes[11] = 0xFF;
addr->bytes[12] = 0xFE;
addr->bytes[13] = 0x00;
addr->bytes[14] = shortAddr[0];
addr->bytes[15] = shortAddr[1];
}
// Generate global address from prefix and short address
void generateGlobalAddr(IPv6Address_t* addr, const uint8_t* prefix, const uint8_t* shortAddr) {
memcpy(addr->bytes, prefix, 8);
addr->bytes[8] = 0x00;
addr->bytes[9] = 0x00;
addr->bytes[10] = 0x00;
addr->bytes[11] = 0xFF;
addr->bytes[12] = 0xFE;
addr->bytes[13] = 0x00;
addr->bytes[14] = shortAddr[0];
addr->bytes[15] = shortAddr[1];
}
// ============ IPHC COMPRESSION ENGINE ============
uint8_t compressIPHC(const IPv6Address_t* srcAddr, const IPv6Address_t* dstAddr,
uint8_t hopLimit, uint8_t nextHeader, uint8_t* output) {
Serial.println("\n============ IPHC COMPRESSION ============");
Serial.println("[IPHC] Compressing IPv6 header:");
Serial.printf(" Original size: %d bytes\n", IPV6_HEADER_SIZE);
uint8_t pos = 0;
uint8_t iphc1 = DISPATCH_IPHC; // 011xxxxx
uint8_t iphc2 = 0x00;
// Traffic Class and Flow Label (TF field)
// Assume TC=0, FL=0 -> TF=11 (elided)
iphc1 |= (0x03 << IPHC_TF_SHIFT);
Serial.println(" TF: Traffic Class and Flow Label elided (TF=11)");
// Next Header (NH field)
if (nextHeader == 17) { // UDP
iphc1 |= IPHC_NH_MASK;
Serial.println(" NH: UDP header will use NHC compression (NH=1)");
}
// Hop Limit (HLIM field)
switch (hopLimit) {
case 1: iphc1 |= IPHC_HLIM_1; break;
case 64: iphc1 |= IPHC_HLIM_64; break;
case 255: iphc1 |= IPHC_HLIM_255; break;
default: iphc1 |= IPHC_HLIM_INLINE; break;
}
Serial.printf(" HLIM: Hop Limit = %d (compressed, HLIM=%d)\n",
hopLimit, iphc1 & IPHC_HLIM_MASK);
// Source Address Mode (SAM field)
bool srcIsLinkLocal = (memcmp(srcAddr->bytes, LINK_LOCAL_PREFIX, 8) == 0);
if (srcIsLinkLocal) {
iphc2 |= (IPHC_ADDR_ELIDED << IPHC_SAM_SHIFT);
Serial.println(" SAM: Source fully elided from link-layer (SAM=11)");
} else {
iphc2 |= (IPHC_ADDR_128BIT << IPHC_SAM_SHIFT);
Serial.println(" SAM: Source inline 128 bits (SAM=00)");
}
// Destination Address Mode (DAM field)
bool dstIsLinkLocal = (memcmp(dstAddr->bytes, LINK_LOCAL_PREFIX, 8) == 0);
if (dstIsLinkLocal) {
iphc2 |= IPHC_ADDR_ELIDED;
Serial.println(" DAM: Destination fully elided from link-layer (DAM=11)");
} else {
iphc2 |= IPHC_ADDR_128BIT;
Serial.println(" DAM: Destination inline 128 bits (DAM=00)");
}
// Write IPHC header
output[pos++] = iphc1;
output[pos++] = iphc2;
// Hop Limit inline if not compressed
if ((iphc1 & IPHC_HLIM_MASK) == IPHC_HLIM_INLINE) {
output[pos++] = hopLimit;
}
// Source address inline if not elided
if ((iphc2 & IPHC_SAM_MASK) == (IPHC_ADDR_128BIT << IPHC_SAM_SHIFT)) {
memcpy(output + pos, srcAddr->bytes, 16);
pos += 16;
}
// Destination address inline if not elided
if ((iphc2 & IPHC_DAM_MASK) == IPHC_ADDR_128BIT) {
memcpy(output + pos, dstAddr->bytes, 16);
pos += 16;
}
device.packetsCompressed++;
device.bytesBeforeCompression += IPV6_HEADER_SIZE;
device.bytesAfterCompression += pos;
float reduction = (1.0 - (float)pos / IPV6_HEADER_SIZE) * 100;
Serial.printf(" Compressed size: %d bytes (%.1f%% reduction)\n", pos, reduction);
return pos;
}
// ============ NHC UDP COMPRESSION ============
uint8_t compressNHC_UDP(uint16_t srcPort, uint16_t dstPort, bool elideChecksum, uint8_t* output) {
Serial.println("\n[NHC] Compressing UDP header:");
Serial.printf(" Original size: %d bytes\n", UDP_HEADER_SIZE);
uint8_t pos = 0;
uint8_t nhcUdp = NHC_UDP;
// Determine port compression mode
bool srcComp = (srcPort >= 0xF0B0 && srcPort <= 0xF0BF);
bool dstComp = (dstPort >= 0xF0B0 && dstPort <= 0xF0BF);
if (srcComp && dstComp) {
nhcUdp |= NHC_PORTS_BOTH_4;
output[pos++] = nhcUdp;
output[pos++] = ((srcPort & 0x0F) << 4) | (dstPort & 0x0F);
Serial.println(" Ports: Both compressed to 4 bits each");
} else {
nhcUdp |= NHC_PORTS_FULL;
output[pos++] = nhcUdp;
output[pos++] = srcPort >> 8;
output[pos++] = srcPort & 0xFF;
output[pos++] = dstPort >> 8;
output[pos++] = dstPort & 0xFF;
Serial.printf(" Ports: Full inline (src=%d, dst=%d)\n", srcPort, dstPort);
}
Serial.printf(" Compressed size: %d bytes\n", pos);
return pos;
}
// ============ FRAGMENTATION ENGINE ============
void fragmentPacket(const uint8_t* packet, uint16_t packetLen, const uint8_t* destMAC) {
Serial.println("\n============ FRAGMENTATION ============");
Serial.printf("[FRAG] Original packet size: %d bytes\n", packetLen);
Serial.printf("[FRAG] Available MTU: %d bytes\n", SIXLOWPAN_MTU);
if (packetLen <= SIXLOWPAN_MTU) {
Serial.println("[FRAG] No fragmentation needed");
return;
}
uint16_t tag = device.fragTag++;
uint16_t offset = 0;
uint8_t fragNum = 1;
// Calculate number of fragments
uint16_t frag1Payload = SIXLOWPAN_MTU - sizeof(Frag1Header_t);
uint16_t fragNPayload = SIXLOWPAN_MTU - sizeof(FragNHeader_t);
uint16_t remaining = packetLen - frag1Payload;
uint16_t numFrags = 1 + (remaining + fragNPayload - 1) / fragNPayload;
Serial.printf("[FRAG] Fragmenting into %d fragments:\n", numFrags);
Serial.printf(" First fragment: %d bytes payload + %d bytes header\n",
frag1Payload, sizeof(Frag1Header_t));
Serial.printf(" Subsequent fragments: up to %d bytes payload + %d bytes header\n",
fragNPayload, sizeof(FragNHeader_t));
// Send FRAG1
uint8_t buffer[SIXLOWPAN_MTU + 10];
Frag1Header_t* frag1 = (Frag1Header_t*)buffer;
frag1->dispatch_size_hi = DISPATCH_FRAG1 | ((packetLen >> 8) & 0x07);
frag1->size_lo = packetLen & 0xFF;
frag1->tag = tag;
memcpy(buffer + sizeof(Frag1Header_t), packet, frag1Payload);
Serial.printf("\n[FRAG1] Sending fragment %d/%d:\n", fragNum, numFrags);
Serial.printf(" Dispatch: 0x%02X (FRAG1)\n", DISPATCH_FRAG1);
Serial.printf(" Datagram size: %d bytes\n", packetLen);
Serial.printf(" Tag: 0x%04X\n", tag);
Serial.printf(" Payload: %d bytes\n", frag1Payload);
esp_now_send(destMAC, buffer, sizeof(Frag1Header_t) + frag1Payload);
device.fragmentsSent++;
offset = frag1Payload;
fragNum++;
// Send FRAGN fragments
while (offset < packetLen) {
uint16_t payloadLen = min((uint16_t)fragNPayload, (uint16_t)(packetLen - offset));
FragNHeader_t* fragN = (FragNHeader_t*)buffer;
fragN->dispatch_size_hi = DISPATCH_FRAGN | ((packetLen >> 8) & 0x07);
fragN->size_lo = packetLen & 0xFF;
fragN->tag = tag;
fragN->offset = offset / 8;
memcpy(buffer + sizeof(FragNHeader_t), packet + offset, payloadLen);
Serial.printf("\n[FRAGN] Sending fragment %d/%d:\n", fragNum, numFrags);
Serial.printf(" Dispatch: 0x%02X (FRAGN)\n", DISPATCH_FRAGN);
Serial.printf(" Offset: %d (bytes: %d)\n", fragN->offset, offset);
Serial.printf(" Payload: %d bytes\n", payloadLen);
esp_now_send(destMAC, buffer, sizeof(FragNHeader_t) + payloadLen);
device.fragmentsSent++;
offset += payloadLen;
fragNum++;
delay(50); // Allow time between fragments
}
Serial.printf("\n[FRAG] Complete! Sent %d fragments, total overhead: %d bytes\n",
numFrags, sizeof(Frag1Header_t) + (numFrags-1) * sizeof(FragNHeader_t));
}
// ============ REASSEMBLY ENGINE ============
bool reassembleFragment(const uint8_t* data, uint8_t len) {
uint8_t dispatch = data[0] & 0xF8;
if (dispatch == DISPATCH_FRAG1) {
Frag1Header_t* frag1 = (Frag1Header_t*)data;
uint16_t datagramSize = ((frag1->dispatch_size_hi & 0x07) << 8) | frag1->size_lo;
uint16_t tag = frag1->tag;
Serial.println("\n[REASSEMBLY] Received FRAG1:");
Serial.printf(" Datagram size: %d bytes\n", datagramSize);
Serial.printf(" Tag: 0x%04X\n", tag);
// Initialize reassembly
device.reassemblySize = datagramSize;
device.reassemblyTag = tag;
device.reassemblyReceived = len - sizeof(Frag1Header_t);
device.reassemblyTimeout = millis() + 60000; // 60 second timeout
memcpy(device.reassemblyBuffer, data + sizeof(Frag1Header_t), device.reassemblyReceived);
Serial.printf(" Received: %d / %d bytes\n",
device.reassemblyReceived, device.reassemblySize);
device.fragmentsReceived++;
} else if (dispatch == DISPATCH_FRAGN) {
FragNHeader_t* fragN = (FragNHeader_t*)data;
uint16_t datagramSize = ((fragN->dispatch_size_hi & 0x07) << 8) | fragN->size_lo;
uint16_t tag = fragN->tag;
uint16_t offset = fragN->offset * 8;
Serial.println("\n[REASSEMBLY] Received FRAGN:");
Serial.printf(" Tag: 0x%04X\n", tag);
Serial.printf(" Offset: %d bytes\n", offset);
if (tag != device.reassemblyTag) {
Serial.println(" ERROR: Tag mismatch, discarding");
return false;
}
if (millis() > device.reassemblyTimeout) {
Serial.println(" ERROR: Reassembly timeout, discarding");
device.reassemblyTag = 0;
return false;
}
uint8_t payloadLen = len - sizeof(FragNHeader_t);
memcpy(device.reassemblyBuffer + offset, data + sizeof(FragNHeader_t), payloadLen);
device.reassemblyReceived += payloadLen;
Serial.printf(" Received: %d / %d bytes\n",
device.reassemblyReceived, device.reassemblySize);
device.fragmentsReceived++;
if (device.reassemblyReceived >= device.reassemblySize) {
Serial.println(" COMPLETE! Datagram reassembled.");
device.reassemblyTag = 0;
return true;
}
}
return false;
}
// ============ RPL ROUTING ENGINE ============
void sendDIO() {
uint8_t buffer[64];
buffer[0] = MSG_TYPE_DIO;
RPL_DIO_t* dio = (RPL_DIO_t*)(buffer + 1);
dio->instanceId = device.instanceId;
dio->version = 1;
dio->rank = device.rank;
dio->mop_prf = 0x00;
dio->dtsn = 0;
memcpy(dio->dodagId, device.linkLocalAddr.bytes, 16);
Serial.println("\n============ RPL DIO ============");
Serial.printf("[DIO] Sending DODAG Information Object:\n");
Serial.printf(" Instance ID: %d\n", dio->instanceId);
Serial.printf(" Rank: %d\n", dio->rank);
Serial.printf(" DODAG ID: ");
printIPv6((IPv6Address_t*)dio->dodagId);
Serial.println();
esp_now_send(broadcastMAC, buffer, 1 + sizeof(RPL_DIO_t));
device.diosSent++;
device.lastDioSent = millis();
}
void sendDAO() {
if (!device.hasParent && device.nodeType != 0) {
Serial.println("[DAO] No parent, cannot send DAO");
return;
}
uint8_t buffer[64];
buffer[0] = MSG_TYPE_DAO;
RPL_DAO_t* dao = (RPL_DAO_t*)(buffer + 1);
dao->instanceId = device.instanceId;
dao->flags = 0x00;
dao->reserved = 0;
dao->daoSequence = device.daoSequence++;
memcpy(dao->targetPrefix, device.linkLocalAddr.bytes, 16);
dao->targetPrefixLen = 128;
if (device.hasParent) {
dao->parentAddr[0] = 0xfe;
dao->parentAddr[1] = 0x80;
memset(&dao->parentAddr[2], 0, 6);
dao->parentAddr[8] = 0x00;
dao->parentAddr[9] = 0x00;
dao->parentAddr[10] = 0x00;
dao->parentAddr[11] = 0xFF;
dao->parentAddr[12] = 0xFE;
dao->parentAddr[13] = 0x00;
dao->parentAddr[14] = device.parentAddr[0];
dao->parentAddr[15] = device.parentAddr[1];
}
Serial.println("\n============ RPL DAO ============");
Serial.printf("[DAO] Sending Destination Advertisement Object:\n");
Serial.printf(" Instance ID: %d\n", dao->instanceId);
Serial.printf(" Sequence: %d\n", dao->daoSequence);
Serial.printf(" Target: ");
printIPv6((IPv6Address_t*)dao->targetPrefix);
Serial.printf("/%d\n", dao->targetPrefixLen);
esp_now_send(broadcastMAC, buffer, 1 + sizeof(RPL_DAO_t));
device.daosSent++;
device.lastDaoSent = millis();
}
void processDIO(const RPL_DIO_t* dio, const uint8_t* senderMAC) {
Serial.println("\n[DIO] Received DODAG Information Object:");
Serial.printf(" From: %02X:%02X:%02X:%02X:%02X:%02X\n",
senderMAC[0], senderMAC[1], senderMAC[2],
senderMAC[3], senderMAC[4], senderMAC[5]);
Serial.printf(" Rank: %d\n", dio->rank);
Serial.printf(" My current rank: %d\n", device.rank);
uint16_t newRank = dio->rank + RPL_DEFAULT_MIN_HOP_RANK_INCREASE;
if (newRank < device.rank) {
Serial.printf(" Better path! New rank would be: %d\n", newRank);
device.parentAddr[0] = senderMAC[4];
device.parentAddr[1] = senderMAC[5];
device.rank = newRank;
device.hasParent = true;
Serial.printf(" Parent selected: %02X:%02X\n",
device.parentAddr[0], device.parentAddr[1]);
bool found = false;
for (int i = 0; i < routingTableSize; i++) {
if (memcmp(routingTable[i].destAddr, device.parentAddr, 2) == 0) {
routingTable[i].rank = dio->rank;
found = true;
break;
}
}
if (!found && routingTableSize < 16) {
memcpy(routingTable[routingTableSize].destAddr, device.parentAddr, 2);
memcpy(routingTable[routingTableSize].nextHop, device.parentAddr, 2);
routingTable[routingTableSize].rank = dio->rank;
routingTable[routingTableSize].valid = true;
routingTableSize++;
}
sendDAO();
} else {
Serial.println(" Ignoring (not a better path)");
}
}
void processDAO(const RPL_DAO_t* dao, const uint8_t* senderMAC) {
Serial.println("\n[DAO] Received Destination Advertisement Object:");
Serial.printf(" From: %02X:%02X:%02X:%02X:%02X:%02X\n",
senderMAC[0], senderMAC[1], senderMAC[2],
senderMAC[3], senderMAC[4], senderMAC[5]);
Serial.printf(" Target: ");
printIPv6((IPv6Address_t*)dao->targetPrefix);
Serial.println();
uint8_t srcShort[2] = {senderMAC[4], senderMAC[5]};
bool found = false;
for (int i = 0; i < routingTableSize; i++) {
if (memcmp(routingTable[i].destAddr, srcShort, 2) == 0) {
found = true;
break;
}
}
if (!found && routingTableSize < 16) {
memcpy(routingTable[routingTableSize].destAddr, srcShort, 2);
memcpy(routingTable[routingTableSize].nextHop, srcShort, 2);
routingTable[routingTableSize].valid = true;
routingTableSize++;
Serial.printf(" Added to routing table (entry %d)\n", routingTableSize);
}
if (device.nodeType == 0) {
Serial.println(" Border router: Route installed");
} else if (device.hasParent) {
Serial.println(" Forwarding DAO toward DODAG root");
}
}
// ============ DATA TRANSMISSION ============
void sendSensorData() {
Serial.println("\n\n================================================");
Serial.println(" 6LoWPAN DATA TRANSMISSION");
Serial.println("================================================");
float temperature = 20.0 + (random(100) / 10.0);
float humidity = 40.0 + (random(400) / 10.0);
uint16_t co2 = 400 + random(600);
Serial.println("\n[SENSOR] Reading values:");
Serial.printf(" Temperature: %.1f C\n", temperature);
Serial.printf(" Humidity: %.1f %%\n", humidity);
Serial.printf(" CO2: %d ppm\n", co2);
uint8_t payload[32];
uint8_t payloadLen = 0;
payload[payloadLen++] = 0x01;
payload[payloadLen++] = 0x00;
int16_t tempFP = (int16_t)(temperature * 10);
payload[payloadLen++] = tempFP >> 8;
payload[payloadLen++] = tempFP & 0xFF;
int16_t humFP = (int16_t)(humidity * 10);
payload[payloadLen++] = humFP >> 8;
payload[payloadLen++] = humFP & 0xFF;
payload[payloadLen++] = co2 >> 8;
payload[payloadLen++] = co2 & 0xFF;
Serial.printf("\n[PAYLOAD] %d bytes: ", payloadLen);
printHex(payload, payloadLen);
Serial.println();
IPv6Address_t destAddr;
uint8_t brShort[2] = {0x00, 0x01};
generateLinkLocalAddr(&destAddr, brShort);
Serial.println("\n[IPv6] Building packet:");
Serial.print(" Source: ");
printIPv6(&device.linkLocalAddr);
Serial.println();
Serial.print(" Destination: ");
printIPv6(&destAddr);
Serial.println();
uint8_t compressedPacket[256];
uint8_t pos = 0;
pos = compressIPHC(&device.linkLocalAddr, &destAddr, 64, 17, compressedPacket);
pos += compressNHC_UDP(5683, 5683, false, compressedPacket + pos);
memcpy(compressedPacket + pos, payload, payloadLen);
pos += payloadLen;
Serial.println("\n[6LoWPAN] Compressed packet summary:");
Serial.printf(" Original IPv6+UDP: %d bytes\n", IPV6_HEADER_SIZE + UDP_HEADER_SIZE);
Serial.printf(" Compressed header: %d bytes\n", pos - payloadLen);
Serial.printf(" Total packet: %d bytes\n", pos);
Serial.printf(" Compression ratio: %.1f%%\n",
(1.0 - (float)(pos - payloadLen) / (IPV6_HEADER_SIZE + UDP_HEADER_SIZE)) * 100);
if (pos > SIXLOWPAN_MTU) {
fragmentPacket(compressedPacket, pos, broadcastMAC);
} else {
Serial.println("\n[TX] Sending single frame (no fragmentation needed)");
esp_now_send(broadcastMAC, compressedPacket, pos);
}
device.dataPacketsSent++;
Serial.println("\n============ STATISTICS ============");
Serial.printf("Packets compressed: %lu\n", device.packetsCompressed);
Serial.printf("Bytes before compression: %lu\n", device.bytesBeforeCompression);
Serial.printf("Bytes after compression: %lu\n", device.bytesAfterCompression);
Serial.printf("Average compression: %.1f%%\n",
device.bytesBeforeCompression > 0 ?
(1.0 - (float)device.bytesAfterCompression / device.bytesBeforeCompression) * 100 : 0);
Serial.printf("DIOs sent: %lu\n", device.diosSent);
Serial.printf("DAOs sent: %lu\n", device.daosSent);
Serial.printf("Data packets: %lu\n", device.dataPacketsSent);
Serial.printf("Fragments sent: %lu\n", device.fragmentsSent);
Serial.printf("Fragments received: %lu\n", device.fragmentsReceived);
}
void sendLargePacket() {
Serial.println("\n\n================================================");
Serial.println(" 6LoWPAN FRAGMENTATION DEMONSTRATION");
Serial.println("================================================");
uint8_t largePayload[200];
for (int i = 0; i < 200; i++) {
largePayload[i] = i & 0xFF;
}
Serial.printf("\n[LARGE] Creating %d byte payload to demonstrate fragmentation\n", 200);
Serial.println("[LARGE] This will require multiple 6LoWPAN frames");
IPv6Address_t destAddr;
uint8_t brShort[2] = {0x00, 0x01};
generateLinkLocalAddr(&destAddr, brShort);
uint8_t compressedPacket[300];
uint8_t pos = 0;
pos = compressIPHC(&device.linkLocalAddr, &destAddr, 64, 17, compressedPacket);
pos += compressNHC_UDP(5683, 5683, false, compressedPacket + pos);
memcpy(compressedPacket + pos, largePayload, 200);
pos += 200;
Serial.printf("[LARGE] Total packet size: %d bytes (MTU: %d)\n", pos, SIXLOWPAN_MTU);
fragmentPacket(compressedPacket, pos, broadcastMAC);
}
// ============ MESH FORWARDING ============
void addMeshHeader(uint8_t* packet, uint8_t* originator, uint8_t* finalDest, uint8_t hopsLeft) {
Serial.println("\n[MESH] Adding mesh addressing header:");
Serial.printf(" Originator: %02X:%02X\n", originator[0], originator[1]);
Serial.printf(" Final dest: %02X:%02X\n", finalDest[0], finalDest[1]);
Serial.printf(" Hops left: %d\n", hopsLeft);
MeshHeader_t mesh;
mesh.dispatch_hops = DISPATCH_MESH | (hopsLeft & MESH_HOPS_MASK);
mesh.originator[0] = originator[0];
mesh.originator[1] = originator[1];
mesh.finalDest[0] = finalDest[0];
mesh.finalDest[1] = finalDest[1];
Serial.print(" Mesh header: ");
printHex((uint8_t*)&mesh, sizeof(MeshHeader_t));
Serial.println();
}
// ============ ESP-NOW CALLBACKS ============
void onDataReceived(const esp_now_recv_info* info, const uint8_t* data, int len) {
uint8_t msgType = data[0];
if (msgType == MSG_TYPE_DIO) {
processDIO((RPL_DIO_t*)(data + 1), info->src_addr);
} else if (msgType == MSG_TYPE_DAO) {
processDAO((RPL_DAO_t*)(data + 1), info->src_addr);
} else if ((data[0] & 0xF8) == DISPATCH_FRAG1 || (data[0] & 0xF8) == DISPATCH_FRAGN) {
reassembleFragment(data, len);
} else if ((data[0] & 0xE0) == DISPATCH_IPHC) {
Serial.println("\n[RX] Received IPHC compressed packet");
Serial.printf(" Size: %d bytes\n", len);
Serial.print(" Data: ");
printHex(data, min(len, 20));
if (len > 20) Serial.print("...");
Serial.println();
}
}
void onDataSent(const uint8_t* mac_addr, esp_now_send_status_t status) {
if (status != ESP_NOW_SEND_SUCCESS) {
Serial.println("[TX] Send failed!");
}
}
// ============ BORDER ROUTER FUNCTIONS ============
void borderRouterSetup() {
Serial.println("\n============ BORDER ROUTER SETUP ============");
Serial.println("[BR] Configuring as DODAG root (Rank 0)");
contexts[0].valid = true;
memcpy(contexts[0].prefix, GLOBAL_PREFIX, 8);
contexts[0].prefixLen = 64;
Serial.print("[BR] Global prefix: 2001:db8:1::");
Serial.println("/64");
Serial.println("[BR] Context 0 configured for stateful compression");
generateGlobalAddr(&device.globalAddr, GLOBAL_PREFIX, device.shortAddr);
Serial.print("[BR] Global address: ");
printIPv6(&device.globalAddr);
Serial.println();
Serial.println("[BR] Border router ready - broadcasting DIO every 10 seconds");
}
void printRoutingTable() {
Serial.println("\n============ ROUTING TABLE ============");
Serial.printf("Entries: %d\n", routingTableSize);
for (int i = 0; i < routingTableSize; i++) {
if (routingTable[i].valid) {
Serial.printf(" [%d] Dest: %02X:%02X -> Next: %02X:%02X, Rank: %d\n",
i,
routingTable[i].destAddr[0], routingTable[i].destAddr[1],
routingTable[i].nextHop[0], routingTable[i].nextHop[1],
routingTable[i].rank);
}
}
}
// ============ SETUP ============
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n\n================================================");
Serial.println(" 6LoWPAN PROTOCOL SIMULATOR FOR ESP32");
Serial.println("================================================");
Serial.println("Demonstrates: IPHC compression, fragmentation,");
Serial.println(" mesh addressing, RPL routing");
Serial.println("================================================\n");
device.nodeType = NODE_TYPE;
device.instanceId = 1;
device.fragTag = random(65535);
device.dioInterval = 10000;
device.shortAddr[0] = 0x00;
device.shortAddr[1] = NODE_TYPE + 1;
generateLinkLocalAddr(&device.linkLocalAddr, device.shortAddr);
switch (NODE_TYPE) {
case 0:
device.rank = 0;
device.hasParent = false;
break;
case 1:
case 2:
device.rank = RPL_INFINITE_RANK;
device.hasParent = false;
break;
case 3:
device.rank = RPL_INFINITE_RANK;
device.hasParent = false;
break;
}
Serial.printf("Node Type: %s\n", getNodeTypeName(NODE_TYPE).c_str());
Serial.printf("Short Address: %02X:%02X\n", device.shortAddr[0], device.shortAddr[1]);
Serial.print("Link-Local Address: ");
printIPv6(&device.linkLocalAddr);
Serial.println();
Serial.printf("Initial Rank: %d\n", device.rank);
WiFi.mode(WIFI_STA);
Serial.print("MAC Address: ");
Serial.println(WiFi.macAddress());
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW init failed!");
return;
}
esp_now_register_recv_cb(onDataReceived);
esp_now_register_send_cb(onDataSent);
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, broadcastMAC, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo);
if (NODE_TYPE == 0) {
borderRouterSetup();
}
Serial.println("\n============ SIMULATION READY ============");
if (NODE_TYPE == 0) {
Serial.println("Border Router mode:");
Serial.println(" - Broadcasting DIO every 10 seconds");
Serial.println(" - Managing DODAG as root");
Serial.println(" - Press BOOT to show routing table");
} else if (NODE_TYPE == 3) {
Serial.println("Leaf Node mode:");
Serial.println(" - Sending sensor data every 30 seconds");
Serial.println(" - Press BOOT for manual send");
Serial.println(" - Long press for fragmentation demo");
} else {
Serial.println("Router Node mode:");
Serial.println(" - Forwarding packets");
Serial.println(" - Participating in RPL DODAG");
Serial.println(" - Press BOOT to send DIS");
}
Serial.println("==========================================\n");
}
// ============ MAIN LOOP ============
unsigned long lastDIO = 0;
unsigned long lastData = 0;
unsigned long buttonPressStart = 0;
bool buttonPressed = false;
#define DIO_INTERVAL 10000
#define DATA_INTERVAL 30000
void loop() {
unsigned long now = millis();
if (NODE_TYPE == 0 && now - lastDIO > DIO_INTERVAL) {
sendDIO();
lastDIO = now;
}
if (NODE_TYPE == 3 && now - lastData > DATA_INTERVAL) {
if (device.hasParent) {
sendSensorData();
} else {
Serial.println("[DATA] No parent yet, waiting for DIO...");
}
lastData = now;
}
if ((NODE_TYPE == 1 || NODE_TYPE == 2) && device.hasParent) {
if (now - lastDIO > DIO_INTERVAL + 5000) {
sendDIO();
lastDIO = now;
}
}
if (digitalRead(0) == LOW) {
if (!buttonPressed) {
buttonPressed = true;
buttonPressStart = now;
} else if (now - buttonPressStart > 2000 && NODE_TYPE == 3) {
sendLargePacket();
while (digitalRead(0) == LOW) delay(10);
buttonPressed = false;
}
} else if (buttonPressed) {
if (now - buttonPressStart < 2000) {
if (NODE_TYPE == 0) {
printRoutingTable();
} else if (NODE_TYPE == 3) {
sendSensorData();
} else {
Serial.println("\n[DIS] Sending DODAG Information Solicitation");
uint8_t dis[3] = {MSG_TYPE_DIS, 0x00, 0x00};
esp_now_send(broadcastMAC, dis, 3);
}
}
buttonPressed = false;
}
delay(10);
}964.7 Step-by-Step Instructions
964.7.1 Step 1: Run the Border Router (DODAG Root)
- Open the Wokwi simulator above
- Copy the complete code into the editor
- Ensure
NODE_TYPEis set to0(Border Router mode) - Click the green Play button
- Watch the Serial Monitor for “Node Type: Border Router (DODAG Root)”
- Observe periodic DIO broadcasts every 10 seconds
964.7.2 Step 2: Run Router Node 1
- Open a new browser tab with Wokwi: wokwi.com/projects/new/esp32
- Copy the same code
- Change
#define NODE_TYPE 1at the top - Run the simulation
- Observe “Node Type: Router Node 1” and DIO reception
- Watch rank change from infinite (65535) to 256
964.7.3 Step 3: Run the Leaf Node (Sensor)
- Open another tab with
NODE_TYPE 3 - Watch the leaf node join the DODAG
- Observe sensor data transmission with IPHC compression
- Note the compression statistics in the output
964.7.4 Step 4: Observe 6LoWPAN Operations
Watch the Serial Monitor on each device to see:
- IPHC compression showing field-by-field encoding decisions
- Header size reduction from 48 bytes (IPv6+UDP) to 4-6 bytes
- RPL DIO/DAO messages building the routing topology
- Rank calculation as nodes join the DODAG
- Fragmentation when sending large payloads (long-press BOOT on leaf node)
964.8 Experiments
964.8.1 Experiment 1: IPHC Compression Modes
Observe how different addressing scenarios affect compression:
When you run the simulation, watch for output like this:
[IPHC] Compressing IPv6 header:
Original size: 40 bytes
TF: Traffic Class and Flow Label elided (TF=11)
NH: UDP header will use NHC compression (NH=1)
HLIM: Hop Limit = 64 (compressed, HLIM=10)
SAM: Source fully elided from link-layer (SAM=11)
DAM: Destination fully elided from link-layer (DAM=11)
Compressed size: 2 bytes (95.0% reduction)
This shows: - TF=11: Traffic class and flow label completely removed (6 bytes saved) - NH=1: Next header compressed using NHC (saves 1 byte) - HLIM=10: Hop limit encoded in 2 bits instead of 8 (common value 64) - SAM=11: Source address derived from link-layer (16 bytes saved) - DAM=11: Destination address derived from link-layer (16 bytes saved)
Result: 40-byte IPv6 header compressed to just 2 bytes.
964.8.2 Experiment 2: Fragmentation
Trigger fragmentation by long-pressing the BOOT button on the leaf node:
Expected Output:
============ FRAGMENTATION ============
[FRAG] Original packet size: 207 bytes
[FRAG] Available MTU: 102 bytes
[FRAG] Fragmenting into 3 fragments:
First fragment: 98 bytes payload + 4 bytes header
Subsequent fragments: up to 97 bytes payload + 5 bytes header
[FRAG1] Sending fragment 1/3:
Dispatch: 0xC0 (FRAG1)
Datagram size: 207 bytes
Tag: 0x1234
Payload: 98 bytes
[FRAGN] Sending fragment 2/3:
Dispatch: 0xE0 (FRAGN)
Offset: 12 (bytes: 98)
Payload: 97 bytes
[FRAGN] Sending fragment 3/3:
Offset: 24 (bytes: 195)
Payload: 12 bytes
[FRAG] Complete! Sent 3 fragments, total overhead: 14 bytes
964.9 Challenge Exercises
964.10 Key Concepts Explained
Why Compression Works: Most IPv6 headers in a 6LoWPAN network contain predictable values that can be derived from context:
| IPv6 Field | Size | Compression Strategy |
|---|---|---|
| Version | 4 bits | Always 6 - elide |
| Traffic Class | 8 bits | Usually 0 - elide |
| Flow Label | 20 bits | Usually 0 - elide |
| Payload Length | 16 bits | Derive from frame size |
| Next Header | 8 bits | Use NHC encoding |
| Hop Limit | 8 bits | Common values (1, 64, 255) use 2 bits |
| Source Address | 128 bits | Derive from link-layer or context |
| Dest Address | 128 bits | Derive from link-layer or context |
Compression Modes: - SAM/DAM = 00: Full 128-bit address (16 bytes) - SAM/DAM = 01: 64-bit IID, prefix from context (8 bytes) - SAM/DAM = 10: 16-bit short address (2 bytes) - SAM/DAM = 11: Fully elided, derive from L2 (0 bytes)
Best Case: Link-local addresses with derivable IIDs compress from 40 bytes to 2 bytes (95% reduction).
Why Fragmentation Is Needed: IPv6 requires 1280-byte MTU support, but IEEE 802.15.4 only provides 127 bytes (102 usable after MAC headers).
Fragmentation Process: 1. Calculate number of fragments: ceil((packet_size - 98) / 97) + 1 2. Generate unique 16-bit tag for reassembly 3. Send FRAG1 with first 98 bytes (4-byte header) 4. Send FRAGN for remaining chunks (5-byte header each) 5. Offset field specifies position in multiples of 8 bytes
Reassembly Challenges: - Memory: Need buffer for full 1280-byte datagram - Timeout: 60 seconds per RFC 4944 (vs. 30-120s for IP fragmentation) - Out-of-order: Fragments may arrive in any sequence - Duplication: Same fragment may arrive multiple times
Performance Impact: - Lost fragment = retransmit entire datagram - 5% per-fragment loss -> 54% datagram loss (12 fragments) - Best practice: Keep payloads under 102 bytes when possible
DODAG: Destination-Oriented Directed Acyclic Graph
Construction Process: 1. Root broadcasts DIO: Border router advertises Rank=0 2. Nodes calculate rank: new_rank = parent_rank + hop_rank_increase 3. Best parent selection: Choose neighbor offering lowest rank 4. DAO propagation: Nodes advertise reachability upward 5. Route installation: Root builds downward routing table from DAOs
Rank Properties: - Strictly increases away from root (prevents loops) - Determines parent preference - Default increment: 256 per hop - Can incorporate link metrics (ETX, latency)
Trickle Timer: - DIO interval doubles when network is stable - Resets to minimum when inconsistency detected - Reduces control overhead in steady state
What a 6LoWPAN Border Router Does:
- DODAG Root: Advertises RPL instance, manages topology
- Prefix Delegation: Assigns /64 prefix to 6LoWPAN network
- Context Distribution: Shares compression contexts with nodes
- ND Proxy: Handles neighbor discovery for external hosts
- Packet Translation: Converts between compressed and native IPv6
- Default Route: All traffic to external networks flows through BR
Border Router Challenges: - Single point of failure (unless redundant) - Limited throughput (one 802.15.4 radio) - Memory for routing tables (thousands of nodes) - Stateless vs. stateful address assignment
Real Implementations: - Linux with WPAN tools + RPL daemon - Contiki-NG border router - Silicon Labs border router SDK - Nordic nRF SDK border router
964.11 Lab Summary
| Exercise | 6LoWPAN Concept | What You Learned |
|---|---|---|
| Basic setup | Network architecture | DODAG structure, node roles |
| IPHC output | Header compression | Field-by-field encoding decisions |
| Address modes | SAM/DAM fields | Link-local vs global compression |
| Large payload | Fragmentation | FRAG1/FRAGN headers, reassembly |
| RPL observation | Routing protocol | DIO/DAO messages, rank calculation |
| Multi-node | Mesh topology | Parent selection, route installation |
| Challenge 1 | Stateful compression | Context-based address encoding |
| Challenge 2 | Mesh addressing | Mesh-under forwarding |
| Challenge 3 | ETX metrics | Link quality routing |
| Challenge 4 | Extension headers | NHC chaining |
964.12 Expected Learning Outcomes
After completing this lab, you should be able to:
- Explain IPHC compression and predict header sizes for different addressing scenarios
- Calculate fragmentation overhead for various payload sizes
- Describe RPL DODAG construction including DIO/DAO message flows
- Identify when 6LoWPAN is appropriate compared to Zigbee, Thread, or BLE
- Design a 6LoWPAN network including border router placement and addressing plan
- Debug compression issues by interpreting IPHC dispatch bytes
- Optimize for constrained devices by minimizing fragmentation and control overhead
After completing this simulation lab, you are ready to work with real 6LoWPAN hardware:
- Hardware: Get an IEEE 802.15.4 radio module:
- nRF52840 (Nordic) - Thread/6LoWPAN capable
- CC2538/CC2652 (TI) - Contiki-NG compatible
- ATSAMR21 (Microchip) - Lightweight 6LoWPAN
- Software Stacks:
- Contiki-NG: Full 6LoWPAN/RPL implementation (research-grade)
- RIOT OS: Modular IoT OS with 6LoWPAN support
- Zephyr: Linux Foundation IoT OS with Thread/6LoWPAN
- OpenThread: Google’s Thread implementation
- Border Router Options:
- Raspberry Pi + wpantund (OpenThread)
- Silicon Labs EFR32 border router
- Linux with lowpan-tools
- Testing Tools:
- Wireshark with 6LoWPAN dissector
- tcpdump with IEEE 802.15.4 support
- Cooja simulator (Contiki-NG)
The packet structures and compression concepts from this lab translate directly to real deployments—the primary difference is the IEEE 802.15.4 physical layer and more sophisticated RPL implementations.
964.13 What’s Next
Continue to:
- 6LoWPAN Hands-on and Security: Security considerations and implementation
- 6LoWPAN Comprehensive Review: Complete protocol reference