Skip to content

SDN Integration

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

SDN Integration with ExaBGP

Bridging SDN controllers with BGP networks

πŸ”— BGP as SDN southbound protocol - ExaBGP enables SDN control over BGP networks


Table of Contents


Overview

SDN (Software-Defined Networking) with ExaBGP enables programmatic control of BGP routing from centralized controllers.

What is SDN?

Software-Defined Networking separates the control plane from the data plane:

Traditional Networking:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Router         β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Control     β”‚ β”‚ ← Integrated
β”‚ β”‚ Plane       β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Data        β”‚ β”‚
β”‚ β”‚ Plane       β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

SDN Networking:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  SDN Controller     β”‚ ← Centralized control
β”‚  (Control Plane)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚ Southbound API
           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Switch/Router  β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Data        β”‚ β”‚ ← Simplified
β”‚ β”‚ Plane       β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits:

  • Centralized control and visibility
  • Programmable network behavior
  • Rapid deployment of new services
  • Network automation
  • Application-aware routing

Why ExaBGP for SDN?

ExaBGP is the perfect BGP southbound interface:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   SDN Controller            β”‚
β”‚   (OpenDaylight, ONOS, etc.)β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ REST API / gRPC
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   ExaBGP                     β”‚ ← BGP API gateway
β”‚   (BGP Speaker)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ BGP Protocol
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Network Devices            β”‚
β”‚   (Routers, Switches)        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ExaBGP advantages:

  • Simple API (STDIN/STDOUT, JSON)
  • Language-agnostic (Python, Go, Java, etc.)
  • Full BGP protocol support
  • Lightweight and portable
  • No RIB/FIB manipulation (pure control plane)

SDN and BGP Convergence

The Evolution of SDN

Phase 1: OpenFlow (2008)

  • New protocol for switch control
  • Limited deployment (requires OpenFlow switches)

Phase 2: Protocol-Agnostic SDN (2012+)

  • NETCONF, RESTCONF for device management
  • Still requires device support

Phase 3: BGP as SDN Protocol (2014+)

  • ExaBGP enables BGP-based SDN
  • Works with existing BGP infrastructure
  • No hardware changes required
  • Leverages BGP's proven scalability

BGP-SDN Use Cases

1. Dynamic Traffic Engineering

SDN Controller monitors traffic
β†’ Detects congestion on link A
β†’ Commands ExaBGP to adjust BGP attributes
β†’ Traffic shifts to link B

2. Service Chaining

Application requests path through firewall + IDS
β†’ SDN controller computes path
β†’ ExaBGP announces routes with specific next-hops
β†’ Traffic flows through service chain

3. Multi-Tenant Isolation

Tenant A and Tenant B require isolated networks
β†’ SDN controller maintains tenant policies
β†’ ExaBGP announces routes with VPN labels/communities
β†’ Network enforces isolation

Architecture Patterns

Pattern 1: ExaBGP as BGP Speaker for SDN Controller

Simple integration:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          SDN Controller              β”‚
β”‚  (OpenDaylight, ONOS, Custom)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ REST API
                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      ExaBGP Middleware              β”‚
β”‚  (API gateway: HTTP β†’ ExaBGP API)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ STDIN/STDOUT
                 β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            ExaBGP                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ BGP
                 β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Network        β”‚
        β”‚ (Routers)      β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Components:

  • SDN Controller: High-level policy and orchestration
  • ExaBGP Middleware: Translates controller commands to BGP
  • ExaBGP: BGP protocol implementation
  • Network: Standard BGP routers

Pattern 2: BGP-LS for Topology Discovery

SDN controller learns network topology via BGP-LS:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          SDN Controller              β”‚
β”‚  (Builds topology graph)            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ BGP-LS
                 β”‚ (link-state info)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            ExaBGP                    β”‚
