]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
BGP: PMSI tunnel attribute support
authorOndrej Zajicek <santiago@crfreenet.org>
Fri, 10 Nov 2023 01:32:15 +0000 (02:32 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Sun, 15 Jun 2025 00:51:46 +0000 (02:51 +0200)
PMSI tunnel attribute is required for EVPN IMET routes.

proto/bgp/attrs.c
proto/bgp/bgp.h
proto/bgp/config.Y
proto/evpn/evpn.c

index e853624b803c3cf2fa9ebfede654b7eff365827d..bdead04fb779094adaa142baf195c09c9f120d75 100644 (file)
@@ -203,6 +203,30 @@ bgp_encode_raw(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size)
 }
 
 
+/*
+ *     PMSI tunnel handling
+ */
+
+adata *
+bgp_pmsi_new_ingress_replication(linpool *pool, ip_addr addr, u32 label)
+{
+  int v4 = ipa_is_ip4(addr);
+  uint dlen = 5 + (v4 ? sizeof(ip4_addr) : sizeof(ip6_addr));
+  adata *ad = lp_alloc_adata(pool, dlen);
+
+  ad->data[0] = 0;
+  ad->data[1] = BGP_PMSI_TYPE_INGRESS_REPLICATION;
+  put_u24(ad->data + 2, label);
+
+  if (v4)
+    put_ip4(ad->data + 5, ipa_to_ip4(addr));
+  else
+    put_ip6(ad->data + 5, ipa_to_ip6(addr));
+
+  return ad;
+}
+
+
 /*
  *     AIGP handling
  */
@@ -849,6 +873,64 @@ bgp_decode_as4_path(struct bgp_parse_state *s, uint code UNUSED, uint flags, byt
 }
 
 
+static void
+bgp_decode_pmsi_tunnel(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
+{
+  if (len < 5)
+    WITHDRAW(BAD_LENGTH, "PMSI_TUNNEL", len);
+
+  uint dlen = len - 5;
+
+  switch (data[1])
+  {
+  case BGP_PMSI_TYPE_NO_INFO:
+    if (dlen != 0)
+      WITHDRAW(BAD_LENGTH, "PMSI_TUNNEL", len);
+    break;
+
+  case BGP_PMSI_TYPE_INGRESS_REPLICATION:
+    if ((dlen != sizeof(ip4_addr)) && (dlen != sizeof(ip6_addr)))
+      WITHDRAW(BAD_LENGTH, "PMSI_TUNNEL", len);
+    break;
+
+  default:
+    flags |= BAF_PARTIAL;
+  }
+
+  bgp_set_attr_data(to, s->pool, BA_PMSI_TUNNEL, flags, data, len);
+}
+
+static void
+bgp_format_pmsi_tunnel(const eattr *a, byte *buf, uint size)
+{
+  const adata *ad = a->u.ptr;
+  uint type = bgp_pmsi_get_type(ad);
+  uint label = bgp_pmsi_get_label(ad);
+
+  char mpls[16] = {};
+  if (label)
+    bsprintf(mpls, " mpls %u", label);
+
+  switch (type)
+  {
+  case BGP_PMSI_TYPE_NO_INFO:
+    bsnprintf(buf, size, "no-info%s", mpls);
+    break;
+
+  case BGP_PMSI_TYPE_INGRESS_REPLICATION:;
+    ip_addr a = bgp_pmsi_ir_get_endpoint(ad);
+    bsnprintf(buf, size, "ingress-replication %I%s", a, mpls);
+    break;
+
+  default:;
+    int n = bsnprintf(buf, size, "type %u%s ", type, mpls);
+    ADVANCE(buf, size, n);
+    bstrbintohex(ad->data + 5, ad->length - 5, buf, size, ':');
+    break;
+  }
+}
+
+
 static void
 bgp_export_aigp(struct bgp_export_state *s, eattr *a)
 {
@@ -1112,6 +1194,14 @@ static const struct bgp_attr_desc bgp_attr_table[] = {
     .decode = bgp_decode_as4_aggregator,
     .format = bgp_format_aggregator,
   },
+  [BA_PMSI_TUNNEL] = {
+    .name = "pmsi_tunnel",
+    .type = EAF_TYPE_OPAQUE,
+    .flags = BAF_OPTIONAL | BAF_TRANSITIVE,
+    .encode = bgp_encode_raw,
+    .decode = bgp_decode_pmsi_tunnel,
+    .format = bgp_format_pmsi_tunnel,
+  },
   [BA_AIGP] = {
     .name = "aigp",
     .type = EAF_TYPE_OPAQUE,
index 7a37b322383e875e6cb0e972f6473d152887484a..5e5b7a912bd145f21a0ae857c55dcbb028c9b7d4 100644 (file)
@@ -691,10 +691,37 @@ void bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *ol
 int bgp_preexport(struct channel *, struct rte *);
 int bgp_get_attr(const struct eattr *e, byte *buf, int buflen);
 void bgp_get_route_info(struct rte *, byte *buf);
+adata * bgp_pmsi_new_ingress_replication(linpool *pool, ip_addr addr, u32 label);
 int bgp_total_aigp_metric_(rte *e, u64 *metric, const struct adata **ad);
 
 byte * bgp_bmp_encode_rte(struct bgp_channel *c, byte *buf, byte *end, const net_addr *n, const struct rte *new, const struct rte_src *src);
 
+#define BGP_PMSI_TYPE_NO_INFO  0
+#define BGP_PMSI_TYPE_INGRESS_REPLICATION      6
+
+static inline uint
+bgp_pmsi_get_type(const adata *ad)
+{ return ad->data[1]; }
+
+static inline u32
+bgp_pmsi_get_label(const adata *ad)
+{ return get_u24(ad->data + 2); }
+
+static inline ip_addr
+bgp_pmsi_ir_get_endpoint(const adata *ad)
+{
+  uint dlen = ad->length - 5;
+  const byte *data = ad->data + 5;
+
+  if (dlen == sizeof(ip4_addr))
+    return ipa_from_ip4(get_ip4(data));
+  else if (dlen == sizeof(ip6_addr))
+    return ipa_from_ip6(get_ip6(data));
+  else
+    return IPA_NONE;
+}
+
+
 #define BGP_AIGP_METRIC                1
 #define BGP_AIGP_MAX           U64(0xffffffffffffffff)
 
@@ -761,6 +788,7 @@ byte *bgp_create_end_mark_(struct bgp_channel *c, byte *buf);
 #define BA_EXT_COMMUNITY       0x10    /* RFC 4360 */
 #define BA_AS4_PATH             0x11   /* RFC 6793 */
 #define BA_AS4_AGGREGATOR       0x12   /* RFC 6793 */
+#define BA_PMSI_TUNNEL         0x16    /* RFC 6514 */
 #define BA_AIGP                        0x1a    /* RFC 7311 */
 #define BA_LARGE_COMMUNITY     0x20    /* RFC 8092 */
 #define BA_ONLY_TO_CUSTOMER    0x23    /* RFC 9234 */
index 8e38091fddd85ff22d0ae6ae1cb4882074e8809d..dcf8e7cfc3936a742e0373bcf273840d3b6b0a97 100644 (file)
@@ -36,7 +36,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
        DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST, ENFORCE,
        FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER,
        RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND, RECV, MIN, MAX,
-       AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE)
+       AUTHENTICATION, NONE, MD5, AO, FORMAT, NATIVE, SINGLE, DOUBLE,
+       BGP_PMSI_TUNNEL)
 
 CF_KEYWORDS(KEY, KEYS, SECRET, DEPRECATED, PREFERRED, ALGORITHM, CMAC, AES128)
 
@@ -517,6 +518,8 @@ dynamic_attr: BGP_CLUSTER_LIST
        { $$ = f_new_dynamic_attr(EAF_TYPE_INT_SET, T_CLIST, EA_CODE(PROTOCOL_BGP, BA_CLUSTER_LIST));   $$.flags = BAF_OPTIONAL; } ;
 dynamic_attr: BGP_EXT_COMMUNITY
        { $$ = f_new_dynamic_attr(EAF_TYPE_EC_SET, T_ECLIST, EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY));  $$.flags = BAF_OPTIONAL | BAF_TRANSITIVE; } ;
