]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
EVPN: Add support for EVPN encapsulation and related communities oz-evpn
authorIgor Putovny <igor.putovny@nic.cz>
Tue, 8 Jul 2025 13:49:16 +0000 (15:49 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Fri, 18 Jul 2025 00:14:28 +0000 (02:14 +0200)
proto/evpn/config.Y
proto/evpn/evpn.c
proto/evpn/evpn.h

index 443a55e8d26214064bcaa6b36b0b6bb6ef77b1b7..122a99abfcc5c0b1ffbfb5bd3d5c61b06f9d9d44 100644 (file)
@@ -14,6 +14,7 @@ CF_HDR
 
 CF_DEFINES
 
+static struct evpn_encap_config *this_evpn_encap;
 static struct evpn_vlan_config *this_evpn_vlan;
 
 #define EVPN_CFG ((struct evpn_config *) this_proto)
@@ -21,7 +22,7 @@ static struct evpn_vlan_config *this_evpn_vlan;
 
 CF_DECLS
 
-CF_KEYWORDS(EVPN, ROUTE, IMPORT, EXPORT, TARGET, RD, DISTINGUISHER, TUNNEL, DEVICE, VNI, VID, VLAN, RANGE)
+CF_KEYWORDS(EVPN, ROUTE, IMPORT, EXPORT, TARGET, RD, DISTINGUISHER, TUNNEL, DEVICE, VNI, VID, VLAN, RANGE, ENCAPSULATION, VXLAN, ROUTER, DEFAULT)
 
 %type <e> evpn_targets
 %type <cc> evpn_channel_start evpn_channel
@@ -50,6 +51,7 @@ evpn_channel: evpn_channel_start channel_opt_list channel_end;
 evpn_proto_start: proto_start EVPN
 {
   this_proto = proto_config_new(&proto_evpn, $1);
+  init_list(&EVPN_CFG->encaps);
   init_list(&EVPN_CFG->vlans);
 };
 
@@ -63,11 +65,10 @@ evpn_proto_item:
  | IMPORT TARGET evpn_targets { EVPN_CFG->import_target = $3; }
  | EXPORT TARGET evpn_targets { EVPN_CFG->export_target = $3; }
  | ROUTE TARGET evpn_targets { EVPN_CFG->import_target = EVPN_CFG->export_target = $3; }
- | TUNNEL DEVICE text { EVPN_CFG->tunnel_dev = if_get_by_name($3); }
- | ROUTER ADDRESS ipa { EVPN_CFG->router_addr = $3; }
  | VNI expr { EVPN_CFG->vni = $2; }
  | VID expr { EVPN_CFG->vid = $2; if ($2 > 4095) cf_error("VID must be in range 0-4095"); }
  | TAG expr { EVPN_CFG->tagX = $2; }
+ | evpn_encap
  | evpn_vlan
  ;
 
@@ -86,6 +87,40 @@ evpn_targets:
  ;
 
 
+evpn_encap: evpn_encap_start evpn_encap_opt_list evpn_encap_end;
+
+evpn_encap_start: ENCAPSULATION VXLAN
+{
+  this_evpn_encap = cfg_allocz(sizeof(struct evpn_encap_config));
+  add_tail(&EVPN_CFG->encaps, &this_evpn_encap->n);
+
+  this_evpn_encap->type        = EVPN_ENCAP_TYPE_VXLAN;
+  this_evpn_encap->router_addr = IPA_NONE;
+  this_evpn_encap->is_default  = false;
+}
+
+evpn_encap_opt:
+   TUNNEL DEVICE  text { this_evpn_encap->tunnel_dev  = if_get_by_name($3); }
+ | ROUTER ADDRESS ipa  { this_evpn_encap->router_addr = $3; }
+ | DEFAULT bool { this_evpn_encap->is_default = $2; }
+ ;
+
+evpn_encap_opts:
+   /* empty */
+ | evpn_encap_opts evpn_encap_opt ';'
+ ;
+
+evpn_encap_opt_list:
+   /* empty */
+ | '{' evpn_encap_opts '}'
+ ;
+
+evpn_encap_end:
+{
+  this_evpn_encap = NULL;
+}
+
+
 evpn_vlan: evpn_vlan_start evpn_vlan_opt_list evpn_vlan_end;
 
 evpn_vlan_start: VLAN expr
index 32fa12c8b7afaf3adff2c6480228aee05d782980..140618eb985b954721d0d5b770a54690ab949a44 100644 (file)
@@ -28,7 +28,6 @@
 
 /*
  * TODO:
- * - Encapsulation community handling
  * - MAC mobility community handling
  * - Review preference handling
  * - Wait for existence (and active state) of the tunnel device
@@ -65,6 +64,8 @@
 #define EA_BGP_PMSI_TUNNEL     EA_CODE(PROTOCOL_BGP, BA_PMSI_TUNNEL)
 #define EA_BGP_MPLS_LABEL_STACK        EA_CODE(PROTOCOL_BGP, BA_MPLS_LABEL_STACK)
 
+#define EC_ENCAP               0x030cU
+
 static inline const struct adata * ea_get_adata(ea_list *e, uint id)
 { eattr *a = ea_find(e, id); return a ? a->u.ptr : &null_adata; }
 
@@ -149,6 +150,24 @@ evpn_prepare_export_targets(struct evpn_proto *p)
   ASSERT(p->export_target_length == len);
 }
 
+static struct adata *
+evpn_encap_ext_comms(struct evpn_proto *p)
+{
+  size_t len = list_length(&p->encaps) * (2 * sizeof(u32));
+  struct adata *ad = lp_alloc_adata(tmp_linpool, len);
+
+  int pos = 0;
+  WALK_LIST_(struct evpn_encap, encap, p->encaps)
+  {
+    u32 hi = EC_ENCAP << 16;
+    u32 lo = encap->type;
+    ec_put(int_set_get_data(ad), pos, ec_generic(hi, lo));
+    pos += 2;
+  }
+
+  return ad;
+}
+
 static void
 evpn_announce_mac(struct evpn_proto *p, const net_addr_eth *n0, rte *new)
 {
@@ -167,7 +186,8 @@ evpn_announce_mac(struct evpn_proto *p, const net_addr_eth *n0, rte *new)
       .pref = c->preference,
     };
 
-    struct adata *ad = evpn_export_targets(p, &null_adata);
+    struct adata *ec = evpn_encap_ext_comms(p);
+    struct adata *ad = evpn_export_targets(p, ec);
     ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_EC_SET, ad);
 
     ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_LABEL, 0, EAF_TYPE_INT, v->vni);
@@ -181,13 +201,29 @@ evpn_announce_mac(struct evpn_proto *p, const net_addr_eth *n0, rte *new)
   }
 }
 
+/*
+ * Since only one type of encapsulation is currently supported, return first
+ * (and only) encapsulation. If there were more encapsulation types, we would
+ * have to choose one here.
+ */
+static struct evpn_encap *
+evpn_get_encap(struct evpn_proto *p)
+{
+  ASSERT(list_length(&p->encaps) == 1);
+  struct evpn_encap *encap = SKIP_BACK(struct evpn_encap, n, HEAD(p->encaps));
+
+  return encap;
+}
+
 static void
 evpn_announce_imet(struct evpn_proto *p, struct evpn_vlan *v, int new)
 {
   struct channel *c = p->evpn_channel;
+  struct evpn_encap *encap = evpn_get_encap(p);
 
+  /* We assume only one encapsulation */
   net_addr *n = alloca(sizeof(net_addr_evpn_imet));
-  net_fill_evpn_imet(n, p->rd, v->tag, p->router_addr);
+  net_fill_evpn_imet(n, p->rd, v->tag, encap->router_addr);
 
   if (new)
   {
@@ -198,10 +234,11 @@ evpn_announce_imet(struct evpn_proto *p, struct evpn_vlan *v, int new)
       .pref = c->preference,
     };
 
-    struct adata *ad = evpn_export_targets(p, &null_adata);
+    struct adata *ec = evpn_encap_ext_comms(p);
+    struct adata *ad = evpn_export_targets(p, ec);
     ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_EXT_COMMUNITY, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_EC_SET, ad);
 
-    ad = bgp_pmsi_new_ingress_replication(tmp_linpool, p->router_addr, v->vni);
+    ad = bgp_pmsi_new_ingress_replication(tmp_linpool, encap->router_addr, v->vni);
     ea_set_attr_ptr(&a->eattrs, tmp_linpool, EA_BGP_PMSI_TUNNEL, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, ad);
 
     rte *e = rte_get_temp(a, p->p.main_source);
@@ -213,10 +250,43 @@ evpn_announce_imet(struct evpn_proto *p, struct evpn_vlan *v, int new)
   }
 }
 
