Skip to content

Text API Reference

Thomas Mangin edited this page Mar 6, 2026 · 15 revisions

Text API Reference

Complete reference for ExaBGP's text-based API commands


Table of Contents


Overview

The text encoder uses human-readable commands for announcing and withdrawing routes.

Configuration:

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

Usage in your program:

import sys

# Announce a route
sys.stdout.write("announce route 100.10.0.0/24 next-hop self\n")
sys.stdout.flush()  # CRITICAL - always flush!

# Withdraw a route
sys.stdout.write("withdraw route 100.10.0.0/24\n")
sys.stdout.flush()

Command Syntax

Basic Structure

<action> <family> <prefix> [attributes]

Actions:

  • announce - Add/update a route
  • withdraw - Remove a route

Families:

  • route - IPv4/IPv6 unicast
  • flow - FlowSpec
  • vpn - L3VPN
  • evpn - EVPN

Critical Rules

  1. Always end with newline (\n)
  2. Always flush after write (sys.stdout.flush())
  3. One command per line
  4. Case-sensitive (announce not ANNOUNCE)

Neighbor Selectors

When ExaBGP is configured with multiple BGP neighbors, you can use neighbor selectors to target specific peers with your API commands.

Syntax

neighbor <ip> [selector-options] <command>

By default, commands apply to all configured neighbors. Use the neighbor keyword to target specific peer(s) instead of broadcasting to all neighbors.

Available Selectors

Selector Description Example
neighbor <ip> Required: Target neighbor by IP neighbor 127.0.0.1
local-ip <ip> Filter by local IP address local-ip 192.168.1.2
local-as <asn> Filter by local ASN local-as 65001
peer-as <asn> Filter by peer ASN peer-as 65000
router-id <id> Filter by router ID router-id 192.168.1.1
family-allowed <families> Filter by address family capabilities family-allowed ipv4-unicast/ipv6-unicast

Examples

Single neighbor:

# Announce to one specific neighbor
print("neighbor 127.0.0.1 announce route 10.0.0.0/24 next-hop 192.168.1.1")

All neighbors (wildcard):

# Announce to ALL configured neighbors
print("neighbor * announce route 10.0.0.0/24 next-hop self")

Multiple neighbors (comma-separated):

# Announce to specific set of neighbors
print("neighbor 10.0.0.1,10.0.0.2,10.0.0.3 announce route 100.10.0.0/24 next-hop self")

With peer-as filter:

# Only announce to neighbors with peer-as 65000
print("neighbor 127.0.0.1 peer-as 65000 announce route 10.0.0.0/24 next-hop self")

With local-as filter:

# Differentiate when multiple neighbors share same IP but different local-as
print("neighbor 127.0.0.1 local-as 65001 announce route 10.0.0.0/24 next-hop self")

Multiple selectors:

# Combine multiple selectors (AND logic)
print("neighbor 127.0.0.1 local-as 65001 peer-as 65000 announce route 10.0.0.0/24 next-hop self")

Filter by address family capabilities:

# Target neighbors with FlowSpec support
print("neighbor * family-allowed ipv4-flowspec announce flow route { match { destination 10.0.0.0/8; } then { discard; } }")

# Target multi-session neighbors with dual-stack
print("neighbor * family-allowed ipv4-unicast/ipv6-unicast announce route 10.0.0.0/24 next-hop self")

# Target single-session neighbors
print("neighbor * family-allowed in-open announce route 10.0.0.0/24 next-hop self")

Note: The family-allowed value is:

  • in-open for single-session neighbors (default - all families in BGP OPEN)
  • Slash-separated families for multi-session neighbors (e.g., ipv4-unicast/ipv6-unicast/ipv4-flowspec)

Complete Example

#!/usr/bin/env python3
"""
selective_announcement.py - Announce routes to specific neighbors
"""
import sys
import time

time.sleep(2)  # Wait for BGP sessions

# Announce to all neighbors
sys.stdout.write("neighbor * announce route 100.10.0.0/24 next-hop self\n")
sys.stdout.flush()