β”‚  (Receives BGP-LS from routers)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                 β”‚ BGP-LS
                 β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ Routers with    β”‚
        β”‚ BGP-LS enabled  β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Benefits:

  • Real-time topology awareness
  • Link utilization monitoring
  • Dynamic path computation
  • Automatic failure detection

Pattern 3: Hybrid SDN (OpenFlow + BGP)

Combine OpenFlow for edge, BGP for core:

     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚   SDN Controller     β”‚
     β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
           β”‚          β”‚
    OpenFlow         BGP
           β”‚          β”‚
           β–Ό          β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ Edge    β”‚  β”‚ExaBGP  β”‚
     β”‚ OpenFlowβ”‚  β”‚        β”‚
     β”‚ Switchesβ”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”˜
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚
           β”‚           β”‚
           β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
                 β–Ό
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚ Core BGP   β”‚
          β”‚ Network    β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Use case: Fine-grained control at edge, scalable BGP in core


BGP-LS for Topology Discovery

What is BGP-LS?

BGP Link-State (RFC 7752) distributes network topology information via BGP.

Information carried:

  • Nodes (routers)
  • Links (connections)
  • Prefixes (routes)
  • Link metrics (bandwidth, delay, utilization)
  • TE attributes

BGP-LS Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     SDN Controller                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Topology Database           β”‚   β”‚
β”‚  β”‚  - Nodes, Links, Metrics     β”‚   β”‚
β”‚  β”‚  - Path computation          β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ BGP-LS
               β”‚ (receives topology updates)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚           ExaBGP                     β”‚
β”‚  (BGP-LS collector)                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚ BGP-LS
               β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚           β”‚          β”‚
   β–Ό           β–Ό          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”
β”‚Routerβ”‚  β”‚Routerβ”‚  β”‚Routerβ”‚
β”‚  A   β”‚  β”‚  B   β”‚  β”‚  C   β”‚
β””β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”˜

ExaBGP BGP-LS Configuration

Receive BGP-LS from routers:

# /etc/exabgp/bgp-ls.conf
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 unicast;
        bgp-ls bgp-ls;  # Enable BGP-LS
    }

    api {
        processes [ bgp-ls-processor ];
    }
}

process bgp-ls-processor {
    run /etc/exabgp/bgp-ls-processor.py;
    encoder json;  # Use JSON for structured data
}

BGP-LS Processing Script

Parse BGP-LS and build topology:

#!/usr/bin/env python3
"""
BGP-LS processor for SDN topology discovery
Receives BGP-LS updates and builds network topology graph
"""
import sys
import json
import time
import requests

SDN_CONTROLLER_URL = "http://localhost:8080/topology"

# Topology database
topology = {
    'nodes': {},
    'links': {},
}

def process_bgp_ls_update(message):
    """Process BGP-LS update message"""
    try:
        msg = json.loads(message)

        if 'neighbor' not in msg:
            return

        neighbor = msg['neighbor']['address']['peer']

        if 'update' in msg:
            update = msg['update']

            # Process BGP-LS announcements
            if 'announce' in update and 'bgp-ls bgp-ls' in update['announce']:
                bgp_ls_data = update['announce']['bgp-ls bgp-ls']

                for nlri_data in bgp_ls_data.values():
                    for nlri in nlri_data:
                        process_nlri(nlri)

            # Process BGP-LS withdrawals
            if 'withdraw' in update and 'bgp-ls bgp-ls' in update['withdraw']:
                bgp_ls_data = update['withdraw']['bgp-ls bgp-ls']

                for nlri_data in bgp_ls_data.values():
                    for nlri in nlri_data:
                        remove_nlri(nlri)

            # Update SDN controller
            update_sdn_controller()

    except Exception as e:
        sys.stderr.write(f"[BGP-LS ERROR] {e}\n")

