Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3c2dd55
Add BIRD VRF support
jbemmel Jun 19, 2026
eb856f8
Fix BIRD VRF OSPF router IDs
jbemmel Jun 19, 2026
e90f047
Fix BIRD VRF mixed redistribution
jbemmel Jun 19, 2026
5333171
Add BIRD VRF route leaking
jbemmel Jun 19, 2026
50f44c5
Fix BIRD VRF OSPF route leaking
jbemmel Jun 19, 2026
5e56910
Use VRF names in BIRD VRF objects
jbemmel Jun 19, 2026
17fc1f9
Advertise BIRD static VRF support
jbemmel Jun 19, 2026
5c122dc
Use VRF names in BIRD routing protocols
jbemmel Jun 19, 2026
3c053ec
Document BIRD VRF feature support
jbemmel Jun 19, 2026
c6d6e9d
Support BIRD VRF BGP imports
jbemmel Jun 19, 2026
12eb5e6
Fix BIRD VRF BGP router IDs
jbemmel Jun 19, 2026
6ca1ebd
Rename BIRD VRF template for config mapping.
jbemmel Jun 20, 2026
0f1d5bc
Split BIRD containerlab config markers.
jbemmel Jun 20, 2026
2589b36
Update BIRD daemon VRF config mapping.
jbemmel Jun 20, 2026
224bd77
Handle BIRD config suffix during cleanup.
jbemmel Jun 20, 2026
90a7e6f
Run BIRD VRF dataplane template in container.
jbemmel Jun 20, 2026
5fee997
Share VRF setup template between FRR and BIRD.
jbemmel Jun 20, 2026
2739d69
Move BIRD VRF protocol rendering into VRF config.
jbemmel Jun 20, 2026
49c62ee
Remove unused BIRD initial marker.
jbemmel Jun 20, 2026
e079c6b
Align daemon config cleanup comment.
jbemmel Jun 20, 2026
0826a30
Keep BIRD OSPF macro instance-only.
jbemmel Jun 20, 2026
365c397
Use selected interface for BIRD BGP local address.
jbemmel Jun 20, 2026
3f7e3fe
Split BIRD protocol macros into templates.
jbemmel Jun 20, 2026
fbafbf3
Document BIRD VRF template macros.
jbemmel Jun 20, 2026
9e2cba8
Inline BIRD VRF BGP rendering.
jbemmel Jun 20, 2026
1ce59db
Restore dev versions after rebase.
jbemmel Jun 21, 2026
cb49ff9
Update BIRD daemon VRF handling.
jbemmel Jun 21, 2026
e0db20c
Remove BIRD EVPN bridge patch.
jbemmel Jun 21, 2026
e7681d2
Cleanup shell config templates
ipspace Jun 24, 2026
a1136c2
Fix: Use /-based path in sysctl
ipspace Jun 24, 2026
9b74343
Fix: Use shared static route config in VRF and Routing
ipspace Jun 24, 2026
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/caveats.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ nodes:

* You must build the BIRD container image with the **netlab clab build bird** command. Without additional arguments, that command installs the latest BIRD v3 package from CZ.NIC BIRD repository on top of Ubuntu 24.04. See [](build-bird) for build targets and version options.
* BIRD is implemented as a pure control-plane daemon running as a container with a single external interface. You can set the node **role** to **router** to turn a BIRD instance into a more traditional networking device with a loopback interface.
* BIRD supports a single router ID used for BGP and OSPF.
* BIRD uses the node BGP router ID as the default router ID for BGP and OSPF protocol instances.
* The VM or container running BIRD in host mode starts with static routes pointing to one of the adjacent routers (see [](linux-forwarding)). After establishing routing adjacencies, BIRD copies BGP and OSPF into the kernel IP routing table.
* The **bird** container starts BIRD in foreground mode with logging messages (including debugging messages) sent to *stderr*. Use the **docker logs** command to inspect the BIRD messages.