+static struct evpn_encap *
+evpn_match_encap_by_ext_comms(struct evpn_proto *p, const struct adata *ad)
+{
+  bool has_any_encap = false;
+
+  /* Find encapsulation communities */
+  EC_SET_WALK(ec, ad)
+  {
+    uint type = ec >> 48;
+    if (type != EC_ENCAP)
+      continue;
+
+    has_any_encap = true;
+
+    /* Match encapsulation type */
+    WALK_LIST_(struct evpn_encap, encap, p->encaps)
+      if (encap->type == (ec & 0xff))
+       return encap;
+  }
+  EC_SET_WALK_END;
+
+  /* If there is any encapsulation, just not matching one, treat it as error */
+  if (has_any_encap)
+    return NULL;
+
+  /* If there is no encapsulation, use default one */
+  WALK_LIST_(struct evpn_encap, encap, p->encaps)
+    if (encap->is_default)
+      return encap;
+
+  /* If there is no default encapsulation, treat it as error */
+  return NULL;
+}
+
 #define BAD(msg, args...) \
   ({ log(L_ERR "%s: " msg, p->p.name, ## args); goto withdraw; })
 
-
 static void
 evpn_receive_mac(struct evpn_proto *p, const net_addr_evpn_mac *n0, rte *new)
 {
@@ -236,6 +306,12 @@ evpn_receive_mac(struct evpn_proto *p, const net_addr_evpn_mac *n0, rte *new)
     if (!ms)
       BAD("Missing MPLS label stack in %N", n0);
 
+    const struct adata *ad = ea_get_adata(new->attrs->eattrs, EA_BGP_EXT_COMMUNITY);
+    struct evpn_encap *encap = evpn_match_encap_by_ext_comms(p, ad);
+
+    if (!encap)
+      BAD("No matching encapsulation found for %N", n0);
+
     rta *a = alloca(RTA_MAX_SIZE);
     *a = (rta) {
       .source = RTS_EVPN,
@@ -243,7 +319,7 @@ evpn_receive_mac(struct evpn_proto *p, const net_addr_evpn_mac *n0, rte *new)
       .dest = RTD_UNICAST,
       .pref = c->preference,
       .nh.gw = *((ip_addr *) nh->u.ptr->data),
-      .nh.iface = p->tunnel_dev,
+      .nh.iface = encap->tunnel_dev,
     };
 
     a->nh.labels = MIN(ms->u.ptr->length / 4, MPLS_MAX_LABEL_STACK);
@@ -282,6 +358,12 @@ evpn_receive_imet(struct evpn_proto *p, const net_addr_evpn_imet *n0, rte *new)
     if (pmsi_type != BGP_PMSI_TYPE_INGRESS_REPLICATION)
       BAD("Unsupported PMSI_TUNNEL type %u in %N", pmsi_type, n0);
 
+    const struct adata *ad = ea_get_adata(new->attrs->eattrs, EA_BGP_EXT_COMMUNITY);
+    struct evpn_encap *encap = evpn_match_encap_by_ext_comms(p, ad);
+
+    if (!encap)
+      BAD("No matching encapsulation found for %N", n0);
+
     rta *a = alloca(RTA_MAX_SIZE);
     *a = (rta) {
       .source = RTS_EVPN,
@@ -289,7 +371,7 @@ evpn_receive_imet(struct evpn_proto *p, const net_addr_evpn_imet *n0, rte *new)
       .dest = RTD_UNICAST,
       .pref = c->preference,
       .nh.gw = bgp_pmsi_ir_get_endpoint(pt->u.ptr),
-      .nh.iface = p->tunnel_dev,
+      .nh.iface = encap->tunnel_dev,
     };
 
     a->nh.labels = 1;
@@ -308,7 +390,6 @@ evpn_receive_imet(struct evpn_proto *p, const net_addr_evpn_imet *n0, rte *new)
   }
 }
 
-
 static void
 evpn_rt_notify(struct proto *P, struct channel *c0 UNUSED, net *net, rte *new, rte *old UNUSED)
 {
@@ -448,6 +529,73 @@ evpn_rte_better(rte *new, rte *old)
 }
 
 
+/*
+ *     EVPN encapsulations
+ */
+
+static struct evpn_encap *
+evpn_new_encap(struct evpn_proto *p, const struct evpn_encap_config *ec)
+{
+  struct evpn_encap *e = mb_allocz(p->p.pool, sizeof(*e));
+
+  *e = (struct evpn_encap) {
+    .type        = ec->type,
+    .tunnel_dev  = ec->tunnel_dev,
+    .router_addr = ec->router_addr,
+    .is_default  = ec->is_default,
+  };
+
+  add_tail(&p->encaps, &e->n);
+
+  return e;
+}
+
+static inline int
+evpn_reconfigure_encap(struct evpn_proto *p UNUSED, struct evpn_encap *e, struct evpn_encap_config *ec)
+{
+  if ((e->type != ec->type) ||
+      (e->tunnel_dev != ec->tunnel_dev)        ||
+      !ipa_equal(e->router_addr, ec->router_addr))
+    return 0;
+
+  return 1;
+}
+
+static int
+evpn_reconfigure_encaps(struct evpn_proto *p, struct evpn_config *cf)
+{
+  ASSERT(list_length(&p->encaps) == 1);
+  ASSERT(list_length(&cf->encaps) == 1);
+
+  struct evpn_encap *e = evpn_get_encap(p);
+  struct evpn_encap_config *ec = SKIP_BACK(struct evpn_encap_config, n, HEAD(cf->encaps));
+
+  if (!e || !ec)
+    return 0;
+
+  return evpn_reconfigure_encap(p, e, ec);
+}
+
+static void
+evpn_postconfig_encaps(struct evpn_config *cf)
+{
+  bool encap_types[EVPN_ENCAP_TYPE_MAX] = { 0 };
+  bool has_encap = false;
+
+  WALK_LIST_(struct evpn_encap_config, ec, cf->encaps)
+  {
+    if (encap_types[ec->type])
+      cf_error("Only one encapsulation of each type is allowed");
+
+    encap_types[ec->type] = true;
+    has_encap = true;
+  }
+
+  if (!has_encap)
+    cf_error("There must be at least one encapsulation");
+}
+
+
 /*
  *     EVPN VLANs
  */
@@ -692,6 +840,7 @@ evpn_postconfig(struct proto_config *CF)
   if (!cf->export_target)
     cf_error("Export target not specified");
 
+  evpn_postconfig_encaps(cf);
   evpn_postconfig_vlans(cf);
 }
 
@@ -726,12 +875,14 @@ evpn_start(struct proto *P)
   p->export_target = cf->export_target;
   p->export_target_data = NULL;
 
-  p->tunnel_dev = cf->tunnel_dev;
-  p->router_addr = cf->router_addr;
   p->vni = cf->vni;
   p->vid = cf->vid;
   p->tagX = cf->tagX;
 
+  init_list(&p->encaps);
+  WALK_LIST_(struct evpn_encap_config, ec, cf->encaps)
+    evpn_new_encap(p, ec);
+
   init_list(&p->vlans);
   memset(&p->vlan_tag_hash, 0, sizeof(p->vlan_tag_hash));
   memset(&p->vlan_vid_hash, 0, sizeof(p->vlan_vid_hash));
@@ -783,12 +934,13 @@ evpn_reconfigure(struct proto *P, struct proto_config *CF)
     return 0;
 
   if (!rd_equal(p->rd, cf->rd) ||
-      (p->tunnel_dev != cf->tunnel_dev) ||
-      (!ipa_equal(p->router_addr, cf->router_addr)) ||
       (p->vni != cf->vni) ||
       (p->vid != cf->vid))
     return 0;
 
+  if (!evpn_reconfigure_encaps(p, cf))
+    return 0;
+
   int import_changed = !same_tree(p->import_target, cf->import_target);
   int export_changed = !same_tree(p->export_target, cf->export_target);
 
@@ -825,9 +977,13 @@ evpn_reconfigure(struct proto *P, struct proto_config *CF)
 }
 
 static void
