diff --git a/docs/plugins/bgp.session.md b/docs/plugins/bgp.session.md index 7119df26c..6e770768e 100644 --- a/docs/plugins/bgp.session.md +++ b/docs/plugins/bgp.session.md @@ -2,37 +2,67 @@ The **bgp.session** plugin (initially contributed by Stefano Sasso) implements several BGP session nerd knobs, including: -* **bgp.allowas_in** is an interface (node-to-link attachment) attribute that controls whether a BGP router accepts one or more copies of its own AS number in incoming update. It takes an integer value between 1 and 10. A *true* value sets it to 1. -* **bgp.as_override** is an interface (node-to-link attachment) boolean attribute that controls whether a BGP router replaces peer AS with its own AS. -* **bgp.default_originate** is a boolean attribute that controls whether a BGP router advertises a default route to its neighbor(s). It can be specified on global, node, link or interface level. -* **bgp.gtsm** is an integer attribute that enables the Generic TTL Security Mechanism (GTSM). It can be specified on global, node, link or interface level and takes an integer value between 1 and 254. A *true* value sets it to 1 (remote router can be up to one hop away). -* **bgp.password** is a string attribute that can be specified on global, node, link or interface level. It specifies the MD5 or TCP-AO password used on EBGP sessions. -* **bgp.tcp_ao** is an attribute that enables TCP-AO on a BGP session. It can be specified on global, node, link or interface level. The attribute value *true* enables TCP-AO with HMAC-SHA1-96 algorithm; you can specify the desired algorithm as a string value of **bgp.tcp_ao** parameter. -* **bgp.timers** is a dictionary of BGP timers that can be specified on global, node, link or interface level. It has three elements: +* **bgp.allowas_in** is an attribute that controls whether a BGP router accepts one or more copies of its own AS number in incoming update. It takes an integer value between 1 and 10. A *true* value sets it to 1. +* **bgp.as_override** is a boolean attribute that controls whether a BGP router replaces peer AS with its own AS. +* **bgp.bfd** is a boolean attribute that enables BFD with BGP neighbors. +* **bgp.default_originate** is a boolean attribute that controls whether a BGP router advertises a default route to its neighbor(s). +* **bgp.gtsm** is an integer attribute that enables the Generic TTL Security Mechanism (GTSM). A *true* value sets it to 1 (remote router can be up to one hop away). +* **bgp.password** is a string attribute that specifies the MD5 or TCP-AO password used on EBGP sessions. +* **bgp.tcp_ao** is an attribute that enables TCP-AO on a BGP session. The attribute value *true* enables TCP-AO with HMAC-SHA1-96 algorithm; you can specify the desired algorithm as a string value of **bgp.tcp_ao** parameter. +* **bgp.timers** is a dictionary of BGP session timers. It has three elements: * **bgp.timers.keepalive** -- keepalive timer in seconds * **bgp.timers.hold** -- hold timer in seconds * **bgp.timers.min_hold** -- minimum hold timer accepted from the remote node. Used only on devices where the minimum hold timer can be specified per BGP neighbor. -The plugin includes Jinja2 templates for the following platforms: - -| Operating system | allowas_in | AS
override | password | default
originate | GTSM | BGP
timers | TCP-AO | -| ------------------- | :--------: | :---------: | :------: | :---------------: | :--: | :--: | :--: | -| Arista EOS | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Cisco IOSv | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | -| Cisco IOS-XE | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Cumulus Linux | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | -| FRR | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | -| Juniper vMX/vPTX/vSRX | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | -| Mikrotik RouterOS 7 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| Nokia SR Linux | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| Nokia SR OS | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | -| VyOS | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +BGP session attributes can be specified on global, node, link, or interface (node-to-link attachment) level. The following table describes where you could apply individual attributes: + +| BGP session attribute | Global | Node | Link | Interface | +|-----------------------|:------:|:----:|:----:|:---------:| +| allowas_in | ❌ | ❌ | ❌ | ✅ | +| as_override | ❌ | ❌ | ❌ | ✅ | +| bfd | ✅ | ✅ | ✅ | ✅ | +| default_originate | ❌ | ✅ | ❌ | ✅ | +| gtsm | ✅ | ✅ | ✅ | ✅ | +| password | ✅ | ✅ | ✅ | ✅ | +| tcp_ao | ✅ | ✅ | ✅ | ✅ | +| timers | ✅ | ✅ | ✅ | ✅ | + +The plugin implements generic BGP session features for the following platforms: + +| Operating system | password | default
originate | GTSM | BGP
timers | TCP-AO | BFD | +| ------------------- | :------: | :---------------: | :--: | :--: | :--: | :-: | +| Arista EOS | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Cisco IOSv | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | +| Cisco IOS-XE | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Cumulus Linux | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | +| FRR | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| Juniper vMX/vPTX/vSRX | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Mikrotik RouterOS 7 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| Nokia SR Linux | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| Nokia SR OS | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | +| VyOS | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | **Notes:** * Arista EOS supports TCP-AO only when running as a virtual machine * _netlab_ always configures HMAC-SHA1-96 as the cryptographic algorithm on IOS XE +The plugin implements AS-path-mangling nerd knobs for the following platforms: + +| Operating system | allowas_in | AS
override | +| ------------------- | :--------: | :---------: | +| Arista EOS | ✅ | ✅ | +| Cisco IOSv | ✅ | ✅ | +| Cisco IOS-XE | ✅ | ✅ | +| Cumulus Linux | ✅ | ✅ | +| FRR | ✅ | ✅ | +| Juniper vMX/vPTX/vSRX | ❌ | ✅ | +| Mikrotik RouterOS 7 | ✅ | ✅ | +| Nokia SR Linux | ✅ | ✅ | +| Nokia SR OS | ✅ | ✅ | +| VyOS | ✅ | ✅ | + + ## Test Topology ``` diff --git a/netsim/extra/bgp.session/_sample_bfd_template.j2 b/netsim/extra/bgp.session/_sample_bfd_template.j2 new file mode 100644 index 000000000..2424b1cd1 --- /dev/null +++ b/netsim/extra/bgp.session/_sample_bfd_template.j2 @@ -0,0 +1,25 @@ +{% macro bfd_timers(bfd) -%} +{%- if bfd.min_tx|default(0) %} + interval {{ bfd.min_tx }} +{%- if bfd.min_rx|default(0) %} + min-rx {{ bfd.min_rx }} +{%- if bfd.multiplier|default(0) %} + multiplier {{ bfd.multiplier }} +{%- endif -%} +{%- endif -%} +{%- endif -%} +{%- endmacro %} + +{% macro ebgp_session(n,af,bgp,bfd) -%} +{% if n.bfd is defined %} + neighbor {{ n[af] }} bfd {{- bfd_timers(bfd) }} +{% endif %} +{%- endmacro %} + +{% set bfd = bfd|default({}) %} +router bgp {{ bgp.as }} +{% for af in ['ipv4','ipv6'] %} +{% for n in bgp.neighbors if n[af] is defined %} +{{ ebgp_session(n,af,bgp,bfd) -}} +{% endfor %} +{% endfor %} diff --git a/netsim/extra/bgp.session/defaults.yml b/netsim/extra/bgp.session/defaults.yml index 0daa7aa08..fe7d516ae 100644 --- a/netsim/extra/bgp.session/defaults.yml +++ b/netsim/extra/bgp.session/defaults.yml @@ -3,41 +3,45 @@ --- devices: csr.features.bgp: - default_originate: True allowas_in: True as_override: True - password: True + bfd: True + default_originate: True gtsm: True - timers: True + password: True tcp_ao: True + timers: True cumulus.features.bgp: - default_originate: True allowas_in: True as_override: True - password: True + bfd: True + default_originate: True gtsm: True + password: True timers: True eos.features.bgp: - default_originate: True allowas_in: True as_override: True - password: True + bfd: True + default_originate: True gtsm: True - timers: True + password: True tcp_ao: [ libvirt, virtualbox, external ] + timers: True frr.features.bgp: - default_originate: True allowas_in: True as_override: True - password: True + default_originate: True gtsm: True + password: True timers: True iosv.features.bgp: - default_originate: True allowas_in: True as_override: True - password: True + bfd: True + default_originate: True gtsm: True + password: True timers: True vsrx.features.bgp: as_override: True @@ -74,10 +78,11 @@ devices: bgp: attributes: ebgp_utils: - attr: [ as_override,allowas_in,default_originate,password,gtsm,timers,tcp_ao ] # All ebgp.utils attributes - local: [ as_override,allowas_in,default_originate,password,gtsm,timers,tcp_ao ] # ebgp.utils attributes with local significance + attr: [ as_override,allowas_in,default_originate,password,gtsm,timers,tcp_ao,bfd ] # All ebgp.utils attributes + local: [ as_override,allowas_in,default_originate,password,gtsm,timers,tcp_ao,bfd ] # ebgp.utils attributes with local significance global: + bfd: bool tcp_ao: type: str valid_values: ['aes-128-cmac','hmac-sha-1',''] @@ -102,6 +107,7 @@ bgp: max_value: 3600 password: str node: + bfd: bool default_originate: bool tcp_ao: copy: global @@ -125,6 +131,7 @@ bgp: copy: global timers: copy: global + bfd: bool link: password: str tcp_ao: @@ -133,3 +140,4 @@ bgp: copy: global timers: copy: global + bfd: bool diff --git a/netsim/extra/bgp.session/eos.j2 b/netsim/extra/bgp.session/eos.j2 index a8a00fafb..308e0ce94 100644 --- a/netsim/extra/bgp.session/eos.j2 +++ b/netsim/extra/bgp.session/eos.j2 @@ -12,6 +12,9 @@ {% if n.timers is defined %} neighbor {{ n[af] }} timers {{ n.timers.keepalive|default(60) }} {{ n.timers.hold|default(180) }} {% endif %} +{% if n.bfd is defined %} + neighbor {{ n[af] }} bfd +{% endif %} {%- endmacro %} {% macro ebgp_neighbor(n,af) -%} diff --git a/netsim/extra/bgp.session/frr.j2 b/netsim/extra/bgp.session/frr.j2 index a9684b4d7..a3dbb30fb 100644 --- a/netsim/extra/bgp.session/frr.j2 +++ b/netsim/extra/bgp.session/frr.j2 @@ -8,6 +8,9 @@ {% if n.timers is defined %} neighbor {{ n[af] }} timers {{ n.timers.keepalive|default(60) }} {{ n.timers.hold|default(180) }} {% endif %} +{% if n.bfd is defined %} + neighbor {{ n[af] }} bfd +{% endif %} {%- endmacro %} ! router bgp {{ bgp.as }} diff --git a/netsim/extra/bgp.session/ios.j2 b/netsim/extra/bgp.session/ios.j2 index 1f52c2498..e58286f29 100644 --- a/netsim/extra/bgp.session/ios.j2 +++ b/netsim/extra/bgp.session/ios.j2 @@ -11,6 +11,9 @@ {% if n.timers is defined %} neighbor {{ n[af] }} timers {{ n.timers.keepalive|default(60) }} {{ n.timers.hold|default(180) }} {{ n.timers.min_hold|default('') }} {% endif %} +{% if n.bfd is defined %} + neighbor {{ n[af] }} fall-over bfd +{% endif %} {%- endmacro %} ! {% macro ebgp_neighbor(n,af) -%} diff --git a/netsim/extra/bgp.session/plugin.py b/netsim/extra/bgp.session/plugin.py index 4f2e7542b..6327edd4b 100644 --- a/netsim/extra/bgp.session/plugin.py +++ b/netsim/extra/bgp.session/plugin.py @@ -3,25 +3,31 @@ from netsim import api from netsim.augment import devices +_config_name = 'bgp.session' + def pre_link_transform(topology: Box) -> None: + global _config_name # Error if BGP module is not loaded if 'bgp' not in topology.module: log.error( 'BGP Module is not loaded.', log.IncorrectValue, - 'ebgp_utils') + _config_name) ''' check_device_attribute_support -- using device BGP features, check whether the device supports the attribute applied to a BGP neighbor ''' def check_device_attribute_support(attr: str, ndata: Box, neigh: Box, topology: Box) -> None: + global _config_name + features = devices.get_device_features(ndata,topology.defaults) enabled = features.bgp.get(attr,None) if not enabled: log.error( f'Attribute {attr} used on BGP neighbor {neigh.name} is not supported by node {ndata.name} (device {ndata.device})', - log.IncorrectValue,'ebgp.utils') + log.IncorrectValue, + _config_name) if not isinstance(enabled,list): return @@ -29,7 +35,8 @@ def check_device_attribute_support(attr: str, ndata: Box, neigh: Box, topology: if not topology.provider in enabled: log.error( f'Node {ndata.name} (device {ndata.device}) does not support BGP attribute {attr} when running with {topology.provider} provider', - log.IncorrectValue,'ebgp.utils') + log.IncorrectValue, + _config_name) ''' Remove session attributes with local significance from BGP neighbors @@ -45,7 +52,7 @@ def cleanup_neighbor_attributes(ndata: Box, topology: Box) -> None: them there in the configuration templates ''' def copy_local_attributes(ndata: Box, topology: Box) -> None: - config_name = api.get_config_name(globals()) # Get the plugin configuration name + global _config_name # Iterate over all ebgp.utils link/interface attributes for (intf,ngb) in _bgp.intf_neighbors(ndata): @@ -56,16 +63,7 @@ def copy_local_attributes(ndata: Box, topology: Box) -> None: check_device_attribute_support(attr,ndata,ngb,topology) ngb[attr] = attr_value # Set neighbor attribute from interface/node value - api.node_config(ndata,config_name) # And remember that we have to do extra configuration - - if 'vrf' not in intf: # Not a VRF interface? - continue # ... great, move on - - # Now do the same 'copy interface attribute to neighbors' thing for VRF neighbors - for neigh in ndata.vrfs[intf.vrf].get('bgp', {}).get('neighbors', []): - if neigh.ifindex == intf.ifindex and neigh.type == 'ebgp': - neigh[attr] = attr_value # Found the neighbor, set neighbor attribute - api.node_config(ndata,config_name) # And remember that we have to do extra configuration + api.node_config(ndata,_config_name) # And remember that we have to do extra configuration ''' For platforms that collect tcp_ao secrets in global management profiles @@ -73,10 +71,13 @@ def copy_local_attributes(ndata: Box, topology: Box) -> None: configurations ''' def process_tcpao_password(neigh: Box, ndata: Box) -> None: + global _config_name + if not 'password' in neigh: log.error( f'BGP neighbor {neigh.name} on node {ndata.name} has TCP-AO configured without a password', - log.MissingValue,'ebgp.utils') + log.MissingValue, + _config_name) return pwd = neigh.password @@ -91,6 +92,23 @@ def process_tcpao_secrets(ndata: Box,topology: Box) -> None: if 'tcp_ao' in ngb: process_tcpao_password(ngb,ndata) +''' +Check whether a node runs BFD with any BGP neighbors +''' +def process_bfd_requests(ndata: Box, topology: Box) -> None: + for ngb in _bgp.neighbors(ndata): + if not 'bfd' in ngb: # No BFD, no worries + continue + if not ngb.bfd: # BFD set to False (or some similar stunt) + ngb.pop('bfd') + continue + + if not 'bfd' in ndata.module: # Do we have BFD enabled on this node? + log.error( + f'node {ndata.name} is running BFD with BGP neighbor {ngb.name} but does not use BFD module', + log.IncorrectValue, + _config_name) + ''' post_transform hook @@ -99,12 +117,11 @@ def process_tcpao_secrets(ndata: Box,topology: Box) -> None: ''' def post_transform(topology: Box) -> None: - config_name = api.get_config_name(globals()) # Get the plugin configuration name - for n, ndata in topology.nodes.items(): if not 'bgp' in ndata.module: # Skip nodes not running BGP continue cleanup_neighbor_attributes(ndata,topology) # Generic neighbor attribute cleanup copy_local_attributes(ndata,topology) - process_tcpao_secrets(ndata,topology) \ No newline at end of file + process_tcpao_secrets(ndata,topology) + process_bfd_requests(ndata,topology) \ No newline at end of file