def process_nlri(nlri):
    """Process BGP-LS NLRI (node, link, or prefix)"""
    nlri_type = nlri.get('ls-nlri-type')

    if nlri_type == 'node':
        # Node advertisement
        node_id = nlri.get('node-descriptors', {}).get('router-id')
        if node_id:
            topology['nodes'][node_id] = {
                'router_id': node_id,
                'as_number': nlri.get('node-descriptors', {}).get('as-number'),
                'isis_area': nlri.get('node-descriptors', {}).get('isis-area-id'),
            }
            sys.stderr.write(f"[BGP-LS] Added node: {node_id}\n")

    elif nlri_type == 'link':
        # Link advertisement
        local_node = nlri.get('local-node-descriptors', {}).get('router-id')
        remote_node = nlri.get('remote-node-descriptors', {}).get('router-id')

        if local_node and remote_node:
            link_id = f"{local_node}_{remote_node}"
            topology['links'][link_id] = {
                'local_node': local_node,
                'remote_node': remote_node,
                'local_ip': nlri.get('link-descriptors', {}).get('ipv4-interface-address'),
                'remote_ip': nlri.get('link-descriptors', {}).get('ipv4-neighbor-address'),
                'metric': nlri.get('link-attributes', {}).get('igp-metric', 0),
                'bandwidth': nlri.get('link-attributes', {}).get('max-link-bandwidth', 0),
            }
            sys.stderr.write(f"[BGP-LS] Added link: {link_id}\n")

    elif nlri_type == 'ipv4-prefix' or nlri_type == 'ipv6-prefix':
        # Prefix advertisement
        prefix = nlri.get('prefix')
        node_id = nlri.get('node-descriptors', {}).get('router-id')

        if prefix and node_id:
            sys.stderr.write(f"[BGP-LS] Node {node_id} has prefix {prefix}\n")

def remove_nlri(nlri):
    """Remove NLRI from topology"""
    nlri_type = nlri.get('ls-nlri-type')

    if nlri_type == 'node':
        node_id = nlri.get('node-descriptors', {}).get('router-id')
        if node_id and node_id in topology['nodes']:
            del topology['nodes'][node_id]
            sys.stderr.write(f"[BGP-LS] Removed node: {node_id}\n")

    elif nlri_type == 'link':
        local_node = nlri.get('local-node-descriptors', {}).get('router-id')
        remote_node = nlri.get('remote-node-descriptors', {}).get('router-id')
        link_id = f"{local_node}_{remote_node}"

        if link_id in topology['links']:
            del topology['links'][link_id]
            sys.stderr.write(f"[BGP-LS] Removed link: {link_id}\n")

def update_sdn_controller():
    """Send topology to SDN controller"""
    try:
        response = requests.post(
            SDN_CONTROLLER_URL,
            json=topology,
            timeout=5
        )
        sys.stderr.write(f"[BGP-LS] Updated SDN controller: {response.status_code}\n")
    except Exception as e:
        sys.stderr.write(f"[BGP-LS] Failed to update controller: {e}\n")

# Main loop
time.sleep(2)
sys.stderr.write("[BGP-LS] Topology collector started\n")

while True:
    line = sys.stdin.readline()
    if line:
        process_bgp_ls_update(line)

Programmatic Route Control

REST API for ExaBGP

HTTP gateway for SDN controllers:

#!/usr/bin/env python3
"""
REST API gateway for ExaBGP
Allows SDN controllers to control BGP via HTTP
"""
from flask import Flask, request, jsonify
import subprocess
import threading
import queue

app = Flask(__name__)

# Command queue for ExaBGP
command_queue = queue.Queue()

# Active routes tracking
active_routes = {}

