Skip to content

Use Cases Multi Tenant

Thomas Mangin edited this page Nov 15, 2025 · 1 revision

Multi-Tenant Networking

ExaBGP enables multi-tenant network isolation using VRFs, Route Target filtering, and tenant separation for cloud providers and service providers.

Table of Contents

Overview

Multi-Tenancy Requirements

Multi-tenant networks require:

  • Isolation: Complete separation between tenants
  • Scalability: Support thousands of tenants
  • Flexibility: Different topologies per tenant
  • Security: Prevent cross-tenant traffic
  • Automation: Dynamic tenant provisioning
  • Resource efficiency: Share infrastructure efficiently

ExaBGP Role

ExaBGP enables multi-tenancy by:

  1. VRF route distribution: Advertise tenant routes with Route Distinguishers
  2. Route Target filtering: Control route import/export per tenant
  3. Dynamic provisioning: Create/delete tenant networks via API
  4. Cross-tenant services: Controlled inter-tenant connectivity

Important: ExaBGP announces tenant routes but does NOT create VRFs or manipulate forwarding tables. Your network OS must configure VRFs and route installation.

VRF Isolation

Basic Tenant Separation

Isolate tenants using VRFs:

#!/usr/bin/env python3
import sys

# Tenant configuration
TENANTS = {
    'tenant-001': {
        'vrf': 'TENANT-001',
        'rd': '10.0.0.1:1001',
        'rt_export': '65000:1001',
        'rt_import': '65000:1001',
        'prefixes': ['192.168.1.0/24', '192.168.2.0/24']
    },
    'tenant-002': {
        'vrf': 'TENANT-002',
        'rd': '10.0.0.1:1002',
        'rt_export': '65000:1002',
        'rt_import': '65000:1002',
        'prefixes': ['192.168.1.0/24', '192.168.3.0/24']  # Can overlap!
    },
    'tenant-003': {
        'vrf': 'TENANT-003',
        'rd': '10.0.0.1:1003',
        'rt_export': '65000:1003',
        'rt_import': '65000:1003',
        'prefixes': ['10.0.0.0/16']
    }
}

NEXT_HOP = "10.0.0.1"

# Announce tenant routes with isolation
for tenant_id, config in TENANTS.items():
    for prefix in config['prefixes']:
        print(f"announce route {prefix} "
              f"next-hop {NEXT_HOP} "
              f"route-distinguisher {config['rd']} "
              f"route-target {config['rt_export']}", flush=True)

while True:
    line = sys.stdin.readline().strip()
    if not line:
        break

Hierarchical Tenants

Support sub-tenants with hierarchical VRFs:

#!/usr/bin/env python3
import sys

# Hierarchical tenant structure
HIERARCHY = {
    'enterprise-a': {
        'rd': '10.0.0.1:1000',
        'rt': '65000:1000',
        'prefixes': ['10.100.0.0/16'],  # Enterprise-wide
        'sub-tenants': {
            'dept-sales': {
                'rd': '10.0.0.1:1001',
                'rt': '65000:1001',
                'rt_import_parent': '65000:1000',  # Import from parent
                'prefixes': ['10.100.1.0/24']
            },
            'dept-eng': {
                'rd': '10.0.0.1:1002',
                'rt': '65000:1002',
                'rt_import_parent': '65000:1000',
                'prefixes': ['10.100.2.0/24']
            }
        }
    }
}

NEXT_HOP = "10.0.0.1"

# Announce parent tenant
for enterprise, config in HIERARCHY.items():
    for prefix in config['prefixes']:
        print(f"announce route {prefix} "
              f"next-hop {NEXT_HOP} "
              f"route-distinguisher {config['rd']} "
              f"route-target {config['rt']}", flush=True)

    # Announce sub-tenants
    for dept, sub_config in config.get('sub-tenants', {}).items():
        for prefix in sub_config['prefixes']:
            # Export both sub-tenant RT and parent RT for selective import
            print(f"announce route {prefix} "
                  f"next-hop {NEXT_HOP} "
                  f"route-distinguisher {sub_config['rd']} "
                  f"route-target {sub_config['rt']} "
                  f"route-target {sub_config['rt_import_parent']}", flush=True)

