Skip to content

Commit

Permalink
Clean up 'unnumbered EBGP sessions' code + Cumulus 4.x implementation
Browse files Browse the repository at this point in the history
* Add device feature checks
* Deal with all possible combinations of unnumbered/ipv4/ipv6 attributes
* Set separate neighbor flags for IPv6 LLA session and RFC 8950-type AF
* Update user and contributor documentation
  • Loading branch information
ipspace committed Sep 4, 2022
1 parent a689633 commit 376a422
Show file tree
Hide file tree
Showing 16 changed files with 1,040 additions and 87 deletions.
1 change: 1 addition & 0 deletions docs/addressing.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ addressing:
* Management IP addresses are assigned from 192.168.121.0/24 CIDR block. The first IP address is 192.168.121.101 (*start* offset plus node ID)
* MAC addresses of management interfaces start with 08-4F-A9. The last byte of the MAC address is the node ID.

(addressing-unnumbered)=
## Unnumbered Interface Support

*netlab* supports unnumbered IPv4 and IPv6 interfaces:
Expand Down
1 change: 1 addition & 0 deletions docs/dev/guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The easiest way to get started is to [add support for a new platform for an exis
device-box.md
device-features.md
devices.md
unnumbered.md
```

```eval_rst
Expand Down
80 changes: 80 additions & 0 deletions docs/dev/unnumbered.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Unnumbered Interfaces

*netlab* supports unnumbered IPv4, IPv6 (LLA) or dual-stack interfaces. There are two ways to create an unnumbered interface (or link):

* Set **unnumbered: true** on interface[^NLA] or link.
* Set **ipv4** and/or **ipv6** attribute to *True*.

**unnumbered** attribute is translated into **ipv4** and/or **ipv6** attributes set to *True* based on address families configured on node's loopback interface. With the default addressing setup, **unnumbered: True** results in **ipv4: True**, to enable dual-stack unnumbered interfaces with **unnumbered** attribute, add **ipv6** prefix to the **loopback** addressing pool. For more details, see [unnumbered interfaces](addressing-unnumbered) part of [addressing](../addressing.md) document.

## Implementing unnumbered interfaces

There is no standard way of implementing IPv4 unnumbered interfaces, and they might not be available on all platforms. IPv4 implementations of unnumbered interfaces usually use the loopback IPv4 address as the interface IPv4 address. For more details, read the [unnumbered interfaces](https://blog.ipspace.net/series/unnumbered-interfaces.html) series of blog posts on ipSpace.net.

Unnumbered IPv6 interfaces should use link-local addresses, a standard IPv6 feature.

If your device supports IPv6 LLA-only interface, set `topology-defaults.yml` attribute **devices._name_.features.initial.ipv6.lla** to *True*.

If your device supports IPv4 unnumbered interfaces, set `topology-defaults.yml` attribute **devices._name_.features.initial.ipv4.unnumbered** to *True*.

## Integration with IGP routing protocols

OSPF and IS-IS implementations might support unnumbered IPv4 interfaces[^OSPFv3]. The routing protocol configuration modules detect unnumbered IPv4 interfaces by checking the **unnumbered** and **ipv4** attributes -- if either one of them is set to *True*, the interface is an unnumbered IPv4 interface.

OSPFv2 can use unnumbered IPv4 interfaces on point-to-point links. If your device supports this functionality, set `topology-defaults.yml` attribute **devices._name_.features.ospf.unnumbered** to *True*. OSPFv2 cannot run over multi-access unnumbered IPv4 links.

Some IS-IS implementations support unnumbered IPv4 P2P links. If your device supports this, set `topology-defaults.yml` attribute **devices._name_.features.isis.unnumbered.ipv4** to *True*.

Fewer IS-IS implementations support unnumbered multi-access IPv4 links. To indicate your device can do that, set `topology-defaults.yml` attribute **devices._name_.features.isis.unnumbered.network** to *True*.

```{note}
If you're unsure what your device can do, set all three feature flags to *True*, start a lab, and check whether the adjacency- and routing tables are populated as expected.
```

## Unnumbered EBGP sessions

Several vendors implemented EBGP sessions between well-known IPv6 LLA addresses[^EBGP_LLA]. *netlab* does not support this half-baked attempt and implements IPv6 LLA sessions only for those devices that can configure EBGP session *on an interface*.

Devices supporting interface-level EBGP sessions between auto-generated IPv6 LLA can use these sessions to:

* Transport IPv6 prefixes with LLA next hop over IPv6 AF
* Transport IPv4 prefixes with IPv6 LLA next hop according to RFC 8950.

*netlab* core and BGP configuration module do not support:

* Running IPv4 AF with IPv4 next hops over IPv6 transport session
* Running IPv4 AF with RFC 8950-style IPv6 next hops over numbered IPv6 interfaces or over IBGP sessions.
* Creating IBGP sessions between IPv6 LLA addresses

```{note}
You can always extend *netlab* functionality with plugins and custom configuration modules.
```

*netlab* will create an IPv6 LLA EBGP session whenever it finds a pair of devices connected to the same link if the devices:

* Belong to different autonomous systems
* Have **ipv6** interface attribute set to *True*.

Whenever *netlab* encounters an EBGP session between IPv6 LLA interfaces, it sets **local_if** attribute in the neighbor data structure to simplify the device configuration templates.

If your device supports EBGP sessions between auto-generated IPv6 link-local addresses, set `topology-defaults.yml` attribute **devices._name_.features.bgp.ipv6_lla** to *True*.

*netlab* device configuration templates will enable RFC 8950-style IPv4-over-IPv6 address family on IPv6 LLA sessions if the interface has **ipv6** interface attribute set to *True* (indicating IPv6 LLA EBGP session) AND **ipv4** interface attribute set to *True*.

RFC 8950-style IPv4 address family [REALLY SHOULD NOT](https://www.rfc-editor.org/rfc/rfc6919#section-3) be enabled for:

* EBGP sessions running on numbered IPv6 interfaces
* Interfaces with IPv4 addresses regardless of the state of **ipv6** attribute.

BGP configuration module simplifies the device configuration templates conforming to the above restriction with the **ipv4_rfc8950** neighbor attribute which is set:

* when link or interface **unnumbered** attribute is set to *True* on both EBGP neighbors or
* when both IPv4 and IPv6 interface attributes are set to *True* on both EBGP neighbors.

If your device supports RFC 8950 (IPv4 with IPv6 next hops) on EBGP sessions between auto-generated IPv6 link-local addresses, set `topology-defaults.yml` attribute **devices._name_.features.bgp.rfc8950** to *True*.

[^NLA]: node-to-link attachment

[^OSPFv3]: OSPFv3 runs over IPv6 LLA. Decent IS-IS implementations should support IPv6 LLA-only segments. *netlab* therefore does not check whether an implementation supports IPv6 LLA-only segments.

[^EBGP_LLA]: An EBGP neighbor has to be configured using remote IPv6 LLA address and an interface name.
40 changes: 14 additions & 26 deletions docs/module/bgp.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ More interesting BGP topologies can be created with [custom plugins](../plugins.
## Supported BGP Features

* Multiple autonomous systems
* IPv4 and IPv6 address families
* Direct (single-hop) EBGP sessions
* IBGP sessions between loopback interfaces
* EBGP sessions between auto-generated IPv6 link-local addresses
* RFC8950-style IPv4 address family on EBGP IPv6 LLA sessions
* BGP route reflectors
* Next-hop-self control on IBGP sessions
* BGP community propagation
* IPv4 and IPv6 address families
* Configurable activation of default address families
* Configurable link prefix advertisement
* Additional (dummy) prefix advertisement
Expand All @@ -36,14 +38,15 @@ More interesting BGP topologies can be created with [custom plugins](../plugins.

[Platforms supporting BGP configuration module](platform-routing-support) support most of the functionality mentioned above. The following features are only supported on a subset of platforms:

| Operating system | Unnumbered<br />interfaces | local AS | IBGP<br>local AS | Configurable<br>default AF |
| --------------------- | :-: | :-: | :-: | :-: |
| Arista EOS |||||
| Cisco IOS/IOS XE |||||
| Cumulus Linux |||||
| Dell OS10 |||||
| FRR 7.5.0 |||||
| Nokia SR Linux |||||
| Operating system | IPv6 LLA<br />EBGP sessions | Unnumbered<br />IPv4 EBGP sessions | local AS | IBGP<br>local AS | Configurable<br>default AF |
| --------------------- | :-: | :-: | :-: | :-: | :-: |
| Arista EOS ||||||
| Cisco IOS/IOS XE ||||||
| Cumulus Linux 4.x ||||||
| Cumulus Linux 5.x ||||||
| Dell OS10 ||||||
| FRR 7.5.0 ||||||
| Nokia SR Linux ||||||

## Global BGP Configuration Parameters

Expand Down Expand Up @@ -222,24 +225,9 @@ See the [Simple BGP Example](bgp_example/simple.md) and [EBGP Data Center Fabric

### Notes on Unnumbered EBGP Sessions

Unnumbered EBGP sessions are supported on a few platforms. The transformed data model includes **unnumbered** and **ifindex** elements on EBGP neighbors reachable over unnumbered interfaces -- compare a regular EBGP neighbor (R4) with an unnumbered EBGP neighbor (R1):
Unnumbered EBGP sessions are supported on a few platforms. *netlab* creates an IPv6 LLA EBGP session when the **unnumbered** link- or interface attribute is set, or when **ipv6** interface address or link prefix is set to *True* (IPv6 LLA).

```
neighbors:
- as: 65000
ifindex: 1
ipv4: true
ipv6: true
local_if: swp1
name: r1
type: ebgp
unnumbered: true
- as: 65200
ifindex: 2
ipv4: 10.10.10.2
name: r3
type: ebgp
```
*netlab* can use an IPv6 LLA EBGP session to transport IPv4 address family with IPv6 next hops (RFC 8950) -- the functionality commonly used to implement *unnumbered EBGP sessions*. *netlab* will enable IPv4 AF over IPv6 LLA EBGP session when the **unnumbered** link- or interface attribute is set, or when **ipv4** interface address or link prefix is set to *True*.

## IPv6 Support

Expand Down
72 changes: 36 additions & 36 deletions netsim/ansible/templates/bgp/cumulus.j2
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,34 @@ router bgp {{ bgp.as }}
bgp cluster-id {{ bgp.rr_cluster_id }}
{% endif %}
!
{% for n in bgp.neighbors %}
{% if n.unnumbered is defined and n.unnumbered is sameas true %}
{% if loop.first %}
{#
Create external peer group if we have at least one IPv6 LLA EBGP neighbor
#}
{% for n in bgp.neighbors if n.local_if is defined %}
{% if loop.first %}
neighbor external peer-group
neighbor external remote-as external
{% endif %}
{% endif %}
{% endfor %}
{#
Create neighbors
#}
{% for n in bgp.neighbors %}
{% if n.local_if is defined %}
neighbor {{ n.local_if }} interface peer-group external
neighbor {{ n.local_if }} description {{ n.name }}
{% else %}
{% for af in ['ipv4','ipv6'] %}
{% if n[af] is defined %}
{% endif %}
{% for af in ['ipv4','ipv6'] if n[af] is defined and n[af] is string %}
neighbor {{ n[af] }} remote-as {{ n.as }}
neighbor {{ n[af] }} description {{ n.name }}
{% if n.type == 'ibgp' %}
{% if n.type == 'ibgp' %}
neighbor {{ n[af] }} update-source lo
{% endif %}
{% endif %}
{% endfor %}
!
{% endif %}
!
{% endif %}
{% endfor %}
{% endfor %}
{#
Activate neighbors, set AF attributes
#}
{% for af in ['ipv4','ipv6'] if bgp[af] is defined %}
address-family {{ af }} unicast
!
Expand All @@ -51,29 +57,23 @@ router bgp {{ bgp.as }}
network {{ pfx|ipaddr('0') }}
{% endfor %}
!
{# {% for n in bgp.neighbors if n[af] is defined %} #}
{% for n in bgp.neighbors if n.activate[af] is defined and n.activate[af] %}
{% if n.unnumbered is defined and n.unnumbered is sameas true %}
neighbor {{ n.local_if }} activate
{% else %}
neighbor {{ n[af] }} activate
{% endif %}
{% if n.type == 'ibgp' %}
{% if bgp.next_hop_self is defined and bgp.next_hop_self %}
neighbor {{ n[af] }} next-hop-self
{% endif %}
{% if bgp.rr|default('') and not n.rr|default('') %}
neighbor {{ n[af] }} route-reflector-client
{% endif %}
{% if bgp.community.ibgp|default([]) %}
neighbor {{ n[af] }} send-community {{ community(bgp.community.ibgp) }}
{% endif %}
{% else %}
{% if bgp.community.ebgp|default([]) %}
{% if n.unnumbered is defined and n.unnumbered is sameas true %}
neighbor {{ n.local_if }} send-community {{ community(bgp.community.ebgp) }}
{% else %}
neighbor {{ n[af] }} send-community {{ community(bgp.community.ebgp) }}
{% set peer = n[af] if n[af] is string else n.local_if|default('') %}
{% if peer %}
neighbor {{ peer }} activate
{% if n.type == 'ibgp' %}
{% if bgp.next_hop_self is defined and bgp.next_hop_self %}
neighbor {{ peer }} next-hop-self
{% endif %}
{% if bgp.rr|default('') and not n.rr|default('') %}
neighbor {{ peer }} route-reflector-client
{% endif %}
{% if bgp.community.ibgp|default([]) %}
neighbor {{ peer }} send-community {{ community(bgp.community.ibgp) }}
{% endif %}
{% else %}
{% if bgp.community.ebgp|default([]) %}
neighbor {{ peer }} send-community {{ community(bgp.community.ebgp) }}
{% endif %}
{% endif %}
{% endif %}
Expand Down
32 changes: 29 additions & 3 deletions netsim/modules/bgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,35 @@ def build_ebgp_sessions(node: Box, sessions: Box, topology: Box) -> None:

extra_data = Box({})
extra_data.ifindex = l.ifindex
if "unnumbered" in l:
extra_data.unnumbered = True
extra_data.local_if = l.ifname

# Figure out whether both neighbors have IPv6 LLA and/or unnumbered IPv4 interfaces
#
ipv6_lla = l.get('ipv6',None) is True and ngb_ifdata.get('ipv6',None) is True
ipv6_num = isinstance(l.get('ipv6',None),str) and isinstance(ngb_ifdata.get('ipv6',None),str)
rfc8950 = l.get('unnumbered',None) is True and ngb_ifdata.get('unnumbered',None) is True
ipv4_unnum = l.get('ipv4',None) is True and ngb_ifdata.get('ipv4',None) is True
# print(f'EBGP node {node.name} neighbor {ngb_name} lla {ipv6_lla} v6num {ipv6_num} v4unnum {ipv4_unnum} rfc8950 {unnumbered}')
if ipv4_unnum and not ipv6_num: # Unnumbered IPv4 w/o IPv6 ==> IPv6 LLA + RFC 8950 IPv4 AF
rfc8950 = True

# print(f'... unnumbered {unnumbered}')
if ipv6_lla or rfc8950:
extra_data.local_if = l.ifname # Set local_if to indicate IPv6 LLA EBGP session
if not features.bgp.ipv6_lla: # IPv6_LLA feature flag has to be set even for IPv4 unnumbered EBGP
common.error(
text=f'{node.name} (device {node.device}) does not support EBGP sessions over auto-generated IPv6 LLA (interface {l.name})',
category=common.IncorrectValue,
module='bgp')
continue

if rfc8950:
extra_data.ipv4_rfc8950 = True # Set unnumbered indicate RFC 8950 IPv4 AF
if not features.bgp.rfc8950:
common.error(
text=f'{node.name} (device {node.device}) does not support IPv4 RFC 8950-style AF over IPv6 LLA EBGP sessions (interface {l.name})',
category=common.IncorrectValue,
module='bgp')
continue

for k in ('local_as','replace_global_as'):
local_as_data = data.get_from_box(l,f'bgp.{k}') or data.get_from_box(node,f'bgp.{k}')
Expand Down
6 changes: 6 additions & 0 deletions netsim/topology-defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,8 @@ devices:
features:
bgp:
activate_af: True
ipv6_lla: True
rfc8950: True
graphite.icon: router

linux:
Expand Down Expand Up @@ -614,6 +616,10 @@ devices:
lla: True
ospf:
unnumbered: True
bgp:
ipv6_lla: True
rfc8950: True
activate_af: True
clab:
mtu: 1500
runtime: docker
Expand Down
47 changes: 47 additions & 0 deletions tests/integration/bgp/dual-stack-ibgp-ebgp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
message: |
Use this topology to test dual-stack IPv4+IPv6 IBGP+EBGP implementation. The
topology requires full IPv6 support, including OSPFv3 implementation.
When all BGP and OSPF sessions are established, the IPv4 and IPv6 BGP tables on R1
should look similar to the one below (please also check the next hops)
Network Next Hop Metric AIGP LocPref Weight Path
* > 10.0.0.1/32 - - - - 0 i
* > 10.0.0.2/32 10.0.0.2 0 - 100 0 i Or-ID: 10.0.0.2 C-LST: 10.0.0.3
* > 10.0.0.3/32 10.0.0.3 0 - 100 0 i
* > 10.0.0.4/32 10.1.0.2 0 - 100 0 65001 i
* > 10.0.0.5/32 10.0.0.2 0 - 100 0 65002 i Or-ID: 10.0.0.2 C-LST: 10.0.0.3
Network Next Hop Metric AIGP LocPref Weight Path
* > 2001:db8:0:1::/64 - - - - 0 i
* > 2001:db8:0:2::/64 2001:db8:0:2::1 0 - 100 0 i Or-ID: 10.0.0.2 C-LST: 10.0.0.3
* > 2001:db8:0:3::/64 2001:db8:0:3::1 0 - 100 0 i
* > 2001:db8:0:4::/64 2001:db8:2::2 0 - 100 0 65001 i
* > 2001:db8:0:5::/64 2001:db8:0:2::1 0 - 100 0 65002 i Or-ID: 10.0.0.2 C-LST: 10.0.0.3
addressing:
loopback:
ipv6: 2001:db8:0::/48
lan:
ipv6: 2001:db8:1::/48
p2p:
ipv6: 2001:db8:2::/48

module: [ bgp, ospf ]
bgp.as: 65000

nodes:
r1:
r2:
rr:
bgp.rr: True
x1:
bgp.as: 65001
x2:
bgp.as: 65002

links:
- r1-x1
- r2-x2
- r1-rr
- r2-rr
- r1-r2
Loading

0 comments on commit 376a422

Please sign in to comment.