Skip to content
Open
25 changes: 23 additions & 2 deletions netsim/ansible/templates/srv6/frr.bgp.j2
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
{% if 'bgp' in srv6.igp|default([]) %}
interface ${SRv6_DEV}
ipv6 address {{ srv6.locator }}
{% endif %}

router bgp {{ bgp.as }}
! bgp default software-version-capability latest-encoding
segment-routing srv6
locator {{ inventory_hostname }}
exit

{% if 'bgp' in srv6.igp|default([]) %}
address-family ipv6 unicast
network {{ srv6.locator }}
exit-address-family
{% endif %}

{#
The core bgp module only provisions neighbors for which the transport matches the address family, i.e. IPv4 over v4
and IPv6 over v6. This template modifies IPv6 neighbors, adding VPNv4/6 AF over IPv6 transport and setting various
next hop handling flags
#}
{% macro bgp_neighbor(n,peer,af) %}
neighbor {{ peer }} activate
neighbor {{ peer }} encapsulation-srv6
neighbor {{ peer }} send-community both
{% if n.next_hop_unchanged is defined %}
neighbor {{ peer }} attribute-unchanged next-hop
Expand All @@ -19,7 +32,7 @@ router bgp {{ bgp.as }}
{% endif %}
{% endmacro -%}

{% if srv6.vpn is defined %}
{% if srv6.vpn is defined or srv6.bgp is defined %}
{% for n in bgp.neighbors|default([]) if n.ipv6 is defined %}
{% set peer = n.ipv6 %}
neighbor {{ peer }} remote-as {{ n.as }}
Expand All @@ -29,10 +42,18 @@ router bgp {{ bgp.as }}
{% endif %}

{%- for af in ['ipv4','ipv6'] %}
{% if n.type in srv6.vpn.get(af,[]) %}
{% if srv6.bgp is defined and n.type in srv6.bgp.get(af,[]) %}
address-family {{ af }} unicast
!
sid export auto
{{ bgp_neighbor(n,peer,af) -}}
!
{% endif %}
{% if srv6.vpn is defined and n.type in srv6.vpn.get(af,[]) %}
address-family {{ af }} vpn
!
{{ bgp_neighbor(n,peer,af) -}}
!
{% endif %}
{% endfor %}
{% endfor %}
Expand Down
13 changes: 12 additions & 1 deletion netsim/ansible/templates/srv6/frr.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,23 @@
set -e

export SRv6_DEV="sr0"
export SRv6_VRF_DEV="vrf-srv6"

if [ ! -e /sys/devices/virtual/net/${SRv6_DEV} ]; then
ip link add ${SRv6_DEV} type dummy
ip link set ${SRv6_DEV} up
fi

#
# Create SRv6 default VRF, needed to install SRv6 nexthops
#
{% if 'bgp' in srv6.igp|default([]) %}
if [ ! -e /sys/devices/virtual/net/${SRv6_VRF_DEV} ]; then
ip link add ${SRv6_VRF_DEV} type vrf table 254
ip link set ${SRv6_VRF_DEV} up
fi
{% endif %}

# See https://onvox.net/2024/12/16/srv6-frr/
sysctl -w net.ipv6.seg6_flowlabel=1
sysctl -w net.ipv6.conf.all.seg6_enabled=1
Expand Down Expand Up @@ -44,7 +55,7 @@ router isis {{ isis.instance }}
exit
{% endif %}

{% if srv6.vpn is defined and bgp.as is defined %}
{% if (srv6.vpn is defined or srv6.bgp is defined) and bgp.as is defined %}
{% include "frr.bgp.j2" %}
{% endif %}

Expand Down
2 changes: 1 addition & 1 deletion netsim/devices/frr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ features:
af: [ ipv4, ipv6 ]
protocol: [ isis, ospfv2 ]
srv6:
bgp: false
bgp: true
isis: true
vpn: true
stp:
Expand Down
11 changes: 3 additions & 8 deletions netsim/modules/srv6.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,16 @@ def get_pool_name() -> str:
"""
def configure_bgp_for_srv6(node: Box, topology: Box) -> None:
srv6_bgp = node.get('srv6.bgp',{})
srv6_vpn = node.get('srv6.vpn',{})
srv6_igp = node.get('srv6.igp',[])
for nb in list(node.get('bgp.neighbors',[])):
if 'ipv6' not in nb: # Skip IPv4-only neighbors
continue

for af in DEFAULT_BGP_AF.keys():
if nb.type in srv6_bgp.get(af,[]) or nb.type in srv6_vpn.get(af,[]):
# If anything, deactivating BGP AFs would break SR-OS and not achieve anything on FRR
# nb.activate[af] = False # Disable regular BGP activation
pass
else:
continue # Skip if neither AF is activated
nb.activate[af] = nb.type in srv6_bgp.get(af,[]) or (af=='ipv6' and nb.type=='ebgp' and 'bgp' in srv6_igp)

# The following code is untested and thus commented out
# if nb.type=='ebgp': # Set next hop unchanged for EBGP peers, to get end-2-end SID routing
# if nb.type=='ebgp': # Set next hop unchanged for EBGP peers, to get end-2-end SID routing
# nb.next_hop_unchanged = True
if af=='ipv4' and 'ipv4' not in nb:
nb.extended_nexthop = True # Enable extended next hops when IPv4 AF is used without IPv4 transport
Expand Down
2 changes: 1 addition & 1 deletion netsim/modules/srv6.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ attributes:
_alt_types: [ bool, BoxList ]
ipv4: { type: list, true_value: [ ibgp ] }
ipv6: { type: list, true_value: [ ibgp ] }
igp: { type: list, valid_values: [ isis, ospf ] }
igp: { type: list, valid_values: [ isis, ospf, bgp ] }
vpn: # BGP VPN v4/v6
_alt_types: [ bool, BoxList ]
ipv4: { type: list, true_value: [ ibgp ] }
Expand Down
133 changes: 133 additions & 0 deletions tests/integration/srv6/03-srv6-bgp-ipv4.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
message: |
The tested device (DUT) is a PE-router running BGP and SRv6 across an IPv6-only network to interconnect
two IPv4-only networks. The validation test checks end-to-end IPv4 connectivity across a SRv6 core.
See https://www.ietf.org/archive/id/draft-mishra-idr-v4-islands-v6-core-4pe-06.html

defaults.sources.extra: [ ../wait_times.yml ]

plugin: [ bgp.session ] # For bgp.allowas_in, the PE AS needs to be able to transit via the P AS

addressing:
core:
ipv4: False # ipv6-only
ipv6: 2001:1::/48 # SRv6 requires ipv6 addresses on interfaces

loopback:
ipv4: False # No need for ipv4 loopbacks, this avoids creating ipv4 ibgp sessions too
ipv6: 2001:db8::/48

lb_ce:
ipv4: 192.168.0.0/24
prefix: 32

srv6.allocate_loopback: False # Don't allocate loopback addresses from the locator range
srv6.igp: [ bgp ]
srv6.bgp:
ipv4: True # Enable IPv4 in the overlay (for IBGP)
ipv6: False
srv6.vpn: False

bgp.as: 65000 # PE iBGP AS for IPv4 overlay
defaults.bgp.warnings.missing_igp: False

groups:
_auto_create: True
hosts:
members: [ h1, h2 ]
device: linux
provider: clab
pe:
members: [ dut, pe2 ]
module: [ bgp, srv6 ]
bgp.import: [ connected ]
bgp.advertise_loopback: True # BGP-only underlay needs loopback reachability in BGP
core:
members: [ p ]
module: [ bgp ] # No srv6 module, just BGP
ce:
members: [ ce1, ce2 ]
module: [ bgp ]
loopback.pool: lb_ce
x_switches:
members: [ p, pe2, ce1, ce2 ]
device: frr
provider: clab

nodes:
dut:
pe2:
p:
bgp.as: 65010 # Different AS to use eBGP for SID/locator exchange
ce1:
bgp.as: 65101
ce2:
bgp.as: 65102

links:
- group: core
pool: core
members:
- dut:
bgp.allowas_in: True # Allow transit via P AS
p:
- p:
pe2:
bgp.allowas_in: True # Allow transit via P AS
- group: edge
members: [ h1-dut, h2-pe2 ]
- group: customer
members: [ ce1-dut, ce2-pe2 ]

validate:
ebgp_ulay_dut:
description: Check eBGP underlay session P-DUT over IPv6
wait: ebgp_session
wait_msg: Waiting for eBGP underlay sessions to start
nodes: [ p ]
plugin: bgp_neighbor(node.bgp.neighbors,'dut',af='ipv6')
stop_on_error: true
ebgp_ulay_pe2:
description: Check eBGP underlay session P-PE2 over IPv6
wait: ebgp_session
wait_msg: Waiting for eBGP underlay sessions to start
nodes: [ p ]
plugin: bgp_neighbor(node.bgp.neighbors,'pe2',af='ipv6')
stop_on_error: true
ebgp_dut:
description: Check EBGP session DUT-CE1
wait: ebgp_session
wait_msg: Waiting for EBGP session DUT-CE1
nodes: [ ce1 ]
plugin: bgp_neighbor(node.bgp.neighbors,'dut',af='ipv4')
ebgp_pe2:
description: Check EBGP session PE2-CE2
wait: ebgp_session
wait_msg: Waiting for EBGP session PE2-CE2
nodes: [ ce2 ]
plugin: bgp_neighbor(node.bgp.neighbors,'pe2',af='ipv4')
ibgp:
description: Check IBGP session with activation of IPv4 over IPv6
wait: 10
wait_msg: Waiting for IBGP sessions to start
nodes: [ pe2 ]
plugin: bgp_neighbor(node.bgp.neighbors,'dut',af='ipv6',activate='ipv4')
stop_on_error: true
ping_hh:
description: Ping-based host-to-host reachability test
wait_msg: We might have to wait a bit longer
wait: 10
nodes: [ h1 ]
plugin: ping('h2')
ping_h_ce:
description: Ping-based host-to-CE reachability test
wait_msg: We might have to wait a bit longer
wait: 10
nodes: [ h1, h2 ]
plugin: ping('ce1')
ping_ce_ce:
description: Ping-based CE-to-CE reachability test
wait_msg: We might have to wait a bit longer
wait: 10
nodes: [ ce1 ]
plugin: ping(nodes.ce2.loopback.ipv4,src=nodes.ce1.loopback.ipv4)
Loading
Loading