# Announce specific route to peer in AS 65000
sys.stdout.write("neighbor * peer-as 65000 announce route 100.20.0.0/24 next-hop self\n")
sys.stdout.flush()

# Announce with AS path prepending to one neighbor
sys.stdout.write("neighbor 192.168.1.1 announce route 100.30.0.0/24 next-hop self as-path [65001 65001 65001]\n")
sys.stdout.flush()

# Announce different routes to different providers
sys.stdout.write("neighbor 10.0.1.1 announce route 100.40.0.0/24 next-hop self community [65001:100]\n")  # Provider A
sys.stdout.flush()

sys.stdout.write("neighbor 10.0.2.1 announce route 100.40.0.0/24 next-hop self community [65001:200]\n")  # Provider B
sys.stdout.flush()

while True:
    time.sleep(60)

FlowSpec with Neighbor Selectors

# Announce FlowSpec rule to specific neighbor
print("neighbor 192.168.1.1 announce flow route { match { destination 10.0.0.0/8; } then { discard; } }")

# Announce FlowSpec to all neighbors
print("neighbor * announce flow route { match { source 203.0.113.0/24; destination-port =80; protocol =tcp; } then { discard; } }")

# Target specific peer-as for DDoS mitigation
print("neighbor * peer-as 65000 announce flow route { match { destination 100.10.0.0/24; } then { rate-limit 1000000; } }")

Withdraw with Neighbor Selectors

# Withdraw from specific neighbor
print("neighbor 192.168.1.1 withdraw route 10.0.0.0/24")

# Withdraw from all neighbors
print("neighbor * withdraw route 10.0.0.0/24")

# Withdraw from multiple neighbors
print("neighbor 10.0.0.1,10.0.0.2 withdraw route 100.10.0.0/24")

Important Notes

  • 💡 Default behavior: Commands without neighbor selector apply to all configured neighbors
  • 💡 Selective targeting: Use neighbor keyword to target specific peer(s)
  • 💡 Selectors use AND logic - all specified selectors must match
  • 💡 neighbor * explicitly targets all configured neighbors (same as omitting selector)
  • 💡 If no neighbor matches your selectors, the command is silently ignored
  • 💡 Comma-separated IPs are treated as OR - any match succeeds

IPv4/IPv6 Unicast

IPv4 Unicast

Basic Announcement

IPv4:

# IPv4 prefix with explicit next-hop
print("announce route 100.10.0.0/24 next-hop 192.0.2.1")

# /32 host route
print("announce route 100.10.0.100/32 next-hop 192.0.2.1")

IPv6 Unicast

IPv6:

# IPv6 prefix
print("announce route 2001:db8::/32 next-hop 2001:db8::1")

# IPv6 /128 host route
print("announce route 2001:db8::100/128 next-hop 2001:db8::1")

Withdrawal

# IPv4
print("withdraw route 100.10.0.0/24")

# IPv6
print("withdraw route 2001:db8::/32")

# Note: No need to specify next-hop or attributes when withdrawing

Route Attributes

Next-Hop

⚠️ Warning: next-hop self is an EXPERIMENTAL feature. Always use explicit next-hop IP addresses in production.

Explicit next-hop (recommended):

print("announce route 100.10.0.0/24 next-hop 192.0.2.1")
# Use the appropriate next-hop IP for your network
print("announce route 2001:db8::/32 next-hop 2001:db8::1")

Third-party next-hop:

# Tell peer to use different next-hop
print("announce route 100.10.0.0/24 next-hop 10.0.0.1")

MED (Multi-Exit Discriminator)

Lower MED is preferred. Use for traffic engineering.

# Preferred path (lower MED)
print("announce route 100.10.0.0/24 next-hop self med 50")

# Backup path (higher MED)
print("announce route 100.10.0.0/24 next-hop self med 100")

Local Preference

Higher local-preference is preferred. iBGP only.

# Preferred path (higher local-pref)
print("announce route 100.10.0.0/24 next-hop self local-preference 200")

# Less preferred
print("announce route 100.10.0.0/24 next-hop self local-preference 100")

