Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Real-World IPv6 Deployments

How IPv6 works in practice and how to simulate each scenario in patchbay.


How ISPs Actually Deploy IPv6

Residential (FTTH, Cable, DSL)

ISPs assign a globally routable prefix (typically /56 or /60) via DHCPv6-PD (Prefix Delegation). The CE (Customer Edge) router carves /64s from this prefix for each LAN segment. Devices get public GUA addresses — no NAT involved. The security boundary is a stateful firewall on the CE router that blocks unsolicited inbound connections (RFC 6092).

IPv4 access is provided in parallel via dual-stack (separate IPv4 address with NAT44) or via DS-Lite / MAP-E / MAP-T (IPv4-in-IPv6 tunneling to the ISP’s AFTR).

Key properties:

  • Devices have globally routable IPv6 addresses
  • No IPv6 NAT — the prefix is public
  • Stateful firewall blocks inbound, allows outbound + established
  • SLAAC for address assignment (not DHCPv6 address assignment)
  • Privacy extensions (RFC 4941) rotate source addresses

Carriers: Deutsche Telekom, Comcast, AT&T, Orange, BT, NTT.

Mobile (4G/5G)

Each device typically gets a single /64 via RA (Router Advertisement). The device is the only host on its /64. There is no home router — the carrier’s gateway acts as the first hop.

For IPv4 access, carriers use either:

  • 464XLAT (RFC 6877): CLAT on device + NAT64 on carrier gateway
  • NAT64 + DNS64: carrier synthesizes AAAA records from A records

Some carriers (T-Mobile US, Jio) are IPv6-only with NAT64. Others (Verizon, NTT Docomo) do dual-stack.

Key properties:

  • One /64 per device (not shared)
  • NAT64/DNS64 for IPv4 access (no real IPv4 address)
  • No firewall — carrier relies on per-device /64 isolation
  • 3GPP CGNAT for remaining IPv4 users

Enterprise / Corporate

Enterprises typically run dual-stack internally with PA (Provider Aggregatable) or PI (Provider Independent) space. Strict firewalls allow only TCP 80/443 and UDP 53 outbound. All other ports are blocked — STUN/TURN on non-standard ports fails, must use TURN-over-TLS on 443.

Some enterprises use ULA (fd00::/8) internally with NAT66 at the border, though this is discouraged by RFC 4864 and IETF best practices.

Hotel / Airport / Guest WiFi

After captive portal authentication, these networks typically allow:

  • TCP 80, 443 (HTTP/HTTPS)
  • TCP/UDP 53 (DNS)
  • All other UDP blocked (kills QUIC, STUN, direct P2P)
  • TCP to other ports sometimes allowed (unlike corporate)

Many guest networks are still IPv4-only. Those with IPv6 usually provide GUA addresses with a restrictive firewall.


ULA + NAT66: Mostly a Myth

RFC 4193 ULA (fd00::/8) was designed for stable internal addressing, not as an IPv6 equivalent of RFC 1918. In practice:

  • No major ISP deploys NAT66 — it defeats the end-to-end principle
  • Android does not support NAT66 (no DHCPv6 client, only SLAAC)
  • ULA is used alongside GUA for stable internal addressing, never alone
  • RFC 6296 NPTv6 (prefix translation) exists but is niche — mostly for multihoming, not general NAT

If you need to simulate “NATted IPv6”, use NPTv6 (NatV6Mode::Nptv6) which does stateless 1:1 prefix translation. But understand this is rare in the real world.


Simulating Real-World Scenarios in Patchbay

Using Router Presets

[RouterPreset] configures NAT, firewall, IP support, and address pool in one call. Individual methods override preset values when called after preset().

#![allow(unused)]
fn main() {
// One-liner for each common case:
let home = lab.add_router("home").preset(RouterPreset::Home).build().await?;
let dc   = lab.add_router("dc").preset(RouterPreset::Datacenter).build().await?;
let corp = lab.add_router("corp").preset(RouterPreset::Corporate).build().await?;

// Override one knob:
let home = lab.add_router("home")
    .preset(RouterPreset::Home)
    .nat(Nat::FullCone)   // swap NAT type, keep everything else
    .build().await?;
}
PresetNATNAT v6FirewallIPPool
HomeHome (EIM+APDF)NoneBlockInboundDualStackPrivate
DatacenterNoneNoneNoneDualStackPublic
IspV4NoneNoneNoneV4OnlyPublic
MobileCgnatNoneBlockInboundDualStackPublic
MobileV6NoneNat64BlockInboundV6OnlyPublic
CorporateCorporate (sym)NoneCorporateDualStackPublic
HotelCorporate (sym)NoneCaptivePortalV4OnlyPrivate
CloudCloudNatNoneNoneDualStackPublic

Scenario 1: Residential Dual-Stack (Most Common)

A home router with NATted IPv4 and public IPv6. The CE router firewall blocks unsolicited inbound on both families.

#![allow(unused)]
fn main() {
let home = lab.add_router("home").preset(RouterPreset::Home).build().await?;
let laptop = lab.add_device("laptop").uplink(home.id()).build().await?;
// laptop.ip()  → 10.0.x.x (private IPv4, NATted)
// laptop.ip6() → fd10:0:x::2 (ULA v6, firewalled)
}