-evpn_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED)
+evpn_copy_config(struct proto_config *dest, struct proto_config *src)
 {
-  /* Just a shallow copy, not many items here */
+  struct evpn_config *d = SKIP_BACK(struct evpn_config, c, dest);
+  struct evpn_config *s = SKIP_BACK(struct evpn_config, c, src);
+
+  cfg_copy_list(&d->encaps, &s->encaps, sizeof(struct evpn_encap_config));
+  cfg_copy_list(&d->vlans, &s->vlans, sizeof(struct evpn_vlan_config));
 }
 
 /*
index a4633c12378588cabdd99dafb3b14d1045242449..53c404affce4922152be47f7e81e2bf7e5a2371c 100644 (file)
 #ifndef _BIRD_EVPN_H_
 #define _BIRD_EVPN_H_
 
+#include "nest/bird.h"
+#include "lib/lists.h"
+#include "lib/hash.h"
+#include "filter/data.h"
+
+
+/* BGP Tunnel Encapsulation Attribute Tunnel Types (RFC 8365) */
+
+enum evpn_encap_type {
+  EVPN_ENCAP_TYPE_VXLAN = 8,
+  EVPN_ENCAP_TYPE_MAX,
+};
+
+
 struct evpn_config {
   struct proto_config c;
 
@@ -17,15 +31,23 @@ struct evpn_config {
   struct f_tree *import_target;
   struct f_tree *export_target;
 
-  struct iface *tunnel_dev;
-  ip_addr router_addr;
   u32 vni;
   u32 vid;
   u32 tagX;
 
+  list encaps;                         /* List of encapsulations (struct evpn_encap_config) */
   list vlans;                          /* List of VLANs (struct evpn_vlan_config) */
 };
 
+struct evpn_encap_config {
+  node n;                              /* Node in evpn_config.encaps */
+
+  enum evpn_encap_type type;
+  bool is_default;
+  struct iface *tunnel_dev;
+  ip_addr router_addr;
+};
+
 struct evpn_vlan_config {
   node n;                              /* Node in evpn_config.vlans */
 
@@ -49,17 +71,26 @@ struct evpn_proto {
   bool eth_refreshing;
   bool evpn_refreshing;
 
-  struct iface *tunnel_dev;
-  ip_addr router_addr;
   u32 vni;
   u32 vid;
   u32 tagX;
 
+  list encaps;                         /* List of encapsulations (struct evpn_encap) */
   list vlans;                          /* List of VLANs (struct evpn_vlan) */
+
   HASH(struct evpn_vlan) vlan_tag_hash;
   HASH(struct evpn_vlan) vlan_vid_hash;
 };
 
+struct evpn_encap {
+  node n;                              /* Node in evpn_proto.encaps */
+
+  enum evpn_encap_type type;
+  bool is_default;
+  struct iface *tunnel_dev;
+  ip_addr router_addr;
+};
+
 struct evpn_vlan {
   node n;                              /* Node in evpn_proto.vlans */
 
@@ -71,5 +102,4 @@ struct evpn_vlan {
   struct evpn_vlan *next_vid;
 };
 
-
 #endif