Skip to content

API Overview

Thomas Mangin edited this page Nov 13, 2025 · 7 revisions

API Overview

Understanding ExaBGP's STDIN/STDOUT API


Table of Contents


What is the ExaBGP API?

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

Why This API Design?

Philosophy

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

How It Works

Configuration

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

Process Startup

When ExaBGP starts:

  1. ExaBGP spawns /etc/exabgp/api/my-program.py as subprocess
  2. ExaBGP connects to program's STDIN/STDOUT
  3. Program can now:
    • Write to STDOUT β†’ Commands to ExaBGP
    • Read from STDIN β†’ BGP messages from ExaBGP

Communication

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Your Program       β”‚
β”‚                     β”‚
β”‚  print("announce    β”‚  STDOUT ──────┐
β”‚    route ...")      β”‚               β”‚
β”‚                     β”‚               β–Ό
β”‚  for line in stdin: β”‚  STDIN  ◀──────┬─────────┐
β”‚    process(line)    β”‚                β”‚ ExaBGP  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚         β”‚
                                       β”‚ BGP     β”‚
                                       β”‚ Speaker β”‚
                                       β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
                                            β”‚
                                            β”‚ BGP Protocol
                                            β–Ό
                                       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                       β”‚ Router  β”‚
                                       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Text vs JSON Encoders

ExaBGP supports two encoding formats: Text and JSON.

⚠️ Important: Commands are ALWAYS text format

  • Commands you send to ExaBGP (STDOUT): Always text format (announce route ..., withdraw route ...)
  • Messages from ExaBGP (STDIN): Can be text or JSON based on encoder setting
  • The encoder setting controls ONLY what format ExaBGP uses when sending messages to your program
  • You CANNOT send JSON commands to ExaBGP - all commands must be text

Text Encoder

Use case: Sending commands + receiving simple text messages

Configuration:

process my-program {
    run /etc/exabgp/api/announce.py;
    encoder text;
}

Format: Human-readable text commands (bidirectional)

Example commands (your program β†’ ExaBGP via STDOUT):

# Commands are ALWAYS text format, regardless of encoder setting
print("announce route 100.10.0.0/24 next-hop 192.0.2.1")
print("withdraw route 100.10.0.0/24")
print("announce flow route { match { destination 100.10.0.0/24; } then { discard; } }")

Pros:

  • βœ… Human-readable (both directions)
  • βœ… Easy to test manually
  • βœ… Simple for both sending and receiving

Cons:

  • ❌ Received messages harder to parse programmatically
  • ❌ Limited structured data from ExaBGP

JSON Encoder

Use case: Receiving BGP updates in structured format (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

Which Encoder Should You Use?

Remember: The encoder setting ONLY affects messages FROM ExaBGP TO your program. Commands from your program to ExaBGP are always text.

Use encoder text when:

  • Only sending commands (announce/withdraw)
  • Want simple, human-readable messages from ExaBGP
  • Testing manually
  • Don't need structured parsing of received messages

Use encoder json when:

  • Receiving BGP updates from router
  • Need structured data parsing
  • Building complex integrations
  • Want easy programmatic parsing of ExaBGP messages

Can use both (common pattern):

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

API Architecture

Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   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  β”‚
                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Process Manager

  • Spawns your programs as subprocesses
  • Monitors process health (restarts if crashed)
  • Routes commands from STDOUT to BGP engine
  • Sends BGP messages to process STDIN

Command Flow

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

Update Flow

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

Basic Examples

Example 1: Simple Route Announcement (Python)

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

Example 2: Health Check (Python)

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

Example 3: Receive BGP Updates (Python)

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

Example 4: Bash Script

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

Example 5: Go Program

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

Process Lifecycle

1. Startup

  1. ExaBGP starts
  2. Reads configuration file
  3. Spawns process(es) defined in process blocks
  4. Connects to process STDIN/STDOUT
  5. Waits for BGP session to establish

Your program should:

  • Sleep 2-5 seconds to wait for BGP session
  • Send initial announcements
  • Enter main loop

2. Running

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

3. Shutdown

Graceful shutdown (SIGTERM):

  1. ExaBGP receives SIGTERM
  2. ExaBGP sends SIGTERM to all processes
  3. Processes should exit cleanly
  4. ExaBGP closes BGP sessions (sends NOTIFICATION)
  5. ExaBGP exits

Process crash:

  1. Process exits/crashes
  2. ExaBGP detects (broken pipe or process exit)
  3. ExaBGP can restart process (if respawn enabled)

Configuration for auto-restart:

process my-program {
    run /etc/exabgp/api/my-program.py;
    encoder text;
}

ExaBGP automatically respawns crashed processes.


Communication Flow

Sending Commands (STDOUT β†’ ExaBGP)

# 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

Receiving Messages (STDIN ← ExaBGP)

# 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

Command Acknowledgment (ACK Feature)

βœ… Available in ExaBGP 4.x and 5.x/main - Default: enabled in both versions

What is ACK?

The ACK (acknowledgment) feature allows your program to receive feedback on whether commands succeeded or failed.

Behavior (when enabled, which is the default):

  • 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

Responses:

  • done\n - Command succeeded
  • error\n - Command failed (syntax error, invalid route, etc.)
  • shutdown\n - ExaBGP is shutting down

Default Configuration (both 4.x and 5.x/main):

# ACK is enabled by default in both versions
export exabgp.api.ack=true  # Default

# To disable (for simpler programs that don't check responses):
export exabgp.api.ack=false

Why Use ACK?

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 ACK is disabled:

  • Commands are fire-and-forget
  • No feedback on success/failure
  • Simpler code (no need to read responses)
  • Check ExaBGP logs for errors

Using ACK in Your Program

Robust ACK handling with polling loop:

#!/usr/bin/env python3
"""
announce_with_ack.py - Send commands with ACK verification
"""
import sys
import select
import time

def wait_for_ack(expected_count=1, timeout=30):
    """
    Wait for ACK responses from ExaBGP.

    Polls STDIN until all expected ACK messages are received.
    ExaBGP may not respond immediately, so we loop with sleep.

    Handles both text and JSON encoder formats:
    - Text: "done", "error", "shutdown"
    - JSON: {"answer": "done|error|shutdown", "message": "..."}

    Args:
        expected_count: Number of ACK messages expected (default: 1)
        timeout: Total timeout in seconds (default: 30)

    Returns:
        True if all ACKs received successfully
        False if any command failed or timeout occurred

    Raises:
        SystemExit: If ExaBGP sends shutdown message
    """
    import json
    received = 0
    start_time = time.time()

    while received < expected_count:
        # Check if we've exceeded timeout
        elapsed = time.time() - start_time
        if elapsed >= timeout:
            return False

        # Poll for data (non-blocking with short timeout)
        ready, _, _ = select.select([sys.stdin], [], [], 0.1)

        if ready:
            line = sys.stdin.readline().strip()

            # Parse response (could be text or JSON)
            answer = None
            if line.startswith('{'):
                # JSON format: {"answer": "done|error|shutdown", ...}
                try:
                    data = json.loads(line)
                    answer = data.get('answer')
                except:
                    pass
            else:
                # Text format: done|error|shutdown
                answer = line

            if answer == "done":
                received += 1
            elif answer == "error":
                return False
            elif answer == "shutdown":
                raise SystemExit(0)
            # Ignore other messages (could be BGP updates if receiving)
        else:
            # No data yet, sleep briefly before next poll
            time.sleep(0.1)

    return True

def send_command(command):
    """Send command and wait for ACK"""
    sys.stdout.write(command + "\n")
    sys.stdout.flush()
    return wait_for_ack(expected_count=1)

# Send commands with verification
if send_command("announce route 100.10.0.0/24 next-hop self"):
    sys.stderr.write("[OK] Route 100.10.0.0/24 announced\n")

if send_command("announce route 100.20.0.0/24 next-hop self"):
    sys.stderr.write("[OK] Route 100.20.0.0/24 announced\n")

# Keep running
while True:
    time.sleep(60)

When ACK is Disabled

When ACK is disabled (exabgp.api.ack=false):

#!/usr/bin/env python3
"""
announce_no_ack.py - Simple announcer when ACK is disabled
"""
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 - command is fire-and-forget
    sys.stderr.write(f"[SENT] {command}\n")

# Simpler code when ACK is disabled
send_command_no_ack("announce route 100.10.0.0/24 next-hop self")

# Keep running
while True:
    time.sleep(60)

Detecting ACK Support

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

Configuration

ACK is enabled by default in both ExaBGP 4.x and 5.x/main:

# Default (ACK enabled)
exabgp /etc/exabgp/exabgp.conf

# Explicitly enable ACK (already the default)
export exabgp.api.ack=true
exabgp /etc/exabgp/exabgp.conf

Disable ACK (for simpler programs that don't need feedback):

export exabgp.api.ack=false
exabgp /etc/exabgp/exabgp.conf

In healthcheck application:

# With ACK (default)
python -m exabgp healthcheck --cmd "curl -sf http://localhost/health"

# Without ACK (simpler mode)
python -m exabgp healthcheck --no-ack --cmd "curl -sf http://localhost/health"

When to Use ACK

Use ACK (recommended for production):

  • Production deployments
  • Automated DDoS mitigation
  • Critical route announcements
  • Any scenario where silent failures are unacceptable
  • When you need reliable error handling

Disable ACK when:

  • Simple static announcements that don't change
  • Testing/development with manual verification
  • Programs that don't need error feedback
  • Simpler code without response handling

Dynamic ACK Control (Runtime Commands)

βœ… Available in ExaBGP 5.x/main - Control ACK behavior dynamically at runtime

In addition to the startup configuration (exabgp.api.ack=true/false), ExaBGP 5.x/main provides three API commands to dynamically control ACK behavior during runtime:

The Three ACK Control Commands

1. enable-ack - Re-enable ACK Responses

enable-ack
  • Re-enables ACK responses for this API connection
  • Sends "done" ACK for the enable-ack command itself
  • All future commands will receive ACK responses (done or error)
  • Use case: Re-enable error checking after disabling it

2. disable-ack - Disable ACK with Final Response

disable-ack
  • Disables ACK responses for this API connection
  • Sends "done" ACK for the disable-ack command itself (final ACK)
  • Future commands will NOT receive ACK responses (fire-and-forget mode)
  • Use case: Disable ACK gracefully after receiving confirmation

3. silence-ack - Immediate Silence (No Response)

silence-ack
  • Disables ACK responses immediately
  • Does NOT send ACK for the silence-ack command itself
  • Future commands will NOT receive ACK responses (fire-and-forget mode)
  • Use case: Disable ACK immediately without waiting for final response

Comparison: disable-ack vs silence-ack

Command Sends ACK for itself? Future commands ACKed? Use Case
enable-ack βœ… Yes ("done") βœ… Yes Re-enable error checking
disable-ack βœ… Yes ("done", final ACK) ❌ No Graceful transition to fire-and-forget
silence-ack ❌ No (immediate silence) ❌ No Immediate transition to fire-and-forget

Key difference:

  • disable-ack: Sends one final "done" response, THEN stops ACKing
  • silence-ack: Stops ACKing immediately (no response to this command)

Example: Dynamic ACK Control

#!/usr/bin/env python3
"""
dynamic_ack_control.py - Demonstrate runtime ACK control
"""
import sys
import select
import time

def wait_for_ack(timeout=2):
    """Wait for ACK response (returns True if 'done', False otherwise)"""
    ready, _, _ = select.select([sys.stdin], [], [], timeout)
    if ready:
        response = sys.stdin.readline().strip()
        return response == "done"
    return False

def send_command(command, expect_ack=True):
    """Send command with optional ACK verification"""
    sys.stdout.write(command + "\n")
    sys.stdout.flush()

    if expect_ack:
        if wait_for_ack():
            sys.stderr.write(f"[OK] {command}\n")
            return True
        else:
            sys.stderr.write(f"[FAIL] {command}\n")
            return False
    else:
        sys.stderr.write(f"[SENT] {command} (no ACK expected)\n")
        return True

# Initial state: ACK is enabled by default
time.sleep(0.2)  # Wait for BGP session establishment

# Step 1: Send command with ACK (baseline)
send_command("announce route 100.10.0.0/24 next-hop self", expect_ack=True)

# Step 2: Disable ACK gracefully (receive final "done")
send_command("disable-ack", expect_ack=True)  # Gets final ACK

# Step 3: Send commands without ACK (fire-and-forget mode)
send_command("announce route 100.20.0.0/24 next-hop self", expect_ack=False)
send_command("announce route 100.30.0.0/24 next-hop self", expect_ack=False)

# Step 4: Re-enable ACK
send_command("enable-ack", expect_ack=True)  # ACK is back

# Step 5: Verify ACK is working again
send_command("announce route 100.40.0.0/24 next-hop self", expect_ack=True)

# Keep running
while True:
    time.sleep(60)

Output:

[OK] announce route 100.10.0.0/24 next-hop self
[OK] disable-ack
[SENT] announce route 100.20.0.0/24 next-hop self (no ACK expected)
[SENT] announce route 100.30.0.0/24 next-hop self (no ACK expected)
[OK] enable-ack
[OK] announce route 100.40.0.0/24 next-hop self

Example: silence-ack (Immediate Silence)

#!/usr/bin/env python3
"""
silence_ack_example.py - Demonstrate immediate ACK silencing
"""
import sys
import select
import time

def wait_for_ack(timeout=1):
    """Wait for ACK response"""
    ready, _, _ = select.select([sys.stdin], [], [], timeout)
    if ready:
        response = sys.stdin.readline().strip()
        return response == "done"
    return False

time.sleep(0.2)

# Step 1: Send command with ACK
sys.stdout.write("announce route 200.10.0.0/24 next-hop self\n")
sys.stdout.flush()
if wait_for_ack():
    sys.stderr.write("[OK] Route announced with ACK\n")

# Step 2: Use silence-ack (NO ACK for this command itself)
sys.stdout.write("silence-ack\n")
sys.stdout.flush()

# Note: No ACK expected for silence-ack itself
if wait_for_ack():
    sys.stderr.write("[ERROR] Unexpected ACK for silence-ack\n")
else:
    sys.stderr.write("[OK] silence-ack executed (no ACK as expected)\n")

# Step 3: Send commands without expecting ACK
sys.stdout.write("announce route 200.20.0.0/24 next-hop self\n")
sys.stdout.flush()
sys.stderr.write("[SENT] Route announced (no ACK expected)\n")

# Keep running
while True:
    time.sleep(60)

Use Cases for Dynamic ACK Control

When to use enable-ack:

  • Re-enable error checking after a batch of fire-and-forget commands
  • Switch from performance mode to reliability mode
  • Debugging: Enable ACK temporarily to verify commands

When to use disable-ack:

  • High-performance bulk announcements where ACK overhead is too high
  • Graceful transition to fire-and-forget mode
  • You still want confirmation that ACK was disabled successfully
  • Best practice: Using ExaBGP 5.x/main with non-ACK-aware API programs

When to use silence-ack:

  • Maximum performance: Skip even the final ACK response
  • Immediate transition to fire-and-forget mode
  • Bulk operations where even one ACK adds latency

Best Practice: Non-ACK-Aware Programs on ExaBGP 5.x/main

If you're running ExaBGP 5.x/main with legacy API programs that don't handle ACK responses, use disable-ack at the start of your program:

#!/usr/bin/env python3
"""
legacy_program.py - Old API program that doesn't handle ACK
"""
import sys
import time

# Best practice: Disable ACK at startup (safe on all ExaBGP versions)
# - ExaBGP 5.x/main: Disables ACK after sending final "done"
# - ExaBGP 4.x: Prints warning but continues (no harm)
sys.stdout.write("disable-ack\n")
sys.stdout.flush()
time.sleep(0.1)  # Give ExaBGP time to process

# Now your legacy code works without modification
while True:
    # Legacy code that doesn't expect ACK responses
    sys.stdout.write("announce route 100.10.0.100/32 next-hop self\n")
    sys.stdout.flush()

    # No ACK handling needed - fire-and-forget mode
    time.sleep(5)

Why this works:

  • βœ… ExaBGP 5.x/main: ACK is disabled, your program works normally
  • ⚠️ ExaBGP 4.x: Command is ignored with warning, but exabgp.api.ack=true default still applies (your program may hang if it doesn't handle ACK)
  • πŸ’‘ Solution: Also set exabgp.api.ack=false in environment for ExaBGP 4.x compatibility

For maximum compatibility across all ExaBGP versions:

# Run your program with both methods:
# 1. Environment variable (works on 4.x and 5.x)
# 2. disable-ack command (works on 5.x, ignored on 4.x)

export exabgp.api.ack=false
exabgp /etc/exabgp/exabgp.conf

Then in your program, still send disable-ack as defense-in-depth:

# Send disable-ack even if environment variable is set
# (no harm, ensures ACK is off regardless of config)
sys.stdout.write("disable-ack\n")
sys.stdout.flush()
time.sleep(0.1)

Example: Bulk Announcement Optimization

#!/usr/bin/env python3
"""
bulk_with_ack_control.py - Optimize bulk announcements
"""
import sys
import time

time.sleep(0.2)

# Step 1: Verify ExaBGP is ready (with ACK)
sys.stdout.write("announce route 10.0.0.1/32 next-hop self\n")
sys.stdout.flush()
# Check ACK...

# Step 2: Disable ACK for bulk operation (10,000 routes)
sys.stdout.write("silence-ack\n")  # Immediate silence for max performance
sys.stdout.flush()

# Step 3: Bulk announce without ACK overhead
for i in range(10000):
    prefix = f"10.{i//256}.{i%256}.0/24"
    sys.stdout.write(f"announce route {prefix} next-hop self\n")
    sys.stdout.flush()

# Step 4: Re-enable ACK to verify completion
sys.stdout.write("enable-ack\n")
sys.stdout.flush()
# Check ACK to confirm ExaBGP processed everything

# Step 5: Send verification command
sys.stdout.write("announce route 10.255.255.0/24 next-hop self\n")
sys.stdout.flush()
# Check ACK to confirm ExaBGP is still responsive

while True:
    time.sleep(60)

Version Compatibility

Feature ExaBGP 4.x ExaBGP 5.x/main
exabgp.api.ack=true/false (startup config) βœ… Yes βœ… Yes
enable-ack (runtime command) ⚠️ Ignored (warning) βœ… Yes
disable-ack (runtime command) ⚠️ Ignored (warning) βœ… Yes
silence-ack (runtime command) ⚠️ Ignored (warning) βœ… Yes

Backward Compatibility:

  • βœ… Safe to send to older versions: These commands are safe to send to ExaBGP 4.x
  • ⚠️ Warning only: Older versions will print a warning but will NOT crash or fail
  • 🎯 Best practice: Safe to include in API programs that may run on different ExaBGP versions

Migration notes:

  • ExaBGP 4.x: ACK behavior can only be set at startup via environment variable
  • ExaBGP 5.x/main: ACK behavior can be changed dynamically at runtime via API commands
  • Recommended: Use disable-ack at the start of your API program if you don't want to handle ACK responses

Error Handling

Command Errors (When ACK is Disabled)

ExaBGP logs errors but continues:

# Invalid command (when ACK is disabled)
print("announce route 100.10.0.0/24")  # Missing next-hop

ExaBGP log:

ERROR:    Could not parse command: announce route 100.10.0.0/24

Your program won't know unless you monitor logs.


Command Errors (When ACK is Enabled - Default)

ExaBGP sends error response:

# Invalid command (with ACK enabled - the default)
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 details

Your program knows immediately that the command failed.


Process Crashes

If your program crashes:

  1. ExaBGP detects (broken pipe)
  2. ExaBGP logs error
  3. ExaBGP restarts process automatically

Best practice:

try:
    main_loop()
except Exception as e:
    sys.stderr.write(f"[ERROR] {e}\n")
    sys.exit(1)

BGP Session Failures

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?
        pass

Best Practices

1. Always Flush STDOUT

sys.stdout.flush()  # After every write

Or use unbuffered mode:

#!/usr/bin/env python3 -u

2. Wait for BGP Session

time.sleep(2)  # Give ExaBGP time to establish session

3. Keep Process Alive

while True:
    time.sleep(60)  # Don't exit immediately

4. Use Structured Logging

import sys

def log(message):
    sys.stderr.write(f"[{time.time()}] {message}\n")

log("Service UP - announced route")

STDERR goes to ExaBGP log.

5. Handle Errors Gracefully

try:
    result = check_service()
except Exception as e:
    log(f"Health check error: {e}")
    # Assume down on error
    result = False

6. Use State Tracking

announced = False

if should_announce and not announced:
    announce()
    announced = True
elif not should_announce and announced:
    withdraw()
    announced = False

Avoids redundant announcements.

7. Validate Commands

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

Next Steps

Detailed References

Examples

Configuration


Ready to dive deeper? Continue to Text API Reference β†’


πŸ‘» Ghost written by Claude (Anthropic AI)

Clone this wiki locally