Scenario 2: IPv6-Only Mobile with NAT64

A carrier network where devices only have IPv6. IPv4 destinations are reached via NAT64 — a userspace SIIT translator on the router converts between IPv6 and IPv4 headers using the well-known prefix 64:ff9b::/96.

#![allow(unused)]
fn main() {
let carrier = lab.add_router("carrier")
    .preset(RouterPreset::MobileV6)
    .build().await?;
let phone = lab.add_device("phone").uplink(carrier.id()).build().await?;
// phone.ip6() → 2001:db8:1:x::2 (public GUA)
// phone.ip()  → None (no IPv4 on the device)

// Reach an IPv4 server via NAT64:
use patchbay::nat64::embed_v4_in_nat64;
let nat64_addr = embed_v4_in_nat64(server_v4_ip);
// Connect to [64:ff9b::<server_v4>]:port — translated to IPv4 by the router
}

The MobileV6 preset configures: IpSupport::V6Only + NatV6Mode::Nat64 + Firewall::BlockInbound + public GUA pool. You can also configure NAT64 manually on any router:

#![allow(unused)]
fn main() {
let carrier = lab.add_router("carrier")
    .ip_support(IpSupport::DualStack)  // or V6Only
    .nat_v6(NatV6Mode::Nat64)
    .build().await?;
}

Scenario 3: Corporate Firewall (Restrictive)

Enterprise network that blocks everything except web traffic. STUN/ICE fails — P2P apps must fall back to TURN-over-TLS on port 443.

#![allow(unused)]
fn main() {
let corp = lab.add_router("corp").preset(RouterPreset::Corporate).build().await?;
let workstation = lab.add_device("ws").uplink(corp.id()).build().await?;
}

Scenario 4: Hotel / Captive Portal

Guest WiFi that allows web traffic but blocks most UDP.

#![allow(unused)]
fn main() {
let hotel = lab.add_router("hotel").preset(RouterPreset::Hotel).build().await?;
let guest = lab.add_device("guest").uplink(hotel.id()).build().await?;
}

Scenario 5: Mobile Carrier (CGNAT + Dual-Stack)

Multiple subscribers sharing a single public IPv4 address. Common on mobile and some fixed-line ISPs.

#![allow(unused)]
fn main() {
let carrier = lab.add_router("carrier").preset(RouterPreset::Mobile).build().await?;
let phone = lab.add_device("phone").uplink(carrier.id()).build().await?;
}

Scenario 6: Peer-to-Peer Connectivity Test Matrix

Test how two peers connect across different network types:

#![allow(unused)]
fn main() {
// Home user: easy NAT, firewalled
let home = lab.add_router("home")
    .preset(RouterPreset::Home)
    .nat(Nat::FullCone)
    .build().await?;
let alice = lab.add_device("alice").uplink(home.id()).build().await?;

// Mobile user: CGNAT
let mobile = lab.add_router("mobile").preset(RouterPreset::Mobile).build().await?;
let bob = lab.add_device("bob").uplink(mobile.id()).build().await?;

// Corporate user: strict firewall
let corp = lab.add_router("corp").preset(RouterPreset::Corporate).build().await?;
let charlie = lab.add_device("charlie").uplink(corp.id()).build().await?;

// Test: can alice reach bob? bob reach charlie? etc.
}

IPv6 Feature Reference

FeatureAPINotes
Dual-stackIpSupport::DualStackBoth v4 and v6
IPv6-onlyIpSupport::V6OnlyNo v4 routes
IPv4-onlyIpSupport::V4OnlyNo v6 routes (default)
NPTv6NatV6Mode::Nptv6Stateless 1:1 prefix translation
NAT66 (masquerade)NatV6Mode::MasqueradeLike NAT44 but for v6
Block inboundFirewall::BlockInboundRFC 6092 CE router
Corporate FWFirewall::CorporateBlock inbound + TCP 80,443 + UDP 53
Captive portal FWFirewall::CaptivePortalBlock inbound + block non-web UDP
Custom FWFirewall::Custom(cfg)Full control via FirewallConfig
NAT64NatV6Mode::Nat64Userspace SIIT + nftables masquerade
DHCPv6-PDnot plannedUse static /64 allocation

Common Pitfalls

NPTv6 and NDP

NPTv6 dnat prefix to rules must include address match clauses (e.g., ip6 daddr <wan_prefix>) to avoid translating NDP packets. Without this, neighbor discovery breaks and the router becomes unreachable.

IPv6 Firewall Is Not Optional

On IPv4, NAT implicitly blocks inbound connections (no port mapping = no access). On IPv6 with public GUA addresses, there is no NAT — devices are directly addressable. Without Firewall::BlockInbound, any host on the IX can connect to your devices. This matches reality: every CE router ships with an IPv6 stateful firewall enabled by default.

When IX-level routers share a /64 IX prefix, their WAN addresses are on-link with each other. Routing prefixes carved from the IX range can cause “on-link” confusion where packets are sent directly (ARP/NDP) rather than via the gateway. Use distinct prefixes for IX (/64) and downstream pools (/48 from a different range).