Hoi folks,

Bird folk, can I ask you to take a look at the rebase patch I sent? I'd love for the 'evpn' branch to be rebased.

On 14.02.2026 12:49, Pim van Pelt via Bird-users wrote:
I've started to toy with VPP and eVPN/VxLAN, and took a look at the evpn branch from a few years ago. For my network, I'll need the OSPFv3 'unnumbered' features we built, so I thought I'd ask - would it be possible to rebase the evpn branch ? I've taken a stab at it (see attached patch) by replaying the 9 commits on top if HEAD (f1a7229d-evpn.diff).

It may not be correct, but it does compile and seemingly work 🙂
I have played around with this 2.18+evpn rebase and created a working eVPN/VxLAN with VPP. I stumbled across a few specifics which I'd like to share:

(1) The evpn export are causing the following assertion failure:
Assertion '!((a->flags ^ desc->flags) & (BAF_OPTIONAL | BAF_TRANSITIVE))' failed at proto/bgp/attrs.c:1269

evpn_announce_mac() and evpn_announce_imet() were using ea_set_attr_ptr() with flags=0 to set BGP attributes BA_EXT_COMMUNITY and BA_PMSI_TUNNEL. Those attributes have descriptor flags BAF_OPTIONAL | BAF_TRANSITIVE, and when BGP's bgp_export_attr() processes those attributes during update encoding, it trips the assertion.

This patch switches to bgp_set_attr_ptr() which automatically normalizes flags from the descriptor table, ensuring the stored attribute flags always match what the descriptor expects. Compare to l3vpn.c which correctly passed BAF_OPTIONAL | BAF_TRANSITIVE explicitly, this feels cleaner.
*See bird2.18+evpn_use_bgp_set_attr.diff for a possible fix.
*
(2) BGP Next Hop for Type-2 should be the 'router address' from evpn protocol. When announcing an IPv4 vxlan evpn on an IPv6 BGP session, default behavior is to set the next hop using the BGP session. This means the MAC nexthops will be IPv6, not 'router address'. More-over, changing this with 'next hop address X' is not possible, because overriding the next-hop will remove the MPLS label (which carries the VNI).

Under the assumption that whatever 'router address' is in the evpn protocol context will determine: 1) the PMSI [already correctly added even if the nexthop is a different family, here it does not matter] 2) the BGP next hops for Type-2 (MAC) announcements [where it matters if the evpn vxlan address family differs to the BGP session address family]

This patch fixes the latter: setting the BGP next hop to the 'router address' field for evpn_announce_mac() and for consistency also for evpn_announce_imet() *See bird2.18+evpn_use_routeraddr_as_bgp_nexthop.diff for a reasonable default.
*
(3) Setting BGP Next Hop clears MPLS Labelstack, filters cannot set this.
When the BGP Next Hop is changed by an export filter, we lose the MPLS labelstack. There is no way to add MPLS labelstack in filters (at least, that I could find), so we cannot use 'next hop address X' to determine the Type-2 MAC VxLAN endpoint. Note: IMET updates do not use the BGP Next Hop, but rather a PSMI attribute with the 'router address' already.

This patch ensures that the MPLS labelstack (the first label carries the VNI in the case of eVPN/VxLAN) is put back for L2VPN MPLS BGP updates.
*See bird2.18+evpn_ensure_mpls_labelstack_is_set.diff for a durable fix.
*
Otherwise, I found that the evpn branch, rebased on 2.18, works a treat, noting that I am not using 'bridge' protocol, but instead reading eVPN information directly from the 'eth table' for my application.

groet,
Pim

--
Pim van Pelt<[email protected]>
PBVP1-RIPEhttps://ipng.ch/

diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index 2c2edb08..447d4a4d 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -1258,13 +1258,17 @@ bgp_use_gateway(struct bgp_export_state *s)
     return 0;
 
   /* Check for non-matching AF */
-  if ((ipa_is_ip4(ra->nh.gw) != bgp_channel_is_ipv4(c)) && !c->ext_next_hop)
+  if ((ipa_is_ip4(ra->nh.gw) != bgp_channel_is_ipv4(c)) && !c->ext_next_hop && 
!bgp_channel_is_l2vpn(c))
     return 0;
 
   /* Do not use gateway from different VRF */
   if (p->p.vrf_set && ra->nh.iface && !if_in_vrf(ra->nh.iface, p->p.vrf))
     return 0;
 
