Skip to content

Use Cases Service Provider VPN

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

Service Provider Layer 3 VPN Services

ExaBGP enables service provider L3VPN services using MPLS L3VPN for enterprise customers, Internet and VPN separation, and BGP/MPLS VPN architecture.

Table of Contents

Overview

L3VPN Service Model

Service providers offer L3VPN services for:

  • Enterprise connectivity: Connect branch offices, headquarters, data centers
  • Internet access: Separate internet and VPN traffic
  • Managed services: Hosting, cloud connectivity, managed WAN
  • Multi-tenancy: Isolate customer networks
  • Any-to-any connectivity: Full mesh within VPN
  • Hub-and-spoke: Centralized internet/DC access

ExaBGP Role

ExaBGP enables L3VPN services by:

  1. VPN route advertisement: Advertise customer prefixes with RD/RT
  2. Route target control: Import/export routes per customer
  3. Service provisioning: Dynamic VPN setup via API
  4. Internet gateway: Provide internet access per VPN

Important: ExaBGP exchanges VPN routes but does NOT create MPLS labels or VRFs. Your PE router must configure the MPLS data plane and VRF forwarding.

MPLS L3VPN Architecture

Basic L3VPN Service

Provide L3VPN for enterprise customer:

#!/usr/bin/env python3
import sys

# Enterprise L3VPN configuration
CUSTOMER_VPN = {
    'customer': 'Enterprise-A',
    'vrf': 'CUSTOMER-A',
    'rd': '10.1.1.1:100',
    'rt_export': '65000:100',
    'rt_import': '65000:100',
    'sites': {
        'hq': '192.168.1.0/24',
        'branch-1': '192.168.2.0/24',
        'branch-2': '192.168.3.0/24',
        'datacenter': '192.168.100.0/24'
    }
}

PE_ADDRESS = "10.1.1.1"
SITE = 'hq'  # This PE serves HQ site

# Announce site prefix
if SITE in CUSTOMER_VPN['sites']:
    prefix = CUSTOMER_VPN['sites'][SITE]

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

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

Multi-Site L3VPN

Connect multiple customer sites via L3VPN:

#!/usr/bin/env python3
import sys

# Multi-site enterprise VPN
ENTERPRISE_VPN = {
    'customer-id': 'ACME-Corp',
    'rd': '10.1.1.1:200',
    'rt': '65000:200',
    'sites': [
        {'pe': '10.1.1.1', 'location': 'New York HQ', 'prefix': '10.100.1.0/24'},
        {'pe': '10.2.2.2', 'location': 'London', 'prefix': '10.100.2.0/24'},
        {'pe': '10.3.3.3', 'location': 'Singapore', 'prefix': '10.100.3.0/24'},
        {'pe': '10.4.4.4', 'location': 'Data Center', 'prefix': '10.100.100.0/24'}
    ]
}

PE_ADDRESS = "10.1.1.1"

# Find sites at this PE
local_sites = [s for s in ENTERPRISE_VPN['sites'] if s['pe'] == PE_ADDRESS]

# Announce local site prefixes
for site in local_sites:
    print(f"announce route {site['prefix']} "
          f"next-hop {PE_ADDRESS} "
          f"route-distinguisher {ENTERPRISE_VPN['rd']} "
          f"route-target {ENTERPRISE_VPN['rt']}", flush=True)

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

Hub-and-Spoke VPN

Central hub site with spoke branches:

#!/usr/bin/env python3
import sys

# Hub-and-spoke VPN
HUB_SPOKE_VPN = {
    'hub': {
        'rd': '10.1.1.1:300',
        'rt_export': '65000:300',
        'rt_import': '65000:301',  # Import from spokes
        'prefix': '10.200.0.0/16'  # Hub prefix
    },
    'spokes': {
        'rd': '10.1.1.1:301',
        'rt_export': '65000:301',  # Export to hub
        'rt_import': '65000:300',  # Import from hub only
        'prefixes': [
            '10.201.1.0/24',  # Branch 1
            '10.201.2.0/24',  # Branch 2
            '10.201.3.0/24'   # Branch 3
        ]
    }
}

