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