NetBurner 3.5.6
PDF Version
IPv4/IPv6 Dual Stack Guide

Overview

NetBurner release 2.8.0 introduced a fundamental change to IP address handling. The IPADDR type evolved from a simple 32-bit integer to a C++ object capable of representing both IPv4 and IPv6 addresses. This guide covers the migration path and best practices for dual-stack networking.

Key Changes in 2.8.0+

Before (IPv4 only):**

IPADDR = 32-bit unsigned integer
Used to hold and manipulate IPv4 and IPv6 addresses in dual stack mode.
Definition ipv6_addr.h:41

After (Dual Stack):**

IPADDR = C++ object (32-bit for IPv4, 128-bit for IPv6)

IPv6 Address Assignment

Unlike IPv4's typical single-address-per-interface model, IPv6 interfaces inherently support multiple addresses simultaneously. Understanding how these addresses are assigned is crucial for dual-stack development.

Address Assignment Flow

Network Interface Connected
|
v
┌─────────────────────────────────────┐
│ Link-Local Address (fe80::/64) │
│ Derived from MAC address │
│ Always present, non-routable │
└─────────────────┬───────────────────┘
|
v
┌─────────────────────────────────────┐
│ Router Advertisement Received? │
└─────────────────┬───────────────────┘
|
┌───────────────┴───────────────┐
v v
┌─────────┐ ┌─────────┐
│ YES │ │ NO │
└────┬────┘ └────┬────┘
| |
v v
┌─────────────────────┐ ┌─────────────────────┐
│ Auto-Configuration │ │ DHCPv6 or Static │
│ (SLAAC) │ │ Configuration Only │
└──────────┬──────────┘ └─────────────────────┘
|
v
┌──────────────────────────────┐
│ Prefix Address Generated │
│ (Router Prefix + MAC-based) │
└──────────────────────────────┘

Link-Local Addresses

Every IPv6 interface automatically generates a link-local address (fe80::/64 prefix) derived from its MAC address.

Address Generation:**

MAC Address: 00:03:f4:01:23:47
|
v
Link-Local: fe80::203:f4ff:fe01:2347

Characteristics:**

  • Non-routable (local network segment only)
  • Used for network negotiation and neighbor discovery
  • Always present on IPv6-enabled interfaces
  • Deterministic (same for a given MAC address)

Router Auto-Configuration (SLAAC)

Routers advertise network prefixes that devices use to auto-configure global IPv6 addresses.

Address Construction:**

┌──────────────────────────────────────────────────────────┐
│ IPv6 Address (128 bits) │
├──────────────────────────────┬───────────────────────────┤
│ Router Prefix (64 bits) │ Host Portion (64 bits) │
│ From Router Advertisement│ From MAC (EUI-64) │
└──────────────────────────────┴───────────────────────────┘
Example:
Router Prefix: 2001:db8:1:2::
MAC-based Suffix: 203:f4ff:fe01:2347
|
v
Full Address: 2001:db8:1:2:203:f4ff:fe01:2347

Multiple Router Scenario:**

Device Interface
|
|-- Router A Prefix > Address A
|-- Router B Prefix > Address B
|-- Router C Prefix > Address C
+-- Link-Local > fe80::...

DHCPv6 Configuration

DHCPv6 servers provide address assignment and network configuration, similar to DHCPv4 but with enhanced capabilities.

DHCPv6 Types:**

DHCPv6 Server
|
┌───────────────┴───────────────┐
v v
┌─────────────────────┐ ┌─────────────────────┐
│ Information-Only │ │ Stateful DHCP
├─────────────────────┤ ├─────────────────────┤
│ - DNS servers │ │ - IP address │
│ - NTP servers │ │ - DNS servers │
│ - Domain search │ │ - NTP servers │
│ - Network params │ │ - Other params │
│ │ │ │
│ NO address assigned │ │ Address assigned │
└─────────────────────┘ └─────────────────────┘
DHCP Namespace.
Definition dhcpd.h:39

Automatic DHCPv6 Activation:**

  • Router advertisements can signal DHCPv6 server presence
  • NetBurner automatically initiates DHCPv6 client when signaled
  • Manual DHCPv6 client start available for networks without routers

Static IP Addresses

Manual address assignment remains available when automatic methods are insufficient or undesired.

Complete Address Assignment Hierarchy

