diff --git a/netsim/ansible/templates/bgp/srlinux.j2 b/netsim/ansible/templates/bgp/srlinux.j2 index 85f0756e3..cbffb6ed9 100644 --- a/netsim/ansible/templates/bgp/srlinux.j2 +++ b/netsim/ansible/templates/bgp/srlinux.j2 @@ -9,5 +9,4 @@ updates: accept: {} {% from "srlinux.macro.j2" import bgp_config with context %} -{% set _add_interfaces = bgp.update( { 'interfaces': interfaces } ) %} {{ bgp_config('default',bgp.as,bgp.router_id,bgp,{}) }} diff --git a/netsim/ansible/templates/bgp/srlinux.macro.j2 b/netsim/ansible/templates/bgp/srlinux.macro.j2 index 513419cc4..e6e66de7d 100644 --- a/netsim/ansible/templates/bgp/srlinux.macro.j2 +++ b/netsim/ansible/templates/bgp/srlinux.macro.j2 @@ -124,7 +124,7 @@ {# {{ bgp_network(af,loopback[af]) }} #} {% endif %} -{% for l in vrf_bgp.interfaces|default([]) if l.bgp.advertise|default("") and l[af] is defined and not 'vrf' in l %} +{% for l in interfaces|default([]) if l.bgp.advertise|default("") and l[af] is defined and not 'vrf' in l %} {# {{ bgp_network(af,l[af]) }} #} {% endfor %} {% for pfx in bgp.originate|default([]) if af == 'ipv4' %} @@ -155,25 +155,40 @@ {% endif %} {% elif n[af]==True and af=='ipv6' %} {# BGP unnumbered for IPv6 LLA #} +{% set peer_group = "ebgp-unnumbered" + (('-' + n.local_as|string()) if n.local_as is defined else '') %} - path: network-instance[name={{vrf}}]/ip-forwarding val: receive-ipv4-check: false _annotate_receive-ipv4-check: "Allow IPv4 on IPv6 unnumbered interfaces" -{% set ns = namespace(l=false) %} -{% for i in vrf_bgp.interfaces|default([]) if i.ifindex == n.ifindex %} -{% set ns.l = i %} -{% endfor %} -{% if ns.l %} -{% set if_name_index = ns.l.ifname.split('.') %} +{% for i in interfaces|default([]) if i.ifindex == n.ifindex %} +{% set if_name_index = i.ifname.split('.') %} {% set if_name = if_name_index[0] %} {% set if_index = if_name_index[1] if if_name_index|length > 1 else '0' %} - path: network-instance[name={{vrf}}]/protocols/bgp/dynamic-neighbors/interface[interface-name={{if_name}}.{{if_index}}] val: - peer-group: "ebgp-unnumbered" + peer-group: "{{ peer_group }}" allowed-peer-as: [ {{ n.as }}..{{ n.as }} ] + +{% if peer_group != "ebgp-unnumbered" %} +- path: network-instance[name={{vrf}}]/protocols/bgp/group[group-name={{peer_group}}] + val: + admin-state: enable + import-policy: accept_all + export-policy: accept_all + timers: + connect-retry: 10 + _annotate_connect-retry: "Reduce default 120s to 10s" + minimum-advertisement-interval: 1 + local-as: + - as-number: {{ n.local_as }} + prepend-global-as: false + ipv4-unicast: + advertise-ipv6-next-hops: true + receive-ipv6-next-hops: true {% endif %} +{% endfor %} {% endif %} {% endfor %} {% endfor %} diff --git a/netsim/augment/links.py b/netsim/augment/links.py index dcf26dec4..51ab8a2ce 100644 --- a/netsim/augment/links.py +++ b/netsim/augment/links.py @@ -335,10 +335,15 @@ def augment_lan_link(link: Box, addr_pools: Box, ndict: dict, defaults: Box) -> node_if['data'].neighbors = [] for remote_if in interfaces: if remote_if['node'] != node_if['node'] or remote_if['data'].ifindex != node_if['data'].ifindex: - ngh_data = { 'ifname': remote_if['data'].ifname, 'node': remote_if['node'] } + ngh_data = Box({ 'ifname': remote_if['data'].ifname, 'node': remote_if['node'] }) for af in ('ipv4','ipv6'): if af in remote_if['data']: ngh_data[af] = remote_if['data'][af] + + # List enabled modules that have interface level attributes; copy those attributes too + mods_with_ifattr = Box({ m : True for m in ndict[remote_if['node']].get('module',[]) if defaults[m].attributes.get('interface',None) }) + ifaddr_add_module(ngh_data,remote_if['data'],mods_with_ifattr) + node_if['data'].neighbors.append(ngh_data) if common.DEBUG: # pragma: no cover (debugging) @@ -418,6 +423,10 @@ def augment_p2p_link(link: Box, addr_pools: Box, ndict: dict, defaults: Box) -> if af in interfaces[i]: link[end_names[i]][af] = interfaces[i][af] + # JvB: copy module specific link attributes like bgp.local_as + mods_with_ifattr = Box({ m : True for m in ndict[remote].get('module',[]) if defaults[m].attributes.get('interface',None) }) + ifaddr_add_module(interfaces[i]['neighbors'][0],interfaces[1-i],mods_with_ifattr) + return link def check_link_attributes(data: Box, nodes: dict, valid: set) -> bool: diff --git a/netsim/extra/ebgp-local_as.py b/netsim/extra/ebgp-local_as.py index 03ee2d75b..02ba587db 100644 --- a/netsim/extra/ebgp-local_as.py +++ b/netsim/extra/ebgp-local_as.py @@ -49,13 +49,12 @@ def ebgp_neighbor(n: Box, asn: int, intf: Box, extra_data: dict) -> Box: different underlay AS """ def build_ebgp_sessions(node: Box, topology: Box) -> None: - # # eBGP sessions - iterate over all links, find adjacent nodes # in different AS numbers, and create eBGP neighbors; set 'local_as' ibgp_as = topology.bgp['as'] for l in [ l for l in node.get("interfaces",[]) if l.type == 'p2p' ]: - node_as = l.bgp.underlay_as if "bgp" in l and "underlay_as" in l.bgp else node.bgp.underlay_as + node_as = l.bgp.underlay_as if "bgp" in l and "underlay_as" in l.bgp else node.bgp.get('underlay_as',None) for ngb_ifdata in l.get("neighbors",[]): ngb_name = ngb_ifdata.node @@ -80,9 +79,15 @@ def build_ebgp_sessions(node: Box, topology: Box) -> None: extra_data.local_if = l.ifname if common.DEBUG: print(f'ebgp-local_as: adding neighbor for node {node.name} peer {neighbor.name} peer_as={peer_as}') - node.bgp.neighbors.append( ebgp_neighbor(neighbor,peer_as,ngb_ifdata,extra_data) ) + ebgp_data = ebgp_neighbor(neighbor,peer_as,ngb_ifdata,extra_data) + if 'vrf' in l: # VRF neighbor + if not node.vrfs[l.vrf].bgp.neighbors: + node.vrfs[l.vrf].bgp.neighbors = [] + node.vrfs[l.vrf].bgp.neighbors.append(ebgp_data) + else: + node.bgp.neighbors.append(ebgp_data) def post_transform(topology: Box) -> None: for node in topology.nodes.values(): - if "bgp" in node and "underlay_as" in node.bgp: + if "bgp" in node: # and "underlay_as" in node.bgp: Can also be at link level build_ebgp_sessions(node,topology) diff --git a/tests/integration/vlan/vlan-vrf-route-leaking.yaml b/tests/integration/vlan/vlan-vrf-route-leaking.yaml new file mode 100644 index 000000000..2427a6a58 --- /dev/null +++ b/tests/integration/vlan/vlan-vrf-route-leaking.yaml @@ -0,0 +1,82 @@ +# +# VRF lite implementation with VLAN trunks, including ebgp peering between vrfs locally +# +# * h1 and h2 should be able to ping each other, as well as h3 and h4 +# * h3 and h4 should be able to ping each other, as well as h1 and h2 +# +# A device has to support the following features to pass this test case: +# +# * Routed VLAN interfaces +# * VRFs +# * OSPF in VRFs +# * BGP unnumbered using ipv6 lla +# +# Please note it might take a while for the lab to work due to +# STP and OSPF setup phase +# +groups: + routers: + members: [ r1,r2,r3 ] + module: [ vlan,vrf,ospf ] + hosts: + device: linux + members: [ h1,h2,h3,h4 ] + +plugin: [ ebgp-local_as ] + +vrfs: + red: + blue: + +vlans: + red: + mode: route + vrf: red + blue: + mode: route + vrf: blue + +nodes: + r1: + r2: + module: [ vlan,vrf,ospf,bgp ] + bgp.as: 65000 + r3: + h1: + h2: + h3: + h4: + +links: +- r1: + r2: + vlan.trunk: [ red, blue ] +- r2: + r3: + vlan.trunk: [ red, blue ] +- interfaces: # VRF route leaking between red and blue on r2, using eBGP peering + - node: r2 + vrf: red + ipv6: True + ipv4: False + bgp.underlay_as: 65001 + # vlan.access: vrf-leak + - node: r2 + vrf: blue + ipv6: True + ipv4: False + bgp.underlay_as: 65002 + # vlan.access: vrf-leak + role: external +- h1: + r1: + vlan.access: red +- h3: + r1: + vlan.access: blue +- h2: + r3: + vlan.access: red +- h4: + r3: + vlan.access: blue diff --git a/tests/topology/expected/bgp-community.yml b/tests/topology/expected/bgp-community.yml index 813ba3a6b..54733195b 100644 --- a/tests/topology/expected/bgp-community.yml +++ b/tests/topology/expected/bgp-community.yml @@ -235,7 +235,9 @@ nodes: linkindex: 2 name: r3 -> r2 neighbors: - - ifname: GigabitEthernet0/1 + - bgp: + local_as: 65002 + ifname: GigabitEthernet0/1 ipv4: 10.1.0.5/30 node: r2 ospf: diff --git a/tests/topology/expected/vlan-vrf-lite.yml b/tests/topology/expected/vlan-vrf-lite.yml index 9091d67e4..0405268a6 100644 --- a/tests/topology/expected/vlan-vrf-lite.yml +++ b/tests/topology/expected/vlan-vrf-lite.yml @@ -215,6 +215,7 @@ nodes: - ifname: Ethernet2 ipv4: 172.16.2.1/24 node: r1 + vrf: red role: stub type: lan mgmt: @@ -242,6 +243,7 @@ nodes: - ifname: Ethernet3 ipv4: 172.16.4.2/24 node: r2 + vrf: red role: stub type: lan mgmt: @@ -269,6 +271,7 @@ nodes: - ifname: Ethernet3 ipv4: 172.16.3.1/24 node: r1 + vrf: blue role: stub type: lan mgmt: @@ -296,6 +299,7 @@ nodes: - ifname: Ethernet4 ipv4: 172.16.5.2/24 node: r2 + vrf: blue role: stub type: lan mgmt: @@ -320,6 +324,7 @@ nodes: - ifname: Ethernet4 ipv4: 10.1.0.2/30 node: r1 + vrf: red type: p2p loopback: ipv4: 10.0.0.8/32 diff --git a/tests/topology/expected/vrf-igp.yml b/tests/topology/expected/vrf-igp.yml index 27ff4660c..9590457e3 100644 --- a/tests/topology/expected/vrf-igp.yml +++ b/tests/topology/expected/vrf-igp.yml @@ -385,6 +385,7 @@ nodes: - ifname: Ethernet2 ipv4: 10.1.0.5/30 node: pe1 + vrf: red role: external type: p2p loopback: @@ -420,6 +421,7 @@ nodes: - ifname: Ethernet2 ipv4: 10.1.0.9/30 node: pe2 + vrf: blue ospf: area: 0.0.0.0 network_type: point-to-point diff --git a/tests/topology/expected/vrf.yml b/tests/topology/expected/vrf.yml index 370c6ef3f..d5aa90533 100644 --- a/tests/topology/expected/vrf.yml +++ b/tests/topology/expected/vrf.yml @@ -279,6 +279,7 @@ nodes: - ifname: Ethernet1 ipv4: 10.1.0.1/30 node: r1 + vrf: red role: external type: p2p - bgp: @@ -365,6 +366,7 @@ nodes: - ifname: Ethernet2 ipv4: 10.1.0.5/30 node: r1 + vrf: blue ospf: area: 0.0.0.0 network_type: point-to-point