@app.route('/bgp/announce', methods=['POST'])
def announce_route():
    """
    Announce BGP route

    POST /bgp/announce
    {
        "prefix": "203.0.113.0/24",
        "next_hop": "192.168.1.10",
        "med": 100,
        "as_path": [65001],
        "communities": ["65000:100"]
    }
    """
    data = request.json

    prefix = data.get('prefix')
    next_hop = data.get('next_hop', 'self')
    med = data.get('med', 100)
    as_path = data.get('as_path', [])
    communities = data.get('communities', [])

    # Build ExaBGP command
    command = f"announce route {prefix} next-hop {next_hop}"

    if med:
        command += f" med {med}"

    if as_path:
        as_path_str = ' '.join(map(str, as_path))
        command += f" as-path [ {as_path_str} ]"

    if communities:
        community_str = ' '.join(communities)
        command += f" community [ {community_str} ]"

    # Send to ExaBGP
    command_queue.put(command)

    # Track active route
    active_routes[prefix] = data

    return jsonify({
        'status': 'success',
        'message': f'Announced {prefix}',
        'command': command
    }), 200

@app.route('/bgp/withdraw', methods=['POST'])
def withdraw_route():
    """
    Withdraw BGP route

    POST /bgp/withdraw
    {
        "prefix": "203.0.113.0/24"
    }
    """
    data = request.json
    prefix = data.get('prefix')

    command = f"withdraw route {prefix}"
    command_queue.put(command)

    # Remove from active routes
    if prefix in active_routes:
        del active_routes[prefix]

    return jsonify({
        'status': 'success',
        'message': f'Withdrew {prefix}'
    }), 200

@app.route('/bgp/routes', methods=['GET'])
def get_routes():
    """Get all active routes"""
    return jsonify({
        'routes': active_routes
    }), 200

def exabgp_worker():
    """Send commands to ExaBGP via STDOUT"""
    import sys
    import time

    time.sleep(2)

    while True:
        try:
            command = command_queue.get(timeout=1)
            sys.stdout.write(command + "\n")
            sys.stdout.flush()
            sys.stderr.write(f"[API] Executed: {command}\n")
        except queue.Empty:
            pass

if __name__ == '__main__':
    # Start ExaBGP worker thread
    worker = threading.Thread(target=exabgp_worker, daemon=True)
    worker.start()

    # Start Flask API
    app.run(host='0.0.0.0', port=5000)

ExaBGP Configuration:

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 unicast;
    }

    api {
        processes [ rest-api ];
    }
}

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

Usage from SDN controller:

# Announce route
curl -X POST http://localhost:5000/bgp/announce \
  -H "Content-Type: application/json" \
  -d '{
    "prefix": "203.0.113.0/24",
    "next_hop": "192.168.1.10",
    "med": 50,
    "communities": ["65000:100"]
  }'

# Withdraw route
curl -X POST http://localhost:5000/bgp/withdraw \
  -H "Content-Type: application/json" \
  -d '{"prefix": "203.0.113.0/24"}'

# List active routes
curl http://localhost:5000/bgp/routes

Integration with SDN Controllers

OpenDaylight Integration

OpenDaylight is a popular open-source SDN controller.

Architecture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    OpenDaylight Controller          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚   BGP Northbound API         β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                 β”‚                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚   BGP Application Module     β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                 β”‚                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚   ExaBGP Connector Plugin    β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β”‚ REST/gRPC
                  β–Ό
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   ExaBGP        β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Implementation:

// OpenDaylight BGP service
public class ExaBGPService {
    private static final String EXABGP_API_URL = "http://localhost:5000";

    public void announceRoute(String prefix, String nextHop, int med) {
        HttpClient client = HttpClient.newHttpClient();

        String json = String.format(
            "{\"prefix\":\"%s\",\"next_hop\":\"%s\",\"med\":%d}",
            prefix, nextHop, med
        );

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(EXABGP_API_URL + "/bgp/announce"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(json))
            .build();

        client.sendAsync(request, HttpRequest.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAccept(System.out::println);
    }

    public void withdrawRoute(String prefix) {
        // Similar implementation for withdrawal
    }
}

ONOS Integration

ONOS (Open Network Operating System) is another SDN controller platform.

ExaBGP Application for ONOS:

#!/usr/bin/env python3
"""
ONOS integration with ExaBGP
Receives routing decisions from ONOS and programs BGP
"""
import sys
import time
import requests
import json

ONOS_API_URL = "http://localhost:8181/onos/v1"
ONOS_USERNAME = "onos"
ONOS_PASSWORD = "rocks"

def get_routing_decisions():
    """Query ONOS for routing decisions"""
    try:
        response = requests.get(
            f"{ONOS_API_URL}/routes",
            auth=(ONOS_USERNAME, ONOS_PASSWORD),
            timeout=5
        )