AS Path

Normal announcement:

print("announce route 100.10.0.0/24 next-hop self")
# AS path: [ 65001 ] (your local-as)

AS path prepending:

# Prepend your AS twice
print("announce route 100.10.0.0/24 next-hop self as-path [ 65001 65001 65001 ]")
# AS path becomes: 65001 65001 65001 (looks like longer path)

Custom AS path:

# Announce as if from different AS
print("announce route 100.10.0.0/24 next-hop self as-path [ 65002 65003 ]")

Communities

Standard communities (RFC 1997):

# Single community
print("announce route 100.10.0.0/24 next-hop self community [ 65001:100 ]")

# Multiple communities
print("announce route 100.10.0.0/24 next-hop self community [ 65001:100 65001:200 ]")

# Well-known communities
print("announce route 100.10.0.0/24 next-hop self community [ no-export ]")
print("announce route 100.10.0.0/24 next-hop self community [ no-advertise ]")

Well-known communities:

  • no-export (0xFFFFFF01) - Don't advertise to eBGP peers
  • no-advertise (0xFFFFFF02) - Don't advertise to any peer
  • no-export-subconfed (0xFFFFFF03) - Don't export outside confederation

Extended communities (RFC 4360):

# Route target
print("announce route 100.10.0.0/24 next-hop self extended-community [ target:65001:100 ]")

# Route origin
print("announce route 100.10.0.0/24 next-hop self extended-community [ origin:65001:100 ]")

# Multiple
print("announce route 100.10.0.0/24 next-hop self extended-community [ target:65001:100 target:65001:200 ]")

Large communities (RFC 8092):

# Format: global:local1:local2
print("announce route 100.10.0.0/24 next-hop self large-community [ 65001:100:200 ]")

Origin

# IGP origin (default)
print("announce route 100.10.0.0/24 next-hop self origin igp")

# EGP origin
print("announce route 100.10.0.0/24 next-hop self origin egp")

# Incomplete origin
print("announce route 100.10.0.0/24 next-hop self origin incomplete")

Combining Attributes

# Complex announcement with multiple attributes
print("announce route 100.10.0.0/24 next-hop self "
      "med 100 "
      "local-preference 200 "
      "community [ 65001:100 65001:200 ] "
      "as-path [ 65001 ]")

Bulk Announcements (announce attributes ... nlri)

Version Availability: ExaBGP 4.0+

Purpose: Announce multiple prefixes with the same attributes in a single command, reducing parsing overhead and improving performance.

Syntax:

announce attribute[s] <attributes> nlri <prefix1> <prefix2> <prefix3> ...
announce attributes <attributes> nlri <prefix1> <prefix2> ...

Both singular attribute and plural attributes work identically.

Basic Usage

Announce multiple prefixes with same next-hop and MED:

# Two prefixes with same attributes
print("announce attributes med 100 next-hop 101.1.101.1 nlri 1.0.0.1/32 1.0.0.2/32")

Multiple prefixes with AS-PATH and local-preference:

print("announce attributes local-preference 200 as-path [ 1 2 3 4 ] next-hop 202.2.202.2 nlri 2.0.0.1/32 2.0.0.2/32")

Advanced: VPN with Labels

# L3VPN with route-distinguisher and label
print("announce attribute route-distinguisher 63333:100 "
      "label [ 110 ] "
      "next-hop 10.0.99.12 "
      "origin igp "
      "as-path [100, 500] "
      "local-preference 100 "
      "extended-community 0:0 "
      "originator-id 10.0.99.12 "
      "nlri 128.0.64.0/18 128.0.0.0/18")

Bulk Announcements (Hundreds of Prefixes)

Efficient for announcing many prefixes:

#!/usr/bin/env python3
import sys

# Build list of prefixes
prefixes = []
for ip in range(0, 224):
    prefixes.append(f"{ip}.0.0.0/8")

# Announce all prefixes with same attributes in ONE command
nlri_list = ' '.join(prefixes)
command = f"announce attribute next-hop 1.2.3.4 med 100 as-path [ 100 101 102 ] nlri {nlri_list}"

