Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/module/bgp.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ _netlab_ BGP configuration module supports these features:
Even more BGP features are implemented in the following plugins:

* [bgp.session](plugin-bgp-session): implements numerous BGP session features, including session protection and AS-path manipulation.
* [bgp.policy](plugin-bgp-policy): implements simple BGP routing policies, including weights, local preference, and MED.
* [bgp.policy](plugin-bgp-policy): implements simple BGP routing policies, including weights, local preference, MED, and [RFC 9234](https://www.rfc-editor.org/rfc/rfc9234.html) BGP Roles on EBGP sessions.
* [ebgp.multihop](plugin-ebgp-multihop): implements multihop EBGP sessions.
* [bgp.domain](plugin-bgp-domain): allows you to build topologies that reuse the same BGP ASN in different network parts.

Expand Down
75 changes: 62 additions & 13 deletions docs/plugins/bgp.policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The **bgp.policy** plugin implements simple BGP routing policies :

* Per-neighbor AS-path prepending, BGP link bandwidth, BGP local preference, BGP MED, and BGP weights
* Default local preference
* [RFC 9234](https://www.rfc-editor.org/rfc/rfc9234.html) BGP Roles on EBGP sessions (route-leak prevention)

You can also use this plugin to apply inbound and outbound [generic routing policies](generic-routing-policies) to EBGP neighbors.

Expand All @@ -26,6 +27,8 @@ The plugin adds the following BGP link attributes:
* **bgp.policy** is a dictionary that applies [predefined routing policies](generic-routing-policies) to inbound (**in**) or outbound (**out**) BGP updates.
* **bgp.prepend** is a dictionary configuring outbound AS-path prepending. It can contain a **count** attribute (number of times the node AS is prepended) or a **path** attribute (the prepended AS-path as a string[^ASPS])
* **bgp.weight** is an integer attribute that sets per-neighbor weight.
* **bgp.role** -- the local BGP role for an EBGP session (RFC 9234). Valid values: **provider**, **customer**, **peer**, **rs-server**, **rs-client**.
* **bgp.role_strict** -- when set to _true_, the BGP session is established only if the remote router also advertises a compatible BGP Role capability (RFC 9234 strict mode).

[^BCP]: _netlab_ configures network devices to propagate BGP Link Bandwidth extended community on IBGP sessions. The value advertised in IBGP updates is device-dependent and could be the value attached to the best path or the aggregate of EBGP values.

Expand All @@ -48,25 +51,30 @@ The following table describes where you could apply individual attributes:
| med | ❌ | ✅ | ❌ |
| policy | ❌ | ✅ | ❌ |
| prepend | ❌ | ✅ | ❌ |
| role | ✅ | ✅ | ❌ |
| role_strict| ✅ | ✅ | ❌ |
| weight | ❌ | ✅ | ❌ |

BGP role attributes can also be specified at the global or link level. The plugin applies **bgp.role** and **bgp.role_strict** to **EBGP** neighbors only. Using these attributes on IBGP sessions results in a configuration error.

## Platform Support

The plugin implements BGP routing policies and individual BGP policy attributes on these devices:

| Operating system | Routing<br>policies | Local<br>preference | MED | Weight | AS-path<br>prepend | Link<br>bandwidth | Address<br>Aggregation |
|---------------------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| Arista EOS |✅ |✅ |✅ |✅|✅ |✅| ✅|
| Aruba AOS-CX |✅ |✅ |✅ |✅|✅ | ❌ | ❌ |
| Cisco IOS/IOS XE[^18v] |✅ |✅ |✅ |✅|✅ |✅[❗](caveats-ios) |✅|
| Cisco IOS XR[^XR] |✅ |✅ |✅ |✅|✅ | ❌ |✅|
| Cumulus Linux |✅ |✅ |✅ |✅|✅ |✅| ❌ |
| Dell OS10 |✅ |✅ |✅ |✅| ❌ | ❌ |✅|
| FRR |✅ |✅ |✅ |✅|✅ |✅| ✅|
| Junos |✅ |✅ |✅ |✅|✅ | ❌ | ❌ |
| Nokia SR Linux |✅ |✅ |✅ | ❌ | ❌ | ❌ | ❌ |
| Nokia SR OS |✅ |✅ |✅ | ❌ | ❌ | ✅| ❌ |
| VyOS |✅ |✅ |✅ | ❌ | ✅ | ❌ | ❌ |
| Operating system | Routing<br>policies | Local<br>preference | MED | Weight | AS-path<br>prepend | Link<br>bandwidth | Address<br>Aggregation | BGP<br>Roles |
|---------------------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| Arista EOS |✅ |✅ |✅ |✅|✅ |✅| ✅| ❌ |
| Aruba AOS-CX |✅ |✅ |✅ |✅|✅ | ❌ | ❌ | ❌ |
| Cisco IOS/IOS XE[^18v] |✅ |✅ |✅ |✅|✅ |✅[❗](caveats-ios) |✅| ❌ |
| Cisco IOS XR[^XR] |✅ |✅ |✅ |✅|✅ | ❌ |✅| ❌ |
| Cumulus Linux |✅ |✅ |✅ |✅|✅ |✅| ❌ | ❌ |
| Dell OS10 |✅ |✅ |✅ |✅| ❌ | ❌ |✅| ❌ |
| FRR |✅ |✅ |✅ |✅|✅ |✅| ✅| ✅ |
| Junos |✅ |✅ |✅ |✅|✅ | ❌ | ❌ | ❌ |
| Nokia SR Linux |✅ |✅ |✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Nokia SR OS |✅ |✅ |✅ | ❌ | ❌ | ✅| ❌ | ❌ |
| VyOS |✅ |✅ |✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| BIRD | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |

[^18v]: Includes Cisco IOSv, Cisco IOSv Layer-2 image, Cisco CSR 1000v, Cisco Catalyst 8000v, Cisco IOS-on-Linux (IOL), and IOL Layer-2 image.

Expand All @@ -79,6 +87,47 @@ See [BGP Policies Test Results](https://release.netlab.tools/_html/coverage.bgp.
**Notes:**

* Arista EOS and Aruba CX do not support node-level default local preference. Node-level **bgp.locpref** attribute (if specified) is thus applied to all interfaces that do not have an explicit **bgp.locpref** attribute. That might interfere with the **bgp.policy** interface attributes.
* FRR implements BGP Roles starting with release 8.4. BIRD implements them starting with release 2.0.11. On BIRD, BGP roles are rendered into the BGP module configuration file (`daemons/bird/bgp.j2`).

(plugin-bgp-policy-role)=
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this section to the end (just before the "Sample Topologies")

## BGP Roles (RFC 9234)

When both routers implement RFC 9234, the local role on one router must match the expected remote role on the other:

| Local role | Remote role |
|------------|-------------|
| provider | customer |
| customer | provider |
| peer | peer |
| rs-server | rs-client |
| rs-client | rs-server |

You can use BGP roles together with **[bgp.session](bgp.session.md)** session attributes. List **bgp.session** before **bgp.policy** if you use route server session features:

```
plugin: [ bgp.session, bgp.policy ]
```

Example:

```yaml
plugin: [ bgp.policy ]
module: [ bgp ]

nodes: [ isp, customer, peer ]

links:
- isp:
bgp.role: provider
customer:
bgp.role: customer
- isp:
bgp.role: peer
peer:
bgp.role: peer
```

Integration test cases for BGP roles are in the `tests/integration/bgp.policy` directory (`70-role-*` files). A sample topology file is in `tests/topology/input/bgp-role.yml`.

## Applying Routing Policies to EBGP Neighbors

Expand Down
1 change: 1 addition & 0 deletions netsim/daemons/bird.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ features:
remove_private_as: true
rs: true
rs_client: true
role: true
timers: true
ospf:
import: [ bgp, connected, static ]
Expand Down
6 changes: 6 additions & 0 deletions netsim/daemons/bird/bgp.j2
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ protocol bgp bgp_{{ n.name }}_{{ af }} {
{% if n.rs_client|default(False) %}
enforce first as off;
{% endif %}
{% if n.role is defined %}
local role {{ n.role|replace('-', '_') }};
{% if n.role_strict|default(False) %}
require roles;
{% endif %}
{% endif %}
{% if bgp.rr|default('') and ((not n.rr|default('') and n.type == 'ibgp') or n.type == 'localas_ibgp') %}
rr client;
{% if bgp.rr|default(False) and bgp.rr_cluster_id|default(False) %}
Expand Down
1 change: 1 addition & 0 deletions netsim/devices/frr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ features:
remove_private_as: true
rs: true
rs_client: true
role: true
timers: true
_default_locpref: true
aggregate: true
Expand Down
1 change: 1 addition & 0 deletions netsim/devices/none.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ features:
local_as: True
vrf_local_as: True
local_as_ibgp: True
role: True
ipv6_lla: True
rfc8950: True
bandwidth: True
Expand Down
48 changes: 37 additions & 11 deletions netsim/extra/bgp.policy/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,33 @@ bgp:
p_attr: # BGP policy attributes
direct: [ weight, prefix_filter, as_filter ] # Things that can be applied directly to a neighbor
node: [ locpref ]
link: [ locpref, med ]
interface: [ locpref, med, prepend, weight ]
link: [ locpref, med, role, role_strict ]
interface: [ locpref, med, prepend, weight, role, role_strict ]
compound: # Things that have to be applied to in/out policy
locpref: in
med: out
prepend: out
bandwidth: both

interface:
policy:
in: str
out: str
bandwidth:
_alt_types: [ autobw ]
in: autobw
out: autobw

role:
attr: [ role, role_strict ]
global:
role:
_description: |
Local BGP Role for the EBGP session (RFC 9234). Negotiated in the BGP OPEN
message to prevent route leaks; enables automatic Only-to-Customer handling.
type: str
valid_values: [ provider, customer, peer, rs-server, rs-client ]
role_strict:
_description: |
Require the remote router to advertise a compatible BGP Role capability
(RFC 9234 strict mode); otherwise the session is not established.
type: bool
node:
role:
copy: global
role_strict:
copy: global
aggregate:
type: list
_subtype:
Expand All @@ -38,6 +47,23 @@ bgp:
suppress_policy: str
attributes: str
_alt_types: [ prefix_str ]
link:
role:
copy: global
role_strict:
copy: global
interface:
policy:
in: str
out: str
bandwidth:
_alt_types: [ autobw ]
in: autobw
out: autobw
role:
copy: global
role_strict:
copy: global
vrf:
aggregate:
copy: node
3 changes: 3 additions & 0 deletions netsim/extra/bgp.policy/frr.j2
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
{% if 'bandwidth' in n %}
neighbor {{ peer }} send-community extended
{% endif %}
{% if 'role' in n %}
neighbor {{ peer }} local-role {{ n.role }}{% if n.role_strict|default(False) %} strict-mode{% endif +%}
{% endif %}
{{ routemap.apply_route_map(n,af,True) }}
{%- endmacro %}

Expand Down
51 changes: 46 additions & 5 deletions netsim/extra/bgp.policy/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from box import Box

from netsim import api, data
from netsim import api, data, modules
from netsim.augment import devices
from netsim.data import types
from netsim.modules.routing.policy import check_routing_policy, import_routing_policy
Expand All @@ -11,7 +11,10 @@

_config_name = 'bgp.policy'

_requires = [ 'bgp' ]
_requires = [ 'bgp' ]
_execute_after = [ 'bgp.session' ]

_ROLE_ATTR_LIST = [ 'role', 'role_strict' ]

@types.type_test()
def must_be_autobw(
Expand Down Expand Up @@ -161,6 +164,44 @@ def apply_config(node: Box, ngb: Box) -> None:
api.node_config(node,_config_name) # Remember that we have to do extra configuration
_bgp.clear_bgp_session(node,ngb)

def _use_role_plugin_template(node: Box) -> bool:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels so wrong. Why would you ever hard-code a device into a plugin code?

"""Return False for the BIRD daemon (roles are rendered in daemons/bird/bgp.j2)."""
return not (node.get('_daemon') and node.device == 'bird')

def apply_role_attributes(node: Box, ngb: Box, intf: Box, topology: Box) -> bool:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like duplicating a lot of functionality that should be already available elsewhere. If you want to ensure "role_strict" is only used when "role" is there, we could solve that in the generic validation code (if it's not there yet).

"""Copy bgp.role* interface attributes to an EBGP neighbor."""
values: dict[str, typing.Any] = {}
for attr in _ROLE_ATTR_LIST:
attr_value = modules.get_effective_module_attribute(
path=f'bgp.{attr}', intf=intf, node=node)
if attr_value:
values[attr] = attr_value

if not values:
return False

if not _bgp.check_device_attribute_support('role', node, ngb, topology, _config_name):
return False

if 'role_strict' in values and 'role' not in values:
where = f'node {node.name}'
if intf is not None:
where += f' interface {intf.name}'
log.error(
f'Cannot use bgp.role_strict without bgp.role ({where})',
category=log.IncorrectValue,
module=_config_name,
)
return False

for attr, attr_value in values.items():
ngb[attr] = attr_value

if _use_role_plugin_template(node):
apply_config(node, ngb)

return True

'''
Apply attributes supported by bgp.policy plugin to a single neighbor
Returns True if at least some relevant attributes were found
Expand Down Expand Up @@ -313,7 +354,7 @@ def post_transform(topology: Box) -> None:
continue

route_aggregation(ndata,topology)
_bgp.cleanup_neighbor_attributes(ndata,topology,_attr_list + [ 'policy' ])
_bgp.cleanup_neighbor_attributes(ndata,topology,_attr_list + _ROLE_ATTR_LIST + [ 'policy' ])
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to add role keywords into "_direct" or "_compound" attribute list? Is there a reason not to do it?

policy_idx = 0

# Get _default_locpref feature flag (could be None), then figure out if we need to copy
Expand All @@ -323,8 +364,7 @@ def post_transform(topology: Box) -> None:
default_locpref = _bgp.get_device_bgp_feature('_default_locpref',ndata,topology)
copy_locpref = False if default_locpref else 'locpref' in ndata.bgp

# Now iterate over all EBGP neighbors (global and VRF) and apply bgp.policy interface
# attributes to the neighbors
# Iterate over BGP neighbors and apply bgp.policy interface attributes to EBGP sessions.
#
for (intf,ngb) in _bgp.intf_neighbors(ndata,select=['ebgp']):
policy_idx += 1
Expand All @@ -338,6 +378,7 @@ def post_transform(topology: Box) -> None:
communities.append('extended')
if copy_locpref and not intf.get('bgp.locpref',False):
intf.bgp.locpref = ndata.bgp.locpref
apply_role_attributes(ndata,ngb,intf,topology)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the only reason you're dealing with roles as an exception is because of Bird template stuff. The easiest way to solve that is to have an empty template in the plugin directory.

if intf.get('bgp.policy',{}):
apply_bgp_routing_policy(ndata,ngb,intf,topology)
if apply_policy_attributes(ndata,ngb,intf,topology): # If we applied at least some bgp.policy attribute to the neighbor
Expand Down
Loading