From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 4ACB41FF0E4 for ; Tue, 14 Jul 2026 13:19:09 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 7A10D213EA; Tue, 14 Jul 2026 13:19:08 +0200 (CEST) From: Gabriel Goller To: pve-devel@lists.proxmox.com Subject: [PATCH frr 1/2] frr: backport VXLAN local VTEP fallback fix Date: Tue, 14 Jul 2026 13:18:46 +0200 Message-ID: <20260714111858.232230-1-g.goller@proxmox.com> X-Mailer: git-send-email 2.47.3 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1784027926766 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.500 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: W2XQWSNUJHZ5IRPTA7OZZECKJPYBNGTI X-Message-ID-Hash: W2XQWSNUJHZ5IRPTA7OZZECKJPYBNGTI X-MailFrom: g.goller@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Backport the upstream fix for EVPN VXLAN interfaces without an explicit local IP address. Encode an unspecified VTEP as 0.0.0.0 so bgpd can fall back to the BGP router ID. Also include the upstream regression topotest. For more information also check the pve patch (optional): https://lore.proxmox.com/pve-devel/20260702143349.252142-1-g.goller@proxmox.com/ Fixes: #7766 Upstream: https://github.com/FRRouting/frr/pull/22555 Signed-off-by: Gabriel Goller --- debian/patches/series | 2 + ...ix-VXLAN-interface-local-IP-fallback.patch | 84 ++++++++++ ...EVPN-L2VNI-unspecified-VTEP-topotest.patch | 153 ++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 debian/patches/upstream/0028-bgpd-zebra-fix-VXLAN-interface-local-IP-fallback.patch create mode 100644 debian/patches/upstream/0029-tests-add-EVPN-L2VNI-unspecified-VTEP-topotest.patch diff --git a/debian/patches/series b/debian/patches/series index 45125e05e6e9..47c123827e2e 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -25,6 +25,8 @@ upstream/0024-Cleanup-snmp-memory-leaks.patch upstream/0025-pimd-cleanup-of-leaked-memory-on-shutdown.patch upstream/0026-tests-make-the-topotest-fail-if-a-memory-leak-is-det.patch upstream/0027-Use-hash_clean_and_free-remove-hash_free.patch +upstream/0028-bgpd-zebra-fix-VXLAN-interface-local-IP-fallback.patch +upstream/0029-tests-add-EVPN-L2VNI-unspecified-VTEP-topotest.patch pve/0001-enable-bgp-bfd-daemons.patch pve/0002-bgpd-add-an-option-for-RT-auto-derivation-to-force-A.patch pve/0003-tests-add-bgp-evpn-autort-test.patch diff --git a/debian/patches/upstream/0028-bgpd-zebra-fix-VXLAN-interface-local-IP-fallback.patch b/debian/patches/upstream/0028-bgpd-zebra-fix-VXLAN-interface-local-IP-fallback.patch new file mode 100644 index 000000000000..1fcc7dad14ef --- /dev/null +++ b/debian/patches/upstream/0028-bgpd-zebra-fix-VXLAN-interface-local-IP-fallback.patch @@ -0,0 +1,84 @@ +From e8da5eda308db2cca319403b2a29ca9a0c6d996d Mon Sep 17 00:00:00 2001 +From: Gabriel Goller +Date: Thu, 2 Jul 2026 16:29:41 +0200 +Subject: [PATCH] bgpd, zebra: fix VXLAN interface local IP fallback + +FRR 10.6 breaks VXLAN interfaces that do not have a local IP. It changes +EVPN VTEP handling from the IPv4 struct in_addr to the generic struct +ipaddr for IPv6 VTEP support. That makes zebra store a missing VXLAN +vxlan local address as IPADDR_NONE instead of IPv4 0.0.0.0. + +ZEBRA_VNI_ADD still uses the normal ipaddr stream helpers. These helpers +reject IPADDR_NONE, so zebra no longer sends an add, and bgpd's existing +router-id fallback for an unspecified VTEP never runs. + +Encode an IPADDR_NONE local VTEP as IPv4 0.0.0.0 when zebra builds +ZEBRA_VNI_ADD. In bgpd, keep the fallback and treat IPv4/IPv6 zero as +unspecified, so bgpd uses the router-id as the EVPN VTEP. + +Signed-off-by: Gabriel Goller +--- + bgpd/bgp_zebra.c | 9 ++++++++- + zebra/zebra_evpn.c | 10 ++++++++-- + 2 files changed, 16 insertions(+), 3 deletions(-) + +diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c +index d1034ff42a18..d6c09003a853 100644 +--- a/bgpd/bgp_zebra.c ++++ b/bgpd/bgp_zebra.c +@@ -3440,9 +3440,16 @@ static int bgp_zebra_process_local_vni(ZAPI_CALLBACK_ARGS) + vrf_id_to_name(vrf_id), vni, + vrf_id_to_name(tenant_vrf_id), svi_ifindex); + +- if (ipaddr_is_zero(&vtep_ip)) { ++ /* Preserve legacy behavior for VXLAN devices with no explicit local ++ * address: treat IPv4/IPv6 zero as unspecified and use the router-id as ++ * the EVPN VTEP/originator IP. ++ */ ++ if (cmd == ZEBRA_VNI_ADD && ipaddr_is_zero(&vtep_ip)) { + SET_IPADDR_V4(&vtep_ip); + vtep_ip.ipaddr_v4 = bgp->router_id; ++ if (BGP_DEBUG(zebra, ZEBRA)) ++ zlog_debug("Rx VNI add with unspecified VTEP IP, using router-id %pIA", ++ &vtep_ip); + } + + if (cmd == ZEBRA_VNI_ADD) { +diff --git a/zebra/zebra_evpn.c b/zebra/zebra_evpn.c +index 258e818ec5d2..724d549a4f51 100644 +--- a/zebra/zebra_evpn.c ++++ b/zebra/zebra_evpn.c +@@ -1102,6 +1102,7 @@ int zebra_evpn_send_add_to_client(struct zebra_evpn *zevpn) + struct zserv *client; + struct stream *s; + ifindex_t svi_index; ++ struct ipaddr local_vtep_ip; + int rc; + + client = zserv_find_client(ZEBRA_ROUTE_BGP, 0); +@@ -1115,7 +1116,12 @@ int zebra_evpn_send_add_to_client(struct zebra_evpn *zevpn) + + zclient_create_header(s, ZEBRA_VNI_ADD, zebra_vrf_get_evpn_id()); + stream_putl(s, zevpn->vni); +- stream_put_ipaddr(s, &zevpn->local_vtep_ip); ++ local_vtep_ip = zevpn->local_vtep_ip; ++ if (IS_IPADDR_NONE(&local_vtep_ip)) { ++ SET_IPADDR_V4(&local_vtep_ip); ++ local_vtep_ip.ipaddr_v4.s_addr = INADDR_ANY; ++ } ++ stream_put_ipaddr(s, &local_vtep_ip); + stream_put(s, &zevpn->vrf_id, sizeof(vrf_id_t)); /* tenant vrf */ + stream_put_in_addr(s, &zevpn->mcast_grp); + stream_put(s, &svi_index, sizeof(ifindex_t)); +@@ -1125,7 +1131,7 @@ int zebra_evpn_send_add_to_client(struct zebra_evpn *zevpn) + + if (IS_ZEBRA_DEBUG_VXLAN) + zlog_debug("Send EVPN_ADD %u %pIA tenant vrf %s(%u) SVI index %u to %s", zevpn->vni, +- &zevpn->local_vtep_ip, vrf_id_to_name(zevpn->vrf_id), zevpn->vrf_id, ++ &local_vtep_ip, vrf_id_to_name(zevpn->vrf_id), zevpn->vrf_id, + (zevpn->svi_if ? zevpn->svi_if->ifindex : 0), + zebra_route_string(client->proto)); + +-- +2.47.3 + diff --git a/debian/patches/upstream/0029-tests-add-EVPN-L2VNI-unspecified-VTEP-topotest.patch b/debian/patches/upstream/0029-tests-add-EVPN-L2VNI-unspecified-VTEP-topotest.patch new file mode 100644 index 000000000000..aff477e4221d --- /dev/null +++ b/debian/patches/upstream/0029-tests-add-EVPN-L2VNI-unspecified-VTEP-topotest.patch @@ -0,0 +1,153 @@ +From 347899ac7ce61bdae48ccef8f91894c2ab210b04 Mon Sep 17 00:00:00 2001 +From: Gabriel Goller +Date: Fri, 3 Jul 2026 13:40:11 +0200 +Subject: [PATCH] tests: add EVPN L2VNI unspecified VTEP topotest + +Add a short topotest that creates an L2VNI without a VXLAN local address +and checks that bgpd uses the BGP router-id as the VTEP. + +Signed-off-by: Gabriel Goller +--- + .../__init__.py | 0 + .../r1/frr.conf | 17 +++++ + .../r2/frr.conf | 16 ++++ + .../test_bgp_evpn_vxlan_local_vtep_none.py | 76 +++++++++++++++++++ + 4 files changed, 109 insertions(+) + create mode 100644 tests/topotests/bgp_evpn_vxlan_local_vtep_none/__init__.py + create mode 100644 tests/topotests/bgp_evpn_vxlan_local_vtep_none/r1/frr.conf + create mode 100644 tests/topotests/bgp_evpn_vxlan_local_vtep_none/r2/frr.conf + create mode 100644 tests/topotests/bgp_evpn_vxlan_local_vtep_none/test_bgp_evpn_vxlan_local_vtep_none.py + +diff --git a/tests/topotests/bgp_evpn_vxlan_local_vtep_none/__init__.py b/tests/topotests/bgp_evpn_vxlan_local_vtep_none/__init__.py +new file mode 100644 +index 000000000000..e69de29bb2d1 +diff --git a/tests/topotests/bgp_evpn_vxlan_local_vtep_none/r1/frr.conf b/tests/topotests/bgp_evpn_vxlan_local_vtep_none/r1/frr.conf +new file mode 100644 +index 000000000000..3b88e4b614f8 +--- /dev/null ++++ b/tests/topotests/bgp_evpn_vxlan_local_vtep_none/r1/frr.conf +@@ -0,0 +1,17 @@ ++! ++interface lo ++ ip address 10.0.0.1/32 ++! ++interface r1-eth0 ++ ip address 192.0.2.1/24 ++! ++router bgp 65000 ++ bgp router-id 10.0.0.1 ++ no bgp default ipv4-unicast ++ neighbor 192.0.2.2 remote-as 65000 ++ ! ++ address-family l2vpn evpn ++ neighbor 192.0.2.2 activate ++ advertise-all-vni ++ exit-address-family ++! +diff --git a/tests/topotests/bgp_evpn_vxlan_local_vtep_none/r2/frr.conf b/tests/topotests/bgp_evpn_vxlan_local_vtep_none/r2/frr.conf +new file mode 100644 +index 000000000000..5d6d8c7a57bb +--- /dev/null ++++ b/tests/topotests/bgp_evpn_vxlan_local_vtep_none/r2/frr.conf +@@ -0,0 +1,16 @@ ++! ++interface lo ++ ip address 10.0.0.2/32 ++! ++interface r2-eth0 ++ ip address 192.0.2.2/24 ++! ++router bgp 65000 ++ bgp router-id 10.0.0.2 ++ no bgp default ipv4-unicast ++ neighbor 192.0.2.1 remote-as 65000 ++ ! ++ address-family l2vpn evpn ++ neighbor 192.0.2.1 activate ++ exit-address-family ++! +diff --git a/tests/topotests/bgp_evpn_vxlan_local_vtep_none/test_bgp_evpn_vxlan_local_vtep_none.py b/tests/topotests/bgp_evpn_vxlan_local_vtep_none/test_bgp_evpn_vxlan_local_vtep_none.py +new file mode 100644 +index 000000000000..876fc40fa73c +--- /dev/null ++++ b/tests/topotests/bgp_evpn_vxlan_local_vtep_none/test_bgp_evpn_vxlan_local_vtep_none.py +@@ -0,0 +1,76 @@ ++#!/usr/bin/env python ++# SPDX-License-Identifier: ISC ++ ++""" ++Verify that an L2VNI with no VXLAN local address reaches bgpd and uses the ++router-id as its originator IP. ++""" ++ ++import os ++import sys ++import pytest ++ ++CWD = os.path.dirname(os.path.realpath(__file__)) ++sys.path.append(os.path.join(CWD, "../")) ++ ++# pylint: disable=C0413 ++from lib import topotest ++from lib.topogen import Topogen, get_topogen ++ ++pytestmark = [pytest.mark.bgpd, pytest.mark.evpn] ++ ++VNI = 101 ++ROUTER_ID = "10.0.0.1" ++ ++ ++def setup_module(mod): ++ topodef = {"s1": ("r1", "r2")} ++ tgen = Topogen(topodef, mod.__name__) ++ tgen.start_topology() ++ ++ router = tgen.gears["r1"] ++ router.cmd_raises(f"ip link add name br{VNI} type bridge") ++ router.cmd_raises("ip link set dev r1-eth0 up") ++ router.cmd_raises( ++ f"ip link add vxlan{VNI} type vxlan id {VNI} dstport 4789 " ++ "group 239.1.1.1 dev r1-eth0 nolearning" ++ ) ++ router.cmd_raises(f"ip link set dev vxlan{VNI} master br{VNI}") ++ router.cmd_raises(f"ip link set dev br{VNI} up") ++ router.cmd_raises(f"ip link set dev vxlan{VNI} up") ++ ++ for router in tgen.routers().values(): ++ router.load_frr_config() ++ ++ tgen.start_router() ++ ++ ++def teardown_module(mod): ++ tgen = get_topogen() ++ tgen.stop_topology() ++ ++ ++def test_bgp_vni_without_local_vtep_uses_router_id(): ++ tgen = get_topogen() ++ ++ if tgen.routers_have_failure(): ++ pytest.skip(tgen.errors) ++ ++ router = tgen.gears["r1"] ++ ++ def _check_bgp_vni(): ++ output = router.vtysh_cmd( ++ f"show bgp l2vpn evpn vni {VNI} json", isjson=True ++ ) ++ expected = {"originatorIp": ROUTER_ID} ++ return topotest.json_cmp(output, expected) ++ ++ _, result = topotest.run_and_expect(_check_bgp_vni, None, count=30, wait=1) ++ assert result is None, ( ++ f"VNI {VNI} originator IP did not fall back to router-id {ROUTER_ID}" ++ ) ++ ++ ++if __name__ == "__main__": ++ args = ["-s"] + sys.argv[1:] ++ sys.exit(pytest.main(args)) +-- +2.47.3 + -- 2.47.3