Skip to content

Commit

Permalink
Selectively apply BGP session attributes to IBGP and EBGP sessions
Browse files Browse the repository at this point in the history
Implements #893

Also:
* Added 'select' parameter to BGP neighbor generators to select a subset
  of BGP neighbors
* Restructured the bgp.session plugin documentation
* Enabled strikethrough Markdown extension
ipspace committed Oct 13, 2023
1 parent 442f4bd commit 137415b
Showing 7 changed files with 244 additions and 37 deletions.
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@
"html_admonition",
"replacements",
"smartquotes",
"strikethrough",
"tasklist",
"attrs_block"
]
46 changes: 45 additions & 1 deletion docs/plugins/bgp.session.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# BGP Sessions Plugin

The **bgp.session** plugin (initially contributed by Stefano Sasso) implements several BGP session nerd knobs, including:
The **bgp.session** plugin (initially contributed by Stefano Sasso) implements numerous BGP session ~~nerd knobs~~ attributes, from BGP session protection to AS path manipulation.

## Supported BGP Attributes

The plugin adds the following BGP session attributes:

* **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.
@@ -28,6 +32,8 @@ BGP session attributes can be specified on global, node, link, or interface (nod
| tcp_ao |||||
| timers |||||

## Platform Support

The plugin implements generic BGP session features for the following platforms:

| Operating system | password | default<br>originate | GTSM | BGP<br>timers | TCP-AO | BFD |
@@ -62,9 +68,47 @@ The plugin implements AS-path-mangling nerd knobs for the following platforms:
| Nokia SR OS |||
| VyOS |||

## Applying BGP Session Attributes to IBGP Sessions

The plugin applies BGP session attributes to EBGP sessions -- either all EBGP sessions a node has if the attribute has been specified on a global- or node level, or all EBGP sessions on a link where the BGP session attribute has been specified.

If you want to apply BGP session attributes to IBGP sessions, you have to set the **bgp.session.apply** global/node parameter -- a list or a dictionary of BGP neighbor types to which the BGP session attributes are applied. The default behavior is equivalent to `bgp.session.apply: [ ebgp ]`; if you want to apply the session attributes to IBGP and EBGP sessions, specify `bgp.session.apply: [ ibgp, ebgp ]` as a global or node attribute.

IBGP sessions are not associated with links/interfaces and therefore cannot inherit the BGP session parameters specified on links or interfaces -- only the parameters specified on global- or node level are applied to IBGP sessions. For example, to use MD5 password `secret` on all IBGP and EBGP sessions in your lab, use the following global settings:

```
bgp.session.apply: [ ibgp, ebgp ]
bgp.password: secret
```

If you don't want to apply all node-level BGP session parameters to IBGP sessions, you could use the dictionary format of **bgp.session.apply** parameter: the BGP neighbor types are the dictionary keys, the values are lists of parameters that you want to apply to IBGP and EBGP sessions (missing value means _apply all parameters_).

For example, the node settings from the next example cause the BGP password to be applied to IBGP and EBGP sessions, while all other BGP session parameters (including the default route origination) are applied only to EBGP sessions:

```
bgp.password: secret
nodes:
core:
bgp.default_originate: True
bgp.session.apply:
ibgp: [ password ]
ebgp:
```

The following example is even more specific: default route origination and a few AS-related session parameters (but not MD5 password) are applied to EBGP sessions, while the MD5 passwords are applied only to IBGP sessions:

```
bgp.password: secret
bgp.session.apply:
ibgp: [ password ]
ebgp: [ default_originate, allowas_in, as_override ]
```

## Test Topology

The following test topology illustrates a subset of plugin capabilities. You might also want to explore the topologies used in [ipSpace.net BGP labs](https://github.com/ipspace/bgplab) and the [BGP session plugin test topology](https://github.com/ipspace/netlab/blob/dev/tests/topology/input/ebgp.utils.yml).

```
---
defaults:
16 changes: 16 additions & 0 deletions netsim/extra/bgp.session/defaults.yml
Original file line number Diff line number Diff line change
@@ -106,6 +106,20 @@ bgp:
min_value: 3
max_value: 3600
password: str
session:
apply:
_list_to_dict: True
ibgp:
type: list
create_empty: False
_alt_types: [ NoneType ]
true_value: [ '*' ]
ebgp:
type: list
create_empty: False
_alt_types: [ NoneType ]
true_value: [ '*' ]

node:
bfd: bool
default_originate: bool
@@ -116,6 +130,8 @@ bgp:
timers:
copy: global
password: str
session:
copy: global
interface:
allowas_in:
type: int
65 changes: 53 additions & 12 deletions netsim/extra/bgp.session/plugin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import typing
from box import Box
from netsim.utils import log,bgp as _bgp
from netsim import api
@@ -18,7 +19,7 @@ def pre_link_transform(topology: Box) -> None:
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:
def check_device_attribute_support(attr: str, ndata: Box, neigh: Box, topology: Box) -> bool:
global _config_name

features = devices.get_device_features(ndata,topology.defaults)
@@ -28,15 +29,19 @@ def check_device_attribute_support(attr: str, ndata: Box, neigh: Box, topology:
f'Attribute {attr} used on BGP neighbor {neigh.name} is not supported by node {ndata.name} (device {ndata.device})',
log.IncorrectValue,
_config_name)
return False

if not isinstance(enabled,list):
return
return True

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,
_config_name)
return False

return True

'''
Remove session attributes with local significance from BGP neighbors
@@ -47,23 +52,59 @@ def cleanup_neighbor_attributes(ndata: Box, topology: Box) -> None:
for attr in topology.defaults.bgp.attributes.ebgp_utils.local:
ngb.pop(attr,None)

'''
Get a list of attributes to apply to IBGP or EBGP sessions
'''
def get_attribute_list(apply_list: typing.Any, topology: Box) -> list:
if apply_list is None or apply_list is True or (isinstance(apply_list,list) and '*' in apply_list):
return topology.defaults.bgp.attributes.ebgp_utils.attr

return list(apply_list)

'''
Apply attributes supported by bgp.session plugin to a single neighbor
Returns False is some of the attributes are not supported
'''
def apply_neighbor_attributes(node: Box, ngb: Box, intf: typing.Optional[Box], apply_list: list, topology: Box) -> bool:
global _config_name

OK = True
for attr in apply_list:
attr_value = None if intf is None else \
intf.get('bgp',{}).get(attr,None) # Get attribute value from interface if specified
attr_value = attr_value or node.bgp.get(attr,None) # ... and try node attribute value if there's no interface value
if not attr_value: # Attribute not defined in node or interface, move on
continue

# Check that the node(device) supports the desired attribute
OK = OK and check_device_attribute_support(attr,node,ngb,topology)
ngb[attr] = attr_value # Set neighbor attribute from interface/node value
api.node_config(node,_config_name) # And remember that we have to do extra configuration

return OK

'''
Copy local session attributes to BGP neighbors because we need
them there in the configuration templates
'''
def copy_local_attributes(ndata: Box, topology: Box) -> None:
global _config_name
apply = ndata.get('bgp.session.apply',{ 'ebgp': None }) # By default we apply all session attributes only to EBGP neighbors

OK = True
if 'ibgp' in apply: # Do we apply session attributes to IBGP neighbors?
apply_list = get_attribute_list(apply.ibgp,topology) # Get attributes to apply to IBGP neighbors
for ngb in _bgp.neighbors(ndata,select=['ibgp']): # Iterate over all neighbors using node attributes
OK = OK and apply_neighbor_attributes(ndata,ngb,None,apply_list,topology)

if not OK: # Skip the second step if we encountered unsupported device
return # ... doesn't make sense to throw the same error(s) twice

# Iterate over all ebgp.utils link/interface attributes
for (intf,ngb) in _bgp.intf_neighbors(ndata):
for attr in topology.defaults.bgp.attributes.ebgp_utils.attr:
attr_value = intf.get('bgp',{}).get(attr,None) or ndata.bgp.get(attr,None)
if not attr_value: # Attribute not defined in node or interface, move on
continue
if 'ebgp' in apply: # Do we apply session attributes to EBGP neighbors?
apply_list = get_attribute_list(apply.ebgp,topology) # Get attributes to apply to IBGP neighbors

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
# Iterate over neighbors that have associated interfaces (all EBGP neighbors are supposed to be directly connected)
for (intf,ngb) in _bgp.intf_neighbors(ndata,select=['ebgp']):
apply_neighbor_attributes(ndata,ngb,intf,apply_list,topology)

'''
For platforms that collect tcp_ao secrets in global management profiles
14 changes: 8 additions & 6 deletions netsim/utils/bgp.py
Original file line number Diff line number Diff line change
@@ -7,28 +7,30 @@

# Return all global and optionaly VRF neighbors
#
def neighbors(node: Box, vrf: bool = True) -> typing.Generator:
def neighbors(node: Box, vrf: bool = True, select: list = ['ibgp','ebgp']) -> typing.Generator:
for ngb in node.get('bgp.neighbors',[]):
yield ngb
if ngb.type in select:
yield ngb

if not vrf:
return

for vdata in node.get('vrfs',{}).values():
for ngb in vdata.get('bgp.neighbors',[]):
yield ngb
if ngb.type in select:
yield ngb

# Return all BGP neighbors associated with interfaces (usually EBGP neighbors)
#
def intf_neighbors(node: Box, vrf: bool = True) -> typing.Generator:
def intf_neighbors(node: Box, vrf: bool = True, select: list = ['ibgp','ebgp']) -> typing.Generator:
for intf in node.interfaces:
if 'vrf' in intf:
if not vrf:
continue
for ngb in node.vrfs[intf.vrf].get('bgp.neighbors',[]):
if ngb.get('ifindex',None) == intf.ifindex:
if ngb.get('ifindex',None) == intf.ifindex and ngb.type in select:
yield (intf,ngb)
else:
for ngb in node.get('bgp.neighbors',[]):
if ngb.get('ifindex',None) == intf.ifindex:
if ngb.get('ifindex',None) == intf.ifindex and ngb.type in select:
yield (intf,ngb)
131 changes: 113 additions & 18 deletions tests/topology/expected/ebgp.utils.yml
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ groups:
as65001:
members:
- r1
- rr
as65002:
members:
- r2
@@ -78,25 +79,39 @@ links:
ipv4: 10.1.0.4/30
role: external
type: p2p
- interfaces:
- ifindex: 3
ifname: Ethernet3
ipv4: 10.1.0.9/30
node: r1
- ifindex: 1
ifname: Ethernet1
ipv4: 10.1.0.10/30
node: rr
linkindex: 3
node_count: 2
prefix:
ipv4: 10.1.0.8/30
type: p2p
- bgp:
password: InVrf
interfaces:
- bgp:
default_originate: true
ifindex: 3
ifname: Ethernet3
ipv4: 10.1.0.9/30
ifindex: 4
ifname: Ethernet4
ipv4: 10.1.0.13/30
node: r1
- bgp:
allowas_in: 1
ifindex: 2
ifname: Ethernet2
ipv4: 10.1.0.10/30
ipv4: 10.1.0.14/30
node: r2
linkindex: 3
linkindex: 4
node_count: 2
prefix:
ipv4: 10.1.0.8/30
ipv4: 10.1.0.12/30
role: external
type: p2p
vrf: red
@@ -121,6 +136,14 @@ nodes:
default_originate: true
ipv4: true
neighbors:
- activate:
ipv4: true
as: 65001
ipv4: 10.0.0.4
name: rr
password: Secret
rr: true
type: ibgp
- activate:
ipv4: true
as: 65002
@@ -142,6 +165,11 @@ nodes:
next_hop_self: true
password: Secret
router_id: 10.0.0.1
session:
apply:
ebgp: null
ibgp:
- password
box: arista/veos
config:
- bgp.session
@@ -175,20 +203,30 @@ nodes:
node: r3
role: external
type: p2p
- bgp:
default_originate: true
password: InVrf
ifindex: 3
- ifindex: 3
ifname: Ethernet3
ipv4: 10.1.0.9/30
linkindex: 3
name: r1 -> rr
neighbors:
- ifname: Ethernet1
ipv4: 10.1.0.10/30
node: rr
type: p2p
- bgp:
default_originate: true
password: InVrf
ifindex: 4
ifname: Ethernet4
ipv4: 10.1.0.13/30
linkindex: 4
name: r1 -> r2
neighbors:
- bgp:
allowas_in: 1
password: InVrf
ifname: Ethernet2
ipv4: 10.1.0.10/30
ipv4: 10.1.0.14/30
node: r2
vrf: red
role: external
@@ -214,8 +252,8 @@ nodes:
neighbors:
- as: 65002
default_originate: true
ifindex: 3
ipv4: 10.1.0.10
ifindex: 4
ipv4: 10.1.0.14
name: r2
password: InVrf
type: ebgp
@@ -285,15 +323,15 @@ nodes:
password: InVrf
ifindex: 2
ifname: Ethernet2
ipv4: 10.1.0.10/30
linkindex: 3
ipv4: 10.1.0.14/30
linkindex: 4
name: r2 -> r1
neighbors:
- bgp:
default_originate: true
password: InVrf
ifname: Ethernet3
ipv4: 10.1.0.9/30
ifname: Ethernet4
ipv4: 10.1.0.13/30
node: r1
vrf: red
role: external
@@ -320,7 +358,7 @@ nodes:
- allowas_in: 1
as: 65001
ifindex: 2
ipv4: 10.1.0.9
ipv4: 10.1.0.13
name: r1
password: InVrf
type: ebgp
@@ -401,6 +439,63 @@ nodes:
name: r3
vrf:
as: 65000
rr:
af:
ipv4: true
bgp:
advertise_loopback: true
as: 65001
community:
ebgp:
- standard
ibgp:
- standard
- extended
ipv4: true
neighbors:
- activate:
ipv4: true
as: 65001
ipv4: 10.0.0.1
name: r1
password: Secret
type: ibgp
next_hop_self: true
password: Secret
router_id: 10.0.0.4
rr: true
rr_cluster_id: 10.0.0.4
session:
apply:
ibgp:
- '*'
box: arista/veos
config:
- bgp.session
device: eos
id: 4
interfaces:
- ifindex: 1
ifname: Ethernet1
ipv4: 10.1.0.10/30
linkindex: 3
name: rr -> r1
neighbors:
- ifname: Ethernet3
ipv4: 10.1.0.9/30
node: r1
type: p2p
loopback:
ipv4: 10.0.0.4/32
mgmt:
ifname: Management1
ipv4: 192.168.121.104
mac: 08:4f:a9:00:00:04
module:
- bgp
name: rr
vrf:
as: 65000
plugin:
- bgp.session
- ebgp.multihop
8 changes: 8 additions & 0 deletions tests/topology/input/ebgp.utils.yml
Original file line number Diff line number Diff line change
@@ -26,10 +26,17 @@ nodes:
r1:
bgp.as: 65001
bgp.default_originate: True
bgp.session.apply:
ibgp: [ password ]
ebgp:
r2:
bgp.as: 65002
r3:
bgp.as: 65003
rr:
bgp.as: 65001
bgp.rr: true
bgp.session.apply: [ ibgp ]

links:
- r1:
@@ -38,3 +45,4 @@ links:
r3:
bgp.allowas_in: True
bgp.password: SomethingElse
- r1-rr

0 comments on commit 137415b

Please sign in to comment.