]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
BGP: Support for AS confederations (RFC 5065)
authorOndrej Zajicek (work) <santiago@crfreenet.org>
Sun, 22 Jan 2017 15:32:42 +0000 (16:32 +0100)
committerOndrej Zajicek (work) <santiago@crfreenet.org>
Sun, 22 Jan 2017 15:32:42 +0000 (16:32 +0100)
doc/bird.sgml
nest/a-path.c
nest/a-path_test.c
nest/attrs.h
proto/bgp/attrs.c

index 999fa294740c37cfd8a69f62488844e9e2256235..ff2c188f713b339b180e1908bcef4346fff59271 100644 (file)
@@ -1945,12 +1945,11 @@ avoid routing loops.
 
 <p>BIRD supports all requirements of the BGP4 standard as defined in
 <rfc id="4271"> It also supports the community attributes (<rfc id="1997">),
-capability negotiation (<rfc id="5492">), MD5 password authentication (<rfc
-id="2385">), extended communities (<rfc id="4360">), route reflectors (<rfc
-id="4456">), graceful restart (<rfc id="4724">), multiprotocol extensions
-(<rfc id="4760">), 4B AS numbers (<rfc id="4893">), and 4B AS numbers in
-extended communities (<rfc id="5668">).
-
+capability negotiation (<rfc id="5492">), MD5 password authentication
+(<rfc id="2385">), extended communities (<rfc id="4360">), route reflectors
+(<rfc id="4456">), AS confederations (<rfc id="5065">), graceful restart
+(<rfc id="4724">), multiprotocol extensions (<rfc id="4760">), 4B AS numbers
+(<rfc id="4893">), and 4B AS numbers in extended communities (<rfc id="5668">).
 
 For IPv6, it uses the standard multiprotocol extensions defined in
 <rfc id="4760"> and applied to IPv6 according to <rfc id="2545">.
@@ -2134,6 +2133,21 @@ using the following configuration parameters:
        accepting incoming connections. In passive mode, outgoing connections
        are not initiated. Default: off.
 
+       <tag><label id="bgp-confederation">confederation <m/number/</tag>
+       BGP confederations (<rfc id="5065">) are collections of autonomous
+       systems that act as one entity to external systems, represented by one
+       confederation identifier (instead of AS numbers). This option allows to
+       enable BGP confederation behavior and to specify the local confederation
+       identifier. When BGP confederations are used, all BGP speakers that are
+       members of the BGP confederation should have the same confederation
+       identifier configured. Default: 0 (no confederation).
+
+       <tag><label id="bgp-confederation-member">confederation member <m/switch/</tag>
+       When BGP confederations are used, this option allows to specify whether
+       the BGP neighbor is a member of the same confederation as the local BGP
+       speaker. The option is unnecessary (and ignored) for IBGP sessions, as
+       the same AS number implies the same confederation. Default: no.
+
        <tag><label id="bgp-rr-client">rr client</tag>
        Be a route reflector and treat the neighbor as a route reflection
        client. Default: disabled.
index 6ced703d8704bbca99541f389d6102a18d93eab7..32ffc72c1b2c89d5e062bbb86c2232b315d65fa0 100644 (file)
@@ -25,7 +25,7 @@
 #define BAD(DSC, VAL) ({ err_dsc = DSC; err_val = VAL; goto bad; })
 
 int
-as_path_valid(byte *data, uint len, int bs, char *err, uint elen)
+as_path_valid(byte *data, uint len, int bs, int confed, char *err, uint elen)
 {
   byte *pos = data;
   char *err_dsc = NULL;
@@ -43,9 +43,21 @@ as_path_valid(byte *data, uint len, int bs, char *err, uint elen)
     if (len < slen)
       BAD("segment framing error", len);
 
-    /* XXXX handle CONFED segments */
-    if ((type != AS_PATH_SET) && (type != AS_PATH_SEQUENCE))
+    switch (type)
+    {
+    case AS_PATH_SET:
+    case AS_PATH_SEQUENCE:
+      break;
+
+    case AS_PATH_CONFED_SEQUENCE:
+    case AS_PATH_CONFED_SET:
+      if (!confed)
+       BAD("AS_CONFED* segment", type);
+      break;
+
+    default:
       BAD("unknown segment", type);
+    }
 
     if (pos[1] == 0)
       BAD("zero-length segment", type);
@@ -157,10 +169,13 @@ as_path_contains_confed(const struct adata *path)
   return 0;
 }
 
