# JSON API Reference **Complete reference for ExaBGP's JSON-encoded messages** --- ## Table of Contents - [Overview](#overview) - [Message Structure](#message-structure) - [Message Types](#message-types) - [UPDATE Messages](#update-messages) - [STATE Messages](#state-messages) - [NOTIFICATION Messages](#notification-messages) - [Parsing Patterns](#parsing-patterns) - [Complete Examples](#complete-examples) - [Best Practices](#best-practices) --- ## Overview The **JSON encoder** provides structured BGP messages to your API program via STDIN. **Configuration:** ```ini 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:** ```python 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) --- ## Message Structure ### Common Fields (All Messages) Every JSON message includes: ```json { "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 --- ## Message Types ### UPDATE Route announcements and withdrawals. **Structure:** ```json { "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 } } } } ``` --- ### STATE BGP session state changes. **Structure:** ```json { "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 --- ### NOTIFICATION BGP error notifications. ```json { "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 --- ### KEEPALIVE BGP keepalive received (rarely needed to process). ```json { "type": "keepalive", "neighbor": { "address": { "local": "192.168.1.2", "peer": "192.168.1.1" } } } ``` --- ### OPEN BGP OPEN message received (session establishment). ```json { "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 flow" ], "four_bytes_asn": true, "route_refresh": true, "graceful_restart": false } } } } } ``` --- ## UPDATE Messages ### IPv4 Unicast Announcement ```json { "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 ] ]` --- ### IPv4 Unicast Withdrawal ```json { "type": "update", "neighbor": { "message": { "update": { "withdraw": { "ipv4 unicast": { "100.10.0.0/24": {} } } } } } } ``` **Key points:** - Empty object `{}` for withdrawn route - No attributes needed --- ### IPv6 Unicast ```json { "type": "update", "neighbor": { "message": { "update": { "announce": { "ipv6 unicast": { "2001:db8::/32": [ { "next-hop": "2001:db8::1", "origin": "igp", "as-path": [ 65000 ] } ] } } } } } } ``` --- ### FlowSpec ```json { "type": "update", "neighbor": { "message": { "update": { "announce": { "ipv4 flow": { "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 MB/s (~8 Mbps) (bytes/sec per RFC 5575) - `redirect:65001:100` - Redirect to VRF - `traffic-marking:46` - Mark DSCP --- ### L3VPN ```json { "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: `:` - Example: `65001:100:100.10.0.0/24` --- ### EVPN **EVPN Type 2 (MAC/IP Advertisement):** ```json { "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):** ```json { "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" ] } } } } } } } ``` --- ### BGP-LS ```json { "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" } } } } } } } } ``` --- ## STATE Messages ### Session UP ```json { "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" } } ``` ### Session DOWN ```json { "type": "state", "neighbor": { "address": { "local": "192.168.1.2", "peer": "192.168.1.1" }, "state": "down", "reason": "Cease - Administrative Reset" } } ``` --- ## NOTIFICATION Messages ### Administrative Reset ```json { "type": "notification", "neighbor": { "address": { "local": "192.168.1.2", "peer": "192.168.1.1" }, "message": { "notification": { "code": 6, "subcode": 2, "data": "Administrative Reset" } } } } ``` ### Hold Timer Expired ```json { "type": "notification", "neighbor": { "message": { "notification": { "code": 4, "subcode": 0, "data": "Hold Timer Expired" } } } } ``` --- ## Parsing Patterns ### Basic Parser ```python #!/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) ``` --- ### Parse IPv4 Unicast Routes ```python 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) ``` --- ### Parse FlowSpec Rules ```python 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 flow' in update['announce']: flows = update['announce']['ipv4 flow'] 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} bytes/sec" 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) ``` --- ### Parse Session State ```python 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) ``` --- ### Track Announced Routes ```python 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) ``` --- ## Complete Examples ### Example 1: Log All Updates ```python #!/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}") ``` --- ### Example 2: Trigger Actions on Routes ```python #!/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) ``` --- ### Example 3: Prometheus Metrics Exporter ```python #!/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) ``` --- ## Best Practices ### 1. Always Parse Safely ```python # Good - safe access nexthop = attrs.get('next-hop', 'unknown') # Bad - can raise KeyError nexthop = attrs['next-hop'] ``` ### 2. Handle Missing Fields ```python # Address family may not exist if 'ipv4 unicast' in update.get('announce', {}): routes = update['announce']['ipv4 unicast'] ``` ### 3. Process Continuously ```python # 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 ``` ### 4. Log to STDERR ```python # STDOUT is for commands to ExaBGP # STDERR is for logging print(f"[INFO] Received route", file=sys.stderr) ``` ### 5. Catch JSON Errors ```python try: msg = json.loads(line) except json.JSONDecodeError: print(f"[ERROR] Invalid JSON: {line}", file=sys.stderr) continue ``` --- ## See Also - **[API Overview](API-Overview)** - API architecture - **[Text API Reference](Text-API-Reference)** - Sending commands - **[Configuration Syntax](Configuration-Syntax)** - Process configuration - **[FlowSpec Overview](FlowSpec-Overview)** - FlowSpec details --- **Need to send commands?** See [Text API Reference](Text-API-Reference) → ---