← Back to overview

Non-routed GUA behind NPTv6

The idea in one line: number your internal network out of global-unicast (GUA) space that is never routed, and translate it to whatever prefix your ISP currently gives you at the boundary, with NPTv6 — stateless 1:1 prefix translation (RFC 6296). Internally, your addressing is stable and first-class. Externally, only the ISP’s current prefix ever appears. The rotation is absorbed at the edge.

Why GUA and not ULA

The one property you actually need internally is first-class address selection. RFC 6724’s default policy table ranks ULA (fc00::/7) below IPv4, so a dual-stack host prefers IPv4 when the alternative is ULA. GUA-shaped space sits at the top of the table, so hosts prefer IPv6 with no per-host policy override. Because NPTv6 hides the internal prefix behind the ISP’s, the internal GUA never needs to be globally routable — it only needs to be GUA-shaped. That is exactly what a non-routed GUA block gives you.

The two planes

Making this safe means getting two independent things right:

The blackhole protects you; the AS0 ROA protects everyone else from an accidental leak out of your edge. You want both.

Carve the block

Give each site or purpose a sub-prefix out of the /48. Two non-overlapping /60s, for example, are plenty for most single-site deployments and leave room to grow:

# edit-required values as variables (adjust to taste)
DARK48=2602:f925:ad::/48
SITE_A=${DARK48%::/48}::/60          # e.g. 2602:f925:ad::/60
SITE_B=${DARK48%::/48}:bb00::/60     # e.g. 2602:f925:ad:bb00::/60

NPTv6 at the boundary (VyOS example)

Translate the internal /60 to the low /60 of your current ISP PD on the way out, and the reverse on the way in. NPTv6 is stateless and checksum-neutral — the host bits pass through unchanged, only the prefix is rewritten:

# edit-required values as variables
set INTERNAL   = 2602:f925:ad::/48          # your dark /48 (or a /60 out of it)
set ISP_PD60   = 2001:db8:abcd:e000::/60      # the /60 you carve from the ISP's PD
set WAN_IF     = eth0                          # ISP-facing interface

# source (egress): internal -> ISP
set nat66 source rule 100 outbound-interface name '$WAN_IF'
set nat66 source rule 100 source prefix '$INTERNAL'
set nat66 source rule 100 translation address '$ISP_PD60'

# destination (ingress): ISP -> internal
set nat66 destination rule 100 inbound-interface name '$WAN_IF'
set nat66 destination rule 100 destination address '$ISP_PD60'
set nat66 destination rule 100 translation address '$INTERNAL'

If your ISP rotates the PD, a short reconciler that reads the current delegation and rewrites the two translation addresses in place keeps egress live across rotations — edit the rules, don’t delete-and-recreate them, so active sessions aren’t dropped.

Blackhole the dark block (data plane)

# pin it hard so nothing accidentally overrides it
set protocols static route6 '2602:f925:ad::/48' blackhole distance '1'

Publish the AS0 ROA (control plane)

In your RIR’s hosted RPKI, create a Route Origin Authorization with origin AS0 for the dark block. Because AS0 means “no AS may originate this,” any real-AS announcement of the prefix becomes RPKI-invalid. Set the ROA’s max length wide (e.g. /128) — for a block that is never routed at any granularity, a wide max length simply extends the “do-not-route” assertion to cover every possible more-specific leak, with no downside.

Origin AS:   AS0
Prefix:      2602:f925:ad::/48Max length:  128        # wide = covers every sub-prefix leak; dark space has no downside
Auto-renew:  yes        # so it never silently expires

Note: AS0 is an RPKI/ROA concept. IRR route objects reject AS0 as an origin, so the do-not-route assertion lives in RPKI, not in an IRR route6 object.

If you are using the shared block

If you don’t have your own allocation, you can number out of the published example block, 2602:f925:ad::/48, which is already held dark with an AS0 ROA and origin blackhole by its maintainer. In that case you skip the “publish the ROA” and “blackhole” steps — they’re already done — and you only need the carve + NPTv6 steps on your own edge. Read the block’s benefits and its limits on The Block before relying on it.

The honest edge

The AS0 ROA makes the block invalid on any RPKI-validating path, and the global return table plus the maintainer’s origin blackhole cover essentially everything else. The one residual gap is traffic that both originates and terminates inside a single network that doesn’t validate RPKI — and that gap exists for all address space, not just this technique. Your NPTv6 boundary is the backstop: never route the dark block; always translate at the edge. Treat the AS0 ROA as a belt over those suspenders, not a replacement for them.