Skip to content

Commit

Permalink
Implement BFD for BGP neighbors in bgp.session plugin
Browse files Browse the repository at this point in the history
* Implements #900 for EOS, IOSv, CSR, Cumulus

Restructured bgp.session documentation

* Moved 'attribute X is available on Y' information into a table
* Split the feature support table into two tables (generic session
  nerd knobs, AS-path-mangling nerd knobs)
  • Loading branch information
ipspace committed Oct 12, 2023
1 parent 473a793 commit 98466f9
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 53 deletions.
72 changes: 51 additions & 21 deletions docs/plugins/bgp.session.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<br>override | password | default<br>originate | GTSM | BGP<br>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<br>originate | GTSM | BGP<br>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<br>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

```
Expand Down
25 changes: 25 additions & 0 deletions netsim/extra/bgp.session/_sample_bfd_template.j2
Original file line number Diff line number Diff line change
@@ -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 %}
36 changes: 22 additions & 14 deletions netsim/extra/bgp.session/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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','']
Expand All @@ -102,6 +107,7 @@ bgp:
max_value: 3600
password: str
node:
bfd: bool
default_originate: bool
tcp_ao:
copy: global
Expand All @@ -125,6 +131,7 @@ bgp:
copy: global
timers:
copy: global
bfd: bool
link:
password: str
tcp_ao:
Expand All @@ -133,3 +140,4 @@ bgp:
copy: global
timers:
copy: global
bfd: bool
3 changes: 3 additions & 0 deletions netsim/extra/bgp.session/eos.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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) -%}
Expand Down
3 changes: 3 additions & 0 deletions netsim/extra/bgp.session/frr.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
3 changes: 3 additions & 0 deletions netsim/extra/bgp.session/ios.j2
Original file line number Diff line number Diff line change
Expand Up @@ -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) -%}
Expand Down
53 changes: 35 additions & 18 deletions netsim/extra/bgp.session/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,40 @@
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

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
Expand All @@ -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):
Expand All @@ -56,27 +63,21 @@ 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
we have to build a list of secrets so we can refer to them in neighbor
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
Expand All @@ -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
Expand All @@ -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)
process_tcpao_secrets(ndata,topology)
process_bfd_requests(ndata,topology)

0 comments on commit 98466f9

Please sign in to comment.