IPv6 Interface
|
┌──────────────┼──────────────┐
v v v
┌──────────────┐ ┌───────────┐ ┌──────────────┐
│ Link-Local │ │ SLAAC │ │ DHCPv6 │
│ (Always) │ │(If Router)│ │(If Available)│
└──────────────┘ └───────────┘ └──────────────┘
|
v
┌─────────────────┐
│ Static Config │
│ (Optional) │
└─────────────────┘

IPADDR Class Migration

Storage Considerations

Critical Change:** When compiled for IPv6, IPADDR grows from 32 bits to 128 bits.

IPv4 Mode: IPv6 Mode:
┌──────────┐ ┌──────────────────────────┐
│ 32 bits │ │ 128 bits │
└──────────┘ └──────────────────────────┘

Impact on Persistent Storage:**

Existing applications storing IPADDR in:

  • UserParams structures
  • File systems
  • Flash memory
  • Configuration files

Will encounter deserialization issues when upgrading to IPv6-enabled firmware.

Migration Strategy - Storage Key Pattern:**

struct Config {
uint32_t storageKey; // Version identifier
IPADDR serverAddress;
// ... other fields
};
const uint32_t CURRENT_KEY = 2; // Increment on structure changes
void LoadConfig(Config& cfg) {
// Read from persistent storage
ReadFromFlash(&cfg, sizeof(cfg));
if (cfg.storageKey != CURRENT_KEY) {
// Key mismatch - initialize with defaults
InitializeDefaults(cfg);
cfg.storageKey = CURRENT_KEY;
SaveConfig(cfg);
}
}

Null Value Checking

Old IPv4 Pattern (No Longer Valid):**

if (EthernetIP == 0) {
// Configure DHCP
}

New Dual-Stack Pattern:**

if (EthernetIP.IsNull()) {
// Configure DHCP
}

Null IP in Function Parameters

Old IPv4 Pattern:**

DNSResult = GetHostByName(serverName, &serverIp, 0, TICKS_PER_SECOND * 20);
int GetHostByName(const char *name, IPADDR *pIpaddr, const IPADDR &dns_server, const TickTimeout tout, uint16_t TYPE1=DNS_A, uint16_t TYPE2=extra_dns_t, uint32_t *ttl=NULL)
Get the IP address associated with the specified domain name.
Definition dns.h:184
#define TICKS_PER_SECOND
System clock ticks per second.
Definition constants.h:49

New Dual-Stack Patterns:**

// Option 1: Using NullIP() static method
DNSResult = GetHostByName(serverName, &serverIp, IPADDR::NullIP(), TICKS_PER_SECOND * 20);
// Option 2: Using INADDR_ANY constant
DNSResult = GetHostByName(serverName, &serverIp, INADDR_ANY, TICKS_PER_SECOND * 20);
static IPADDR6 NullIP()
Static function to return a null IPADDR6 object.

Note: INADDR_ANY is defined as IPADDR::NullIP() in system headers.

IPADDR Member Functions

The IPADDR class uses object-oriented syntax with member functions accessed via dot notation.

Function Types

IPADDR Object
|
+-- Member Functions (instance.function())
| |
| +-- Setting values
| +-- Checking values
| +-- Output functions
|
+-- Static Functions (IPADDR::function())
|
+-- Factory methods
+-- Utility functions

Setting IP Address Values

Member Functions (Instance Methods):**

IPADDR myIpObject;
// Set to null
myIpObject.SetNull();
// Set from string
myIpObject.SetFromAscii("192.168.1.100");
myIpObject.SetFromAscii("2001:db8::1");
// Set from IPv4 address
IPADDR4 ipv4Addr = 0xC0A80164; // 192.168.1.100
myIpObject.SetFromIP4(ipv4Addr);
Used to store and manipulate IPv4 addresses in dual stack mode.
Definition nettypes.h:225
void SetFromAscii(const char *cp, bool bembed_v4addresses=true)
Set the IP address value of an IPADDR6 object.
void SetFromIP4(IPADDR4 ip)
Set the IP address value of an IPADDR6 object from an IPADD4 object.
void SetNull()
Set the IP address value of an IPADDR6 object to null.
Definition ipv6_addr.h:320

Static Functions (Class Methods):**