        if response.status_code == 200:
            return response.json().get('routes', [])

    except Exception as e:
        sys.stderr.write(f"[ONOS] Error querying routes: {e}\n")

    return []

def announce_route(prefix, next_hop, attributes):
    """Announce route via ExaBGP"""
    command = f"announce route {prefix} next-hop {next_hop}"

    if 'med' in attributes:
        command += f" med {attributes['med']}"

    if 'communities' in attributes:
        community_str = ' '.join(attributes['communities'])
        command += f" community [{community_str}]"

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

    sys.stderr.write(f"[ONOS] Announced: {command}\n")

time.sleep(2)
sys.stderr.write("[ONOS] ExaBGP-ONOS integration started\n")

while True:
    routes = get_routing_decisions()

    for route in routes:
        prefix = route.get('prefix')
        next_hop = route.get('nextHop')
        attributes = route.get('attributes', {})

        if prefix and next_hop:
            announce_route(prefix, next_hop, attributes)

    time.sleep(30)

Path Computation

Constraint-Based Routing

SDN controller computes paths with constraints:

#!/usr/bin/env python3
"""
Path computation engine for SDN
Uses topology from BGP-LS to compute optimal paths
"""
import networkx as nx

class PathComputationEngine:
    def __init__(self):
        self.topology = nx.Graph()

    def update_topology(self, bgp_ls_data):
        """Update topology from BGP-LS"""
        # Add nodes
        for node_id, node_data in bgp_ls_data['nodes'].items():
            self.topology.add_node(node_id, **node_data)

        # Add links
        for link_id, link_data in bgp_ls_data['links'].items():
            local = link_data['local_node']
            remote = link_data['remote_node']
            weight = link_data.get('metric', 1)

            self.topology.add_edge(
                local, remote,
                weight=weight,
                bandwidth=link_data.get('bandwidth', 0)
            )

    def compute_path(self, source, destination, constraints=None):
        """
        Compute path from source to destination

        Constraints:
        - min_bandwidth: Minimum link bandwidth required
        - max_latency: Maximum acceptable latency
        - avoid_nodes: List of nodes to avoid
        """
        if constraints is None:
            constraints = {}

        # Filter topology based on constraints
        filtered_graph = self.topology.copy()

        # Bandwidth constraint
        min_bandwidth = constraints.get('min_bandwidth', 0)
        if min_bandwidth > 0:
            edges_to_remove = [
                (u, v) for u, v, data in filtered_graph.edges(data=True)
                if data.get('bandwidth', 0) < min_bandwidth
            ]
            filtered_graph.remove_edges_from(edges_to_remove)

        # Node avoidance constraint
        avoid_nodes = constraints.get('avoid_nodes', [])
        filtered_graph.remove_nodes_from(avoid_nodes)

        # Compute shortest path
        try:
            path = nx.shortest_path(
                filtered_graph,
                source=source,
                target=destination,
                weight='weight'
            )
            return path
        except nx.NetworkXNoPath:
            return None