-static void
-as_path_strip_confed_(byte *dst, const byte *src, uint len)
+struct adata *
+as_path_strip_confed(struct linpool *pool, const struct adata *path)
 {
-  const byte *end = src + len;
+  struct adata *res = lp_alloc_adata(pool, path->length);
+  const byte *src = path->data;
+  const byte *end = src + path->length;
+  byte *dst = res->data;
 
   while (src < end)
   {
@@ -176,18 +191,15 @@ as_path_strip_confed_(byte *dst, const byte *src, uint len)
 
     src += slen;
   }
-}
 
-struct adata *
-as_path_strip_confed(struct linpool *pool, const struct adata *op)
-{
-  struct adata *np = lp_alloc_adata(pool, op->length);
-  as_path_strip_confed_(np->data, op->data, op->length);
-  return np;
+  /* Fix the result length */
+  res->length = dst - res->data;
+
+  return res;
 }
 
 struct adata *
-as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as, int strip)
+as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as)
 {
   struct adata *np;
   const byte *pos = op->data;
@@ -218,10 +230,7 @@ as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as,
   {
     byte *dst = np->data + 2 + BS * np->data[1];
 
-    if (strip)
-      as_path_strip_confed_(dst, pos, len);
-    else
-      memcpy(dst, pos, len);
+    memcpy(dst, pos, len);
   }
 
   return np;
@@ -325,46 +334,49 @@ as_path_merge(struct linpool *pool, struct adata *p1, struct adata *p2)
 }
 
 void
-as_path_format(const struct adata *path, byte *buf, uint size)
+as_path_format(const struct adata *path, byte *bb, uint size)
 {
-  const byte *p = path->data;
-  const byte *e = p + path->length;
-  byte *end = buf + size - 16;
-  int sp = 1;
-  int l, isset;
+  buffer buf = { .start = bb, .pos = bb, .end = bb + size }, *b = &buf;
+  const byte *pos = path->data;
+  const byte *end = pos + path->length;
+  const char *ops, *cls;
+
+  b->pos[0] = 0;
+
+  while (pos < end)
+  {
+    uint type = pos[0];
+    uint len  = pos[1];
+    pos += 2;
 
-  while (p < e)
+    switch (type)
     {
-      if (buf > end)
-       {
-         strcpy(buf, " ...");
-         return;
-       }
-      isset = (*p++ == AS_PATH_SET);
-      l = *p++;
-      if (isset)
-       {
-         if (!sp)
-           *buf++ = ' ';
-         *buf++ = '{';
-         sp = 0;
-       }
-      while (l-- && buf <= end)
-       {
-         if (!sp)
-           *buf++ = ' ';
-         buf += bsprintf(buf, "%u", get_as(p));
-         p += BS;
-         sp = 0;
-       }
-      if (isset)
-       {
-         *buf++ = ' ';
-         *buf++ = '}';
-         sp = 0;
-       }
+    case AS_PATH_SET:                  ops = "{";      cls = "}";      break;
+    case AS_PATH_SEQUENCE:             ops = NULL;     cls = NULL;     break;
+    case AS_PATH_CONFED_SEQUENCE:      ops = "(";      cls = ")";      break;
+    case AS_PATH_CONFED_SET:           ops = "({";     cls = "})";     break;
+    default: bug("Invalid path segment");
     }
-  *buf = 0;
+
+    if (ops)
+      buffer_puts(b, ops);
+
+    while (len--)
+    {
+      buffer_print(b, len ? "%u " : "%u", get_as(pos));
+      pos += BS;
+    }
+
+    if (cls)
+      buffer_puts(b, cls);
+
+    if (pos < end)
+      buffer_puts(b, " ");
+  }
+
+  /* Handle overflow */
+  if (b->pos == b->end)
+    strcpy(b->end - 12, "...");
 }
 
 int
