-
Notifications
You must be signed in to change notification settings - Fork 461
API Overview
Understanding ExaBGP's STDIN/STDOUT API
- What is the ExaBGP API?
- Why This API Design?
- How It Works
- Text vs JSON Encoders
- API Architecture
- Basic Examples
- Process Lifecycle
- Communication Flow
- Error Handling
- Best Practices
- Next Steps
The ExaBGP API is a simple STDIN/STDOUT interface that allows your programs to control BGP route announcements and receive BGP updates.
Key characteristics:
- Language-agnostic: Works with Python, Bash, Ruby, Go, Rust, Node.js, or any language
- Simple: No libraries needed - just read/write text or JSON
- Bidirectional: Your program sends commands, receives BGP messages
- Process-based: ExaBGP spawns your program as a subprocess
Think of it as:
Your Program ↔ STDIN/STDOUT ↔ ExaBGP ↔ BGP Protocol ↔ Router
"The best API is no API"
ExaBGP uses UNIX pipes (STDIN/STDOUT) instead of complex libraries.
Advantages:
- ✅ No dependencies: Works with any language that can read/write streams
- ✅ Simple:
print()to send commands,readline()to receive - ✅ Debugging-friendly: Can test manually with echo/cat
- ✅ Language-agnostic: Not tied to Python or any specific ecosystem
- ✅ Process isolation: Your program crashes don't affect ExaBGP
- ✅ Easy integration: Wrap existing tools without modification
Compared to alternatives:
- Library-based (e.g., GoBGP's gRPC): Requires language-specific client
- REST API: Adds HTTP overhead, state management complexity
- File-based: Requires polling, slower updates
STDIN/STDOUT is:
- Fast (no network/HTTP overhead)
- Simple (just streams)
- Universal (every language supports it)
ExaBGP configuration defines which programs to run:
neighbor 192.168.1.1 {
router-id 192.168.1.2;
local-address 192.168.1.2;
local-as 65001;
peer-as 65000;
# API configuration
api {
processes [ my-program ];
}
}
process my-program {
run /etc/exabgp/api/my-program.py;
encoder text; # or 'json'
}When ExaBGP starts:
- ExaBGP spawns
/etc/exabgp/api/my-program.pyas subprocess - ExaBGP connects to program's STDIN/STDOUT
- Program can now:
- Write to STDOUT → Commands to ExaBGP
- Read from STDIN → BGP messages from ExaBGP
┌─────────────────────┐
│ Your Program │
│ │
│ print("announce │ STDOUT ──────┐
│ route ...") │ │
│ │ ▼
│ for line in stdin: │ STDIN ◀──────┬─────────┐
│ process(line) │ │ ExaBGP │
└─────────────────────┘ │ │
│ BGP │
│ Speaker │
└────┬────┘
│
│ BGP Protocol
▼
┌─────────┐
│ Router │
└─────────┘
ExaBGP supports two encoding formats: Text and JSON.
Use case: Sending commands (announcing/withdrawing routes)
Configuration:
process my-program {
run /etc/exabgp/api/announce.py;
encoder text;
}Format: Human-readable text commands
Example commands:
# Announce
print("announce route 100.10.0.0/24 next-hop self")
# Withdraw
print("withdraw route 100.10.0.0/24")
# FlowSpec
print("announce flow route { match { destination 100.10.0.0/24; } then { discard; } }")Pros:
- ✅ Human-readable
- ✅ Easy to test manually
- ✅ Simple parsing
Cons:
- ❌ Harder to parse programmatically
- ❌ Limited structured data
Use case: Receiving BGP updates (routes, session state)
Configuration:
process my-program {
run /etc/exabgp/api/receive.py;
encoder json;
receive {
parsed; # Receive parsed BGP messages
updates; # Receive route updates
}
}Format: JSON lines (one JSON object per line)
Example message:
{
"exabgp": "4.2.25",
"time": 1699564800.0,
"type": "update",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"message": {
"update": {
"announce": {
"ipv4 unicast": {
"100.10.0.0/24": [
{
"next-hop": "192.168.1.2"
}
]
}
}
}
}
}
}Pros:
- ✅ Structured data
- ✅ Easy to parse (every language has JSON support)
- ✅ Complete information
Cons:
- ❌ Verbose
- ❌ Harder to read manually
Use encoder text when:
- Only sending commands (announce/withdraw)
- Want simple, human-readable format
- Testing manually
Use encoder json when:
- Receiving BGP updates from router
- Need structured data parsing
- Building complex integrations
Can use both:
# Process 1: Send commands (text)
process announce {
run /etc/exabgp/api/announce.py;
encoder text;
}
# Process 2: Receive updates (JSON)
process receive {
run /etc/exabgp/api/receive.py;
encoder json;
receive {
parsed;
updates;
}
}
neighbor 192.168.1.1 {
router-id 192.168.1.2;
local-address 192.168.1.2;
local-as 65001;
peer-as 65000;
api {
processes [ announce, receive ];
}
}┌──────────────────────────────────────────────────────┐
│ Your Program(s) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ announce.py │ │ receive.py │ │ health.py │ │
│ │ (text) │ │ (json) │ │ (text) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬─────┘ │
│ │ STDOUT │ STDIN │ STDOUT│
└─────────┼─────────────────┼─────────────────┼───────┘
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────┐
│ ExaBGP │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Process │ │ BGP State │ │ Route │ │
│ │ Manager │ │ Machine │ │ Manager │ │
│ └─────────────┘ └──────────────┘ └──────────┘ │
└──────────────────────────┬───────────────────────────┘
│ BGP Protocol
▼
┌──────────┐
│ Router │
└──────────┘
- Spawns your programs as subprocesses
- Monitors process health (restarts if crashed)
- Routes commands from STDOUT to BGP engine
- Sends BGP messages to process STDIN
1. Your program: print("announce route 100.10.0.0/24 next-hop self")
2. Process writes to STDOUT
3. ExaBGP reads from process STDOUT
4. ExaBGP parses command
5. ExaBGP generates BGP UPDATE message
6. ExaBGP sends UPDATE to router via TCP 179
7. Router installs route in RIB/FIB
1. Router sends BGP UPDATE to ExaBGP
2. ExaBGP receives UPDATE
3. ExaBGP parses BGP message
4. ExaBGP converts to JSON (if encoder json)
5. ExaBGP writes JSON to process STDIN
6. Your program reads from STDIN
7. Your program processes the update
#!/usr/bin/env python3
"""
announce.py - Announce static routes
"""
import sys
import time
# Wait for ExaBGP to be ready
time.sleep(2)
# Announce routes
routes = [
"100.10.0.0/24",
"100.20.0.0/24",
]
for route in routes:
sys.stdout.write(f"announce route {route} next-hop self\n")
sys.stdout.flush() # CRITICAL: Must flush!
# Keep process alive
while True:
time.sleep(60)Configuration:
process announce {
run /etc/exabgp/api/announce.py;
encoder text;
}#!/usr/bin/env python3
"""
healthcheck.py - Announce route only when service is healthy
"""
import sys
import time
import socket
SERVICE_IP = "100.10.0.100"
SERVICE_PORT = 80
CHECK_INTERVAL = 5
def is_healthy():
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex((SERVICE_IP, SERVICE_PORT))
sock.close()
return result == 0
except:
return False
time.sleep(2)
announced = False
while True:
if is_healthy():
if not announced:
sys.stdout.write(f"announce route {SERVICE_IP}/32 next-hop self\n")
sys.stdout.flush()
announced = True
else:
if announced:
sys.stdout.write(f"withdraw route {SERVICE_IP}/32\n")
sys.stdout.flush()
announced = False
time.sleep(CHECK_INTERVAL)#!/usr/bin/env python3
"""
receive.py - Process incoming BGP updates
"""
import sys
import json
while True:
line = sys.stdin.readline().strip()
if not line:
break
try:
msg = json.loads(line)
# Route announcement
if msg['type'] == 'update':
if 'update' in msg['neighbor']['message']:
update = msg['neighbor']['message']['update']
# New routes announced
if 'announce' in update:
if 'ipv4 unicast' in update['announce']:
routes = update['announce']['ipv4 unicast']
for prefix, attrs in routes.items():
nexthop = attrs[0]['next-hop']
print(f"[RECEIVED] {prefix} via {nexthop}", file=sys.stderr)
# Routes withdrawn
if 'withdraw' in update:
if 'ipv4 unicast' in update['withdraw']:
for prefix in update['withdraw']['ipv4 unicast'].keys():
print(f"[WITHDRAWN] {prefix}", file=sys.stderr)
# Session state change
elif msg['type'] == 'state':
state = msg['neighbor']['state']
print(f"[STATE] BGP session: {state}", file=sys.stderr)
except Exception as e:
print(f"[ERROR] {e}", file=sys.stderr)Configuration:
process receive {
run /etc/exabgp/api/receive.py;
encoder json;
receive {
parsed;
updates;
}
}#!/bin/bash
# announce.sh - Announce routes from bash
# Wait for ExaBGP
sleep 2
# Announce routes
echo "announce route 100.10.0.0/24 next-hop self"
echo "announce route 100.20.0.0/24 next-hop self"
# Keep running
while true; do
sleep 60
donepackage main
import (
"fmt"
"os"
"time"
)
func main() {
// Wait for ExaBGP
time.Sleep(2 * time.Second)
// Announce routes
routes := []string{
"100.10.0.0/24",
"100.20.0.0/24",
}
for _, route := range routes {
fmt.Printf("announce route %s next-hop self\n", route)
}
// Keep alive
for {
time.Sleep(60 * time.Second)
}
}- ExaBGP starts
- Reads configuration file
- Spawns process(es) defined in
processblocks - Connects to process STDIN/STDOUT
- Waits for BGP session to establish
Your program should:
- Sleep 2-5 seconds to wait for BGP session
- Send initial announcements
- Enter main loop
Your program:
- Continuously runs (infinite loop or event-driven)
- Writes commands to STDOUT when needed
- Reads messages from STDIN if receiving updates
- Flushes STDOUT after each write
ExaBGP:
- Monitors process (checks if still running)
- Routes commands to BGP engine
- Sends BGP updates to process
Graceful shutdown (SIGTERM):
- ExaBGP receives SIGTERM
- ExaBGP sends SIGTERM to all processes
- Processes should exit cleanly
- ExaBGP closes BGP sessions (sends NOTIFICATION)
- ExaBGP exits
Process crash:
- Process exits/crashes
- ExaBGP detects (broken pipe or process exit)
- ExaBGP can restart process (if
respawnenabled)
Configuration for auto-restart:
process my-program {
run /etc/exabgp/api/my-program.py;
encoder text;
}ExaBGP automatically respawns crashed processes.
# WRONG - Not flushed, nothing happens
print("announce route 100.10.0.0/24 next-hop self")
# CORRECT - Flushed, ExaBGP receives it
sys.stdout.write("announce route 100.10.0.0/24 next-hop self\n")
sys.stdout.flush()
# ALSO CORRECT - Print and flush
print("announce route 100.10.0.0/24 next-hop self")
sys.stdout.flush()
# ALSO CORRECT - Unbuffered mode
print("announce route 100.10.0.0/24 next-hop self", flush=True)Critical: Always flush STDOUT, or use unbuffered mode:
python3 -u /etc/exabgp/api/announce.py# Read lines forever
while True:
line = sys.stdin.readline()
if not line:
break # EOF
process(line)Message types (JSON):
-
type: "state"- BGP session state changes -
type: "update"- Route announcements/withdrawals -
type: "notification"- BGP errors -
type: "keepalive"- Keepalive messages
⚠️ Version Difference: ACK feature availability depends on ExaBGP version.
Behavior:
- Your program writes commands to STDOUT
- ExaBGP reads commands
- No response sent back
- Your program doesn't know if command succeeded
# ExaBGP 4.x - No feedback
print("announce route 100.10.0.0/24 next-hop self")
sys.stdout.flush()
# Command sent, but no way to know if it workedProblem: Silent failures - bad commands are ignored without notification.
Behavior:
- Your program writes command to STDOUT
- ExaBGP reads and validates command
- ExaBGP sends response on your program's STDIN
- Your program can verify success/failure
Enable ACK:
export exabgp.api.ack=true # Default in main/5.xResponses:
-
done\n- Command succeeded -
error\n- Command failed (syntax error, invalid route, etc.) -
shutdown\n- ExaBGP is shutting down
With select() to read response:
#!/usr/bin/env python3
"""
announce_with_ack.py - Send commands with ACK verification
"""
import sys
import select
def send_command(command):
"""Send command and wait for ACK"""
# Send command
sys.stdout.write(command + "\n")
sys.stdout.flush()
# Wait for response using select()
# Timeout after 5 seconds
ready, _, _ = select.select([sys.stdin], [], [], 5.0)
if ready:
response = sys.stdin.readline().strip()
if response == "done":
sys.stderr.write(f"[SUCCESS] {command}\n")
return True
elif response == "error":
sys.stderr.write(f"[ERROR] Command failed: {command}\n")
return False
elif response == "shutdown":
sys.stderr.write(f"[SHUTDOWN] ExaBGP is shutting down\n")
sys.exit(0)
else:
sys.stderr.write(f"[TIMEOUT] No response for: {command}\n")
return False
# Send commands with verification
send_command("announce route 100.10.0.0/24 next-hop self")
send_command("announce route 100.20.0.0/24 next-hop self")
# Keep running
while True:
import time
time.sleep(60)#!/usr/bin/env python3
"""
robust_announcer.py - Robust command sending with retries
"""
import sys
import select
import time
def send_with_retry(command, max_retries=3):
"""Send command with retry on failure"""
for attempt in range(max_retries):
sys.stdout.write(command + "\n")
sys.stdout.flush()
ready, _, _ = select.select([sys.stdin], [], [], 5.0)
if ready:
response = sys.stdin.readline().strip()
if response == "done":
sys.stderr.write(f"[OK] {command}\n")
return True
elif response == "error":
sys.stderr.write(f"[ERROR] Attempt {attempt + 1}/{max_retries}: {command}\n")
if attempt < max_retries - 1:
time.sleep(1)
continue
return False
elif response == "shutdown":
sys.stderr.write("[SHUTDOWN] ExaBGP shutting down\n")
sys.exit(0)
else:
sys.stderr.write(f"[TIMEOUT] Attempt {attempt + 1}/{max_retries}\n")
if attempt < max_retries - 1:
time.sleep(1)
continue
return False
return False
# Use it
if send_with_retry("announce route 100.10.0.0/24 next-hop self"):
sys.stderr.write("[INFO] Route announced successfully\n")
else:
sys.stderr.write("[FATAL] Failed to announce route after retries\n")
sys.exit(1)For ExaBGP 4.x or when ACK is disabled:
#!/usr/bin/env python3
"""
announce_no_ack.py - Works with ExaBGP 4.x (no ACK)
"""
import sys
import time
def send_command_no_ack(command):
"""Send command without waiting for ACK"""
sys.stdout.write(command + "\n")
sys.stdout.flush()
# No feedback - just hope it worked
sys.stderr.write(f"[SENT] {command}\n")
# ExaBGP 4.x style
send_command_no_ack("announce route 100.10.0.0/24 next-hop self")
# Keep running
while True:
time.sleep(60)#!/usr/bin/env python3
"""
detect_ack.py - Auto-detect if ACK is enabled
"""
import sys
import select
import os
def has_ack_support():
"""Test if ExaBGP has ACK enabled"""
# Check environment variable
ack = os.getenv('exabgp.api.ack', 'true')
return ack.lower() == 'true'
def send_command(command):
"""Send command with or without ACK based on support"""
sys.stdout.write(command + "\n")
sys.stdout.flush()
if has_ack_support():
# Wait for ACK
ready, _, _ = select.select([sys.stdin], [], [], 5.0)
if ready:
response = sys.stdin.readline().strip()
return response == "done"
return False
else:
# No ACK - assume success
return True
# Auto-adapts to ExaBGP version
send_command("announce route 100.10.0.0/24 next-hop self")Enable ACK (main/5.x default):
export exabgp.api.ack=true
exabgp /etc/exabgp/exabgp.confDisable ACK (for 4.x compatibility):
export exabgp.api.ack=false
exabgp /etc/exabgp/exabgp.confIn healthcheck application:
# For ExaBGP 4.x or when ACK disabled
python -m exabgp healthcheck --no-ack --cmd "curl -sf http://localhost/health"Benefits:
- ✅ Reliability: Know if commands succeeded
- ✅ Error Detection: Catch syntax errors immediately
- ✅ Debugging: Easier to troubleshoot failures
- ✅ Retry Logic: Can retry failed commands
- ✅ Production-Ready: Critical for automated systems
When to use:
- Production deployments
- Automated DDoS mitigation
- Critical route announcements
- Any scenario where silent failures are unacceptable
When not needed:
- Simple static announcements
- Testing/development
- ExaBGP 4.x compatibility required
ExaBGP logs errors but continues:
# Invalid command (ExaBGP 4.x behavior)
print("announce route 100.10.0.0/24") # Missing next-hopExaBGP log:
ERROR: Could not parse command: announce route 100.10.0.0/24
Your program won't know unless you monitor logs.
ExaBGP sends error response:
# Invalid command (ExaBGP 5.x with ACK)
sys.stdout.write("announce route 100.10.0.0/24\n") # Missing next-hop
sys.stdout.flush()
# Wait for response
ready, _, _ = select.select([sys.stdin], [], [], 5.0)
if ready:
response = sys.stdin.readline().strip()
if response == "error":
sys.stderr.write("[ERROR] Command rejected by ExaBGP\n")
# Check ExaBGP logs for detailsYour program knows immediately that the command failed.
If your program crashes:
- ExaBGP detects (broken pipe)
- ExaBGP logs error
- ExaBGP restarts process automatically
Best practice:
try:
main_loop()
except Exception as e:
sys.stderr.write(f"[ERROR] {e}\n")
sys.exit(1)If BGP session drops:
- ExaBGP continues running
- Your process continues running
- Commands are queued until session re-establishes
Receive updates about session state:
if msg['type'] == 'state':
if msg['neighbor']['state'] == 'down':
# Session dropped - stop announcing?
passsys.stdout.flush() # After every writeOr use unbuffered mode:
#!/usr/bin/env python3 -utime.sleep(2) # Give ExaBGP time to establish sessionwhile True:
time.sleep(60) # Don't exit immediatelyimport sys
def log(message):
sys.stderr.write(f"[{time.time()}] {message}\n")
log("Service UP - announced route")STDERR goes to ExaBGP log.
try:
result = check_service()
except Exception as e:
log(f"Health check error: {e}")
# Assume down on error
result = Falseannounced = False
if should_announce and not announced:
announce()
announced = True
elif not should_announce and announced:
withdraw()
announced = FalseAvoids redundant announcements.
def announce_route(prefix, nexthop="self"):
if not is_valid_prefix(prefix):
log(f"Invalid prefix: {prefix}")
return
sys.stdout.write(f"announce route {prefix} next-hop {nexthop}\n")
sys.stdout.flush()- Text API Reference - All text commands
- JSON API Reference - JSON message format
- API Commands - Complete command reference
- Writing API Programs - Best practices guide
- Production Best Practices - Production deployment
- Quick Start - Basic examples
- High Availability - Health check patterns
- DDoS Mitigation - FlowSpec automation
- Configuration Syntax - Process configuration
- Environment Variables - API environment
Ready to dive deeper? Continue to Text API Reference →
👻 Ghost written by Claude (Anthropic AI)
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