Expand Down
2 changes: 1 addition & 1 deletion docs/module/bgp.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The following features are only supported on a subset of platforms:
| ------------------------ | :-: | :-: | :-: | :-: |
| Arista EOS | ✅ | ✅ | ✅ | ✅ |
| Aruba AOS-CX | ✅ | ❌ | ✅ | ✅ |
| BIRD | ✅ | ✅ | | |
| BIRD | ✅ | ✅ | | |
| Cisco IOS/IOS XE[^18v] | ✅ | ✅ | ✅ | ✅ |
| Cisco IOS XR[^XR] | ✅ | ✅ | ✅ | ✅ |
| Cumulus Linux 4.x | ✅ | ❌ | ✅ | ✅ |
Expand Down
1 change: 1 addition & 0 deletions docs/module/routing-static.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ _netlab_ supports static routes on these platforms:
|---------------------|:--:|:--:|:--:|:--:|
| Arista EOS | ✅ | ✅ | ✅ | ✅ |
| Aruba AOS-CX | ✅ | ✅ | ✅ | [❗](caveats-aruba) |
| BIRD | ✅ | ✅ | ✅ | ✅ |
| Cisco IOS/XE[^18v] | ✅ | ✅ | ✅ | ✅ |
| Cisco IOS XR[^XR] | ✅ | ✅ | ✅ | ✅ |
| Cisco Nexus OS | ✅ | ✅ | ✅ | ❌ |
Expand Down
1 change: 1 addition & 0 deletions docs/module/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The following table describes high-level per-platform support of generic routing
| ------------------ |:--:|:--:|:--:|:--:|:--:|
| Arista EOS | ✅ | ✅ | ✅ | ✅ | ✅ |
| Aruba AOS-CX | ✅ | ✅ | ✅ | ✅ | ✅ |
| BIRD | ❌ | ❌ | ❌ | ❌ | ✅ |
| Cisco IOS/IOS XE[^18v] | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cisco IOS XR[^XR] | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cisco Nexus OS | ❌ | ❌ | ❌ | ❌ | ✅ |
Expand Down
2 changes: 2 additions & 0 deletions docs/module/vrf.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ VRFs are supported on these platforms:
| --------------------- | :-: | :-: | :-: |
| Arista EOS | ✅ | ✅ | ✅ |
| Aruba AOS-CX | ✅ | ✅ | ✅ |
| BIRD | ✅ | ✅ | ✅ |
| Cisco IOS | ✅ | ✅ | ✅ |
| Cisco IOS XE[^18v] | ✅ | ✅ | ✅ |
| Cisco IOS XR[^XR] | ✅ | ✅ | ✅ |
Expand Down Expand Up @@ -51,6 +52,7 @@ These platforms support routing protocols in VRFs:
| --------------------- | :-: | :-: | :-: | :-: | :-: | :-: |
| Arista EOS | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| Aruba AOS-CX | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| BIRD | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
| Cisco IOS/IOSvL2 | ✅ [❗](caveats-iosv) | ✅ | ✅ | ✅ | ✅ | ❌ |
| Cisco IOS XE[^18v] | ✅ [❗](caveats-csr) | ✅ | ✅ | ✅ | ✅ | ❌ |
| Cisco IOS XR[^XR] | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
Expand Down
2 changes: 1 addition & 1 deletion docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ The data plane [configuration modules](module-reference.md) are supported on the
| --------------------- |:--:|:--:|:--:|:--:|:--:|:--:|
| Arista EOS | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Aruba AOS-CX | ✅ | ✅ | ✅[❗](caveats-aruba) | [❗](caveats-aruba) | ❌ | ❌ |
| BIRD | ✅ | | ✅ | ❌ | ❌ | ❌ |
| BIRD | ✅ | | ✅ | ❌ | ❌ | ❌ |
| Cisco 8000v (IOS XR) | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
| Cisco Catalyst 8000v | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Cisco CSR 1000v | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
Expand Down
7 changes: 5 additions & 2 deletions netsim/ansible/templates/vrf/bird.j2
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/bin/sh
#!/bin/bash
#
set -e
#
# Placeholder: VRF data-plane configuration for BIRD
{% if vrfs is defined %}
{% include "frr.data-plane.j2" +%}
{% endif %}
#
touch /var/run/vrf.done
exit 0
14 changes: 14 additions & 0 deletions netsim/ansible/templates/vrf/frr.data-plane.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Create VRF tables
{% set _vrfs = vrfs|default({}) %}
{% for vname,vdata in _vrfs.items() %}
if [ ! -e /sys/devices/virtual/net/{{vname}} ]; then
ip link add {{vname}} type vrf table {{ vdata.vrfidx }}
fi
ip link set {{vname}} up
{% endfor %}