    def compute_multiple_paths(self, source, destination, k=3):
        """Compute K shortest paths"""
        try:
            paths = list(nx.shortest_simple_paths(
                self.topology,
                source=source,
                target=destination,
                weight='weight'
            ))
            return paths[:k]
        except:
            return []

# Example usage
pce = PathComputationEngine()

# Update with BGP-LS data
pce.update_topology(bgp_ls_topology)

# Compute path with constraints
path = pce.compute_path(
    source='10.0.0.1',
    destination='10.0.0.10',
    constraints={
        'min_bandwidth': 1000,  # 1 Gbps
        'avoid_nodes': ['10.0.0.5'],  # Avoid this router
    }
)

if path:
    print(f"Computed path: {' β†’ '.join(path)}")
    # Program path via ExaBGP
else:
    print("No path found with constraints")

Use Case Scenarios

Use Case 1: Dynamic Traffic Engineering

Scenario: Automatically shift traffic away from congested links

#!/usr/bin/env python3
"""
Dynamic traffic engineering based on link utilization
Monitors BGP-LS metrics and adjusts BGP announcements
"""
import sys
import time

CONGESTION_THRESHOLD = 80  # Percentage

def get_link_utilization(link_id):
    """Get link utilization from BGP-LS or SNMP"""
    # TODO: Query from BGP-LS or monitoring system
    return 0

def adjust_routing_for_congestion(link_id, utilization):
    """Adjust BGP to avoid congested link"""
    if utilization > CONGESTION_THRESHOLD:
        # Prepend AS-PATH for routes using this link
        sys.stdout.write(
            f"announce route 203.0.113.0/24 "
            f"next-hop self "
            f"as-path [ 65001 65001 65001 ]\n"  # Prepend to make less attractive
        )
        sys.stdout.flush()

        sys.stderr.write(
            f"[SDN-TE] Link {link_id} congested ({utilization}%), "
            f"prepending AS-PATH\n"
        )
    else:
        # Normal announcement
        sys.stdout.write(
            f"announce route 203.0.113.0/24 next-hop self\n"
        )
        sys.stdout.flush()

time.sleep(2)

while True:
    # Check all critical links
    for link_id in ['link1', 'link2', 'link3']:
        utilization = get_link_utilization(link_id)
        adjust_routing_for_congestion(link_id, utilization)

    time.sleep(30)

Use Case 2: Service Function Chaining

Scenario: Route traffic through security appliances

#!/usr/bin/env python3
"""
Service Function Chaining with BGP
Route traffic through specific next-hops (firewall, IDS, etc.)
"""
import sys
import time

# Service chain topology
SERVICE_CHAINS = {
    'critical': [
        '192.168.10.1',  # Firewall
        '192.168.10.2',  # IDS
        '192.168.10.3',  # DLP
    ],
    'standard': [
        '192.168.10.1',  # Firewall only
    ],
}

def announce_with_service_chain(prefix, chain_type):
    """Announce prefix with specific next-hop for service chaining"""
    chain = SERVICE_CHAINS.get(chain_type, [])

    if not chain:
        return

    # Use first service in chain as next-hop
    next_hop = chain[0]

    # Tag with community indicating chain type
    community = f"65000:{100 if chain_type == 'critical' else 200}"

    sys.stdout.write(
        f"announce route {prefix} "
        f"next-hop {next_hop} "
        f"community [{community}]\n"
    )
    sys.stdout.flush()

    sys.stderr.write(
        f"[SFC] {prefix} β†’ {chain_type} chain via {next_hop}\n"
    )

# Announce critical applications through full service chain
announce_with_service_chain("10.1.0.0/24", "critical")

# Announce standard applications through firewall only
announce_with_service_chain("10.2.0.0/24", "standard")

Use Case 3: Multi-Tenant SDN

Scenario: Isolated networks for different tenants

#!/usr/bin/env python3
"""
Multi-tenant routing with VPN labels
Each tenant gets isolated routing domain
"""
import sys
import time

# Tenant configuration
TENANTS = {
    'tenant_a': {
        'prefixes': ['172.16.0.0/16'],
        'rd': '65001:100',
        'rt_export': '65001:100',
        'rt_import': '65001:100',
    },
    'tenant_b': {
        'prefixes': ['172.17.0.0/16'],
        'rd': '65001:200',
        'rt_export': '65001:200',
        'rt_import': '65001:200',
    },
}

def announce_tenant_routes():
    """Announce routes with VPN labels for tenant isolation"""
    for tenant_id, config in TENANTS.items():
        rd = config['rd']
        rt_export = config['rt_export']

