Skip to content

IPv4 Unicast

Thomas Mangin edited this page Mar 6, 2026 · 1 revision

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.

Table of Contents


Overview

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.)

When to Use IPv4 Unicast

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)

Configuration

Basic IPv4 Unicast Configuration

# /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;
}

Route Announcement Script

#!/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)

API Examples

Announce Routes

Basic Route Announcement

# 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()

Route with Attributes

# 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()

Advanced Attributes

# 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 Routes

# 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()

Receive Routes

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)

BGP Attributes

Mandatory Attributes

ORIGIN: Route origin (IGP, EGP, INCOMPLETE)

origin igp         # Learned from IGP (default)
origin egp         # Learned from EGP
origin incomplete  # Learned by other means

AS-PATH: AS path the route has traversed

as-path [ 65001 65002 65003 ]  # Route passed through these ASes

NEXT-HOP: IPv4 address to forward traffic to

next-hop self           # Use local router's IP
next-hop 192.0.2.2      # Explicit next-hop

Well-Known Discretionary Attributes

LOCAL_PREF (iBGP only): Preference for exit point (higher = better)

local-preference 200  # Prefer this route over LOCAL_PREF 100

ATOMIC_AGGREGATE: Indicates route aggregation with information loss

atomic-aggregate

Optional Transitive Attributes

AGGREGATOR: AS and router ID that performed aggregation

aggregator 192.0.2.1:65001

COMMUNITY: 32-bit route tags for policy application

community [ 65001:100 65001:200 ]

Optional Non-Transitive Attributes

MED (Multi-Exit Discriminator): Hint to external AS for entry point (lower = better)

med 50  # Prefer this entry point over MED 100

ORIGINATOR_ID (iBGP Route Reflection): Original route injector

originator-id 192.0.2.1

CLUSTER_LIST (iBGP Route Reflection): Route reflector path

cluster-list [ 192.0.2.1 192.0.2.2 ]

Use Cases

1. Anycast Service Announcement

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)

2. Dynamic Load Balancing with MED

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 seconds

3. Traffic Engineering with Communities

Scenario: 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()

4. Prefix Aggregation

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()

5. AS-PATH Prepending for Traffic Engineering

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()

Common Errors and Solutions

Error: "Invalid next-hop"

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")

Error: "Route not accepted by peer"

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

Error: "Route announced but not in peer's RIB"

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).

Error: "Multiple announcements of same prefix"

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()

Error: "Routes announced but traffic not flowing"

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.


Best Practices

1. Use Health Checks Before Announcing

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")

2. Implement Dampening for Health Checks

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)

3. Flush stdout After Every Command

ExaBGP reads from STDIN line-by-line. Always flush:

print("announce route 100.64.1.0/24 next-hop self")
sys.stdout.flush()  # Required!

4. Use Communities for Policy Control

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-facing

5. Log All Route Changes

Always 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}")

Important Considerations

ExaBGP Does Not Manipulate RIB/FIB

⚠️ CRITICAL: ExaBGP is a BGP protocol engine. It does NOT:

  • 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:

  1. ExaBGP announces route via BGP → Router receives route
  2. Router installs route in RIB/FIB (if best path)
  3. 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.1

Correct Usage:

  • Use ExaBGP to announce service IPs that exist on loopback interfaces
  • Ensure announced next-hop is reachable
  • Service must actually be running locally

BGP Decision Process

Routes announced by ExaBGP compete with other routes. BGP selects best path based on:

  1. Highest LOCAL_PREF (iBGP)
  2. Shortest AS-PATH
  3. Lowest ORIGIN (IGP < EGP < INCOMPLETE)
  4. Lowest MED (same AS only)
  5. eBGP over iBGP
  6. Lowest IGP metric to next-hop
  7. Lowest router ID

Implication: Even if ExaBGP announces a route, peer may not use it if a better path exists.

IPv4 Unicast is Default

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)
    }
}

See Also

ExaBGP Documentation

Use Cases

Operations

Getting Started


References

RFCs

ExaBGP Resources


Clone this wiki locally