# Move interfaces and loopbacks to vrfs
{% for i in interfaces if i.vrf is defined %}
sysctl -qw net/ipv6/conf/{{ i.ifname }}/keep_addr_on_down=1

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Similar issue in

sysctl -w net.ipv6.conf.{{ i.ifname }}.addr_gen_mode=3
?

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.

That's not a problem (not saying it should not be fixed) because SVI interface names don't have dots in them.

ip link set {{ i.ifname }} master {{ i.vrf }}
{% endfor %}
22 changes: 3 additions & 19 deletions netsim/ansible/templates/vrf/frr.j2
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
#!/bin/bash
#
set -e # Exit immediately when any command fails
set -e
#

# Create VRF tables
{% set _vrfs = vrfs|default({}) %}
{% for vname,vdata in _vrfs.items() %}
if [ ! -e /sys/devices/virtual/net/{{vname}} ]; then
ip link add {{vname}} type vrf table {{ vdata.vrfidx }}
fi
ip link set {{vname}} up
{% endfor %}

# Move interfaces and loopbacks to vrfs
{% for i in interfaces if i.vrf is defined %}
sysctl -qw net.ipv6.conf.{{ i.ifname }}.keep_addr_on_down=1
ip link set {{ i.ifname }} master {{ i.vrf }}
{% endfor %}

{% if _vrfs %}
{% if vrfs is defined %}
{% include "frr.data-plane.j2" +%}
{% include "frr.frr-config.j2" +%}
{% endif %}
exit $?
12 changes: 9 additions & 3 deletions netsim/daemons/bird.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ features:
large: [ large ]
extended: [ extended ]
2octet: [ standard ]
import: [ ospf, connected, static ]
import: [ ospf, connected, static, vrf ]
bfd: true
default_originate: true
gtsm: true
Expand All @@ -72,7 +72,10 @@ features:
unnumbered: true
areas: true
routing:
static.discard: true
static:
vrf: true
inter_vrf: true
discard: true
dhcp: false
gateway:
protocol: [ anycast ]
Expand All @@ -85,4 +88,7 @@ features:
roles: [ host, router ]
vxlan:
vtep6: true
# vrf: {} # Placeholder, has to be replaced with the real VRF code
vrf:
ospfv2: true
ospfv3: true
bgp: true
158 changes: 8 additions & 150 deletions netsim/daemons/bird/bgp.j2
Original file line number Diff line number Diff line change
@@ -1,154 +1,12 @@
{% import 'bgp.macros.j2' as bgp_config with context %}

{% if 'router_id' in bgp %}
router id {{ bgp.router_id }};
{% endif %}

{% for ngb in bgp.neighbors|default([]) if ngb.default_originate|default(False) %}
{% if loop.first %}
{% for _af in ['ipv4','ipv6'] if _af in af %}
protocol static static_bgp_default_{{ _af }} {
{{ _af }};
route {{ '0.0.0.0/0' if _af=='ipv4' else '::/0' }} reject;
}
{% endfor %}
{% endif %}
{% endfor %}
{% for pfx_af in ['ipv4','ipv6'] %}
{% for pfx in bgp.advertise|default([]) if pfx_af in pfx %}
{% if loop.first %}

define bgp_advertise_{{ pfx_af }} = [
{% endif %}
{{ pfx[pfx_af] }}{% if not loop.last %},{% endif +%}
{% if loop.last %}
];
{% endif %}
{% endfor %}
{% endfor %}
function bgp_prefixes( bool originate_default ) {
if net.len = 0 && !originate_default
then reject "Don't originate default route";

if source ~ [ RTS_BGP ]
then accept "BGP route:", net;

if proto ~ "static_bgp_default*"
then accept "BGP prefix origination:", net;

{% if bgp.import is defined %}
{% for proto in bgp.import %}
if source ~ [ {{ netlab_import_map[proto] }} ]
then accept "{{ proto }} route:", net;
{% endfor %}
{% endif %}
{% for pfx_af in ['ipv4','ipv6'] %}
{% for pfx in bgp.advertise|default([]) if pfx_af in pfx %}
{% if loop.first %}

if net ~ bgp_advertise_{{ pfx_af }} then accept "advertise prefix:", net;
{% endif %}
{% endfor %}
{% endfor %}

reject "not accepted:", net, " source=", source, " preference=", preference, " proto=", proto;
}