// Create null IP address
IPADDR nullAddr = IPADDR::NullIP();
// Create from ASCII string
IPADDR serverAddr = IPADDR::AsciiToIp("192.168.1.1");
IPADDR ipv6Addr = IPADDR::AsciiToIp("2001:db8::8a2e:370:7334");

Reading and Checking IP Address Values

Boolean Checks:**

IPADDR addr;
if (addr.IsNull()) { /* Address is null */ }
if (addr.IsLoopBack()) { /* Is loopback (127.0.0.1 or ::1) */ }
if (addr.IsMultiCast()) { /* Is multicast address */ }
if (addr.IsLinkLocal()) { /* Is IPv6 link-local (fe80::/10) */ }
if (addr.IsEmbeddedIPV4()) { /* Is IPv4-mapped IPv6 (::FFFF:x.x.x.x) */ }
bool IsLinkLocal() const
Check if the IP address is the link-local address for the interface.
Definition ipv6_addr.h:175
bool IsEmbeddedIPV4() const
An IPADDR6 object can store a IPv4 or IPv6 address. This function returns true if the instance contai...
Definition ipv6_addr.h:66
bool IsMultiCast() const
Check if the IPADDR6 object contains a Multicast IP address the interface.
Definition ipv6_addr.h:164
bool IsNull() const
Check if the IP address is null.
Definition ipv6_addr.h:133
bool IsLoopBack() const
Check if the IP address is the loopback address for the interface.
Definition ipv6_addr.h:151

Address Type Detection:**

Input Address
|
v
┌─────────────┐
│ IsNull()? │<────────────────────┐
└─────┬───────┘ │
| │
NO v │ YES
┌──────────────────┐ │
│ IsLinkLocal()? │ │ Null Address
└─────┬────────────┘ │
| │
NO v │
┌──────────────────┐ │
│ IsEmbeddedIPV4()?│ │
└─────┬────────────┘ │
| │
NO v │
┌──────────────────┐ │
│ IsMultiCast()? │ │
└─────┬────────────┘ │
| │
v │
Full Global │
IPv6 Address │

Multicast MAC Conversion:**

IPADDR mcastAddr = IPADDR::AsciiToIp("ff02::1");
if (mcastAddr.IsMultiCast()) {
MACADR mac = mcastAddr.McastMac();
// Use MAC address for multicast operations
}
MACADR McastMac() const
Return the MAC address used for Multicasts for the interface.
Used to store and manipulate MAC addresses.
Definition nettypes.h:69

Output Functions

Console Output:**

IPADDR addr = IPADDR::AsciiToIp("192.168.1.100");
// Print to stdout (debug serial port)
addr.printf();
// Output: 192.168.1.100

File Descriptor Output:**

int tcpSocket = /* ... established TCP connection ... */;
// Print to TCP socket
serverAddr.fdprintf(tcpSocket);

String Buffer Output:**

char buffer[128];
int written = addr.sprintf(buffer, sizeof(buffer));
printf("Address: %s (%d chars)\n", buffer, written);
int sprintf(char *cp, int maxl, bool bCompact=true, bool bShowV4Raw=false) const
Print the IP address to the specified buffer.

Output Method Comparison:**

┌───────────────┐
IPADDR Object │
└──────┬────────┘
|
+-> printf() > stdout (serial port)
|
+-> fdprintf(fd) > file descriptor (socket, file)
|
+-> sprintf(buf, max) > string buffer
int fdprintf(int fd, const char *format,...)
Print formatted output to a file descriptor.

Domain Name Service (DNS)

DNS function signatures vary between IPv4 and IPv6 modes, but can be written to work identically in both.

DNS Function Variants

DNS Functions
|
┌──────────────────┼─────────────────┐
v v v
┌────────────────┐ ┌──────────────┐ ┌─────────────┐
│GetHostByName4()│ │GetHostByName6│ │GetHostByName│
│ │ │ │ │ │
│ IPv4 only │ │ IPv6 enabled │ │ Context- │
│ Always avail. │ │ Requires IPv6│ │ dependent │
└────────────────┘ └──────────────┘ └─────────────┘
|
v
┌─────────────────────────────┐
│ IPv4 mode: GetHostByName4() │
│ IPv6 mode: GetHostByName6() │
└─────────────────────────────┘

Universal DNS Code Pattern

Works in both IPv4 and IPv6 modes:**

