From 00024ff4a07237cd1b2f64512daad4014452e56f Mon Sep 17 00:00:00 2001 From: Ivan Pepelnjak Date: Sun, 21 Jun 2026 13:03:48 +0200 Subject: [PATCH 1/3] BIRD: Anycast gateway support * Data-plane configuration is identical to FRR * There is no control-plane configuration * FRR template was split into DP/CP components to make it reusable for BIRD Based on work by @jbemmel in #3475 --- docs/module/gateway.md | 1 + docs/platforms.md | 1 + netsim/ansible/templates/gateway/bird.j2 | 5 ++ .../templates/gateway/frr.data-plane.j2 | 51 +++++++++++++++++ netsim/ansible/templates/gateway/frr.j2 | 56 +------------------ netsim/daemons/bird.yml | 2 + 6 files changed, 63 insertions(+), 53 deletions(-) create mode 100644 netsim/ansible/templates/gateway/bird.j2 create mode 100644 netsim/ansible/templates/gateway/frr.data-plane.j2 diff --git a/docs/module/gateway.md b/docs/module/gateway.md index 839fe56f21..29829c1c66 100644 --- a/docs/module/gateway.md +++ b/docs/module/gateway.md @@ -21,6 +21,7 @@ The module is supported on these platforms: | --------------------- | :-: | :-: | :-: | | Arista EOS | ✅ | ✅ | ✅ | | Aruba AOS-CX | ✅ | ✅ | ✅ | +| BIRD | ✅ | ❌ | ❌ | | Cisco IOS XE[^18v] | ❌ | ✅ | ✅ | | Cisco IOS XR[^XR] | ❌ | ✅ | ✅ | | Cisco Nexus OS | ❌ | ✅ | ✅ | diff --git a/docs/platforms.md b/docs/platforms.md index 939a17d6b1..d1e35414ce 100644 --- a/docs/platforms.md +++ b/docs/platforms.md @@ -418,6 +418,7 @@ These devices support additional control-plane protocols or BGP address families | --------------------- | :-: | :-: | :-: | :-: | | Arista EOS | ✅ | ✅ | ✅ | ✅ | | Aruba AOS-CX | ✅ | ✅ | ✅ | ✅ | +| BIRD | ❌ | ❌ | ❌ | ✅ | | Cisco IOS XE[^XE] | ✅ | ✅ | ✅ | ✅ | | Cisco IOSv/IOSvL2 | ✅ | ❌ | ✅ | ✅ | | Cisco IOS XR[^XR] | ❌ | ✅ | ✅ | ✅ | diff --git a/netsim/ansible/templates/gateway/bird.j2 b/netsim/ansible/templates/gateway/bird.j2 new file mode 100644 index 0000000000..fe9c00edf6 --- /dev/null +++ b/netsim/ansible/templates/gateway/bird.j2 @@ -0,0 +1,5 @@ +#!/bin/bash +# +set -e +# +{% include 'gateway/frr.data-plane.j2' +%} diff --git a/netsim/ansible/templates/gateway/frr.data-plane.j2 b/netsim/ansible/templates/gateway/frr.data-plane.j2 new file mode 100644 index 0000000000..43097db867 --- /dev/null +++ b/netsim/ansible/templates/gateway/frr.data-plane.j2 @@ -0,0 +1,51 @@ +sysctl -w net.ipv6.conf.all.enhanced_dad=0 +sysctl -w net.ipv6.conf.default.enhanced_dad=0 +{% for intf in interfaces if intf.gateway.protocol|default('none') == 'vrrp' %} +{% for afm in ['ipv4','ipv6'] if afm in intf.gateway %} +{% set v_if = 'vrrp%s-%s-%s'|format('6' if afm == 'ipv6' else '',intf.ifindex,intf.gateway.vrrp.group) %} +{% set v_mac = intf.gateway.vrrp.mac[afm] %} +if [ ! -e /sys/class/net/{{ v_if }} ]; then + ip link add {{ v_if }} link {{ intf.ifname }} type macvlan mode bridge + ip link set dev {{ v_if }} address {{ v_mac }} addrgenmode {{ 'none' if afm == 'ipv4' else 'random' }} + ip addr add {{ intf.gateway[afm] }} dev {{ v_if }} +{% if afm=='ipv4' %} + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_announce=2 + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_ignore=2 + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_accept=1 +{% endif %} +{% if 'vrf' in intf %} + ip link set dev {{ v_if }} master {{ intf.vrf }} +{% endif %} + ip link set dev {{ v_if }} up +fi +{% endfor %} +{% endfor %} +{% for intf in interfaces if intf.gateway.protocol|default('none') == 'anycast' %} +{% set v_if = 'varp-%s'|format(intf.ifindex) %} +if [ ! -e /sys/class/net/{{ v_if }} ]; then + ip link add {{ v_if }} link {{ intf.ifname }} type macvlan mode private + ip link set dev {{ v_if }} address {{ intf.gateway.anycast.mac|ansible.utils.hwaddr('linux') }} +{% if intf.type == 'svi' %} + bridge fdb replace {{ intf.gateway.anycast.mac|ansible.utils.hwaddr('linux') }} dev {{ intf.ifname }} self +{% endif %} +{% for afm in ['ipv4','ipv6'] if afm in intf.gateway %} + ip addr add {{ intf.gateway[afm] }} dev {{ v_if }} metric 1024 +{% endfor %} +{% if 'ipv4' in intf.gateway %} + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_announce=2 + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_ignore=2 + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_accept=1 +{% endif %} +{% if 'ipv6' in intf.gateway %} + sysctl -w net.ipv6.conf.{{ v_if }}.enhanced_dad=0 + sysctl -w net.ipv6.conf.{{ v_if }}.accept_dad=0 + sysctl -w net.ipv6.conf.{{ v_if }}.dad_transmits=0 +{% else %} + sysctl -w net.ipv6.conf.{{ v_if }}.disable_ipv6=1 +{% endif %} +{% if 'vrf' in intf %} + ip link set dev {{ v_if }} master {{ intf.vrf }} +{% endif %} + ip link set dev {{ v_if }} up +fi +{% endfor %} diff --git a/netsim/ansible/templates/gateway/frr.j2 b/netsim/ansible/templates/gateway/frr.j2 index 72d59ebace..9ded9612c9 100644 --- a/netsim/ansible/templates/gateway/frr.j2 +++ b/netsim/ansible/templates/gateway/frr.j2 @@ -1,56 +1,6 @@ #!/bin/bash # -set -e # Exit immediately when any command fails +set -e # -sysctl -w net.ipv6.conf.all.enhanced_dad=0 -sysctl -w net.ipv6.conf.default.enhanced_dad=0 -{% for intf in interfaces if intf.gateway.protocol|default('none') == 'vrrp' %} -{% for afm in ['ipv4','ipv6'] if afm in intf.gateway %} -{% set v_if = 'vrrp%s-%s-%s'|format('6' if afm == 'ipv6' else '',intf.ifindex,intf.gateway.vrrp.group) %} -{% set v_mac = intf.gateway.vrrp.mac[afm] %} -if [ ! -e /sys/class/net/{{ v_if }} ]; then - ip link add {{ v_if }} link {{ intf.ifname }} type macvlan mode bridge - ip link set dev {{ v_if }} address {{ v_mac }} addrgenmode {{ 'none' if afm == 'ipv4' else 'random' }} - ip addr add {{ intf.gateway[afm] }} dev {{ v_if }} -{% if afm=='ipv4' %} - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_announce=2 - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_ignore=2 - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_accept=1 -{% endif %} -{% if 'vrf' in intf %} - ip link set dev {{ v_if }} master {{ intf.vrf }} -{% endif %} - ip link set dev {{ v_if }} up -fi -{% endfor %} -{% endfor %} -{% for intf in interfaces if intf.gateway.protocol|default('none') == 'anycast' %} -{% set v_if = 'varp-%s'|format(intf.ifindex) %} -if [ ! -e /sys/class/net/{{ v_if }} ]; then - ip link add {{ v_if }} link {{ intf.ifname }} type macvlan mode private - ip link set dev {{ v_if }} address {{ intf.gateway.anycast.mac|ansible.utils.hwaddr('linux') }} -{% if intf.type == 'svi' %} - bridge fdb replace {{ intf.gateway.anycast.mac|ansible.utils.hwaddr('linux') }} dev {{ intf.ifname }} self -{% endif %} -{% for afm in ['ipv4','ipv6'] if afm in intf.gateway %} - ip addr add {{ intf.gateway[afm] }} dev {{ v_if }} metric 1024 -{% endfor %} -{% if 'ipv4' in intf.gateway %} - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_announce=2 - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_ignore=2 - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_accept=1 -{% endif %} -{% if 'ipv6' in intf.gateway %} - sysctl -w net.ipv6.conf.{{ v_if }}.enhanced_dad=0 - sysctl -w net.ipv6.conf.{{ v_if }}.accept_dad=0 - sysctl -w net.ipv6.conf.{{ v_if }}.dad_transmits=0 -{% else %} - sysctl -w net.ipv6.conf.{{ v_if }}.disable_ipv6=1 -{% endif %} -{% if 'vrf' in intf %} - ip link set dev {{ v_if }} master {{ intf.vrf }} -{% endif %} - ip link set dev {{ v_if }} up -fi -{% endfor %} -{% include 'frr.vrrp-config.j2' %} +{% include 'frr.data-plane.j2' +%} +{% include 'frr.vrrp-config.j2' +%} diff --git a/netsim/daemons/bird.yml b/netsim/daemons/bird.yml index 1b56e9875b..85f0a9585b 100644 --- a/netsim/daemons/bird.yml +++ b/netsim/daemons/bird.yml @@ -72,6 +72,8 @@ features: routing: static.discard: true dhcp: false + gateway: + protocol: [ anycast ] initial: ipv4: unnumbered: peer From 62b0ff0da20ba22daf24de8df75d24228b63735b Mon Sep 17 00:00:00 2001 From: Ivan Pepelnjak Date: Sun, 21 Jun 2026 13:14:58 +0200 Subject: [PATCH 2/3] No need to use complex path when including FRR DP config into BIRD config Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- netsim/ansible/templates/gateway/bird.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netsim/ansible/templates/gateway/bird.j2 b/netsim/ansible/templates/gateway/bird.j2 index fe9c00edf6..8fd355c14d 100644 --- a/netsim/ansible/templates/gateway/bird.j2 +++ b/netsim/ansible/templates/gateway/bird.j2 @@ -2,4 +2,4 @@ # set -e # -{% include 'gateway/frr.data-plane.j2' +%} +{% include 'frr.data-plane.j2' +%} From 16932e12fa1ed1b7a7d08913b37981b189e123cc Mon Sep 17 00:00:00 2001 From: Ivan Pepelnjak Date: Sun, 21 Jun 2026 16:51:48 +0200 Subject: [PATCH 3/3] Split FRR data-plane configuration into per-protocol chunks --- netsim/ansible/templates/gateway/bird.j2 | 2 +- netsim/ansible/templates/gateway/frr.j2 | 4 ++-- .../{frr.data-plane.j2 => frr/anycast-dp.j2} | 22 ------------------- .../templates/gateway/frr/data-plane.j2 | 5 +++++ .../{frr.vrrp-config.j2 => frr/vrrp-cp.j2} | 0 .../ansible/templates/gateway/frr/vrrp-dp.j2 | 20 +++++++++++++++++ 6 files changed, 28 insertions(+), 25 deletions(-) rename netsim/ansible/templates/gateway/{frr.data-plane.j2 => frr/anycast-dp.j2} (55%) create mode 100644 netsim/ansible/templates/gateway/frr/data-plane.j2 rename netsim/ansible/templates/gateway/{frr.vrrp-config.j2 => frr/vrrp-cp.j2} (100%) create mode 100644 netsim/ansible/templates/gateway/frr/vrrp-dp.j2 diff --git a/netsim/ansible/templates/gateway/bird.j2 b/netsim/ansible/templates/gateway/bird.j2 index 8fd355c14d..dc821fc0b7 100644 --- a/netsim/ansible/templates/gateway/bird.j2 +++ b/netsim/ansible/templates/gateway/bird.j2 @@ -2,4 +2,4 @@ # set -e # -{% include 'frr.data-plane.j2' +%} +{% include 'frr/anycast-dp.j2' +%} diff --git a/netsim/ansible/templates/gateway/frr.j2 b/netsim/ansible/templates/gateway/frr.j2 index 9ded9612c9..7e42545874 100644 --- a/netsim/ansible/templates/gateway/frr.j2 +++ b/netsim/ansible/templates/gateway/frr.j2 @@ -2,5 +2,5 @@ # set -e # -{% include 'frr.data-plane.j2' +%} -{% include 'frr.vrrp-config.j2' +%} +{% include 'frr/data-plane.j2' +%} +{% include 'frr/vrrp-cp.j2' +%} diff --git a/netsim/ansible/templates/gateway/frr.data-plane.j2 b/netsim/ansible/templates/gateway/frr/anycast-dp.j2 similarity index 55% rename from netsim/ansible/templates/gateway/frr.data-plane.j2 rename to netsim/ansible/templates/gateway/frr/anycast-dp.j2 index 43097db867..5d1f1cad4f 100644 --- a/netsim/ansible/templates/gateway/frr.data-plane.j2 +++ b/netsim/ansible/templates/gateway/frr/anycast-dp.j2 @@ -1,25 +1,3 @@ -sysctl -w net.ipv6.conf.all.enhanced_dad=0 -sysctl -w net.ipv6.conf.default.enhanced_dad=0 -{% for intf in interfaces if intf.gateway.protocol|default('none') == 'vrrp' %} -{% for afm in ['ipv4','ipv6'] if afm in intf.gateway %} -{% set v_if = 'vrrp%s-%s-%s'|format('6' if afm == 'ipv6' else '',intf.ifindex,intf.gateway.vrrp.group) %} -{% set v_mac = intf.gateway.vrrp.mac[afm] %} -if [ ! -e /sys/class/net/{{ v_if }} ]; then - ip link add {{ v_if }} link {{ intf.ifname }} type macvlan mode bridge - ip link set dev {{ v_if }} address {{ v_mac }} addrgenmode {{ 'none' if afm == 'ipv4' else 'random' }} - ip addr add {{ intf.gateway[afm] }} dev {{ v_if }} -{% if afm=='ipv4' %} - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_announce=2 - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_ignore=2 - sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_accept=1 -{% endif %} -{% if 'vrf' in intf %} - ip link set dev {{ v_if }} master {{ intf.vrf }} -{% endif %} - ip link set dev {{ v_if }} up -fi -{% endfor %} -{% endfor %} {% for intf in interfaces if intf.gateway.protocol|default('none') == 'anycast' %} {% set v_if = 'varp-%s'|format(intf.ifindex) %} if [ ! -e /sys/class/net/{{ v_if }} ]; then diff --git a/netsim/ansible/templates/gateway/frr/data-plane.j2 b/netsim/ansible/templates/gateway/frr/data-plane.j2 new file mode 100644 index 0000000000..1ce607365b --- /dev/null +++ b/netsim/ansible/templates/gateway/frr/data-plane.j2 @@ -0,0 +1,5 @@ +sysctl -w net.ipv6.conf.all.enhanced_dad=0 +sysctl -w net.ipv6.conf.default.enhanced_dad=0 +# +{% include 'frr/anycast-dp.j2' +%} +{% include 'frr/vrrp-dp.j2' %} diff --git a/netsim/ansible/templates/gateway/frr.vrrp-config.j2 b/netsim/ansible/templates/gateway/frr/vrrp-cp.j2 similarity index 100% rename from netsim/ansible/templates/gateway/frr.vrrp-config.j2 rename to netsim/ansible/templates/gateway/frr/vrrp-cp.j2 diff --git a/netsim/ansible/templates/gateway/frr/vrrp-dp.j2 b/netsim/ansible/templates/gateway/frr/vrrp-dp.j2 new file mode 100644 index 0000000000..9fc6978d0c --- /dev/null +++ b/netsim/ansible/templates/gateway/frr/vrrp-dp.j2 @@ -0,0 +1,20 @@ +{% for intf in interfaces if intf.gateway.protocol|default('none') == 'vrrp' %} +{% for afm in ['ipv4','ipv6'] if afm in intf.gateway %} +{% set v_if = 'vrrp%s-%s-%s'|format('6' if afm == 'ipv6' else '',intf.ifindex,intf.gateway.vrrp.group) %} +{% set v_mac = intf.gateway.vrrp.mac[afm] %} +if [ ! -e /sys/class/net/{{ v_if }} ]; then + ip link add {{ v_if }} link {{ intf.ifname }} type macvlan mode bridge + ip link set dev {{ v_if }} address {{ v_mac }} addrgenmode {{ 'none' if afm == 'ipv4' else 'random' }} + ip addr add {{ intf.gateway[afm] }} dev {{ v_if }} +{% if afm=='ipv4' %} + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_announce=2 + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_ignore=2 + sysctl -w net.ipv4.conf.{{ intf.ifname }}.arp_accept=1 +{% endif %} +{% if 'vrf' in intf %} + ip link set dev {{ v_if }} master {{ intf.vrf }} +{% endif %} + ip link set dev {{ v_if }} up +fi +{% endfor %} +{% endfor %}