function remove_private_as() {
bgp_path.delete([64512..65534, 4200000000..4294967294]);
}

{#
Build a BGP export filter per neighbor type, to filter communities
#}
{% for ntype in [ 'ebgp', 'ibgp', 'localas_ibgp', 'confed_ebgp' ] %}
function bgp_export_{{ ntype }}( bool originate_default; bool rem_private_as ) {
{% set list = bgp.community[ntype]|default([]) %}
{% if 'standard' not in list %}
bgp_community.empty;
{% endif %}
{% if 'large' not in list %}
bgp_large_community.empty;
{% endif %}
{% if 'extended' not in list %}
bgp_ext_community.empty;
{% endif %}

if rem_private_as then remove_private_as();
bgp_prefixes(originate_default);
}
{% endfor %}

{% for n in bgp.neighbors %}
{% for af in [ 'ipv4','ipv6' ] if af in n and n[af] is string %}
protocol bgp bgp_{{ n.name }}_{{ af }} {
{% set loopback = loopback|default({}) %}
{% set local_ip = loopback[af]|default('') %}
local {{ local_ip.split('/')[0] if n.type == 'ibgp' else '' }} as {{ n.local_as|default(bgp.as) }};
{% if bgp.confederation is defined %}
confederation {{ bgp.confederation.as }};
{% if n.type == 'confed_ebgp' %}
confederation member yes;
{% endif %}
{% endif %}
neighbor {{ n[af] }} as {{ n['as'] }};
connect retry time 5;
error wait time 5,10;
startup hold time 10;
{% if n.local_if is defined %}
interface "{{ n.local_if }}";
{% endif %}
{% if n.bfd is defined %}
bfd yes;
{% endif %}
{% if n.passive is defined %}
passive yes;
{% endif %}
{% if n.timers is defined %}
hold time {{ n.timers.hold|default(180) }};
keepalive time {{ n.timers.keepalive|default(60) }};
{% endif %}
{% if n.password is defined %}
password "{{ n.password }}";
{% endif %}
{% if n.rs|default(False) %}
rs client;
{% endif %}
{% if n.rs_client|default(False) %}
enforce first as off;
{% endif %}
{% if n.role is defined %}
local role {{ n.role.name|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) %}
rr cluster id {{ bgp.rr_cluster_id }};
{% endif %}
{% endif %}
{% if n.gtsm is defined %}
ttl security on;
{% if n.type=='ibgp' %}
multihop {{ n.gtsm }};
{% endif %}
{% endif %}
{% for _af in [ 'ipv4','ipv6' ] if _af==af or (_af=='ipv4' and n.ipv4_rfc8950|default(False)) %}
{% if _af in n.activate and n.activate[_af] %}
{{ _af }} {
import all;
export filter { bgp_export_{{ n.type }}( {{ 'true' if n.get('default_originate') else 'false' }},
{{ 'true' if n.get('remove_private_as') else 'false' }}); };
{% if n.next_hop_self|default(false) %}
next hop self {{ 'on' if n.next_hop_self == 'all' else 'ebgp' }};
{% endif %}
{% if _af=='ipv4' and n.ipv4_rfc8950|default(False) %}
extended next hop on;
# require extended next hop on;
{% endif %}
};
{% endif %}
{% endfor %}
}
{% endfor %}
{{ bgp_config.bgp_default_originate_routes(bgp) }}
{{ bgp_config.bgp_advertise_list('bgp_advertise',bgp.advertise) }}
{{ bgp_config.bgp_prefixes_function('bgp_prefixes',bgp,'bgp_advertise',bgp.import|default([]),True) }}
{{ bgp_config.bgp_export_filters('bgp_export_','bgp_prefixes') }}
{% for n in bgp.neighbors|default([]) %}
{{ bgp_config.bgp_session(n,bgp,'',{},netlab_interfaces) }}
{% endfor %}
Loading