-
Notifications
You must be signed in to change notification settings - Fork 460
JSON API Reference
Complete reference for ExaBGP's JSON-encoded messages
- Overview
- Message Structure
- Message Types
- UPDATE Messages
- STATE Messages
- NOTIFICATION Messages
- Parsing Patterns
- Complete Examples
- Best Practices
The JSON encoder provides structured BGP messages to your API program via STDIN.
Configuration:
process receive-routes {
run /etc/exabgp/api/receive.py;
encoder json;
receive {
parsed; # Receive parsed BGP messages
updates; # Receive route updates
neighbor-changes; # Receive session state changes
}
}Usage in your program:
import sys
import json
while True:
line = sys.stdin.readline()
if not line:
break
message = json.loads(line)
print(f"Message type: {message['type']}")Format:
- One JSON object per line
- UTF-8 encoded
- No newlines within JSON (compact format)
- Continuous stream (process must read continuously)
Every JSON message includes:
{
"exabgp": "4.2.25", // ExaBGP version
"time": 1699564800.123, // Unix timestamp (float)
"host": "hostname", // Hostname
"pid": 12345, // ExaBGP process ID
"ppid": 1, // Parent process ID
"counter": 1, // Message counter (increments)
"type": "update", // Message type
"neighbor": { // Neighbor-specific data
...
}
}Message types:
-
update- Route announcement/withdrawal -
state- BGP session state change -
notification- BGP error/notification -
keepalive- BGP keepalive received -
open- BGP OPEN message received -
refresh- Route refresh message
Route announcements and withdrawals.
Structure:
{
"type": "update",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"asn": {
"local": 65001,
"peer": 65000
},
"direction": "receive", // or "send"
"message": {
"update": {
"announce": { ... }, // New/updated routes
"withdraw": { ... } // Withdrawn routes
}
}
}
}BGP session state changes.
Structure:
{
"type": "state",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"asn": {
"local": 65001,
"peer": 65000
},
"state": "up" // or "down", "connected", etc.
}
}Possible states:
-
idle- Not connected -
connect- Attempting connection -
active- Connection failed, retrying -
opensent- OPEN message sent -
openconfirm- OPEN message received -
up- Session established β -
down- Session terminated
BGP error notifications.
{
"type": "notification",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"message": {
"notification": {
"code": 6,
"subcode": 2,
"data": "Administrative Reset"
}
}
}
}Common notification codes:
- 1 - Message Header Error
- 2 - OPEN Message Error
- 3 - UPDATE Message Error
- 4 - Hold Timer Expired
- 5 - Finite State Machine Error
- 6 - Cease
BGP keepalive received (rarely needed to process).
{
"type": "keepalive",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
}
}
}BGP OPEN message received (session establishment).
{
"type": "open",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"message": {
"open": {
"version": 4,
"asn": 65000,
"hold_time": 180,
"router_id": "192.168.1.1",
"capabilities": {
"multiprotocol": [
"ipv4 unicast",
"ipv6 unicast",
"ipv4 flowspec"
],
"four_bytes_asn": true,
"route_refresh": true,
"graceful_restart": false
}
}
}
}
}{
"exabgp": "4.2.25",
"time": 1699564800.0,
"type": "update",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"asn": {
"local": 65001,
"peer": 65000
},
"direction": "receive",
"message": {
"update": {
"announce": {
"ipv4 unicast": {
"100.10.0.0/24": [
{
"next-hop": "192.168.1.1",
"origin": "igp",
"as-path": [ 65000 ],
"med": 100,
"local-preference": 100,
"community": [
[ 65000, 100 ],
[ 65000, 200 ]
]
}
]
}
}
}
}
}
}Key points:
- Address family:
"ipv4 unicast"(space, not hyphen) - Attributes in array:
[ { ... } ]even for single route - Communities as nested arrays:
[ [ AS, value ], [ AS, value ] ]
{
"type": "update",
"neighbor": {
"message": {
"update": {
"withdraw": {
"ipv4 unicast": {
"100.10.0.0/24": {}
}
}
}
}
}
}Key points:
- Empty object
{}for withdrawn route - No attributes needed
{
"type": "update",
"neighbor": {
"message": {
"update": {
"announce": {
"ipv6 unicast": {
"2001:db8::/32": [
{
"next-hop": "2001:db8::1",
"origin": "igp",
"as-path": [ 65000 ]
}
]
}
}
}
}
}
}{
"type": "update",
"neighbor": {
"message": {
"update": {
"announce": {
"ipv4 flowspec": {
"flow": {
"destination-prefix": "100.10.0.0/24",
"source-prefix": "10.0.0.0/8",
"protocol": [ "=6" ],
"destination-port": [ "=80" ],
"tcp-flags": [ "syn" ]
},
"attributes": {
"extended-community": [
"traffic-rate:0:0" // Discard
]
}
}
}
}
}
}
}FlowSpec match operators:
-
=80- Equals 80 -
>1023- Greater than -
<1024- Less than -
>=1024&<=65535- Range
FlowSpec actions (extended-community):
-
traffic-rate:0:0- Discard (rate-limit to 0) -
traffic-rate:0:1000000- Rate-limit to 1 Mbps -
redirect:65001:100- Redirect to VRF -
traffic-marking:46- Mark DSCP
{
"type": "update",
"neighbor": {
"message": {
"update": {
"announce": {
"ipv4 mpls-vpn": {
"65001:100:100.10.0.0/24": [
{
"next-hop": "192.168.1.1",
"origin": "igp",
"as-path": [ 65000 ],
"label": 10000,
"route-distinguisher": "65001:100",
"extended-community": [
"target:65001:100",
"origin:65001:100"
]
}
]
}
}
}
}
}
}Key format:
- Prefix format:
<RD>:<prefix> - Example:
65001:100:100.10.0.0/24
EVPN Type 2 (MAC/IP Advertisement):
{
"type": "update",
"neighbor": {
"message": {
"update": {
"announce": {
"evpn": {
"mac-ip-advertisement": {
"route-distinguisher": "65001:100",
"ethernet-segment-identifier": "00:00:00:00:00:00:00:00:00:00",
"ethernet-tag": 0,
"mac": "aa:bb:cc:dd:ee:ff",
"ip": "100.10.0.100",
"label": [ 10000 ],
"next-hop": "192.168.1.1",
"extended-community": [
"target:65001:100",
"encapsulation:vxlan"
]
}
}
}
}
}
}
}EVPN Type 3 (Inclusive Multicast):
{
"type": "update",
"neighbor": {
"message": {
"update": {
"announce": {
"evpn": {
"inclusive-multicast": {
"route-distinguisher": "65001:100",
"ethernet-tag": 0,
"originating-router": "192.168.1.1",
"next-hop": "192.168.1.1",
"extended-community": [
"target:65001:100"
]
}
}
}
}
}
}
}{
"type": "update",
"neighbor": {
"message": {
"update": {
"announce": {
"ipv4 link-state": {
"node": {
"protocol-id": "isis-level2",
"identifier": "0000.0000.0001.00",
"local-node-descriptors": {
"asn": 65000,
"bgp-ls-identifier": 0,
"router-id": "192.168.1.1"
},
"attributes": {
"node-name": "router1",
"isis-area-identifier": "49.0001"
}
}
}
}
}
}
}
}{
"exabgp": "4.2.25",
"time": 1699564800.0,
"type": "state",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"asn": {
"local": 65001,
"peer": 65000
},
"state": "up"
}
}{
"type": "state",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"state": "down",
"reason": "Cease - Administrative Reset"
}
}{
"type": "notification",
"neighbor": {
"address": {
"local": "192.168.1.2",
"peer": "192.168.1.1"
},
"message": {
"notification": {
"code": 6,
"subcode": 2,
"data": "Administrative Reset"
}
}
}
}{
"type": "notification",
"neighbor": {
"message": {
"notification": {
"code": 4,
"subcode": 0,
"data": "Hold Timer Expired"
}
}
}
}#!/usr/bin/env python3
import sys
import json
while True:
line = sys.stdin.readline()
if not line:
break
try:
msg = json.loads(line)
# Route update
if msg['type'] == 'update':
handle_update(msg)
# Session state
elif msg['type'] == 'state':
handle_state(msg)
# Notification
elif msg['type'] == 'notification':
handle_notification(msg)
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)def handle_update(msg):
"""Process UPDATE messages"""
if 'update' not in msg['neighbor']['message']:
return
update = msg['neighbor']['message']['update']
# Announced routes
if 'announce' in update:
if 'ipv4 unicast' in update['announce']:
routes = update['announce']['ipv4 unicast']
for prefix, attrs_list in routes.items():
for attrs in attrs_list:
nexthop = attrs.get('next-hop', 'unknown')
med = attrs.get('med', 0)
print(f"[ANNOUNCE] {prefix} via {nexthop} (MED {med})",
file=sys.stderr)
# Withdrawn routes
if 'withdraw' in update:
if 'ipv4 unicast' in update['withdraw']:
routes = update['withdraw']['ipv4 unicast']
for prefix in routes.keys():
print(f"[WITHDRAW] {prefix}", file=sys.stderr)def handle_flowspec(msg):
"""Process FlowSpec updates"""
if 'update' not in msg['neighbor']['message']:
return
update = msg['neighbor']['message']['update']
if 'announce' in update:
if 'ipv4 flowspec' in update['announce']:
flows = update['announce']['ipv4 flowspec']
for flow, attrs in flows.items():
# Parse match conditions
dest = flow.get('destination-prefix', 'any')
src = flow.get('source-prefix', 'any')
port = flow.get('destination-port', ['any'])[0]
proto = flow.get('protocol', ['any'])[0]
# Parse action
ext_comm = attrs['attributes'].get('extended-community', [])
action = 'unknown'
for comm in ext_comm:
if comm.startswith('traffic-rate'):
rate = comm.split(':')[2]
action = f"rate-limit {rate} bps" if rate != '0' else 'discard'
elif comm.startswith('redirect'):
action = f"redirect {comm}"
print(f"[FLOWSPEC] {src} -> {dest}:{port} proto:{proto} action:{action}",
file=sys.stderr)def handle_state(msg):
"""Process STATE messages"""
peer = msg['neighbor']['address']['peer']
state = msg['neighbor']['state']
if state == 'up':
print(f"[STATE] BGP session with {peer} is UP", file=sys.stderr)
elif state == 'down':
reason = msg['neighbor'].get('reason', 'unknown')
print(f"[STATE] BGP session with {peer} is DOWN: {reason}",
file=sys.stderr)
else:
print(f"[STATE] BGP session with {peer}: {state}", file=sys.stderr)class RouteTracker:
def __init__(self):
self.routes = {} # prefix -> attributes
def update(self, msg):
if 'update' not in msg['neighbor']['message']:
return
update = msg['neighbor']['message']['update']
# Add/update announced routes
if 'announce' in update:
if 'ipv4 unicast' in update['announce']:
routes = update['announce']['ipv4 unicast']
for prefix, attrs_list in routes.items():
self.routes[prefix] = attrs_list[0]
print(f"[TRACKER] Added {prefix}, total routes: {len(self.routes)}",
file=sys.stderr)
# Remove withdrawn routes
if 'withdraw' in update:
if 'ipv4 unicast' in update['withdraw']:
routes = update['withdraw']['ipv4 unicast']
for prefix in routes.keys():
if prefix in self.routes:
del self.routes[prefix]
print(f"[TRACKER] Removed {prefix}, total routes: {len(self.routes)}",
file=sys.stderr)
def get_all_routes(self):
return self.routes
# Usage
tracker = RouteTracker()
while True:
line = sys.stdin.readline()
msg = json.loads(line)
if msg['type'] == 'update':
tracker.update(msg)#!/usr/bin/env python3
"""
log_updates.py - Log all BGP updates to file
"""
import sys
import json
import time
LOG_FILE = '/var/log/exabgp-updates.log'
def log(message):
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
with open(LOG_FILE, 'a') as f:
f.write(f"{timestamp} {message}\n")
while True:
line = sys.stdin.readline()
if not line:
break
try:
msg = json.loads(line)
if msg['type'] == 'update':
update = msg['neighbor']['message']['update']
peer = msg['neighbor']['address']['peer']
if 'announce' in update:
if 'ipv4 unicast' in update['announce']:
for prefix in update['announce']['ipv4 unicast'].keys():
log(f"ANNOUNCE from {peer}: {prefix}")
if 'withdraw' in update:
if 'ipv4 unicast' in update['withdraw']:
for prefix in update['withdraw']['ipv4 unicast'].keys():
log(f"WITHDRAW from {peer}: {prefix}")
elif msg['type'] == 'state':
peer = msg['neighbor']['address']['peer']
state = msg['neighbor']['state']
log(f"STATE {peer}: {state}")
except Exception as e:
log(f"ERROR: {e}")#!/usr/bin/env python3
"""
route_trigger.py - Trigger actions when specific routes appear
"""
import sys
import json
import subprocess
WATCH_PREFIXES = [
'100.10.0.0/24',
'100.20.0.0/24'
]
def trigger_action(prefix, action):
"""Run script when route changes"""
if action == 'announce':
subprocess.run(['/etc/exabgp/scripts/on_route_add.sh', prefix])
elif action == 'withdraw':
subprocess.run(['/etc/exabgp/scripts/on_route_remove.sh', prefix])
while True:
line = sys.stdin.readline()
if not line:
break
try:
msg = json.loads(line)
if msg['type'] == 'update':
update = msg['neighbor']['message']['update']
# Check announcements
if 'announce' in update:
if 'ipv4 unicast' in update['announce']:
for prefix in update['announce']['ipv4 unicast'].keys():
if prefix in WATCH_PREFIXES:
print(f"[TRIGGER] Route announced: {prefix}", file=sys.stderr)
trigger_action(prefix, 'announce')
# Check withdrawals
if 'withdraw' in update:
if 'ipv4 unicast' in update['withdraw']:
for prefix in update['withdraw']['ipv4 unicast'].keys():
if prefix in WATCH_PREFIXES:
print(f"[TRIGGER] Route withdrawn: {prefix}", file=sys.stderr)
trigger_action(prefix, 'withdraw')
except Exception as e:
print(f"[ERROR] {e}", file=sys.stderr)#!/usr/bin/env python3
"""
metrics.py - Export BGP metrics for Prometheus
"""
import sys
import json
from prometheus_client import start_http_server, Counter, Gauge
# Metrics
routes_announced = Counter('bgp_routes_announced_total', 'Total routes announced')
routes_withdrawn = Counter('bgp_routes_withdrawn_total', 'Total routes withdrawn')
active_routes = Gauge('bgp_active_routes', 'Number of active routes')
session_state = Gauge('bgp_session_up', 'BGP session state (1=up, 0=down)', ['peer'])
# Start metrics server
start_http_server(9101)
route_count = 0
while True:
line = sys.stdin.readline()
if not line:
break
try:
msg = json.loads(line)
if msg['type'] == 'update':
update = msg['neighbor']['message']['update']
if 'announce' in update:
if 'ipv4 unicast' in update['announce']:
count = len(update['announce']['ipv4 unicast'])
routes_announced.inc(count)
route_count += count
active_routes.set(route_count)
if 'withdraw' in update:
if 'ipv4 unicast' in update['withdraw']:
count = len(update['withdraw']['ipv4 unicast'])
routes_withdrawn.inc(count)
route_count -= count
active_routes.set(route_count)
elif msg['type'] == 'state':
peer = msg['neighbor']['address']['peer']
state = msg['neighbor']['state']
session_state.labels(peer=peer).set(1 if state == 'up' else 0)
except Exception as e:
print(f"[ERROR] {e}", file=sys.stderr)# Good - safe access
nexthop = attrs.get('next-hop', 'unknown')
# Bad - can raise KeyError
nexthop = attrs['next-hop']# Address family may not exist
if 'ipv4 unicast' in update.get('announce', {}):
routes = update['announce']['ipv4 unicast']# Good - continuous loop
while True:
line = sys.stdin.readline()
if not line:
break
process(line)
# Bad - read once and exit
line = sys.stdin.readline()
process(line)
# Process exits, ExaBGP restarts it constantly# STDOUT is for commands to ExaBGP
# STDERR is for logging
print(f"[INFO] Received route", file=sys.stderr)try:
msg = json.loads(line)
except json.JSONDecodeError:
print(f"[ERROR] Invalid JSON: {line}", file=sys.stderr)
continue- API Overview - API architecture
- Text API Reference - Sending commands
- Configuration Syntax - Process configuration
- FlowSpec Overview - FlowSpec details
Need to send commands? See Text API Reference β
π» Ghost written by Claude (Anthropic AI)
π Home
π Getting Started
π§ API
π‘οΈ Use Cases
π Address Families
βοΈ Configuration
π Operations
π Reference
- Architecture
- BGP State Machine
- Communities (RFC)
- Extended Communities
- BGP Ecosystem
- Capabilities (AFI/SAFI)
- RFC Support
π Migration
π Community
π External
- GitHub Repo β
- Slack β
- Issues β
π» Ghost written by Claude (Anthropic AI)