sys.stdout.write(command + "\n")
sys.stdout.flush()

This announces 224 prefixes in a single command instead of 224 separate announce route commands.

Performance Benefits

Without bulk announcement (slow):

# 1000 separate commands
for i in range(1000):
    print(f"announce route 10.{i//256}.{i%256}.0/24 next-hop 192.0.2.1 med 100")
    sys.stdout.flush()

With bulk announcement (fast):

# 1 command for 1000 prefixes
prefixes = [f"10.{i//256}.{i%256}.0/24" for i in range(1000)]
print(f"announce attributes next-hop 192.0.2.1 med 100 nlri {' '.join(prefixes)}")
sys.stdout.flush()

Performance gain: ~10-100x faster for bulk operations (reduces parsing overhead, fewer command round-trips).

Withdrawal (Bulk)

Syntax:

withdraw attribute[s] <attributes> nlri <prefix1> <prefix2> ...

Withdraw multiple prefixes:

# Withdraw multiple prefixes
print("withdraw attributes nlri 1.0.0.1/32 1.0.0.2/32")

With next-hop (if required):

# Some configurations require next-hop for withdrawal
print("withdraw attributes next-hop 101.1.101.1 nlri 2.0.0.1/32 2.0.0.2/32")

When to Use Bulk Announcements

Use bulk announcements when:

  • Announcing 10+ prefixes with identical attributes
  • Building initial full BGP table (thousands of routes)
  • Performance-critical applications
  • High route churn environments

Use individual announce route when:

  • Prefixes have different attributes
  • Announcing 1-5 routes
  • Attributes differ per prefix
  • Debugging (easier to see individual commands)

Examples

Example 1: Anycast service announcement:

# Announce service IPs from multiple PoPs with same attributes
service_ips = [
    "100.64.1.1/32",
    "100.64.1.2/32",
    "100.64.1.3/32",
    "100.64.1.4/32",
]

print(f"announce attributes next-hop self community [ 65001:100 ] nlri {' '.join(service_ips)}")

Example 2: DDoS blackhole (bulk):

# Blackhole multiple attacking IPs
attackers = [
    "203.0.113.10/32",
    "203.0.113.20/32",
    "203.0.113.30/32",
]

# Blackhole community + no-export
print(f"announce attributes next-hop 192.0.2.1 community [ 65001:666 no-export ] nlri {' '.join(attackers)}")

Example 3: Load balancer pool (dynamic):

# Announce healthy backends only
healthy_backends = check_health()  # Returns list of IPs

if healthy_backends:
    prefixes = [f"{ip}/32" for ip in healthy_backends]
    print(f"announce attributes next-hop self local-preference 200 nlri {' '.join(prefixes)}")

Common Errors

❌ Wrong: Mixing attributes per prefix

# This does NOT work - all prefixes get same attributes
print("announce attributes next-hop 1.2.3.4 nlri 10.0.0.0/24 next-hop 5.6.7.8 20.0.0.0/24")
# ERROR: Syntax error

✅ Correct: Use separate commands for different attributes

# Different attributes = different commands
print("announce route 10.0.0.0/24 next-hop 1.2.3.4")
print("announce route 20.0.0.0/24 next-hop 5.6.7.8")

❌ Wrong: Forgetting nlri keyword

# Missing 'nlri' keyword
print("announce attributes next-hop 1.2.3.4 10.0.0.0/24 20.0.0.0/24")
# ERROR: Will fail to parse

✅ Correct: Include nlri keyword

print("announce attributes next-hop 1.2.3.4 nlri 10.0.0.0/24 20.0.0.0/24")

Compatibility

Feature ExaBGP 3.x ExaBGP 4.x ExaBGP 5.x
announce attribute[s] ... nlri ❌ Not available ✅ Available ✅ Available
withdraw attribute[s] ... nlri ❌ Not available ✅ Available ✅ Available

Migration note: If upgrading from 3.x, you can now use bulk announcements to improve performance.