@@ -399,66 +411,80 @@ as_path_getlen(const struct adata *path)
 int
 as_path_get_last(const struct adata *path, u32 *orig_as)
 {
+  const byte *pos = path->data;
+  const byte *end = pos + path->length;
   int found = 0;
-  u32 res = 0;
-  const u8 *p = path->data;
-  const u8 *q = p+path->length;
-  int len;
+  u32 val = 0;
 
-  while (p<q)
+  while (pos < end)
+  {
+    uint type = pos[0];
+    uint len  = pos[1];
+    pos += 2;
+
+    if (!len)
+      continue;
+
+    switch (type)
     {
-      switch (*p++)
-       {
-       case AS_PATH_SET:
-         if (len = *p++)
-           {
-             found = 0;
-             p += BS * len;
-           }
-         break;
-       case AS_PATH_SEQUENCE:
-         if (len = *p++)
-           {
-             found = 1;
-             res = get_as(p + BS * (len - 1));
-             p += BS * len;
-           }
-         break;
-       default: bug("Invalid path segment");
-       }
+    case AS_PATH_SET:
+    case AS_PATH_CONFED_SET:
+      found = 0;
+      break;
+
+    case AS_PATH_SEQUENCE:
+    case AS_PATH_CONFED_SEQUENCE:
+      val = get_as(pos + BS * (len - 1));
+      found = 1;
+      break;
+
+    default:
+      bug("Invalid path segment");
     }
 
+    pos += BS * len;
+  }
+
   if (found)
-    *orig_as = res;
+    *orig_as = val;
   return found;
 }
 
 u32
 as_path_get_last_nonaggregated(const struct adata *path)
 {
-  const u8 *p = path->data;
-  const u8 *q = p+path->length;
-  u32 res = 0;
-  int len;
+  const byte *pos = path->data;
+  const byte *end = pos + path->length;
+  u32 val = 0;
 
-  while (p<q)
+  while (pos < end)
+  {
+    uint type = pos[0];
+    uint len  = pos[1];
+    pos += 2;
+
+    if (!len)
+      continue;
+
+    switch (type)
     {
-      switch (*p++)
-       {
-       case AS_PATH_SET:
-         return res;
+    case AS_PATH_SET:
+    case AS_PATH_CONFED_SET:
+      return val;
 
-       case AS_PATH_SEQUENCE:
-         if (len = *p++)
-           res = get_as(p + BS * (len - 1));
-         p += BS * len;
-         break;
+    case AS_PATH_SEQUENCE:
+    case AS_PATH_CONFED_SEQUENCE:
+      val = get_as(pos + BS * (len - 1));
+      break;
 
-       default: bug("Invalid path segment");
-       }
+    default:
+      bug("Invalid path segment");
     }
 
-  return res;
+    pos += BS * len;
+  }
+
+  return val;
 }
 
 int
@@ -468,11 +494,47 @@ as_path_get_first(const struct adata *path, u32 *last_as)
 
   if ((path->length == 0) || (p[0] != AS_PATH_SEQUENCE) || (p[1] == 0))
     return 0;
-  else
+
+  *last_as = get_as(p+2);
+  return 1;
+}
+
+int
+as_path_get_first_regular(const struct adata *path, u32 *last_as)
+{
+  const byte *pos = path->data;
+  const byte *end = pos + path->length;
+
+  while (pos < end)
+  {
+    uint type = pos[0];
+    uint len  = pos[1];
+    pos += 2;
+
+    switch (type)
     {
-      *last_as = get_as(p+2);
+    case AS_PATH_SET:
+      return 0;
+
+    case AS_PATH_SEQUENCE:
+      if (len == 0)
+       return 0;
+
+      *last_as = get_as(pos);
       return 1;
+
+    case AS_PATH_CONFED_SEQUENCE:
+    case AS_PATH_CONFED_SET:
+      break;
+
+    default:
+      bug("Invalid path segment");
     }
+
+    pos += BS * len;
+  }
+
+  return 0;
 }
 
 int