+  /* For L2VPN (EVPN), always use the gateway as the VTEP next hop */
+  if (bgp_channel_is_l2vpn(c))
+    return 1;
+
   /* Use it when exported to internal peers */
   if (p->is_interior)
     return 1;
diff --git a/proto/evpn/evpn.c b/proto/evpn/evpn.c
index 1c8cad92..5e9efb57 100644
--- a/proto/evpn/evpn.c
+++ b/proto/evpn/evpn.c
@@ -158,8 +158,12 @@ evpn_announce_mac(struct evpn_proto *p, const net_addr_eth 
*n0, rte *new)
     *a = (rta) {
       .source = RTS_EVPN,
       .scope = SCOPE_UNIVERSE,
+      .dest = ipa_nonzero(p->router_addr) ? RTD_UNICAST : RTD_UNREACHABLE,
       .pref = c->preference,
+      .nh.gw = p->router_addr,
+      .nh.labels = 1,
     };
+    a->nh.label[0] = p->vni;
 
     struct adata *ad = evpn_export_targets(p, &null_adata);
     ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, 0, 
EAF_TYPE_EC_SET, ad);
@@ -189,7 +193,9 @@ evpn_announce_imet(struct evpn_proto *p, int new)
     *a = (rta) {
       .source = RTS_EVPN,
       .scope = SCOPE_UNIVERSE,
+      .dest = ipa_nonzero(p->router_addr) ? RTD_UNICAST : RTD_UNREACHABLE,
       .pref = c->preference,
+      .nh.gw = p->router_addr,
     };
 
     struct adata *ad = evpn_export_targets(p, &null_adata);
diff --git a/proto/evpn/evpn.c b/proto/evpn/evpn.c
index 5e9efb57..925f57f6 100644
--- a/proto/evpn/evpn.c
+++ b/proto/evpn/evpn.c
@@ -166,7 +166,7 @@ evpn_announce_mac(struct evpn_proto *p, const net_addr_eth 
*n0, rte *new)
     a->nh.label[0] = p->vni;
 
     struct adata *ad = evpn_export_targets(p, &null_adata);
-    ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, 0, 
EAF_TYPE_EC_SET, ad);
+    bgp_set_attr_ptr(&a->eattrs, tmp_linpool, BA_EXT_COMMUNITY, 0, ad);
 
     ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_LABEL, 0, EAF_TYPE_INT, 
p->vni);
 
@@ -199,10 +199,10 @@ evpn_announce_imet(struct evpn_proto *p, int new)
     };
 
     struct adata *ad = evpn_export_targets(p, &null_adata);
-    ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, 0, 
EAF_TYPE_EC_SET, ad);
+    bgp_set_attr_ptr(&a->eattrs, tmp_linpool, BA_EXT_COMMUNITY, 0, ad);
 
     ad = bgp_pmsi_new_ingress_replication(tmp_linpool, p->router_addr, p->vni);
-    ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_PMSI_TUNNEL, 0, 
EAF_TYPE_OPAQUE, ad);
+    bgp_set_attr_ptr(&a->eattrs, tmp_linpool, BA_PMSI_TUNNEL, 0, ad);
 
     rte *e = rte_get_temp(a, p->p.main_source);
     rte_update2(c, n, e, p->p.main_source);
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index 447d4a4d..d9107da1 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -1314,6 +1314,19 @@ bgp_update_next_hop_ip(struct bgp_export_state *s, eattr 
*a, ea_list **to)
     }
   }
 
+  /* For L2VPN (EVPN): ensure MPLS label stack is set even if next hop was 
filter-overridden */
+  if (s->mpls && bgp_channel_is_l2vpn(s->channel) && !bgp_find_attr(*to, 
BA_MPLS_LABEL_STACK))
+  {
+    rta *ra = s->route->attrs;
+    if (ra->nh.labels)
+      bgp_set_attr_data(to, s->pool, BA_MPLS_LABEL_STACK, 0, ra->nh.label, 
ra->nh.labels * 4);
+    else
+    {
+      u32 label = ea_get_int(ra->eattrs, EA_MPLS_LABEL, BGP_MPLS_NULL);
+      bgp_set_attr_data(to, s->pool, BA_MPLS_LABEL_STACK, 0, &label, 4);
+    }
+  }
+
   /* Check if next hop is valid */
   a = bgp_find_attr(*to, BA_NEXT_HOP);
   if (!a)

Reply via email to