FlowSpec (IPv4/IPv6)

FlowSpec allows BGP-based traffic filtering. ExaBGP pioneered open-source FlowSpec support (now also in GoBGP, FRRouting, BIRD).

Basic Structure

announce flow route {
    match {
        <match-conditions>
    }
    then {
        <actions>
    }
}

Match Conditions

Destination prefix:

print("announce flow route { "
      "match { destination 100.10.0.0/24; } "
      "then { discard; } "
      "}")

Source prefix:

print("announce flow route { "
      "match { source 10.0.0.0/8; } "
      "then { discard; } "
      "}")

Ports:

# Destination port
print("announce flow route { "
      "match { destination-port =80; } "
      "then { discard; } "
      "}")

# Source port
print("announce flow route { "
      "match { source-port =1234; } "
      "then { discard; } "
      "}")

# Port range
print("announce flow route { "
      "match { destination-port >=1024&<=65535; } "
      "then { discard; } "
      "}")

Port operators:

  • =80 - Equals 80
  • >1023 - Greater than 1023
  • <1024 - Less than 1024
  • >=1024 - Greater than or equal
  • <=65535 - Less than or equal
  • >=1024&<=65535 - Range (AND)

Protocol:

# TCP (6)
print("announce flow route { "
      "match { protocol =tcp; } "
      "then { discard; } "
      "}")

# UDP (17)
print("announce flow route { "
      "match { protocol =udp; } "
      "then { discard; } "
      "}")

# ICMP (1)
print("announce flow route { "
      "match { protocol =icmp; } "
      "then { discard; } "
      "}")

# Numeric
print("announce flow route { "
      "match { protocol =6; } "
      "then { discard; } "
      "}")

TCP flags:

# SYN flag set
print("announce flow route { "
      "match { tcp-flags [ syn ]; } "
      "then { discard; } "
      "}")

# SYN+ACK
print("announce flow route { "
      "match { tcp-flags [ syn ack ]; } "
      "then { discard; } "
      "}")

# All flags
print("announce flow route { "
      "match { tcp-flags [ fin syn rst psh ack urg ]; } "
      "then { discard; } "
      "}")

Packet length:

print("announce flow route { "
      "match { packet-length >=1000; } "
      "then { discard; } "
      "}")

ICMP type/code:

# ICMP echo request (type 8)
print("announce flow route { "
      "match { icmp-type =8; } "
      "then { discard; } "
      "}")

# ICMP echo reply (type 0)
print("announce flow route { "
      "match { icmp-type =0; icmp-code =0; } "
      "then { discard; } "
      "}")

DSCP (Differentiated Services Code Point):

print("announce flow route { "
      "match { dscp =46; } "
      "then { discard; } "
      "}")

Fragment:

# Fragmented packets
print("announce flow route { "
      "match { fragment [ is-fragment ]; } "
      "then { discard; } "
      "}")

# First fragment
print("announce flow route { "
      "match { fragment [ first-fragment ]; } "
      "then { discard; } "
      "}")

# Don't fragment (DF bit set)
print("announce flow route { "
      "match { fragment [ dont-fragment ]; } "
      "then { discard; } "
      "}")

Multiple Match Conditions (AND logic)

# Block SYN floods to port 80 from 10.0.0.0/8
print("announce flow route { "
      "match { "
        "source 10.0.0.0/8; "
        "destination-port =80; "
        "protocol =tcp; "
        "tcp-flags [ syn ]; "
      "} "
      "then { discard; } "
      "}")

Actions

Discard (drop):

print("announce flow route { "
      "match { destination 100.10.0.0/24; } "
      "then { discard; } "
      "}")

Rate-limit:

# Limit to 1 MB/s (~8 Mbps) (1,000,000 bytes/sec per RFC 5575)
print("announce flow route { "
      "match { destination 100.10.0.0/24; } "
      "then { rate-limit 1000000; } "
      "}")

# Limit to 10 MB/s (~80 Mbps)
print("announce flow route { "
      "match { source 10.0.0.0/8; } "
      "then { rate-limit 10000000; } "
      "}")