@@ -597,43 +659,50 @@ struct pm_pos
 };
 
 static int
-parse_path(const struct adata *path, struct pm_pos *pos)
+parse_path(const struct adata *path, struct pm_pos *pp)
 {
-  const u8 *p = path->data;
-  const u8 *q = p + path->length;
-  struct pm_pos *opos = pos;
-  int i, len;
+  const byte *pos = path->data;
+  const byte *end = pos + path->length;
+  struct pm_pos *op = pp;
+  uint i;
 
+  while (pos < end)
+  {
+    uint type = pos[0];
+    uint len  = pos[1];
+    pos += 2;
 
-  while (p < q)
-    switch (*p++)
+    switch (type)
+    {
+    case AS_PATH_SET:
+    case AS_PATH_CONFED_SET:
+      pp->set = 1;
+      pp->mark = 0;
+      pp->val.sp = pos - 1;
+      pp++;
+
+      pos += BS * len;
+      break;
+
+    case AS_PATH_SEQUENCE:
+    case AS_PATH_CONFED_SEQUENCE:
+      for (i = 0; i < len; i++)
       {
-      case AS_PATH_SET:
-       pos->set = 1;
-       pos->mark = 0;
-       pos->val.sp = p;
-       len = *p;
-       p += 1 + BS * len;
-       pos++;
-       break;
-      
-      case AS_PATH_SEQUENCE:
-       len = *p++;
-       for (i = 0; i < len; i++)
-         {
-           pos->set = 0;
-           pos->mark = 0;
-           pos->val.asn = get_as(p);
-           p += BS;
-           pos++;
-         }
-       break;
-
-      default:
-       bug("as_path_match: Invalid path component");
+       pp->set = 0;
+       pp->mark = 0;
+       pp->val.asn = get_as(pos);
+       pp++;
+
+       pos += BS;
       }
+      break;
 
-  return pos - opos;
+    default:
+      bug("Invalid path segment");
+    }
+  }
+
+  return pp - op;
 }
 
 static int
@@ -680,7 +749,7 @@ pm_mark(struct pm_pos *pos, int i, int plen, int *nl, int *nh)
 }
 
 /* AS path matching is nontrivial. Because AS path can
- * contain sets, it is not a plain wildcard matching. A set 
+ * contain sets, it is not a plain wildcard matching. A set
  * in an AS path is interpreted as it might represent any
  * sequence of AS numbers from that set (possibly with
  * repetitions). So it is also a kind of a pattern,
@@ -718,7 +787,7 @@ as_path_match(const struct adata *path, struct f_path_mask *mask)
 
   l = h = 0;
   pos[0].mark = 1;
-  
+
   while (mask)
     {
       /* We remove this mark to not step after pos[plen] */
index 289b2df66fa41ed6467a4048e885fc76a6d91661..fbf0f8928572ca755ea98637c18d72c477b2f8ed 100644 (file)
@@ -85,15 +85,19 @@ t_path_format(void)
     bt_debug("Prepending ASN: %10u \n", i);
   }
 
-#define BUFFER_SIZE 26
+#define BUFFER_SIZE 120
   byte buf[BUFFER_SIZE] = {};
+
+  as_path_format(&empty_as_path, buf, BUFFER_SIZE);
+  bt_assert_msg(strcmp(buf, "") == 0, "Buffer(%zu): '%s'", strlen(buf), buf);
+
   as_path_format(as_path, buf, BUFFER_SIZE);
