diff --git a/netsim/ansible/templates/srv6/frr.bgp.j2 b/netsim/ansible/templates/srv6/frr.bgp.j2 index e57db3a438..e996e6bf39 100644 --- a/netsim/ansible/templates/srv6/frr.bgp.j2 +++ b/netsim/ansible/templates/srv6/frr.bgp.j2 @@ -1,8 +1,20 @@ +{% 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 @@ -10,6 +22,7 @@ router bgp {{ bgp.as }} #} {% 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 @@ -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 }} @@ -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 %} diff --git a/netsim/ansible/templates/srv6/frr.j2 b/netsim/ansible/templates/srv6/frr.j2 index 9615e64644..9d2bea4650 100644 --- a/netsim/ansible/templates/srv6/frr.j2 +++ b/netsim/ansible/templates/srv6/frr.j2 @@ -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 @@ -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 %} diff --git a/netsim/devices/frr.yml b/netsim/devices/frr.yml index 9cbb367d86..bddd5f4d8a 100644 --- a/netsim/devices/frr.yml +++ b/netsim/devices/frr.yml @@ -184,7 +184,7 @@ features: af: [ ipv4, ipv6 ] protocol: [ isis, ospfv2 ] srv6: - bgp: false + bgp: true isis: true vpn: true stp: diff --git a/netsim/modules/srv6.py b/netsim/modules/srv6.py index b904476b6f..070a2cb11f 100644 --- a/netsim/modules/srv6.py +++ b/netsim/modules/srv6.py @@ -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 diff --git a/netsim/modules/srv6.yml b/netsim/modules/srv6.yml index 06290e6cbc..f05561a306 100644 --- a/netsim/modules/srv6.yml +++ b/netsim/modules/srv6.yml @@ -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 ] } diff --git a/tests/integration/srv6/03-srv6-bgp-ipv4.yml b/tests/integration/srv6/03-srv6-bgp-ipv4.yml new file mode 100644 index 0000000000..5a89ac1ad8 --- /dev/null +++ b/tests/integration/srv6/03-srv6-bgp-ipv4.yml @@ -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) diff --git a/tests/integration/srv6/04-srv6-bgp-ipv6.yml b/tests/integration/srv6/04-srv6-bgp-ipv6.yml new file mode 100644 index 0000000000..5250ff1ae4 --- /dev/null +++ b/tests/integration/srv6/04-srv6-bgp-ipv6.yml @@ -0,0 +1,140 @@ +--- +message: | + The tested device (DUT) is a PE-router running BGP and SRv6 across an IPv6-only network to interconnect + two IPv6 customer sites. The validation test checks end-to-end IPv6 connectivity across an 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: + ipv6: 2001:db8:ce::/48 + prefix: 32 + + p2p: + ipv6: 2001:db8:4::/48 + ipv4: False + lan: + ipv4: False + ipv6: 2001:db8:42::/48 + +srv6.allocate_loopback: False # Don't allocate loopback addresses from the locator range +srv6.igp: [ bgp ] +srv6.bgp: + ipv4: False + ipv6: True # Enable IPv6 in the overlay (for IBGP) +srv6.vpn: False + +bgp.as: 65000 # PE iBGP AS for IPv6 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 (IPv6) + wait: ebgp_session + wait_msg: Waiting for EBGP session DUT-CE1 + nodes: [ ce1 ] + plugin: bgp_neighbor(node.bgp.neighbors,'dut',af='ipv6') + ebgp_pe2: + description: Check EBGP session PE2-CE2 (IPv6) + wait: ebgp_session + wait_msg: Waiting for EBGP session PE2-CE2 + nodes: [ ce2 ] + plugin: bgp_neighbor(node.bgp.neighbors,'pe2',af='ipv6') + ibgp: + description: Check IBGP session for IPv6 unicast over IPv6 transport + wait: 10 + wait_msg: Waiting for IBGP sessions to start + nodes: [ pe2 ] + plugin: bgp_neighbor(node.bgp.neighbors,'dut',af='ipv6') + 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',af='ipv6') + 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',af='ipv6') + 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.ipv6,src=nodes.ce1.loopback.ipv6,af='ipv6')