Redirect to VRF:

print("announce flow route { "
      "match { destination 100.10.0.0/24; } "
      "then { redirect 65001:100; } "
      "}")

Mark (DSCP remarking):

print("announce flow route { "
      "match { destination 100.10.0.0/24; } "
      "then { mark 46; } "
      "}")

Community (tagging):

print("announce flow route { "
      "match { destination 100.10.0.0/24; } "
      "then { community [ 65001:666 ]; } "
      "}")

FlowSpec Withdrawal

# Withdraw by match conditions
print("withdraw flow route { "
      "match { destination 100.10.0.0/24; } "
      "}")

FlowSpec Examples

Block DDoS attack:

# Block UDP flood from 10.0.0.0/8 to DNS port
print("announce flow route { "
      "match { "
        "source 10.0.0.0/8; "
        "destination-port =53; "
        "protocol =udp; "
      "} "
      "then { discard; } "
      "}")

Rate-limit HTTP traffic:

# Rate-limit HTTP traffic (100 MB/sec = bytes/sec per RFC 5575)
print("announce flow route { "
      "match { "
        "destination-port =80; "
        "protocol =tcp; "
      "} "
      "then { rate-limit 100000000; } "
      "}")

Block large packets (possible attack):

# Drop packets > 1500 bytes
print("announce flow route { "
      "match { packet-length >1500; } "
      "then { discard; } "
      "}")

L3VPN (VPNv4/VPNv6)

Layer 3 VPN (RFC 4364) for MPLS VPNs.

Announcement

# Basic L3VPN route
print("announce ipv4 mpls-vpn 100.10.0.0/24 "
      "next-hop self "
      "route-distinguisher 65001:100 "
      "label 10000")

# With route target
print("announce ipv4 mpls-vpn 100.10.0.0/24 "
      "next-hop self "
      "route-distinguisher 65001:100 "
      "extended-community [ target:65001:100 ] "
      "label 10000")

# Multiple route targets (export to multiple VRFs)
print("announce ipv4 mpls-vpn 100.10.0.0/24 "
      "next-hop self "
      "route-distinguisher 65001:100 "
      "extended-community [ target:65001:100 target:65001:200 ] "
      "label 10000")

Withdrawal

print("withdraw ipv4 mpls-vpn 100.10.0.0/24 route-distinguisher 65001:100")

EVPN

Ethernet VPN (RFC 7432) for data center fabrics.

Note: The text API does not have a dedicated announce evpn command. EVPN routes are typically configured via the ExaBGP configuration file, not the text API. The examples below show the configuration file syntax for reference.

EVPN Route Type 2 (MAC/IP Advertisement) - Configuration File

MAC only:

route-type mac-ip aa:bb:cc:dd:ee:ff {
    route-distinguisher 65001:100;
    next-hop self;
    label [ 10000 ];
    extended-community [ target:65001:100 ];
}

MAC + IP:

route-type mac-ip aa:bb:cc:dd:ee:ff 100.10.0.100 {
    route-distinguisher 65001:100;
    next-hop self;
    label [ 10000 ];
    extended-community [ target:65001:100 ];
}

With VNI (VXLAN):

route-type mac-ip aa:bb:cc:dd:ee:ff 100.10.0.100 {
    route-distinguisher 65001:100;
    next-hop self;
    label [ 5000 ];
    extended-community [ target:65001:100 encapsulation:vxlan ];
}

EVPN Route Type 3 (Inclusive Multicast) - Configuration File

route-type multicast 100.10.0.1 {
    route-distinguisher 65001:100;
    next-hop self;
    extended-community [ target:65001:100 ];
}

Note on EVPN via the Text API

EVPN routes cannot be announced or withdrawn via the text API. Use the ExaBGP configuration file to define EVPN routes within an l2vpn family block. See the Configuration Syntax for details.


BGP-LS

BGP Link-State (RFC 7752) for topology collection.

Note: BGP-LS is typically receive-only in ExaBGP. Used for collecting topology information from IGP.

Configuration to receive BGP-LS:

neighbor 192.168.1.1 {
    router-id 192.168.1.2;
    local-address 192.168.1.2;
    local-as 65001;
    peer-as 65000;

    family {
        ipv4 link-state;
        ipv6 link-state;
    }

    api {
        processes [ receive-topology ];
    }
}

No text API for announcing BGP-LS - it's receive-only.


VPLS

Virtual Private LAN Service (L2VPN).

# VPLS route
print("announce vpls "
      "route-distinguisher 65001:100 "
      "endpoint 192.168.1.2 "
      "offset 10 "
      "size 8 "
      "base 10000 "
      "extended-community [ target:65001:100 ]")

Withdrawal

print("withdraw vpls route-distinguisher 65001:100")

Multicast

Multicast routing (IPv4 and IPv6 multicast).

IPv4 Multicast

# Announce IPv4 multicast route
print("announce route 239.1.1.0/24 next-hop self")

# With source (S,G multicast)
print("announce route 239.1.1.0/24 next-hop self")

IPv6 Multicast

# Announce IPv6 multicast route
print("announce route ff02::1/128 next-hop 2001:db8::1")

Note: Multicast configuration requires appropriate address family configuration in ExaBGP config file.


RT-Constraint

Route Target Constraint (RFC 4684) for efficient VPN route distribution.

Announcement

# Announce RT membership (I'm interested in routes with this RT)
print("announce rtc 65001:100 next-hop self")

# Withdraw RT membership
print("withdraw rtc 65001:100")

Use case: Reduces unnecessary VPN route advertisements by signaling which route targets you're interested in.


Withdraw Commands

IPv4/IPv6 Unicast Withdrawal

# IPv4
print("withdraw route 100.10.0.0/24")

# IPv6
print("withdraw route 2001:db8::/32")

FlowSpec Withdrawal

# Withdraw FlowSpec rule by match conditions
print("withdraw flow route { match { destination 100.10.0.0/24; } }")

L3VPN Withdrawal

print("withdraw ipv4 mpls-vpn 100.10.0.0/24 route-distinguisher 65001:100")

EVPN Withdrawal

Note: EVPN routes cannot be withdrawn via the text API. EVPN is configured via the ExaBGP configuration file. See the EVPN section above.

Bulk Withdrawal

# Withdraw multiple prefixes at once
print("withdraw attributes nlri 100.10.0.0/24 100.20.0.0/24 100.30.0.0/24")

Operational Commands

Refresh Routes

Force route refresh (send all routes again):

print("announce route-refresh ipv4 unicast")
print("announce route-refresh ipv6 unicast")

Operational Messages

Send operational messages:

# Query operational state
print("operational afi ipv4 safi unicast advisory rib in")

ACK Control Commands (ExaBGP 5.x/main)

Dynamically control command acknowledgment behavior at runtime:

# Re-enable ACK responses (sends "done" for this command, then enables ACK for future commands)
print("enable-ack")

# Disable ACK responses gracefully (sends final "done" for this command, then disables ACK)
print("disable-ack")

# Disable ACK immediately (NO response for this command, immediate silence)
print("silence-ack")

Use cases:

  • enable-ack: Re-enable error checking after fire-and-forget mode
  • disable-ack: Graceful transition to high-performance mode (final ACK confirms the change)
  • silence-ack: Maximum performance (no overhead, immediate silence)

Example: Optimize bulk announcements

import sys
import time

# 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 for "done" response...

# 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 for "done" to confirm ExaBGP is responsive

Version compatibility:

  • ExaBGP 4.x: Commands are ignored with warning (safe, no harm)
  • ExaBGP 5.x/main: ACK can be controlled dynamically at runtime with these commands

Best practice for non-ACK-aware programs:

Use disable-ack at the start of your program when running on ExaBGP 5.x/main:

#!/usr/bin/env python3
import sys
import time

# Best practice: Safe on all ExaBGP versions
# - ExaBGP 5.x/main: Disables ACK (works as intended)
# - ExaBGP 4.x: Prints warning but continues (no harm)
sys.stdout.write("disable-ack\n")
sys.stdout.flush()
time.sleep(0.1)  # Brief pause for processing