-  bt_assert_msg(strcmp(buf, "4294967294 4294967293 ...") == 0, "Buffer(%zu): '%s'", strlen(buf), buf);
+  bt_assert_msg(strcmp(buf, "4294967294 4294967293 4294967292 4294967291 4294967290 4294967289 4294967288 4294967287 4294967286 4294967285") == 0, "Buffer(%zu): '%s'", strlen(buf), buf);
 
 #define SMALL_BUFFER_SIZE 25
   byte buf2[SMALL_BUFFER_SIZE] = {};
   as_path_format(as_path, buf2, SMALL_BUFFER_SIZE);
-  bt_assert_msg(strcmp(buf2, "4294967294 ...") == 0, "Small Buffer(%zu): '%s'", strlen(buf2), buf2);
+  bt_assert_msg(strcmp(buf2, "4294967294 42...") == 0, "Small Buffer(%zu): '%s'", strlen(buf2), buf2);
 
   rfree(lp);
 
index 810ff5831a934d2bda501ad3b76aa8556ac80a05..84403cf38f708e0b76c693ce9a321b3741df6774 100644 (file)
 
 struct f_tree;
 
-int as_path_valid(byte *data, uint len, int bs, char *err, uint elen);
+int as_path_valid(byte *data, uint len, int bs, int confed, char *err, uint elen);
 int as_path_16to32(byte *dst, byte *src, uint len);
 int as_path_32to16(byte *dst, byte *src, uint len);
 int as_path_contains_as4(const struct adata *path);
 int as_path_contains_confed(const struct adata *path);
 struct adata *as_path_strip_confed(struct linpool *pool, const struct adata *op);
-struct adata *as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as, int strip);
+struct adata *as_path_prepend2(struct linpool *pool, const struct adata *op, int seq, u32 as);
 struct adata *as_path_to_old(struct linpool *pool, const struct adata *path);
 void as_path_cut(struct adata *path, uint num);
 struct adata *as_path_merge(struct linpool *pool, struct adata *p1, struct adata *p2);
@@ -44,6 +44,7 @@ void as_path_format(const struct adata *path, byte *buf, uint size);
 int as_path_getlen(const struct adata *path);
 int as_path_getlen_int(const struct adata *path, int bs);
 int as_path_get_first(const struct adata *path, u32 *orig_as);
+int as_path_get_first_regular(const struct adata *path, u32 *last_as);
 int as_path_get_last(const struct adata *path, u32 *last_as);
 u32 as_path_get_last_nonaggregated(const struct adata *path);
 int as_path_contains(const struct adata *path, u32 as, int min);
@@ -51,7 +52,7 @@ int as_path_match_set(const struct adata *path, struct f_tree *set);
 struct adata *as_path_filter(struct linpool *pool, struct adata *path, struct f_tree *set, u32 key, int pos);
 
 static inline struct adata *as_path_prepend(struct linpool *pool, const struct adata *path, u32 as)
-{ return as_path_prepend2(pool, path, AS_PATH_SEQUENCE, as, 0); }
+{ return as_path_prepend2(pool, path, AS_PATH_SEQUENCE, as); }
 
 
 #define PM_ASN         0
index 227ddadc1fc270528af8082c00d604d560b17ce6..35aecdb038ed644fa0ddc3fe3f6483285d1dc03d 100644 (file)
  * specifies that such updates should be ignored, but that is generally
  * a bad idea.
  *
- * Error checking of optional transitive attributes is done according to
- * draft-ietf-idr-optional-transitive-03, but errors are handled always
- * as withdraws.
- *
- * Unexpected AS_CONFED_* segments in AS_PATH are logged and removed,
- * but unknown segments cause a session drop with Malformed AS_PATH
- * error (see validate_path()). The behavior in such case is not
- * explicitly specified by RFC 4271. RFC 5065 specifies that
- * inconsistent AS_CONFED_* segments should cause a session drop, but
- * implementations that pass invalid AS_CONFED_* segments are
- * widespread.
- *
- * Error handling of AS4_* attributes is done as specified by
- * draft-ietf-idr-rfc4893bis-03. There are several possible
- * inconsistencies between AGGREGATOR and AS4_AGGREGATOR that are not
- * handled by that draft, these are logged and ignored (see
- * bgp_reconstruct_4b_attrs()).
- *
  * BGP attribute table has several hooks:
  *
  * export - Hook that validates and normalizes attribute during export phase.