PE_ADDRESS = "10.1.1.1"
ROLE = 'hub'  # or 'spoke'

if ROLE == 'hub':
    # Announce hub prefix
    print(f"announce route {HUB_SPOKE_VPN['hub']['prefix']} "
          f"next-hop {PE_ADDRESS} "
          f"route-distinguisher {HUB_SPOKE_VPN['hub']['rd']} "
          f"route-target {HUB_SPOKE_VPN['hub']['rt_export']}", flush=True)
else:
    # Announce spoke prefixes
    for prefix in HUB_SPOKE_VPN['spokes']['prefixes']:
        print(f"announce route {prefix} "
              f"next-hop {PE_ADDRESS} "
              f"route-distinguisher {HUB_SPOKE_VPN['spokes']['rd']} "
              f"route-target {HUB_SPOKE_VPN['spokes']['rt_export']}", flush=True)

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

Internet and VPN Separation

Separate Internet and VPN Routes

Provide both internet and VPN access per customer:

#!/usr/bin/env python3
import sys

# Customer with both VPN and internet
CUSTOMER = {
    'vpn': {
        'rd': '10.1.1.1:100',
        'rt_export': '65000:100',
        'rt_import': '65000:100',
        'prefixes': ['192.168.1.0/24', '192.168.2.0/24']
    },
    'internet': {
        'rd': '10.1.1.1:999',
        'rt_export': '65000:999',  # Internet VRF
        'rt_import': '65000:999',
        'default_route': '0.0.0.0/0',
        'gateway': '10.1.1.254'
    }
}

PE_ADDRESS = "10.1.1.1"

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

# Announce default route for internet VRF
print(f"announce route {CUSTOMER['internet']['default_route']} "
      f"next-hop {CUSTOMER['internet']['gateway']} "
      f"route-distinguisher {CUSTOMER['internet']['rd']} "
      f"route-target {CUSTOMER['internet']['rt_export']}", flush=True)

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

Per-Customer Internet Gateways

Different internet breakout per customer:

#!/usr/bin/env python3
import sys

# Multiple customers with dedicated internet gateways
CUSTOMERS = {
    'customer-a': {
        'vpn-rd': '10.1.1.1:100',
        'vpn-rt': '65000:100',
        'internet-gateway': '10.1.10.1',
        'internet-rd': '10.1.1.1:1000',
        'internet-rt': '65000:1000'
    },
    'customer-b': {
        'vpn-rd': '10.1.1.1:200',
        'vpn-rt': '65000:200',
        'internet-gateway': '10.1.20.1',
        'internet-rd': '10.1.1.1:2000',
        'internet-rt': '65000:2000'
    }
}

# Announce default routes with customer-specific gateways
for customer_id, config in CUSTOMERS.items():
    print(f"announce route 0.0.0.0/0 "
          f"next-hop {config['internet-gateway']} "
          f"route-distinguisher {config['internet-rd']} "
          f"route-target {config['internet-rt']}", flush=True)

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

Multi-VRF Customer Services

Multiple VRFs per Customer

Provide network segmentation within customer network:

#!/usr/bin/env python3
import sys

# Customer with multiple VRFs
CUSTOMER_VRFS = {
    'ACME-Corp': {
        'corporate': {
            'rd': '10.1.1.1:100',
            'rt': '65000:100',
            'prefixes': ['10.100.0.0/16']
        },
        'guest': {
            'rd': '10.1.1.1:101',
            'rt': '65000:101',
            'prefixes': ['10.200.0.0/16']
        },
        'iot': {
            'rd': '10.1.1.1:102',
            'rt': '65000:102',
            'prefixes': ['10.300.0.0/16']
        },
        'dmz': {
            'rd': '10.1.1.1:103',
            'rt': '65000:103',
            'prefixes': ['10.400.0.0/16']
        }
    }
}

PE_ADDRESS = "10.1.1.1"