while True:
    line = sys.stdin.readline().strip()
    if not line:
        break

Route Target Filtering

RT-Constrain for Scale

Use Route Target Constraint (RFC 4684) to reduce route propagation:

#!/usr/bin/env python3
import sys

# Tenant-to-PE mapping
PE_TENANTS = {
    'pe1': ['65000:1001', '65000:1002', '65000:1003'],
    'pe2': ['65000:2001', '65000:2002'],
    'pe3': ['65000:3001', '65000:3002', '65000:3003', '65000:3004']
}

PE_ID = 'pe1'  # Current PE

# Announce RT-Constrain for subscribed tenants
for rt in PE_TENANTS.get(PE_ID, []):
    # RFC 4684 Route Target Constraint
    print(f"announce route-target {rt}", flush=True)

while True:
    line = sys.stdin.readline().strip()
    if not line:
        break

Selective Route Import

Control which tenant routes are imported:

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

# Tenant route import policy
IMPORT_POLICY = {
    'tenant-001': ['65000:1001', '65000:9999'],  # Import own + shared services
    'tenant-002': ['65000:1002', '65000:9999'],
    'tenant-003': ['65000:1003']  # Isolated, no shared services
}

def should_import_route(tenant_id, route_targets):
    """Determine if route should be imported for tenant"""
    allowed_rts = IMPORT_POLICY.get(tenant_id, [])
    return any(rt in allowed_rts for rt in route_targets)

# Listen for route updates
while True:
    line = sys.stdin.readline().strip()
    if not line:
        continue

    try:
        data = json.loads(line)
        # Parse and filter routes based on policy
        # This is illustrative - actual filtering happens in router
    except:
        pass

Tenant Provisioning

Dynamic Tenant Creation

Create tenants on-demand via API:

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

# Tenant database (in-memory for example)
TENANTS = {}

# Route Distinguisher allocation
RD_BASE = "10.0.0.1"
RT_BASE = 65000
next_tenant_id = 1000

def create_tenant(tenant_name, prefixes):
    """Dynamically create new tenant"""
    global next_tenant_id

    tenant_id = next_tenant_id
    next_tenant_id += 1

    tenant_config = {
        'name': tenant_name,
        'id': tenant_id,
        'rd': f"{RD_BASE}:{tenant_id}",
        'rt_export': f"{RT_BASE}:{tenant_id}",
        'rt_import': f"{RT_BASE}:{tenant_id}",
        'prefixes': prefixes
    }

    TENANTS[tenant_name] = tenant_config

    # Announce tenant routes
    for prefix in prefixes:
        print(f"announce route {prefix} "
              f"next-hop self "
              f"route-distinguisher {tenant_config['rd']} "
              f"route-target {tenant_config['rt_export']}", flush=True)

    return tenant_config

def delete_tenant(tenant_name):
    """Remove tenant and withdraw routes"""
    if tenant_name not in TENANTS:
        return

    tenant_config = TENANTS[tenant_name]

    # Withdraw all tenant routes
    for prefix in tenant_config['prefixes']:
        print(f"withdraw route {prefix} "
              f"route-distinguisher {tenant_config['rd']}", flush=True)

    del TENANTS[tenant_name]

def add_tenant_prefix(tenant_name, prefix):
    """Add prefix to existing tenant"""
    if tenant_name not in TENANTS:
        return

    tenant_config = TENANTS[tenant_name]
    tenant_config['prefixes'].append(prefix)

    print(f"announce route {prefix} "
          f"next-hop self "
          f"route-distinguisher {tenant_config['rd']} "
          f"route-target {tenant_config['rt_export']}", flush=True)

# Listen for provisioning commands
while True:
    line = sys.stdin.readline().strip()
    if not line:
        continue

    try:
        command = json.loads(line)

        if command.get('action') == 'create_tenant':
            create_tenant(
                command['tenant_name'],
                command.get('prefixes', [])
            )
        elif command.get('action') == 'delete_tenant':
            delete_tenant(command['tenant_name'])
        elif command.get('action') == 'add_prefix':
            add_tenant_prefix(
                command['tenant_name'],
                command['prefix']
            )
    except Exception as e:
        # Log error
        pass