        for prefix in config['prefixes']:
            # Announce with route-distinguisher and route-target
            sys.stdout.write(
                f"announce route {prefix} "
                f"next-hop self "
                f"route-distinguisher {rd} "
                f"extended-community [ target:{rt_export} ]\n"
            )

        sys.stderr.write(f"[SDN] Announced routes for {tenant_id}\n")

    sys.stdout.flush()

time.sleep(2)

# Announce tenant routes
announce_tenant_routes()

# Keep process alive
while True:
    time.sleep(300)

Best Practices

1. Separate Control and Data Planes

Good:
SDN Controller β†’ ExaBGP β†’ BGP Network
(Control)         (API)    (Data)

Bad:
SDN Controller directly manipulating router configs

2. Use BGP-LS for Topology Awareness

# Always maintain accurate topology
def update_topology_regularly():
    while True:
        topology = get_bgp_ls_topology()
        validate_topology(topology)
        update_path_computation(topology)
        time.sleep(60)

3. Validate Computed Paths

def validate_path(path):
    """Ensure computed path is valid"""
    # Check path exists
    if not path:
        return False

    # Check no loops
    if len(path) != len(set(path)):
        return False

    # Check all links operational
    for i in range(len(path) - 1):
        if not is_link_up(path[i], path[i+1]):
            return False

    return True

4. Implement Graceful Degradation

# If optimal path not available, use backup
primary_path = compute_path(src, dst, constraints)
if not primary_path:
    # Relax constraints
    backup_path = compute_path(src, dst, relaxed_constraints)
    if backup_path:
        use_path(backup_path)

5. Monitor SDN-BGP Integration

from prometheus_client import Gauge, Counter

topology_nodes = Gauge('sdn_topology_nodes', 'Number of nodes')
topology_links = Gauge('sdn_topology_links', 'Number of links')
paths_computed = Counter('sdn_paths_computed', 'Paths computed')
bgp_routes_announced = Counter('sdn_bgp_announced', 'BGP routes announced')

Monitoring and Validation

Verify Topology Accuracy

def verify_topology():
    """Compare BGP-LS topology with actual network"""
    bgp_ls_topology = get_bgp_ls_topology()
    actual_topology = poll_network_devices()

    # Check for discrepancies
    missing_nodes = set(actual_topology['nodes']) - set(bgp_ls_topology['nodes'])
    missing_links = set(actual_topology['links']) - set(bgp_ls_topology['links'])

    if missing_nodes or missing_links:
        alert(f"Topology mismatch: {len(missing_nodes)} nodes, {len(missing_links)} links")

Validate BGP Announcements

# Check routes announced by ExaBGP
exabgpcli show adj-rib out

# Verify routes received by routers
show ip bgp neighbors 192.168.1.2 routes

Troubleshooting

Issue 1: Topology Not Updating

Symptoms: BGP-LS not receiving updates

Check:

# Verify BGP-LS enabled on routers
show bgp link-state link-state summary

# Check ExaBGP receiving BGP-LS
tail -f /var/log/exabgp.log | grep bgp-ls

Issue 2: Computed Paths Not Applied

Symptoms: ExaBGP announces routes but traffic doesn't follow

Diagnosis:

# Check if routes in router RIB
show ip bgp 203.0.113.0

# Check if routes installed in FIB
show ip route 203.0.113.0

# Verify next-hops are reachable
ping 192.168.1.10

Issue 3: API Timeouts

Symptoms: SDN controller commands timeout

Solutions:

  • Increase API timeout values
  • Check network connectivity
  • Monitor ExaBGP process health
  • Implement request queuing

Next Steps

Learn More

Operations

Configuration


Ready to integrate SDN? See Quick Start β†’


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

Clone this wiki locally