# Announce all VRFs
for customer, vrfs in CUSTOMER_VRFS.items():
    for vrf_name, config in vrfs.items():
        for prefix in config['prefixes']:
            print(f"announce route {prefix} "
                  f"next-hop {PE_ADDRESS} "
                  f"route-distinguisher {config['rd']} "
                  f"route-target {config['rt']}", flush=True)

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

Cross-VRF Route Leaking

Allow controlled communication between VRFs:

#!/usr/bin/env python3
import sys

# VRFs with selective route import/export
VRFS_WITH_LEAKING = {
    'corporate': {
        'rd': '10.1.1.1:100',
        'rt_export': ['65000:100'],
        'rt_import': ['65000:100', '65000:999'],  # Import shared services
        'prefixes': ['10.100.0.0/16']
    },
    'guest': {
        'rd': '10.1.1.1:101',
        'rt_export': ['65000:101'],
        'rt_import': ['65000:101', '65000:999'],  # Import shared services
        'prefixes': ['10.200.0.0/16']
    },
    'shared-services': {
        'rd': '10.1.1.1:999',
        'rt_export': ['65000:999'],  # All VRFs import this
        'rt_import': ['65000:100', '65000:101'],  # Can reach all VRFs
        'prefixes': ['10.255.0.0/16']  # DNS, NTP, etc.
    }
}

PE_ADDRESS = "10.1.1.1"

# Announce routes with appropriate RTs
for vrf_name, config in VRFS_WITH_LEAKING.items():
    for prefix in config['prefixes']:
        # Export with all configured RTs
        rt_list = ' '.join(config['rt_export'])
        print(f"announce route {prefix} "
              f"next-hop {PE_ADDRESS} "
              f"route-distinguisher {config['rd']} "
              f"route-target {rt_list}", flush=True)

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

Configuration Examples

Provider Edge (PE) Router

Configuration (/etc/exabgp/l3vpn-pe.conf):

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

# Connection to route reflector
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;
        ipv6 mpls-vpn;
    }

    api {
        processes [ vpn-controller ];
    }
}

Dynamic VPN Provisioning

API Program (/etc/exabgp/l3vpn-announce.py):

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

# Active VPNs
ACTIVE_VPNS = {}
next_vpn_id = 1000

PE_ADDRESS = "10.1.1.1"
RD_BASE = "10.1.1.1"
RT_BASE = 65000

def create_vpn(customer_id, prefixes):
    """Create new L3VPN for customer"""
    global next_vpn_id

    vpn_id = next_vpn_id
    next_vpn_id += 1

    vpn_config = {
        'customer': customer_id,
        'vpn_id': vpn_id,
        'rd': f"{RD_BASE}:{vpn_id}",
        'rt': f"{RT_BASE}:{vpn_id}",
        'prefixes': prefixes
    }

    # Announce prefixes
    for prefix in prefixes:
        print(f"announce route {prefix} "
              f"next-hop {PE_ADDRESS} "
              f"route-distinguisher {vpn_config['rd']} "
              f"route-target {vpn_config['rt']}", flush=True)

    ACTIVE_VPNS[customer_id] = vpn_config
    return vpn_config

def delete_vpn(customer_id):
    """Delete L3VPN"""
    if customer_id not in ACTIVE_VPNS:
        return False

    vpn = ACTIVE_VPNS[customer_id]

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

    del ACTIVE_VPNS[customer_id]
    return 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_vpn':
            vpn = create_vpn(
                command['customer_id'],
                command.get('prefixes', [])
            )
            print(json.dumps({'status': 'success', 'vpn': vpn}),
                  file=sys.stderr, flush=True)

        elif command.get('action') == 'delete_vpn':
            success = delete_vpn(command['customer_id'])
            print(json.dumps({'status': 'success' if success else 'failed'}),
                  file=sys.stderr, flush=True)

    except Exception as e:
        print(json.dumps({'status': 'error', 'message': str(e)}),
              file=sys.stderr, flush=True)

See Also

Address Families

Related Use Cases

Configuration

Features


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

Clone this wiki locally