-
Notifications
You must be signed in to change notification settings - Fork 461
IPv6 Unicast
IPv6 Unicast is the BGP address family for advertising IPv6 prefixes for unicast (one-to-one) traffic forwarding. As IPv6 adoption continues globally, ExaBGP provides full support for IPv6 unicast route announcements, withdrawals, and reception, enabling applications to programmatically control IPv6 routing using the same API patterns as IPv4.
- Overview
- When to Use IPv6 Unicast
- IPv6 vs IPv4 Differences
- Configuration
- API Examples
- BGP Attributes
- Use Cases
- IPv6 Next-Hop Considerations
- Common Errors and Solutions
- Best Practices
- Important Considerations
- See Also
- References
IPv6 Unicast is the BGP address family (AFI 2, SAFI 1) for distributing IPv6 routing information via MP-BGP (Multiprotocol BGP per RFC 4760).
Key Characteristics:
- Address Family Identifier (AFI): 2 (IPv6)
- Subsequent Address Family Identifier (SAFI): 1 (Unicast)
-
Prefix Format: IPv6 CIDR notation (e.g.,
2001:db8::/32) - Next-Hop: IPv6 address (or IPv4-mapped IPv6 in some cases)
What IPv6 Unicast Routes Carry:
- IPv6 prefix (network + prefix length)
- Next-hop IPv6 address
- BGP path attributes (AS-PATH, MED, LOCAL_PREF, COMMUNITY, etc.)
MP-BGP: Unlike IPv4 unicast (which uses traditional BGP UPDATE format), IPv6 uses MP-BGP extensions with MP_REACH_NLRI and MP_UNREACH_NLRI attributes.
Use IPv6 unicast for:
✅ IPv6 Internet Routing: Global IPv6 routing, ISP peering ✅ Dual-Stack Networks: Running IPv4 and IPv6 in parallel ✅ IPv6-Only Networks: Modern cloud and mobile networks ✅ Anycast IPv6 Services: DNS64, NAT64, content delivery ✅ High Availability: IPv6 service announcements with health checks ✅ Traffic Engineering: Controlling IPv6 traffic with BGP attributes ✅ IPv6 Migration: Transitioning from IPv4 to IPv6 ✅ IoT and Mobile: 5G networks, IoT device addressing
Not for:
- ❌ L2 VPN services (use EVPN)
- ❌ L3 VPN services (use VPNv6)
- ❌ Multicast routing (use IPv6 Multicast SAFI)
- ❌ Traffic filtering (use FlowSpec IPv6)
| Feature | IPv4 | IPv6 |
|---|---|---|
| Address Length | 32 bits | 128 bits |
| Notation | Dotted decimal (192.0.2.1) | Colon-hexadecimal (2001:db8::1) |
| Address Space | ~4.3 billion | 340 undecillion |
| Prefix Example | 192.0.2.0/24 | 2001:db8::/32 |
| Feature | IPv4 Unicast | IPv6 Unicast |
|---|---|---|
| AFI/SAFI | AFI 1, SAFI 1 | AFI 2, SAFI 1 |
| BGP Encoding | Traditional UPDATE | MP-BGP (MP_REACH_NLRI) |
| Next-Hop | IPv4 address | IPv6 address (link-local or global) |
| Address Family | Default in BGP | Must be explicitly enabled |
- No NAT Required: End-to-end addressability
- No Broadcast: Uses multicast for neighbor discovery
- Link-Local Addresses: Every interface has link-local (fe80::/10)
- Global Unicast Addresses: Public IPv6 addresses (2000::/3)
# /etc/exabgp/ipv6-unicast.conf
neighbor 2001:db8::1 {
router-id 192.0.2.2; # Router ID is still IPv4 format
local-address 2001:db8::2; # Local IPv6 address
local-as 65001;
peer-as 65000;
# Enable IPv6 unicast
family {
ipv6 unicast;
}
# API process for dynamic route announcements
api {
processes [ ipv6-route-injector ];
}
}
process ipv6-route-injector {
run python3 /etc/exabgp/announce-ipv6.py;
encoder text;
}# Run both IPv4 and IPv6 over the same BGP session
neighbor 192.0.2.1 {
router-id 192.0.2.2;
local-address 192.0.2.2; # IPv4 transport
local-as 65001;
peer-as 65000;
# Enable both IPv4 and IPv6 unicast
family {
ipv4 unicast;
ipv6 unicast;
}
api {
processes [ dual-stack-injector ];
}
}#!/usr/bin/env python3
# /etc/exabgp/announce-ipv6.py
import sys
import time
def announce_ipv6_route(prefix, nexthop="self", **attributes):
"""Announce IPv6 unicast route"""
cmd = f"announce route {prefix} next-hop {nexthop}"
# Add optional attributes
if 'local_preference' in attributes:
cmd += f" local-preference {attributes['local_preference']}"
if 'med' in attributes:
cmd += f" med {attributes['med']}"
if 'community' in attributes:
communities = ' '.join(attributes['community'])
cmd += f" community [ {communities} ]"
print(cmd)
sys.stdout.flush()
def withdraw_ipv6_route(prefix, nexthop="self"):
"""Withdraw IPv6 unicast route"""
print(f"withdraw route {prefix} next-hop {nexthop}")
sys.stdout.flush()
# Example: Announce IPv6 service prefix
announce_ipv6_route(
"2001:db8:100::/48",
nexthop="self",
local_preference=200,
community=["65001:100"]
)
# Keep process running
while True:
time.sleep(60)# Simplest form (using 'self' for next-hop)
print("announce route 2001:db8::/32 next-hop self")
sys.stdout.flush()
# Explicit IPv6 next-hop
print("announce route 2001:db8::/32 next-hop 2001:db8::1")
sys.stdout.flush()
# /128 host route (anycast service IP)
print("announce route 2001:db8:100::1/128 next-hop self")
sys.stdout.flush()
# Common IPv6 prefix allocations
print("announce route 2001:db8:1000::/36 next-hop self") # /36 allocation
print("announce route 2001:db8:2000::/48 next-hop self") # /48 site prefix
print("announce route 2001:db8:3000::/64 next-hop self") # /64 subnet
sys.stdout.flush()# With LOCAL_PREF and MED
print("announce route 2001:db8::/32 "
"next-hop self "
"local-preference 200 "
"med 50")
sys.stdout.flush()
# With communities
print("announce route 2001:db8::/32 "
"next-hop self "
"community [ 65001:100 65001:200 ]")
sys.stdout.flush()
# With AS-PATH prepending
print("announce route 2001:db8::/32 "
"next-hop self "
"as-path [ 65001 65001 65001 ]") # Prepend own AS 3 times
sys.stdout.flush()
# Complete example
print("announce route 2001:db8::/32 "
"next-hop self "
"local-preference 200 "
"med 50 "
"community [ 65001:100 ] "
"as-path [ 65001 ] "
"origin igp")
sys.stdout.flush()# With large communities (recommended for IPv6)
print("announce route 2001:db8::/32 "
"next-hop self "
"large-community [ 65001:100:200 ]")
sys.stdout.flush()
# With extended communities
print("announce route 2001:db8::/32 "
"next-hop self "
"extended-community [ origin:65001:100 ]")
sys.stdout.flush()# Withdraw IPv6 route (must match announcement exactly)
print("withdraw route 2001:db8::/32 next-hop self")
sys.stdout.flush()
# Withdraw with explicit next-hop
print("withdraw route 2001:db8::/32 next-hop 2001:db8::1")
sys.stdout.flush()Configure ExaBGP to receive IPv6 routes from peer:
neighbor 2001:db8::1 {
# ... basic config ...
api {
processes [ ipv6-route-receiver ];
receive {
parsed;
update;
}
}
}
process ipv6-route-receiver {
run python3 /etc/exabgp/receive-ipv6.py;
encoder json;
}IPv6 Receiver Script:
#!/usr/bin/env python3
import sys
import json
while True:
line = sys.stdin.readline()
if not line:
break
try:
msg = json.loads(line)
if msg.get('type') != 'update':
continue
update = msg.get('neighbor', {}).get('message', {}).get('update', {})
# Process announced IPv6 routes
if 'announce' in update:
if 'ipv6 unicast' in update['announce']:
routes = update['announce']['ipv6 unicast']
for next_hop, prefixes in routes.items():
for prefix, attributes in prefixes.items():
med = attributes.get('med', 'N/A')
local_pref = attributes.get('local-preference', 'N/A')
print(f"[ANNOUNCE] {prefix} via {next_hop} "
f"(LP: {local_pref}, MED: {med})",
file=sys.stderr)
# Process withdrawn IPv6 routes
if 'withdraw' in update:
if 'ipv6 unicast' in update['withdraw']:
routes = update['withdraw']['ipv6 unicast']
for prefix in routes:
print(f"[WITHDRAW] {prefix}", file=sys.stderr)
except json.JSONDecodeError as e:
print(f"JSON parse error: {e}", file=sys.stderr)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)IPv6 unicast supports all the same BGP attributes as IPv4:
- ORIGIN: IGP, EGP, INCOMPLETE
- AS-PATH: AS path traversed
- NEXT-HOP: IPv6 next-hop address
- LOCAL_PREF: iBGP preference (higher = better)
- MED: External AS entry point preference (lower = better)
- COMMUNITY: 32-bit route tags
- LARGE_COMMUNITY: 96-bit route tags (recommended for IPv6)
- EXTENDED_COMMUNITY: 64-bit extended attributes
- AGGREGATOR: Aggregation information
- ATOMIC_AGGREGATE: Route aggregation indicator
Syntax is identical to IPv4, only the prefix format differs:
# IPv4
print("announce route 192.0.2.0/24 next-hop self med 50")
# IPv6 (same syntax, different prefix)
print("announce route 2001:db8::/32 next-hop self med 50")Scenario: Multiple DNS servers announce the same IPv6 anycast address.
#!/usr/bin/env python3
import sys
import subprocess
import time
DNS_ANYCAST_IP = "2001:db8:53::1/128"
def check_dns_health():
"""Check if DNS service is healthy"""
try:
result = subprocess.run(
['dig', '@::1', 'example.com', '+short'],
timeout=2,
capture_output=True
)
return result.returncode == 0
except:
return False
def announce():
print(f"announce route {DNS_ANYCAST_IP} next-hop self")
sys.stdout.flush()
def withdraw():
print(f"withdraw route {DNS_ANYCAST_IP} next-hop self")
sys.stdout.flush()
announced = False
while True:
healthy = check_dns_health()
if healthy and not announced:
announce()
announced = True
print(f"[INFO] DNS healthy, announced {DNS_ANYCAST_IP}", file=sys.stderr)
elif not healthy and announced:
withdraw()
announced = False
print(f"[WARN] DNS unhealthy, withdrew {DNS_ANYCAST_IP}", file=sys.stderr)
time.sleep(5)Scenario: Announce both IPv4 and IPv6 addresses for a service.
#!/usr/bin/env python3
import sys
import time
SERVICE_IPv4 = "100.64.1.1/32"
SERVICE_IPv6 = "2001:db8:100::1/128"
def check_service_health():
# Check service health (HTTP, TCP, etc.)
# Return True if healthy
return True
def announce_both():
print(f"announce route {SERVICE_IPv4} next-hop self")
print(f"announce route {SERVICE_IPv6} next-hop self")
sys.stdout.flush()
def withdraw_both():
print(f"withdraw route {SERVICE_IPv4} next-hop self")
print(f"withdraw route {SERVICE_IPv6} next-hop self")
sys.stdout.flush()
announced = False
while True:
if check_service_health() and not announced:
announce_both()
announced = True
elif not check_service_health() and announced:
withdraw_both()
announced = False
time.sleep(10)Scenario: Distribute traffic across multiple IPv6-enabled servers using MED.
#!/usr/bin/env python3
import sys
import psutil
import time
SERVICE_PREFIX = "2001:db8:100::/48"
def get_load_metric():
"""Calculate MED based on CPU load"""
cpu_percent = psutil.cpu_percent(interval=1)
return int(100 + (cpu_percent * 4))
def announce_with_load():
med = get_load_metric()
print(f"announce route {SERVICE_PREFIX} "
f"next-hop self "
f"med {med}")
sys.stdout.flush()
print(f"[INFO] Announced {SERVICE_PREFIX} with MED {med}", file=sys.stderr)
while True:
announce_with_load()
time.sleep(10)Scenario: Announce aggregated IPv6 prefix, suppress more-specifics.
# Announce aggregate /32
print("announce route 2001:db8::/32 "
"next-hop self "
"atomic-aggregate "
"aggregator 2001:db8::1:65001")
sys.stdout.flush()
# Suppress more-specific /48 prefixes with NO_ADVERTISE
for i in range(1, 10):
print(f"announce route 2001:db8:{i:x}000::/48 "
f"next-hop self "
f"community [ no-advertise ]")
sys.stdout.flush()Scenario: Control inbound IPv6 traffic using AS-PATH prepending.
# Primary path (short AS-PATH)
print("announce route 2001:db8::/32 "
"next-hop self "
"as-path [ 65001 ]")
sys.stdout.flush()
# Backup path (longer AS-PATH via prepending)
print("announce route 2001:db8::/32 "
"next-hop self "
"as-path [ 65001 65001 65001 65001 ] "
"community [ 65001:backup ]")
sys.stdout.flush()IPv6 BGP can use two types of next-hop addresses:
Link-Local Next-Hop (fe80::/10):
- Common in eBGP sessions on directly connected links
- Requires interface specification (not supported by all implementations)
- ExaBGP typically uses global unicast next-hop
Global Unicast Next-Hop (2000::/3):
- Recommended for ExaBGP
- Works across non-directly-connected peers
- More compatible
⚠️ Warning:next-hop selfis an EXPERIMENTAL feature. Always use explicit next-hop IP addresses in production.
Best Practice: Use explicit global unicast IPv6 next-hop addresses.
# Recommended: Explicit global unicast next-hop
print("announce route 2001:db8::/32 next-hop 2001:db8::1")
sys.stdout.flush()Some implementations support advertising IPv4 prefixes with IPv6 next-hop. ExaBGP supports this via RFC 5549 (Extended Next-Hop capability).
Cause: Incorrect IPv6 address notation.
Solution: Use proper IPv6 format with colons, zero compression allowed.
# Incorrect
print("announce route 2001:db8:0:0:0:0:0:0/32 next-hop self") # Not compressed
# Correct (compressed)
print("announce route 2001:db8::/32 next-hop self")
# Also correct (fully expanded)
print("announce route 2001:0db8:0000:0000:0000:0000:0000:0000/32 next-hop self")Cause: Peer router doesn't have IPv6 unicast address family configured.
Solution: Enable IPv6 unicast on both ExaBGP and peer.
Cisco IOS-XR:
router bgp 65000
neighbor 2001:db8::2
address-family ipv6 unicast ← Enable IPv6
!
!
!
Juniper Junos:
protocols {
bgp {
group exabgp {
family inet6 { ← Enable IPv6
unicast;
}
}
}
}
Cause: IPv6 next-hop address is not in a connected network.
Solution: Ensure next-hop is reachable. Use next-hop self with proper local-address.
neighbor 2001:db8::1 {
local-address 2001:db8::2; # Must be configured on local interface
# ...
}Cause: Peer may prefer another path (BGP decision process) or filter is blocking.
Solution: Check BGP attributes and peer's import policies.
Cause: IPv6 connectivity issue between ExaBGP and peer.
Solution: Verify IPv6 connectivity:
# Test IPv6 reachability
ping6 2001:db8::1
# Check IPv6 routing
ip -6 route show
# Verify IPv6 address on interface
ip -6 addr showCommon IPv6 prefix allocations:
# ISP allocation: /32
print("announce route 2001:db8::/32 next-hop self")
# Large organization: /36 or /40
print("announce route 2001:db8::/36 next-hop self")
# Site/customer: /48
print("announce route 2001:db8:1000::/48 next-hop self")
# Subnet: /64 (typical LAN size)
print("announce route 2001:db8:1000:1::/64 next-hop self")
# Host/anycast: /128
print("announce route 2001:db8::1/128 next-hop self")Always verify service before announcing:
rise_count = 0
fall_count = 0
announced = False
while True:
if check_ipv6_service_health():
rise_count += 1
fall_count = 0
if rise_count >= 3 and not announced:
announce()
announced = True
else:
fall_count += 1
rise_count = 0
if fall_count >= 2 and announced:
withdraw()
announced = False
time.sleep(5)Avoid link-local addresses for BGP next-hop:
# Good: Global unicast
print("announce route 2001:db8::/32 next-hop 2001:db8::1")
# Bad: Link-local (may not work)
# print("announce route 2001:db8::/32 next-hop fe80::1")Run IPv4 and IPv6 together for maximum reach:
def announce_dual_stack():
# Announce both IPv4 and IPv6
print("announce route 100.64.1.1/32 next-hop self")
print("announce route 2001:db8::1/128 next-hop self")
sys.stdout.flush()Large communities (96-bit) are better suited for IPv6 networks with large ASNs:
# Standard community (limited to 2-byte AS)
print("announce route 2001:db8::/32 "
"next-hop self "
"community [ 65001:100 ]")
# Large community (supports 4-byte AS)
print("announce route 2001:db8::/32 "
"next-hop self "
"large-community [ 4200000000:100:200 ]")
sys.stdout.flush()- Install IPv6 routes in the Linux kernel routing table
- Configure IPv6 addresses on interfaces
- Forward IPv6 traffic
- Handle IPv6 neighbor discovery
What ExaBGP DOES:
- ✅ Send/receive BGP UPDATE messages with IPv6 unicast routes
- ✅ Provide API for applications to control BGP announcements
- ✅ Handle BGP session management
To Forward IPv6 Traffic:
- Configure IPv6 address on interface:
ip -6 addr add 2001:db8::1/128 dev lo - ExaBGP announces route via BGP: Router receives route
- Router installs route in RIB/FIB (if best path)
- Router forwards IPv6 traffic based on its routing table
BGP sessions can run over IPv4 or IPv6 transport:
IPv6 Transport (recommended for IPv6 routes):
neighbor 2001:db8::1 {
local-address 2001:db8::2; # IPv6 transport
# Announce IPv6 routes
}IPv4 Transport (also works):
neighbor 192.0.2.1 {
local-address 192.0.2.2; # IPv4 transport
family {
ipv6 unicast; # Can carry IPv6 routes over IPv4 transport
}
}Even in pure IPv6 deployments, BGP router-id is always in IPv4 dotted-decimal format:
neighbor 2001:db8::1 {
router-id 192.0.2.2; # IPv4 format (required)
local-address 2001:db8::2; # IPv6 address
}Tip: If no IPv4 address exists, use a private IPv4 address (e.g., 10.0.0.1) for router-id.
- IPv4 Unicast - IPv4 routing
- Configuration Syntax - Configuration reference
- Text API Reference - API commands
- JSON API Reference - JSON message format
- Anycast Management - Anycast service IPs
- Service High Availability - HA with health checks
- Load Balancing - BGP-based load distribution
- Debugging - Troubleshooting BGP issues
- Monitoring - Monitoring BGP sessions
- First BGP Session - Basic BGP setup
- Quick Start - 5-minute tutorial
-
RFC 4271: A Border Gateway Protocol 4 (BGP-4)
- Core BGP specification
- https://datatracker.ietf.org/doc/html/rfc4271
-
RFC 4760: Multiprotocol Extensions for BGP-4
- MP-BGP for IPv6 unicast
- https://datatracker.ietf.org/doc/html/rfc4760
-
RFC 2545: Use of BGP-4 Multiprotocol Extensions for IPv6 Inter-Domain Routing
- IPv6-specific BGP extensions
- https://datatracker.ietf.org/doc/html/rfc2545
-
RFC 5549: Advertising IPv4 Network Layer Reachability Information with an IPv6 Next Hop
- IPv4 routes with IPv6 next-hop
- https://datatracker.ietf.org/doc/html/rfc5549
-
RFC 8092: BGP Large Communities Attribute
- 96-bit communities (recommended for IPv6)
- https://datatracker.ietf.org/doc/html/rfc8092
- ExaBGP GitHub: https://github.com/Exa-Networks/exabgp
- RFC Implementation: RFC-Information.md
- IPv6 Address Allocation: IANA IPv6 address space registry
- IPv6 Deployment Guide: Best practices for IPv6 migration
Getting Started
Configuration
- Configuration Syntax
- Neighbor Configuration
- Directives A-Z
- Templates
- Environment Variables
- Process Configuration
API
- API Overview
- Text API Reference
- JSON API Reference
- API Commands
- Writing API Programs
- Error Handling
- Production Best Practices
Address Families
- Overview
- IPv4 Unicast
- IPv6 Unicast
- FlowSpec
- EVPN
- L3VPN
- BGP-LS
- VPLS
- SRv6 / MUP
- Multicast
- RT Constraint
Features
Use Cases
Tools
Operations
Reference
- Architecture
- Design
- Attribute Reference
- Command Reference
- BGP State Machine
- Capabilities
- Communities
- Examples Index
- Glossary
- RFC Support
Integration
Migration
Community
External