BGP: Support for AS confederations (RFC 5065)

This commit is contained in:
Ondrej Zajicek (work) 2017-01-22 16:32:42 +01:00
parent f8aad5d5b7
commit 5509e17d0c
5 changed files with 277 additions and 185 deletions

View 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.

View 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;
}
/* Fix the result length */
res->length = dst - res->data;
return res;
}
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;
}
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,9 +230,6 @@ 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);
}
@ -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;
while (p < e)
b->pos[0] = 0;
while (pos < end)
{
if (buf > end)
uint type = pos[0];
uint len = pos[1];
pos += 2;
switch (type)
{
strcpy(buf, " ...");
return;
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");
}
isset = (*p++ == AS_PATH_SET);
l = *p++;
if (isset)
if (ops)
buffer_puts(b, ops);
while (len--)
{
if (!sp)
*buf++ = ' ';
*buf++ = '{';
sp = 0;
buffer_print(b, len ? "%u " : "%u", get_as(pos));
pos += BS;
}
while (l-- && buf <= end)
{
if (!sp)
*buf++ = ' ';
buf += bsprintf(buf, "%u", get_as(p));
p += BS;
sp = 0;
if (cls)
buffer_puts(b, cls);
if (pos < end)
buffer_puts(b, " ");
}
if (isset)
{
*buf++ = ' ';
*buf++ = '}';
sp = 0;
}
}
*buf = 0;
/* 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)
{
switch (*p++)
uint type = pos[0];
uint len = pos[1];
pos += 2;
if (!len)
continue;
switch (type)
{
case AS_PATH_SET:
if (len = *p++)
{
case AS_PATH_CONFED_SET:
found = 0;
p += BS * len;
}
break;
case AS_PATH_SEQUENCE:
if (len = *p++)
{
case AS_PATH_CONFED_SEQUENCE:
val = get_as(pos + BS * (len - 1));
found = 1;
res = get_as(p + BS * (len - 1));
p += BS * len;
}
break;
default: bug("Invalid path segment");
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)
{
switch (*p++)
uint type = pos[0];
uint len = pos[1];
pos += 2;
if (!len)
continue;
switch (type)
{
case AS_PATH_SET:
return res;
case AS_PATH_CONFED_SET:
return val;
case AS_PATH_SEQUENCE:
if (len = *p++)
res = get_as(p + BS * (len - 1));
p += BS * len;
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)
{
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:
pos->set = 1;
pos->mark = 0;
pos->val.sp = p;
len = *p;
p += 1 + BS * len;
pos++;
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:
len = *p++;
case AS_PATH_CONFED_SEQUENCE:
for (i = 0; i < len; i++)
{
pos->set = 0;
pos->mark = 0;
pos->val.asn = get_as(p);
p += BS;
pos++;
pp->set = 0;
pp->mark = 0;
pp->val.asn = get_as(pos);
pp++;
pos += BS;
}
break;
default:
bug("as_path_match: Invalid path component");
bug("Invalid path segment");
}
}
return pos - opos;
return pp - op;
}
static int

View 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);

View file

@ -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

View file

@ -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);