# Your legacy code works without modification
while True:
    sys.stdout.write("announce route 100.10.0.100/32 next-hop self\n")
    sys.stdout.flush()
    time.sleep(5)

For maximum compatibility across ExaBGP 4.x and 5.x:

# Combine environment variable + disable-ack command
export exabgp.api.ack=false  # Works on 4.x and 5.x
exabgp /etc/exabgp/exabgp.conf

See also:


Examples by Use Case

High Availability with Anycast

Announce service IP when healthy:

import sys
import time
import socket

SERVICE_IP = "100.10.0.100"
SERVICE_PORT = 80

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

DDoS Mitigation (FlowSpec)

Auto-block detected attacks:

import sys
import time

def detect_attack():
    # Your DDoS detection logic
    # Returns (source_ip, dest_port, protocol) if attack detected
    return ("10.0.0.0/8", 80, "tcp")

time.sleep(2)

while True:
    attack = detect_attack()
    if attack:
        source, port, proto = attack
        # Block the attack
        sys.stdout.write(
            f"announce flow route {{ "
            f"match {{ source {source}; destination-port ={port}; protocol ={proto}; }} "
            f"then {{ discard; }} "
            f"}}\n"
        )
        sys.stdout.flush()
        sys.stderr.write(f"[BLOCKED] Attack from {source} to port {port}\n")

    time.sleep(1)

Load Balancing with Metrics

Announce with different metrics for load distribution:

import sys
import time

time.sleep(2)

# This server gets most traffic (lower MED)
sys.stdout.write("announce route 100.10.0.100/32 next-hop self med 100\n")
sys.stdout.flush()

# Another server as backup (higher MED)
# On the other server, announce with med 200

Traffic Engineering

Prefer specific path with communities:

import sys
import time

time.sleep(2)

# Mark route with community for policy
sys.stdout.write(
    "announce route 100.10.0.0/24 next-hop self "
    "community [ 65001:100 ] "
    "local-preference 200\n"
)
sys.stdout.flush()

Common Errors

1. Forgot to Flush

Wrong:

print("announce route 100.10.0.0/24 next-hop self")
# Nothing happens - stuck in buffer

Correct:

print("announce route 100.10.0.0/24 next-hop self")
sys.stdout.flush()  # Now it works

2. Missing next-hop

Wrong:

print("announce route 100.10.0.0/24")
# Error: next-hop required

Correct:

print("announce route 100.10.0.0/24 next-hop self")

3. Invalid Syntax

Wrong:

print("announce 100.10.0.0/24 next-hop self")
# Missing 'route' keyword

Correct:

print("announce route 100.10.0.0/24 next-hop self")

4. FlowSpec Missing Braces

Wrong:

print("announce flow route match destination 100.10.0.0/24 then discard")
# Missing { } braces

Correct:

print("announce flow route { match { destination 100.10.0.0/24; } then { discard; } }")

5. Withdraw with Attributes

Wrong:

print("withdraw route 100.10.0.0/24 next-hop self")
# Withdraw doesn't need attributes

Correct:

print("withdraw route 100.10.0.0/24")

Quick Reference

IPv4 Unicast

announce route <prefix> next-hop self [attributes]
withdraw route <prefix>

Bulk Announcements (4.0+)

announce attributes <attributes> nlri <prefix1> <prefix2> ...
withdraw attributes nlri <prefix1> <prefix2> ...

FlowSpec

announce flow route { match { <conditions>; } then { <action>; } }
withdraw flow route { match { <conditions>; } }

L3VPN

announce ipv4 mpls-vpn <prefix> next-hop self route-distinguisher <rd> label <label> [extended-community [...]]
withdraw ipv4 mpls-vpn <prefix> route-distinguisher <rd>

EVPN

EVPN routes are configured via the ExaBGP configuration file, not the text API. See the EVPN section above.


See Also


Need JSON format? See JSON API Reference


Clone this wiki locally