IPADDR resultIP;
int result = GetHostByName(
"thename.com", // Hostname to resolve
&resultIP, // Result storage
IPADDR::NullIP(), // Use system DNS server
10 * TICKS_PER_SECOND // Timeout
);
if (result == DNS_OK) {
printf("Resolved to: ");
resultIP.printf();
printf("\n");
}
#define DNS_OK
Success.
Definition dns.h:38

DNS Record Types

DNS Query
|
v
┌───────────────────────────────────────┐
│ Record Type Selection │
├───────────────────────────────────────┤
DNS_A - IPv4 address (A record) │
DNS_AAAA - IPv6 address (AAAA) │
DNS_CNAME - Canonical name │
DNS_MX - Mail exchange │
DNS_MB - Mailbox resource │
DNS_MG - Mail group resource │
└───────────────────────────────────────┘
#define DNS_MX
Mail exchange record.
Definition dns.h:53
#define DNS_AAAA
128-bit IPv6 address
Definition dns.h:54
#define DNS_A
32-bit IPv4 address
Definition dns.h:49
#define DNS_CNAME
Canonical name record.
Definition dns.h:50
#define DNS_MG
Mailing list subscriber list.
Definition dns.h:52
#define DNS_MB
Mailing list subscriber list.
Definition dns.h:51

IPv6 DNS Priority Control

GetHostByName6() supports dual-stack resolution with configurable priority:

int GetHostByName6(
const char *name, // Name to resolve
IPADDR *pResultIP, // Result storage
const IPADDR &dnsServerIP, // DNS server (or NullIP)
WORD timeout, // Timeout in ticks
WORD TYPE1 = DNS_A, // Primary type (default: IPv4)
WORD TYPE2 = DNS_AAAA // Secondary type (default: IPv6)
);

Resolution Flow:**

DNS Query: "example.com"
|
v
Try TYPE1
|
┌────┴────┐
v v
Success Failure
| |
| v
| Try TYPE2
| |
| ┌────┴────┐
| v v
| Success Failure
| | |
+────+ v
| Return Error
v
Return Result

Default Behavior (IPv4 first):**

// Tries IPv4 (DNS_A), falls back to IPv6 (DNS_AAAA)
GetHostByName("example.com", &result, IPADDR::NullIP(), 10 * TICKS_PER_SECOND);

IPv6-First Resolution:**

// Tries IPv6 first, falls back to IPv4
"example.com",
&result,
DNS_AAAA, // Try IPv6 first
DNS_A // Fall back to IPv4
);

IPv6-Only Resolution:**

// Only accepts IPv6 results
"example.com",
&result,
DNS_AAAA, // IPv6 only
DNS_AAAA // No fallback
);

DNS Resolution Strategy Matrix

┌──────────────┬────────────┬─────────────┬─────────────┐
│ Scenario │ TYPE1 │ TYPE2 │ Result │
├──────────────┼────────────┼─────────────┼─────────────┤
│ Default │ DNS_ADNS_AAAA │ IPv4 first │
│ IPv6 prefer │ DNS_AAAADNS_A │ IPv6 first │
│ IPv4 only │ DNS_ADNS_A │ IPv4 only │
│ IPv6 only │ DNS_AAAADNS_AAAA │ IPv6 only │
└──────────────┴────────────┴─────────────┴─────────────┘

Platform-Specific Considerations

macOS and BSD Systems

BSD-derived network stacks (including macOS) have specific requirements for IPv6 link-local address access.

Browser Navigation Limitation:**

Link-Local Address: fe80::203:f4ff:fe01:2347
|
v
┌────────────────────────────────────┐
│ Cannot access directly in browser │
│ BSD/macOS requirement: Scope ID │
└────────────────────────────────────┘
|
v
Required Format: fe80::203:f4ff:fe01:2347%en0
^^^
Scope ID

Scope ID Format:**

fe80::203:f4ff:fe01:2347%en0 // Ethernet interface
fe80::203:f4ff:fe01:2347%en1 // WiFi interface
fe80::203:f4ff:fe01:2347%4 // Interface index

URL Format in Browsers:**

http://[fe80::203:f4ff:fe01:2347%en0]/
^^ ^^^
| |
IPv6 brackets Scope ID

Common Interface Names:**

  • en0 - Primary Ethernet
  • en1 - WiFi
  • lo0 - Loopback
  • Use ifconfig to list available interfaces