@@ -281,11 +263,19 @@ bgp_encode_as_path(struct bgp_write_state *s, eattr *a, byte *buf, uint size)
 static void
 bgp_decode_as_path(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
 {
+  struct bgp_proto *p = s->proto;
+  int as_length = s->as4_session ? 4 : 2;
+  int as_confed = p->cf->confederation && p->is_interior;
   char err[128];
 
-  if (!as_path_valid(data, len, (s->as4_session ? 4 : 2), err, sizeof(err)))
+  if (!as_path_valid(data, len, as_length, as_confed, err, sizeof(err)))
     WITHDRAW("Malformed AS_PATH attribute - %s", err);
 
+  /* In some circumstances check for initial AS_CONFED_SEQUENCE; RFC 5065 5.0 */
+  if (p->is_interior && !p->is_internal &&
+      ((len < 2) || (data[0] != AS_PATH_CONFED_SEQUENCE)))
+    WITHDRAW("Malformed AS_PATH attribute - %s", "missing initial AS_CONFED_SEQUENCE");
+
   if (!s->as4_session)
   {
     /* Prepare 32-bit AS_PATH (from 16-bit one) in a temporary buffer */
@@ -603,11 +593,20 @@ bgp_decode_as4_path(struct bgp_parse_state *s, uint code UNUSED, uint flags, byt
   if (len < 6)
     DISCARD(BAD_LENGTH, "AS4_PATH", len);
 
-  if (!as_path_valid(data, len, 4, err, sizeof(err)))
+  if (!as_path_valid(data, len, 4, 1, err, sizeof(err)))
     DISCARD("Malformed AS4_PATH attribute - %s", err);
 
-  /* XXXX remove CONFED segments */
-  bgp_set_attr_data(to, s->pool, BA_AS4_PATH, flags, data, len);
+  struct adata *a = lp_alloc_adata(s->pool, len);
+  memcpy(a->data, data, len);
+
+  /* AS_CONFED* segments are invalid in AS4_PATH; RFC 6793 6 */
+  if (as_path_contains_confed(a))
+  {
+    REPORT("Discarding AS_CONFED* segment from AS4_PATH attribute");
+    a = as_path_strip_confed(s->pool, a);
+  }
+
+  bgp_set_attr_ptr(to, s->pool, BA_AS4_PATH, flags, a);
 }
 
 static void
@@ -1042,7 +1041,7 @@ bgp_decode_attrs(struct bgp_parse_state *s, byte *data, uint len)
   if (bgp_as_path_loopy(p, attrs, p->local_as))
     goto withdraw;
 
-  /* Reject routes with our Confederation ID in AS_PATH attribute; RFC 5065 4 */
+  /* Reject routes with our Confederation ID in AS_PATH attribute; RFC 5065 4.0 */
   if ((p->public_as != p->local_as) && bgp_as_path_loopy(p, attrs, p->public_as))
     goto withdraw;
 
@@ -1322,15 +1321,7 @@ bgp_import_control(struct proto *P, rte **new, ea_list **attrs UNUSED, struct li
   return 0;
 }
 
-static const adata null_adata; /* adata of length 0 */
-
-static inline void
-bgp_path_prepend(ea_list **attrs, struct linpool *pool, int seg, u32 as, int strip)
-{
-  eattr *a = bgp_find_attr(*attrs, BA_AS_PATH);
-  adata *d = as_path_prepend2(pool, a ? a->u.ptr : &null_adata, seg, as, strip);
-  bgp_set_attr_ptr(attrs, pool, BA_AS_PATH, 0, d);
-}
+static adata null_adata;       /* adata of length 0 */
 
 static inline void
 bgp_cluster_list_prepend(ea_list **attrs, struct linpool *pool, u32 id)
@@ -1352,23 +1343,33 @@ bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *at
   if (! bgp_find_attr(attrs, BA_ORIGIN))
     bgp_set_attr_u32(&attrs, pool, BA_ORIGIN, 0, src ? ORIGIN_INCOMPLETE : ORIGIN_IGP);
 
+  /* AS_PATH attribute */
+  a = bgp_find_attr(attrs, BA_AS_PATH);
+  adata *ad = a ? a->u.ptr : &null_adata;
+
+  /* AS_PATH attribute - strip AS_CONFED* segments outside confederation */
+  if ((!p->cf->confederation || !p->is_interior) && as_path_contains_confed(ad))
+    ad = as_path_strip_confed(pool, ad);
+
   /* AS_PATH attribute - keep or prepend ASN */
   if (p->is_internal ||
       (p->rs_client && src && src->rs_client))
   {
     /* IBGP or route server -> just ensure there is one */
-    if (! bgp_find_attr(attrs, BA_AS_PATH))
-      bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, lp_alloc_adata(pool, 0));
+    if (!a)
+      bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, &null_adata);
   }
   else if (p->is_interior)
   {
-    /* Confederation -> prepend ASN as CONFED_SEQUENCE, keep CONFED_* segments */
-    bgp_path_prepend(&attrs, pool, AS_PATH_CONFED_SEQUENCE, p->public_as, 0);
+    /* Confederation -> prepend ASN as AS_CONFED_SEQUENCE */
+    ad = as_path_prepend2(pool, ad, AS_PATH_CONFED_SEQUENCE, p->public_as);
+    bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, ad);
   }
   else /* Regular EBGP (no RS, no confederation) */
   {
-    /* Regular EBGP -> prepend ASN as regular segment, strip CONFED_* segments */
-    bgp_path_prepend(&attrs, pool, AS_PATH_SEQUENCE, p->public_as, 1);
+    /* Regular EBGP -> prepend ASN as regular sequence */
+    ad = as_path_prepend2(pool, ad, AS_PATH_SEQUENCE, p->public_as);
+    bgp_set_attr_ptr(&attrs, pool, BA_AS_PATH, 0, ad);
 
     /* MULTI_EXIT_DESC attribute - accept only if set in export filter */
     a = bgp_find_attr(attrs, BA_MULTI_EXIT_DISC);
@@ -1460,10 +1461,12 @@ bgp_get_neighbor(rte *r)
   eattr *e = ea_find(r->attrs->eattrs, EA_CODE(EAP_BGP, BA_AS_PATH));
   u32 as;
 
-  if (e && as_path_get_first(e->u.ptr, &as))
+  if (e && as_path_get_first_regular(e->u.ptr, &as))
     return as;
-  else
-    return ((struct bgp_proto *) r->attrs->src->proto)->remote_as;
+
+  /* If AS_PATH is not defined, we treat rte as locally originated */
+  struct bgp_proto *p = (void *) r->attrs->src->proto;
+  return p->cf->confederation ?: p->local_as;
 }
 
 static inline int
@@ -1662,7 +1665,7 @@ bgp_rte_mergable(rte *pri, rte *sec)
   }
 
   /* RFC 4271 9.1.2.2. d) Prefer external peers */
-  if (pri_bgp->is_internal != sec_bgp->is_internal)
+  if (pri_bgp->is_interior != sec_bgp->is_interior)
     return 0;
 
   /* RFC 4271 9.1.2.2. e) Compare IGP metrics */
@@ -1852,6 +1855,7 @@ bgp_process_as4_attrs(ea_list **attrs, struct linpool *pool)
   /* Handle AS_PATH attribute */
   if (p2 && p4)
   {
+    /* Both as_path_getlen() and as_path_cut() take AS_CONFED* as zero length */
     int p2_len = as_path_getlen(p2->u.ptr);
     int p4_len = as_path_getlen(p4->u.ptr);