-
Notifications
You must be signed in to change notification settings - Fork 461
IPv4 Unicast
IPv4 Unicast is the most fundamental BGP address family, used for advertising IPv4 prefixes for unicast (one-to-one) traffic forwarding. ExaBGP provides full support for IPv4 unicast route announcements, withdrawals, and reception, enabling applications to programmatically control IPv4 routing.
- Overview
- When to Use IPv4 Unicast
- Configuration
- API Examples
- BGP Attributes
- Use Cases
- Common Errors and Solutions
- Best Practices
- Important Considerations
- See Also
- References
IPv4 Unicast is the original BGP address family (AFI 1, SAFI 1), used to advertise IPv4 prefixes for standard unicast routing.
Key Characteristics:
- Address Family Identifier (AFI): 1 (IPv4)
- Subsequent Address Family Identifier (SAFI): 1 (Unicast)
-
Prefix Format: IPv4 CIDR notation (e.g.,
192.0.2.0/24) - Next-Hop: IPv4 address indicating where to forward traffic
What IPv4 Unicast Routes Carry:
- IPv4 prefix (network + mask length)
- Next-hop IP address
- BGP path attributes (AS-PATH, MED, LOCAL_PREF, COMMUNITY, etc.)
Use IPv4 unicast for:
✅ Internet Routing: Default-free zone (DFZ) routing, ISP peering ✅ Enterprise BGP: Branch office connectivity, data center routing ✅ Anycast Services: Announcing same IP from multiple locations (DNS, CDN, load balancers) ✅ High Availability: Service IP announcements with health checks ✅ Traffic Engineering: Controlling ingress/egress traffic with BGP attributes ✅ Load Balancing: Distributing traffic across multiple servers using BGP metrics ✅ Network Automation: Programmatic route injection for cloud, containers, VMs
Not for:
- ❌ L2 VPN services (use EVPN)
- ❌ L3 VPN services (use VPNv4)
- ❌ Multicast routing (use IPv4 Multicast SAFI)
- ❌ Traffic filtering (use FlowSpec)
# /etc/exabgp/ipv4-unicast.conf
neighbor 192.0.2.1 {
router-id 192.0.2.2;
local-address 192.0.2.2;
local-as 65001;
peer-as 65000;
# IPv4 unicast is enabled by default
family {
ipv4 unicast;
}
# API process for dynamic route announcements
api {
processes [ route-injector ];
}
}
process route-injector {
run python3 /etc/exabgp/announce-routes.py;
encoder text;
}#!/usr/bin/env python3
# /etc/exabgp/announce-routes.py
import sys
import time
def announce_route(prefix, nexthop="self", **attributes):
"""Announce IPv4 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} ]"
if 'as_path' in attributes:
as_path = ' '.join(map(str, attributes['as_path']))
cmd += f" as-path [ {as_path} ]"
print(cmd)
sys.stdout.flush()
def withdraw_route(prefix, nexthop="self"):
"""Withdraw IPv4 unicast route"""
print(f"withdraw route {prefix} next-hop {nexthop}")
sys.stdout.flush()
# Example: Announce service IP
announce_route(
"100.64.1.1/32",
nexthop="self",
local_preference=200,
community=["65001:100", "65001:200"]
)
# Keep process running
while True:
time.sleep(60)# Simplest form
print("announce route 100.64.1.0/24 next-hop self")
sys.stdout.flush()
# Explicit next-hop
print("announce route 100.64.1.0/24 next-hop 192.0.2.2")
sys.stdout.flush()
# /32 host route (anycast service IP)
print("announce route 100.64.1.1/32 next-hop self")
sys.stdout.flush()# With LOCAL_PREF and MED
print("announce route 100.64.1.0/24 "
"next-hop self "
"local-preference 200 "
"med 50")
sys.stdout.flush()
# With communities
print("announce route 100.64.1.0/24 "
"next-hop self "
"community [ 65001:100 65001:200 ]")
sys.stdout.flush()
# With AS-PATH prepending
print("announce route 100.64.1.0/24 "
"next-hop self "
"as-path [ 65001 65001 65001 ]") # Prepend own AS 3 times
sys.stdout.flush()# With large communities
print("announce route 100.64.1.0/24 "
"next-hop self "
"large-community [ 65001:100:200 ]")
sys.stdout.flush()
# With extended communities
print("announce route 100.64.1.0/24 "
"next-hop self "
"extended-community [ origin:65001:100 ]")
sys.stdout.flush()
# Complete example with all common attributes
print("announce route 100.64.1.0/24 "
"next-hop self "
"local-preference 200 "
"med 50 "
"community [ 65001:100 ] "
"as-path [ 65001 ] "
"origin igp")
sys.stdout.flush()# Withdraw route (must match announcement exactly)
print("withdraw route 100.64.1.0/24 next-hop self")
sys.stdout.flush()
# Withdraw with explicit next-hop
print("withdraw route 100.64.1.0/24 next-hop 192.0.2.2")
sys.stdout.flush()Configure ExaBGP to receive routes from peer:
neighbor 192.0.2.1 {
# ... basic config ...
api {
processes [ route-receiver ];
receive {
parsed; # Receive routes in structured format
update; # Receive UPDATE messages
}
}
}
process route-receiver {
run python3 /etc/exabgp/receive-routes.py;
encoder json; # Use JSON for easier parsing
}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 routes
if 'announce' in update:
if 'ipv4 unicast' in update['announce']:
routes = update['announce']['ipv4 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 routes
if 'withdraw' in update:
if 'ipv4 unicast' in update['withdraw']:
routes = update['withdraw']['ipv4 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)ORIGIN: Route origin (IGP, EGP, INCOMPLETE)
origin igp # Learned from IGP (default)
origin egp # Learned from EGP
origin incomplete # Learned by other meansAS-PATH: AS path the route has traversed
as-path [ 65001 65002 65003 ] # Route passed through these ASesNEXT-HOP: IPv4 address to forward traffic to
next-hop self # Use local router's IP
next-hop 192.0.2.2 # Explicit next-hopLOCAL_PREF (iBGP only): Preference for exit point (higher = better)
local-preference 200 # Prefer this route over LOCAL_PREF 100ATOMIC_AGGREGATE: Indicates route aggregation with information loss
atomic-aggregateAGGREGATOR: AS and router ID that performed aggregation
aggregator 192.0.2.1:65001COMMUNITY: 32-bit route tags for policy application
community [ 65001:100 65001:200 ]MED (Multi-Exit Discriminator): Hint to external AS for entry point (lower = better)
med 50 # Prefer this entry point over MED 100ORIGINATOR_ID (iBGP Route Reflection): Original route injector
originator-id 192.0.2.1CLUSTER_LIST (iBGP Route Reflection): Route reflector path
cluster-list [ 192.0.2.1 192.0.2.2 ]Scenario: Multiple servers announce the same service IP; traffic routed to nearest server.
#!/usr/bin/env python3
import sys
import subprocess
import time
def check_service_health():
"""Check if local service is healthy"""
try:
result = subprocess.run(['curl', '-sf', 'http://localhost/health'],
timeout=2, capture_output=True)
return result.returncode == 0
except:
return False
def announce():
print("announce route 100.64.1.1/32 next-hop self")
sys.stdout.flush()
def withdraw():
print("withdraw route 100.64.1.1/32 next-hop self")
sys.stdout.flush()
# Initial announcement
announced = False
while True:
healthy = check_service_health()
if healthy and not announced:
announce()
announced = True
print("[INFO] Service healthy, announced route", file=sys.stderr)
elif not healthy and announced:
withdraw()
announced = False
print("[WARN] Service unhealthy, withdrew route", file=sys.stderr)
time.sleep(5)Scenario: Distribute traffic based on server load using BGP MED.
#!/usr/bin/env python3
import sys
import psutil
import time
def get_load_metric():
"""Calculate MED based on CPU load (higher load = higher MED)"""
cpu_percent = psutil.cpu_percent(interval=1)
# MED 100-500 based on 0-100% CPU
return int(100 + (cpu_percent * 4))
def announce_with_load():
med = get_load_metric()
print(f"announce route 100.64.1.100/32 "
f"next-hop self "
f"med {med}")
sys.stdout.flush()
print(f"[INFO] Announced with MED {med}", file=sys.stderr)
while True:
announce_with_load()
time.sleep(10) # Update every 10 secondsScenario: Control BGP propagation using communities.
# Announce route with NO_EXPORT community (don't advertise to eBGP peers)
print("announce route 100.64.1.0/24 "
"next-hop self "
"community [ no-export ]") # 0xFFFFFF01
sys.stdout.flush()
# Announce route with NO_ADVERTISE (don't advertise to any peer)
print("announce route 100.64.2.0/24 "
"next-hop self "
"community [ no-advertise ]") # 0xFFFFFF02
sys.stdout.flush()
# Announce with custom community for policy (e.g., "announce to customers only")
print("announce route 100.64.3.0/24 "
"next-hop self "
"community [ 65001:100 ]") # Custom policy
sys.stdout.flush()Scenario: Announce aggregate prefix to reduce routing table size.
# Announce aggregate
print("announce route 100.64.0.0/16 "
"next-hop self "
"atomic-aggregate "
"aggregator 192.0.2.2:65001")
sys.stdout.flush()
# Suppress more-specific prefixes with NO_ADVERTISE
print("announce route 100.64.1.0/24 "
"next-hop self "
"community [ no-advertise ]")
sys.stdout.flush()Scenario: Make path less attractive for inbound traffic.
# Normal announcement
print("announce route 100.64.1.0/24 "
"next-hop self "
"as-path [ 65001 ]") # Single AS in path
sys.stdout.flush()
# Prepend AS to make path longer (less attractive)
print("announce route 100.64.2.0/24 "
"next-hop self "
"as-path [ 65001 65001 65001 65001 ]") # Prepend 3 extra times
sys.stdout.flush()Cause: Next-hop IP address is unreachable or not in a connected network.
Solution: Use next-hop self or ensure the next-hop IP is reachable.
# Incorrect (if 203.0.113.1 is not local)
print("announce route 100.64.1.0/24 next-hop 203.0.113.1")
# Correct
print("announce route 100.64.1.0/24 next-hop self")Cause: Peer has import filters blocking the prefix or AS-PATH.
Solution: Verify peer's BGP import policy. Check for:
- Prefix filters (e.g., only accepting RFC1918 or specific ranges)
- AS-PATH filters
- Community-based filtering
Cause: Route may be rejected due to BGP decision process (better path exists).
Solution: Check BGP attributes. Increase LOCAL_PREF (iBGP) or decrease MED (eBGP).
Cause: Announcing same prefix multiple times without withdrawing.
Solution: ExaBGP allows updates, but ensure intent is to update, not duplicate.
# First announcement
print("announce route 100.64.1.0/24 next-hop self med 100")
sys.stdout.flush()
# Update (change MED)
print("announce route 100.64.1.0/24 next-hop self med 50") # Updates existing route
sys.stdout.flush()Cause: ExaBGP does NOT install routes in FIB.
Solution: ExaBGP only announces via BGP. The receiving router must:
- Accept the route (no import filters)
- Install route in RIB/FIB
- Have reachable next-hop
Remember: ExaBGP is BGP protocol only, not a router.
Always verify service is healthy before announcing routes:
# BAD: Announce without health check
print("announce route 100.64.1.1/32 next-hop self")
# GOOD: Health check first
if check_service_health():
print("announce route 100.64.1.1/32 next-hop self")Avoid route flapping due to transient failures:
rise_count = 0
fall_count = 0
announced = False
while True:
if check_service_health():
rise_count += 1
fall_count = 0
if rise_count >= 3 and not announced: # 3 consecutive passes
announce()
announced = True
else:
fall_count += 1
rise_count = 0
if fall_count >= 2 and announced: # 2 consecutive failures
withdraw()
announced = False
time.sleep(5)ExaBGP reads from STDIN line-by-line. Always flush:
print("announce route 100.64.1.0/24 next-hop self")
sys.stdout.flush() # Required!Tag routes with communities for flexible policy:
# Tag routes by source
print("announce route 100.64.1.0/24 "
"next-hop self "
"community [ 65001:100 ]") # e.g., 100 = datacenter-1
# Tag by purpose
print("announce route 100.64.2.0/24 "
"next-hop self "
"community [ 65001:200 ]") # e.g., 200 = customer-facingAlways log announcements and withdrawals for troubleshooting:
import logging
logging.basicConfig(level=logging.INFO)
def announce_route(prefix):
print(f"announce route {prefix} next-hop self")
sys.stdout.flush()
logging.info(f"Announced: {prefix}")
def withdraw_route(prefix):
print(f"withdraw route {prefix} next-hop self")
sys.stdout.flush()
logging.warning(f"Withdrew: {prefix}")- Install routes in the Linux kernel routing table
- Configure network interfaces
- Forward IPv4 traffic
- Act as a router
What ExaBGP DOES:
- ✅ Send/receive BGP UPDATE messages with IPv4 unicast routes
- ✅ Provide API for applications to control BGP announcements
- ✅ Handle BGP session management (OPEN, KEEPALIVE, NOTIFICATION)
To Forward Traffic:
- ExaBGP announces route via BGP → Router receives route
- Router installs route in RIB/FIB (if best path)
- Router forwards traffic based on its routing table
Common Mistake:
# This announces the route via BGP
print("announce route 100.64.1.1/32 next-hop self")
# ExaBGP does NOT:
# - Configure 100.64.1.1 on a local interface
# - Install route in kernel routing table
# - Handle traffic to 100.64.1.1Correct Usage:
- Use ExaBGP to announce service IPs that exist on loopback interfaces
- Ensure announced next-hop is reachable
- Service must actually be running locally
Routes announced by ExaBGP compete with other routes. BGP selects best path based on:
- Highest LOCAL_PREF (iBGP)
- Shortest AS-PATH
- Lowest ORIGIN (IGP < EGP < INCOMPLETE)
- Lowest MED (same AS only)
- eBGP over iBGP
- Lowest IGP metric to next-hop
- Lowest router ID
Implication: Even if ExaBGP announces a route, peer may not use it if a better path exists.
If no family is specified, IPv4 unicast is assumed:
# These are equivalent
neighbor 192.0.2.1 {
# ... config ...
}
neighbor 192.0.2.1 {
family {
ipv4 unicast; # Explicit (same as default)
}
}- IPv6 Unicast - IPv6 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
- Traffic Engineering - AS-PATH, MED, communities
- Debugging - Troubleshooting BGP issues
- Monitoring - Monitoring BGP sessions and routes
- First BGP Session - Basic BGP setup
- Quick Start - 5-minute tutorial
-
RFC 4271: A Border Gateway Protocol 4 (BGP-4)
- Core BGP specification
- IPv4 unicast routing
- https://datatracker.ietf.org/doc/html/rfc4271
-
RFC 1997: BGP Communities Attribute
- Standard communities for policy
- https://datatracker.ietf.org/doc/html/rfc1997
-
RFC 8092: BGP Large Communities Attribute
- 96-bit communities
- https://datatracker.ietf.org/doc/html/rfc8092
- ExaBGP GitHub: https://github.com/Exa-Networks/exabgp
- RFC Implementation: RFC-Information.md
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