]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
EVPN: BGP/MPLS Ethernet VPNs using VXLAN tunnels - preliminary support
authorOndrej Zajicek <santiago@crfreenet.org>
Mon, 6 Nov 2023 02:38:40 +0000 (03:38 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Wed, 31 Jan 2024 16:35:25 +0000 (17:35 +0100)
Mostly based on L3VPN

configure.ac
nest/protocol.h
nest/route.h
nest/rt-attr.c
nest/rt-table.c
proto/evpn/Doc [new file with mode: 0644]
proto/evpn/Makefile [new file with mode: 0644]
proto/evpn/config.Y [new file with mode: 0644]
proto/evpn/evpn.c [new file with mode: 0644]
proto/evpn/evpn.h [new file with mode: 0644]

index 9586daef4614c391baedeae1f53e74078218044f..82fefb4914817f440aa320df8d4ea0b30c5c258e 100644 (file)
@@ -312,7 +312,7 @@ if test "$enable_mpls_kernel" != no ; then
   fi
 fi
 
-all_protocols="aggregator $proto_bfd babel bgp bridge l3vpn mrt ospf perf pipe radv rip rpki static"
+all_protocols="aggregator $proto_bfd babel bgp bridge evpn l3vpn mrt ospf perf pipe radv rip rpki static"
 
 all_protocols=`echo $all_protocols | sed 's/ /,/g'`
 
@@ -326,6 +326,7 @@ AH_TEMPLATE([CONFIG_BFD],   [BFD protocol])
 AH_TEMPLATE([CONFIG_BGP],      [BGP protocol])
 AH_TEMPLATE([CONFIG_BMP],      [BMP protocol])
 AH_TEMPLATE([CONFIG_BRIDGE],   [Bridge protocol])
+AH_TEMPLATE([CONFIG_EVPN],     [EVPN protocol])
 AH_TEMPLATE([CONFIG_L3VPN],    [L3VPN protocol])
 AH_TEMPLATE([CONFIG_MRT],      [MRT protocol])
 AH_TEMPLATE([CONFIG_OSPF],     [OSPF protocol])
index 16dc48be8f1ace8ba488d7e758e0801de94a3fb7..001404f39f2df9d39921fcc787fa251780e5e9ec 100644 (file)
@@ -48,6 +48,7 @@ enum protocol_class {
   PROTOCOL_BRIDGE,
   PROTOCOL_DEVICE,
   PROTOCOL_DIRECT,
+  PROTOCOL_EVPN,
   PROTOCOL_KERNEL,
   PROTOCOL_L3VPN,
   PROTOCOL_OSPF,
@@ -106,7 +107,7 @@ void protos_dump_all(void);
  */
 
 extern struct protocol
-  proto_device, proto_radv, proto_rip, proto_static, proto_mrt,
+  proto_device, proto_radv, proto_rip, proto_static, proto_mrt, proto_evpn,
   proto_ospf, proto_perf, proto_l3vpn, proto_aggregator, proto_bridge,
   proto_pipe, proto_bgp, proto_bmp, proto_bfd, proto_babel, proto_rpki;
 
index 128ba870b45e4dd4b567e86a02bba3e2ee0eb3b5..63707280cb5f599f0ec9faa2140e18f0d701af05 100644 (file)
@@ -483,7 +483,8 @@ typedef struct rta {
 #define RTS_L3VPN 16                   /* MPLS L3VPN */
 #define RTS_AGGREGATED 17              /* Aggregated route */
 #define RTS_BRIDGE 18
-#define RTS_MAX 19
+#define RTS_EVPN 19
+#define RTS_MAX 20
 
 #define RTD_NONE 0                     /* Undefined next hop */
 #define RTD_UNICAST 1                  /* Next hop is neighbor router */
index bc6864a321702e4b9af822ce3f1e02a45643df3e..7def86b03f7cf6db7e093e2bd800b66fb7b177a3 100644 (file)
@@ -79,6 +79,7 @@ const char * const rta_src_names[RTS_MAX] = {
   [RTS_L3VPN]          = "L3VPN",
   [RTS_AGGREGATED]     = "aggregated",
   [RTS_BRIDGE]         = "bridge",
+  [RTS_EVPN]           = "EVPN",
 };
 
 const char * rta_dest_names[RTD_MAX] = {
@@ -1312,7 +1313,7 @@ rta_dump(rta *a)
                         "RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
                         "RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
                         "RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL",
-                        "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED", "RTS_BRIDGE" };
+                        "RTS_RPKI", "RTS_PERF", "RTS_AGGREGATED", "RTS_BRIDGE", "RTS_EVPN" };
   static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
 
   debug("pref=%d uc=%d %s %s%s h=%04x",
index 1b30e7dc794e84a4ea994e66ad94d774d10952cb..b61a78d2655225f6f834b5c7116097734ebc76a6 100644 (file)
@@ -973,6 +973,10 @@ rte_validate(rte *e)
     if (net_is_flow(n->n.addr) && (e->attrs->dest == RTD_UNREACHABLE))
       return 1;
 
+    /* XXX hack */
+    if (n->n.addr->type == NET_EVPN)
+      return 1;
+
     log(L_WARN "Ignoring route %N with invalid dest %d received via %s",
        n->n.addr, e->attrs->dest, e->sender->proto->name);
     return 0;
diff --git a/proto/evpn/Doc b/proto/evpn/Doc
new file mode 100644 (file)
index 0000000..84f282f
--- /dev/null
@@ -0,0 +1 @@
+S evpn.c
diff --git a/proto/evpn/Makefile b/proto/evpn/Makefile
new file mode 100644 (file)
index 0000000..7c7dfc9
--- /dev/null
@@ -0,0 +1,6 @@
+src := evpn.c
+obj := $(src-o-files)
+$(all-daemon)
+$(cf-local)
+
+tests_objs := $(tests_objs) $(src-o-files)
diff --git a/proto/evpn/config.Y b/proto/evpn/config.Y
new file mode 100644 (file)
index 0000000..06cb533
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *     BIRD -- BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ *     (c) 2023 Ondrej Zajicek <santiago@crfreenet.org>
+ *     (c) 2023 CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/evpn/evpn.h"
+
+
+CF_DEFINES
+
+#define EVPN_CFG ((struct evpn_config *) this_proto)
+
+
+CF_DECLS
+
+CF_KEYWORDS(EVPN, ROUTE, IMPORT, EXPORT, TARGET, RD, DISTINGUISHER, TUNNEL, DEVICE, VNI, VID)
+
+%type <e> evpn_targets
+%type <cc> evpn_channel_start evpn_channel
+
+
+CF_GRAMMAR
+
+proto: evpn_proto;
+
+
+evpn_channel_start: net_type_base
+{
+  /* Redefining proto_channel to change default values */
+  $$ = this_channel = channel_config_get(NULL, net_label[$1], $1, this_proto);
+  if (!this_channel->copy)
+  {
+    this_channel->out_filter = FILTER_ACCEPT;
+    this_channel->preference = ($1 == NET_ETH) ?
+      DEF_PREF_L3VPN_IMPORT :
+      DEF_PREF_L3VPN_EXPORT;
+  }
+};
+
+evpn_channel: evpn_channel_start channel_opt_list channel_end;
+
+evpn_proto_start: proto_start EVPN
+{
+  this_proto = proto_config_new(&proto_evpn, $1);
+};
+
+
+evpn_proto_item:
+   proto_item
+ | evpn_channel
+ | mpls_channel
+ | RD VPN_RD { EVPN_CFG->rd = $2; }
+ | ROUTE DISTINGUISHER VPN_RD { EVPN_CFG->rd = $3; }
+ | 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"); }
+ ;
+
+evpn_proto_opts:
+   /* empty */
+ | evpn_proto_opts evpn_proto_item ';'
+ ;
+
+evpn_proto:
+   evpn_proto_start proto_name '{' evpn_proto_opts '}';
+
+
+evpn_targets:
+   ec_item { f_tree_only_rt($1); $$ = $1; }
+ | '[' ec_items ']' { f_tree_only_rt($2); $$ = build_tree($2); }
+ ;
+
+
+CF_CODE
+
+CF_END
diff --git a/proto/evpn/evpn.c b/proto/evpn/evpn.c
new file mode 100644 (file)
index 0000000..e686cfa
--- /dev/null
@@ -0,0 +1,558 @@
+/*
+ *     BIRD -- BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ *     (c) 2023 Ondrej Zajicek <santiago@crfreenet.org>
+ *     (c) 2023 CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ * The EVPN protocol implements RFC 7432 BGP Etherent VPNs using VXLAN overlays.
+ * It works similarly to L3VPN. It connects ethernet table (one per VRF) with
+ * (global) EVPN table. Routes passed from EVPN table to ethernet table are
+ * stripped of RD and filtered by import targets, routes passed in the other
+ * direction are extended with RD, MPLS/VNI labels, and export targets in
+ * extended communities.
+ *
+ * The EVPN protocol supports MAC (type 2) and IMET (type 3) EVPN routes, there
+ * is no support for EAD / ES routes, or routes with non-zero tag. There is also
+ * no support for MPLS backbone, just VXLAN overlays.
+ *
+ * Supported standards:
+ * RFC 7432 - BGP MPLS-Based Ethernet VPN
+ * RFC 8365 - Network Virtualization Using Ethernet VPN
+ */
+
+/*
+ * TODO:
+ * - Encapsulation community handling
+ * - MAC mobility community handling
+ * - Review preference handling
+ * - Wait for existence (and active state) of the tunnel device
+ * - Learn VNI / router address from the tunnel device
+ * - Improved VLAN handling
+ * - MPLS encapsulation mode
+ */
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/protocol.h"
+#include "nest/route.h"
+#include "nest/mpls.h"
+#include "nest/cli.h"
+#include "conf/conf.h"
+#include "filter/filter.h"
+#include "filter/data.h"
+#include "lib/string.h"
+
+#include "evpn.h"
+
+#include "proto/bgp/bgp.h"
+
+#define EA_BGP_NEXT_HOP                EA_CODE(PROTOCOL_BGP, BA_NEXT_HOP)
+#define EA_BGP_EXT_COMMUNITY   EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY)
+#define EA_BGP_MPLS_LABEL_STACK        EA_CODE(PROTOCOL_BGP, BA_MPLS_LABEL_STACK)
+
+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; }
+
+static inline int
+mpls_valid_nexthop(const rta *a)
+{
+  /* MPLS does not support special blackhole targets */
+  if (a->dest != RTD_UNICAST)
+    return 0;
+
+  /* MPLS does not support ARP / neighbor discovery */
+  for (const struct nexthop *nh = &a->nh; nh ; nh = nh->next)
+    if (ipa_zero(nh->gw) && (nh->iface->flags & IF_MULTIACCESS))
+      return 0;
+
+  return 1;
+}
+
+static int
+evpn_import_targets(struct evpn_proto *p, const struct adata *list)
+{
+  return (p->import_target_one) ?
+    ec_set_contains(list, p->import_target->from.val.ec) :
+    eclist_match_set(list, p->import_target);
+}
+
+static struct adata *
+evpn_export_targets(struct evpn_proto *p, const struct adata *src)
+{
+  u32 *s = int_set_get_data(src);
+  int len = int_set_get_size(src);
+
+  struct adata *dst = lp_alloc(tmp_linpool, sizeof(struct adata) + (len + p->export_target_length) * sizeof(u32));
+  u32 *d = int_set_get_data(dst);
+  int end = 0;
+
+  for (int i = 0; i < len; i += 2)
+  {
+    /* Remove existing route targets */
+    uint type = s[i] >> 16;
+    if (ec_type_is_rt(type))
+      continue;
+
+    d[end++] = s[i];
+    d[end++] = s[i+1];
+  }
+
+  /* Add new route targets */
+  memcpy(d + end, p->export_target_data, p->export_target_length * sizeof(u32));
+  end += p->export_target_length;
+
+  /* Set length */
+  dst->length = end * sizeof(u32);
+
+  return dst;
+}
+
+static inline void
+evpn_prepare_import_targets(struct evpn_proto *p)
+{
+  const struct f_tree *t = p->import_target;
+  p->import_target_one = !t->left && !t->right && (t->from.val.ec == t->to.val.ec);
+}
+
+static void
+evpn_add_ec(const struct f_tree *t, void *P)
+{
+  struct evpn_proto *p = P;
+  ec_put(p->export_target_data, p->export_target_length, t->from.val.ec);
+  p->export_target_length += 2;
+}
+
+static void
+evpn_prepare_export_targets(struct evpn_proto *p)
+{
+  if (p->export_target_data)
+    mb_free(p->export_target_data);
+
+  uint len = 2 * tree_node_count(p->export_target);
+  p->export_target_data = mb_alloc(p->p.pool, len * sizeof(u32));
+  p->export_target_length = 0;
+  tree_walk(p->export_target, evpn_add_ec, p);
+  ASSERT(p->export_target_length == len);
+}
+
+static void
+evpn_announce_mac(struct evpn_proto *p, const net_addr_eth *n0, rte *new)
+{
+  struct channel *c = p->evpn_channel;
+
+  net_addr *n = alloca(sizeof(net_addr_evpn_mac));
+  net_fill_evpn_mac(n, p->rd, 0, n0->mac);
+
+  if (new)
+  {
+    rta *a = alloca(RTA_MAX_SIZE);
+    *a = (rta) {
+      .source = RTS_EVPN,
+      .scope = SCOPE_UNIVERSE,
+      .pref = c->preference,
+    };
+
+    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);
+
+    ea_set_attr_u32(&a->eattrs, tmp_linpool, EA_MPLS_LABEL, 0, EAF_TYPE_INT, p->vni);
+
+    rte *e = rte_get_temp(a, p->p.main_source);
+    rte_update2(c, n, e, p->p.main_source);
+  }
+  else
+  {
+    rte_update2(c, n, NULL, p->p.main_source);
+  }
+}
+
+static void
+evpn_announce_imet(struct evpn_proto *p, int new)
+{
+  struct channel *c = p->evpn_channel;
+
+  net_addr *n = alloca(sizeof(net_addr_evpn_imet));
+  net_fill_evpn_imet(n, p->rd, 0, p->router_addr);
+
+  if (new)
+  {
+    rta *a = alloca(RTA_MAX_SIZE);
+    *a = (rta) {
+      .source = RTS_EVPN,
+      .scope = SCOPE_UNIVERSE,
+      .pref = c->preference,
+    };
+
+    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);
+
+    rte *e = rte_get_temp(a, p->p.main_source);
+    rte_update2(c, n, e, p->p.main_source);
+  }
+  else
+  {
+    rte_update2(c, n, NULL, p->p.main_source);
+  }
+}
+
+#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)
+{
+  struct channel *c = p->eth_channel;
+
+  net_addr *n = alloca(sizeof(net_addr_eth));
+  net_fill_eth(n, n0->mac, p->vid);
+
+  if (new && rte_resolvable(new))
+  {
+    eattr *nh = ea_find(new->attrs->eattrs, EA_BGP_NEXT_HOP);
+    if (!nh)
+      BAD("Missing NEXT_HOP attribute in %N", n0);
+
+    eattr *ms = ea_find(new->attrs->eattrs, EA_BGP_MPLS_LABEL_STACK);
+    if (!ms)
+      BAD("Missing MPLS label stack in %N", n0);
+
+    rta *a = alloca(RTA_MAX_SIZE);
+    *a = (rta) {
+      .source = RTS_EVPN,
+      .scope = SCOPE_UNIVERSE,
+      .dest = RTD_UNICAST,
+      .pref = c->preference,
+      .nh.gw = *((ip_addr *) nh->u.ptr->data),
+      .nh.iface = p->tunnel_dev,
+    };
+
+    a->nh.labels = MIN(ms->u.ptr->length / 4, MPLS_MAX_LABEL_STACK);
+    memcpy(a->nh.label, ms->u.ptr->data, a->nh.labels * 4);
+
+    rte *e = rte_get_temp(a, p->p.main_source);
+    rte_update2(c, n, e, p->p.main_source);
+  }
+  else
+  {
+  withdraw:
+    rte_update2(c, n, NULL, p->p.main_source);
+  }
+}
+
+static void
+evpn_receive_imet(struct evpn_proto *p, const net_addr_evpn_imet *n0, rte *new)
+{
+  struct channel *c = p->eth_channel;
+  struct rte_src *s = rt_get_source(&p->p, n0->rd);
+
+  net_addr *n = alloca(sizeof(net_addr_eth));
+  net_fill_eth(n, MAC_NONE, p->vid);
+
+  if (new && rte_resolvable(new))
+  {
+    eattr *nh = ea_find(new->attrs->eattrs, EA_BGP_NEXT_HOP);
+
+    rta *a = alloca(RTA_MAX_SIZE);
+    *a = (rta) {
+      .source = RTS_EVPN,
+      .scope = SCOPE_UNIVERSE,
+      .dest = RTD_UNICAST,
+      .pref = c->preference,
+      .nh.gw = nh ? *((ip_addr *) nh->u.ptr->data) : IPA_NONE,
+      .nh.iface = p->tunnel_dev,
+    };
+
+    rte *e = rte_get_temp(a, s);
+    rte_update2(c, n, e, s);
+  }
+  else
+  {
+    rte_update2(c, n, NULL, s);
+  }
+}
+
+
+
+static void
+evpn_rt_notify(struct proto *P, struct channel *c0 UNUSED, net *net, rte *new, rte *old UNUSED)
+{
+  struct evpn_proto *p = (void *) P;
+  const net_addr *n = net->n.addr;
+
+  switch (n->type)
+  {
+  case NET_ETH:
+    evpn_announce_mac(p, (const net_addr_eth *) n, new);
+    return;
+
+  case NET_EVPN:
+    switch (((const net_addr_evpn *) n)->subtype)
+    {
+    case NET_EVPN_MAC:
+      evpn_receive_mac(p, (const net_addr_evpn_mac *) n, new);
+      return;
+
+    case NET_EVPN_IMET:
+      evpn_receive_imet(p, (const net_addr_evpn_imet *) n, new);;
+      return;
+    }
+    return;
+
+  case NET_MPLS:
+    return;
+  }
+}
+
+
+static int
+evpn_preexport(struct channel *C, rte *e)
+{
+  struct evpn_proto *p = (void *) C->proto;
+  struct proto *pp = e->sender->proto;
+  const net_addr *n = e->net->n.addr;
+
+  if (pp == C->proto)
+    return -1; /* Avoid local loops automatically */
+
+  switch (n->type)
+  {
+  case NET_ETH:
+    if (((const net_addr_eth *) n)->vid != p->vid)
+      return -1;
+
+    return 0;
+
+  case NET_EVPN:
+    return evpn_import_targets(p, ea_get_adata(e->attrs->eattrs, EA_BGP_EXT_COMMUNITY)) ? 0 : -1;
+
+  case NET_MPLS:
+    return -1;
+
+  default:
+    bug("invalid type");
+  }
+}
+
+static void
+evpn_reload_routes(struct channel *C)
+{
+  struct evpn_proto *p = (void *) C->proto;
+
+  /* Route reload on one channel is just refeed on the other */
+  switch (C->net_type)
+  {
+  case NET_ETH:
+    channel_request_feeding(p->evpn_channel);
+    break;
+
+  case NET_EVPN:
+    channel_request_feeding(p->eth_channel);
+    break;
+
+  case NET_MPLS:
+    channel_request_feeding(p->eth_channel);
+    break;
+  }
+}
+
+static inline u32
+evpn_metric(rte *e)
+{
+  u32 metric = ea_get_int(e->attrs->eattrs, EA_GEN_IGP_METRIC, e->attrs->igp_metric);
+  return MIN(metric, IGP_METRIC_UNKNOWN);
+}
+
+static int
+evpn_rte_better(rte *new, rte *old)
+{
+  /* This is hack, we should have full BGP-style comparison */
+  return evpn_metric(new) < evpn_metric(old);
+}
+
+static void
+evpn_postconfig(struct proto_config *CF)
+{
+  struct evpn_config *cf = (void *) CF;
+
+  if (!proto_cf_find_channel(CF, NET_ETH))
+    cf_error("Ethernet channel not specified");
+
+  if (!proto_cf_find_channel(CF, NET_EVPN))
+    cf_error("EVPN channel not specified");
+
+//  if (!proto_cf_find_channel(CF, NET_MPLS))
+//    cf_error("MPLS channel not specified");
+
+  if (!cf->rd)
+    cf_error("Route distinguisher not specified");
+
+  if (!cf->import_target && !cf->export_target)
+    cf_error("Route target not specified");
+
+  if (!cf->import_target)
+    cf_error("Import target not specified");
+
+  if (!cf->export_target)
+    cf_error("Export target not specified");
+}
+
+static struct proto *
+evpn_init(struct proto_config *CF)
+{
+  struct proto *P = proto_new(CF);
+  struct evpn_proto *p = (void *) P;
+  // struct evpn_config *cf = (void *) CF;
+
+  proto_configure_channel(P, &p->eth_channel, proto_cf_find_channel(CF, NET_ETH));
+  proto_configure_channel(P, &p->evpn_channel, proto_cf_find_channel(CF, NET_EVPN));
+  proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF, NET_MPLS));
+
+  P->rt_notify = evpn_rt_notify;
+  P->preexport = evpn_preexport;
+  P->reload_routes = evpn_reload_routes;
+  P->rte_better = evpn_rte_better;
+
+  return P;
+}
+
+static int
+evpn_start(struct proto *P)
+{
+  struct evpn_proto *p = (void *) P;
+  struct evpn_config *cf = (void *) P->cf;
+
+  p->rd = cf->rd;
+  p->import_target = cf->import_target;
+  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;
+
+  evpn_prepare_import_targets(p);
+  evpn_prepare_export_targets(p);
+
+  proto_setup_mpls_map(P, RTS_EVPN, 1);
+
+  // XXX ?
+  if (P->vrf_set)
+    P->mpls_map->vrf_iface = P->vrf;
+
+  proto_notify_state(P, PS_UP);
+
+  evpn_announce_imet(p, 1);
+
+  return PS_UP;
+}
+
+static int
+evpn_shutdown(struct proto *P)
+{
+  // struct evpn_proto *p = (void *) P;
+
+  proto_shutdown_mpls_map(P, 1);
+
+  return PS_DOWN;
+}
+
+static int
+evpn_reconfigure(struct proto *P, struct proto_config *CF)
+{
+  struct evpn_proto *p = (void *) P;
+  struct evpn_config *cf = (void *) CF;
+
+  if (!proto_configure_channel(P, &p->eth_channel, proto_cf_find_channel(CF, NET_ETH)) ||
+      !proto_configure_channel(P, &p->evpn_channel, proto_cf_find_channel(CF, NET_EVPN)) ||
+      !proto_configure_channel(P, &P->mpls_channel, proto_cf_find_channel(CF, NET_MPLS)))
+    return 0;
+
+  if ((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;
+
+  int import_changed = !same_tree(p->import_target, cf->import_target);
+  int export_changed = !same_tree(p->export_target, cf->export_target);
+
+  /* Update pointers to config structures */
+  p->import_target = cf->import_target;
+  p->export_target = cf->export_target;
+
+  proto_setup_mpls_map(P, RTS_EVPN, 1);
+
+  if (import_changed)
+  {
+    TRACE(D_EVENTS, "Import target changed");
+
+    evpn_prepare_import_targets(p);
+
+    if (p->evpn_channel && (p->evpn_channel->channel_state == CS_UP))
+      channel_request_feeding(p->evpn_channel);
+  }
+
+  if (export_changed)
+  {
+    TRACE(D_EVENTS, "Export target changed");
+
+    evpn_prepare_export_targets(p);
+
+    if (p->eth_channel && (p->eth_channel->channel_state == CS_UP))
+      channel_request_feeding(p->eth_channel);
+  }
+
+  return 1;
+}
+
+static void
+evpn_copy_config(struct proto_config *dest UNUSED, struct proto_config *src UNUSED)
+{
+  /* Just a shallow copy, not many items here */
+}
+
+/*
+static void
+evpn_get_route_info(rte *rte, byte *buf)
+{
+  u32 metric = evpn_metric(rte);
+  if (metric < IGP_METRIC_UNKNOWN)
+    bsprintf(buf, " (%u/%u)", rte->attrs->pref, metric);
+  else
+    bsprintf(buf, " (%u/?)", rte->attrs->pref);
+}
+*/
+
+
+struct protocol proto_evpn = {
+  .name =              "EVPN",
+  .template =          "evpn%d",
+  .class =             PROTOCOL_EVPN,
+  .channel_mask =      NB_ETH | NB_EVPN | NB_MPLS,
+  .proto_size =                sizeof(struct evpn_proto),
+  .config_size =       sizeof(struct evpn_config),
+  .postconfig =                evpn_postconfig,
+  .init =              evpn_init,
+  .start =             evpn_start,
+  .shutdown =          evpn_shutdown,
+  .reconfigure =       evpn_reconfigure,
+  .copy_config =       evpn_copy_config,
+//  .get_route_info =  evpn_get_route_info
+};
+
+void
+evpn_build(void)
+{
+  proto_build(&proto_evpn);
+}
diff --git a/proto/evpn/evpn.h b/proto/evpn/evpn.h
new file mode 100644 (file)
index 0000000..e511cd0
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *     BIRD -- BGP/MPLS Ethernet Virtual Private Networks (EVPN)
+ *
+ *     (c) 2023 Ondrej Zajicek <santiago@crfreenet.org>
+ *     (c) 2023 CZ.NIC z.s.p.o.
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_EVPN_H_
+#define _BIRD_EVPN_H_
+
+struct evpn_config {
+  struct proto_config c;
+
+  u64 rd;
+  struct f_tree *import_target;
+  struct f_tree *export_target;
+
+  struct iface *tunnel_dev;
+  ip_addr router_addr;
+  u32 vni;
+  u32 vid;
+};
+
+struct evpn_proto {
+  struct proto p;
+  struct channel *eth_channel;
+  struct channel *evpn_channel;
+
+  u64 rd;
+  struct f_tree *import_target;
+  struct f_tree *export_target;
+  u32 *export_target_data;
+  uint export_target_length;
+  uint import_target_one;
+
+  struct iface *tunnel_dev;
+  ip_addr router_addr;
+  u32 vni;
+  u32 vid;
+};
+
+#endif