Migration Checklist

Code Audit

┌─────────────────────────────────────────┐
│ Audit Existing IPv4 Code │
└─────────────────────┬───────────────────┘
|
┌─────────────┴─────────────┐
v v
┌──────────────────┐ ┌──────────────────┐
│ Search for: │ │ Search for: │
│ - if (ip == 0) │ │ - Function param │
│ - IPADDR storage │ │ with 0 for IP │
│ - Null checks │ │ - Hardcoded IPs │
└──────────────────┘ └──────────────────┘
| |
v v
┌──────────────────┐ ┌──────────────────┐
│ Replace with: │ │ Replace with: │
│ - IsNull() │ │ - NullIP() │
│ - Key versioning │ │ - INADDR_ANY │
└──────────────────┘ └──────────────────┘

Storage Migration

Step 1: Add Version Key
┌──────────────────────┐
│ uint32_t version; │
IPADDR addresses... │
└──────────────────────┘
|
v
Step 2: Check on Load
┌──────────────────────┐
if (version != cur) │
│ Initialize() │
└──────────────────────┘
|
v
Step 3: Save with Key
┌──────────────────────┐
│ config.version = cur │
│ SaveToFlash(config) │
└──────────────────────┘

Testing Strategy

Dual-Stack Testing Matrix:**

Test Environments
|
┌────────────────┼────────────────┐
v v v
┌─────────┐ ┌──────────┐ ┌──────────┐
│IPv4 Only│ │IPv6 Only │ │Dual Stack│
└─────────┘ └──────────┘ └──────────┘
| | |
v v v
Verify: Verify: Verify:
- Legacy ops - New features - Both modes
- Null checks - Link-local - Priority
- DNS IPv4 - DNS IPv6 - Fallback

Best Practices

Portable Code Patterns

Use GetHostByName() without version suffix:**

// Portable across IPv4/IPv6
GetHostByName(hostname, &result, IPADDR::NullIP(), timeout);

Always use IsNull() for comparisons:**

// Portable null checking
if (addr.IsNull()) {
// Configure or error
}

Use IPADDR::NullIP() for null parameters:**

// Portable null parameter
Connect(host, port, IPADDR::NullIP());

Storage Design

Version-aware configuration structures:**

struct NetworkConfig {
uint32_t configVersion; // First field
IPADDR staticIP;
IPADDR gateway;
IPADDR dnsServer;
// ... other fields
};

Always validate on load:**

void LoadConfig(NetworkConfig& cfg) {
ReadFromStorage(&cfg, sizeof(cfg));
if (cfg.configVersion != CURRENT_CONFIG_VERSION) {
InitDefaults(cfg);
SaveConfig(cfg);
}
}

DNS Resolution

Prefer IPv6 when available:**

// IPv6-first resolution with IPv4 fallback
hostname,
&result,
timeout,
DNS_AAAA, // Try IPv6 first
DNS_A // IPv4 fallback
);

Specify timeout appropriately:**

// Allow time for both TYPE1 and TYPE2 queries
WORD timeout = 20 * TICKS_PER_SECOND; // Not 10 seconds

Summary

Key Takeaways

┌────────────────────────────────────────────────────┐
IPADDR is now an object, not a 32-bit integer │
├────────────────────────────────────────────────────┤
│ Use IsNull() instead of == 0 │
├────────────────────────────────────────────────────┤
│ Use NullIP() instead of 0 in parameters │
├────────────────────────────────────────────────────┤
│ IPv6 interfaces have multiple addresses │
├────────────────────────────────────────────────────┤
│ Storage size changes: 32-bit > 128-bit │
├────────────────────────────────────────────────────┤
│ Use version keys for persistent storage │
├────────────────────────────────────────────────────┤
│ DNS supports dual-stack with priority control │
├────────────────────────────────────────────────────┤
│ Link-local addresses need scope ID on BSD/macOS │
└────────────────────────────────────────────────────┘

Migration Path

Current IPv4 Code
|
v
Audit for patterns
|
v
Update null checks
|
v
Add storage versioning
|
v
Test in IPv4 mode
|
v
Test in IPv6 mode
|
v
Test dual-stack
|
v
Deploy gradually

For detailed API documentation, consult the IPADDR6 class reference in the NetBurner API documentation.