diff options
author | Ondrej Zajicek (work) <santiago@crfreenet.org> | 2017-01-22 16:32:42 +0100 |
---|---|---|
committer | Ondrej Zajicek (work) <santiago@crfreenet.org> | 2017-01-22 16:32:42 +0100 |
commit | 5509e17d0c1b4e75d5911864f75ba119769e5725 (patch) | |
tree | 3ae4a63f807f3740611bb40f01524742fe9bf1f3 | |
parent | f8aad5d5b7601d0500841e57bafa5796cc3156ab (diff) |
BGP: Support for AS confederations (RFC 5065)
-rw-r--r-- | doc/bird.sgml | 26 | ||||
-rw-r--r-- | nest/a-path.c | 331 | ||||
-rw-r--r-- | nest/a-path_test.c | 10 | ||||
-rw-r--r-- | nest/attrs.h | 7 | ||||
-rw-r--r-- | proto/bgp/attrs.c | 88 |
5 files changed, 277 insertions, 185 deletions
diff --git a/doc/bird.sgml b/doc/bird.sgml index 999fa294..ff2c188f 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -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. diff --git a/nest/a-path.c b/nest/a-path.c index 6ced703d..32ffc72c 100644 --- a/nest/a-path.c +++ b/nest/a-path.c @@ -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] */ diff --git a/nest/a-path_test.c b/nest/a-path_test.c index 289b2df6..fbf0f892 100644 --- a/nest/a-path_test.c +++ b/nest/a-path_test.c @@ -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); diff --git a/nest/attrs.h b/nest/attrs.h index 810ff583..84403cf3 100644 --- a/nest/attrs.h +++ b/nest/attrs.h @@ -30,13 +30,13 @@ 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 diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index 227ddadc..35aecdb0 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -41,24 +41,6 @@ * 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); |