+dynamic_attr: BGP_PMSI_TUNNEL
+       { $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_PMSI_TUNNEL)); $$.flags = BAF_OPTIONAL | BAF_TRANSITIVE; } ;
 dynamic_attr: BGP_AIGP
        { $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_AIGP));       $$.flags = BAF_OPTIONAL; } ;
 dynamic_attr: BGP_LARGE_COMMUNITY
index bbfa6b995539edb8a5fa4d529dc25b987520cf12..73d3006e59417d6f1c4b1b0e265de796951f0b7a 100644 (file)
@@ -56,6 +56,7 @@
 
 #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_PMSI_TUNNEL     EA_CODE(PROTOCOL_BGP, BA_PMSI_TUNNEL)
 #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)
@@ -194,6 +195,9 @@ 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, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_EC_SET, 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, BAF_OPTIONAL | BAF_TRANSITIVE, EAF_TYPE_OPAQUE, ad);
+
     rte *e = rte_get_temp(a, p->p.main_source);
     rte_update2(c, n, e, p->p.main_source);
   }
@@ -259,7 +263,13 @@ evpn_receive_imet(struct evpn_proto *p, const net_addr_evpn_imet *n0, rte *new)
 
   if (new && rte_resolvable(new))
   {
-    eattr *nh = ea_find(new->attrs->eattrs, EA_BGP_NEXT_HOP);
+    eattr *pt = ea_find(new->attrs->eattrs, EA_BGP_PMSI_TUNNEL);
+    if (!pt)
+      BAD("Missing PMSI_TUNNEL attribute in %N", n0);
+
+    uint pmsi_type = bgp_pmsi_get_type(pt->u.ptr);
+    if (pmsi_type != BGP_PMSI_TYPE_INGRESS_REPLICATION)
+      BAD("Unsupported PMSI_TUNNEL type %u in %N", pmsi_type, n0);
 
     rta *a = alloca(RTA_MAX_SIZE);
     *a = (rta) {
@@ -267,15 +277,19 @@ evpn_receive_imet(struct evpn_proto *p, const net_addr_evpn_imet *n0, rte *new)
       .scope = SCOPE_UNIVERSE,
       .dest = RTD_UNICAST,
       .pref = c->preference,
-      .nh.gw = nh ? *((ip_addr *) nh->u.ptr->data) : IPA_NONE,
+      .nh.gw = bgp_pmsi_ir_get_endpoint(pt->u.ptr),
       .nh.iface = p->tunnel_dev,
     };
 
+    a->nh.labels = 1;
+    a->nh.label[0] = bgp_pmsi_get_label(pt->u.ptr);
+
     rte *e = rte_get_temp(a, s);
     rte_update2(c, n, e, s);
   }
   else
   {
+  withdraw:
     rte_update2(c, n, NULL, s);
   }
 }