Integration with Orchestration

Integrate with OpenStack/Kubernetes for automatic tenant provisioning:

#!/usr/bin/env python3
import sys
import json
from neutronclient.v2_0 import client as neutron_client

# OpenStack Neutron integration
OS_AUTH_URL = "http://controller:5000/v3"
OS_USERNAME = "admin"
OS_PASSWORD = "secret"
OS_PROJECT = "admin"

RD_BASE = "10.0.0.1"
RT_BASE = 65000

def get_neutron_client():
    return neutron_client.Client(
        username=OS_USERNAME,
        password=OS_PASSWORD,
        project_name=OS_PROJECT,
        auth_url=OS_AUTH_URL
    )

def sync_neutron_networks():
    """Sync Neutron networks to BGP VPNs"""
    neutron = get_neutron_client()
    networks = neutron.list_networks()['networks']

    for network in networks:
        network_id = network['id']
        network_name = network['name']

        # Derive VPN parameters from network ID
        tenant_id = int(network_id[:8], 16) % 65535
        rd = f"{RD_BASE}:{tenant_id}"
        rt = f"{RT_BASE}:{tenant_id}"

        # Get subnets for this network
        subnets = neutron.list_subnets(network_id=network_id)['subnets']

        for subnet in subnets:
            cidr = subnet['cidr']

            print(f"announce route {cidr} "
                  f"next-hop self "
                  f"route-distinguisher {rd} "
                  f"route-target {rt}", flush=True)

# Initial sync
sync_neutron_networks()

# Periodic sync
import time
while True:
    time.sleep(300)  # Re-sync every 5 minutes
    sync_neutron_networks()

Configuration Examples

Multi-Tenant PE Router

Configuration (/etc/exabgp/multi-tenant-pe.conf):

process tenant-controller {
    run python3 /etc/exabgp/tenant-announce.py;
    encoder json;
}

neighbor 10.0.0.1 {
    router-id 10.1.1.1;
    local-address 10.1.1.1;
    local-as 65001;
    peer-as 65001;

    family {
        ipv4 mpls-vpn;
        ipv4 rtc;  # Route Target Constraint
    }

    api {
        processes [ tenant-controller ];
    }
}

Shared Services VRF

API Program (/etc/exabgp/shared-services.py):

#!/usr/bin/env python3
import sys

# Shared services VRF (DNS, NTP, monitoring)
SHARED_SERVICES = {
    'rd': '10.0.0.1:9999',
    'rt_export': '65000:9999',  # All tenants import this
    'prefixes': [
        '10.255.1.0/24',  # DNS servers
        '10.255.2.0/24',  # NTP servers
        '10.255.3.0/24'   # Monitoring
    ]
}

NEXT_HOP = "10.0.0.1"

# Announce shared services
for prefix in SHARED_SERVICES['prefixes']:
    print(f"announce route {prefix} "
          f"next-hop {NEXT_HOP} "
          f"route-distinguisher {SHARED_SERVICES['rd']} "
          f"route-target {SHARED_SERVICES['rt_export']}", flush=True)

while True:
    line = sys.stdin.readline().strip()
    if not line:
        break

Internet Gateway per Tenant

Different internet gateways per tenant:

#!/usr/bin/env python3
import sys

# Tenant internet gateway mappings
TENANT_GATEWAYS = {
    'tenant-001': {
        'gateway': '10.1.1.1',
        'rd': '10.0.0.1:1001',
        'rt': '65000:1001'
    },
    'tenant-002': {
        'gateway': '10.1.2.1',
        'rd': '10.0.0.1:1002',
        'rt': '65000:1002'
    }
}

# Announce default route per tenant
for tenant, config in TENANT_GATEWAYS.items():
    print(f"announce route 0.0.0.0/0 "
          f"next-hop {config['gateway']} "
          f"route-distinguisher {config['rd']} "
          f"route-target {config['rt']}", flush=True)

while True:
    line = sys.stdin.readline().strip()
    if not line:
        break

See Also

Address Families

Related Use Cases

Configuration

Features


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

Clone this wiki locally