summaryrefslogtreecommitdiff
path: root/proto
diff options
context:
space:
mode:
authorOndrej Zajicek (work) <santiago@crfreenet.org>2019-09-28 14:17:20 +0200
committerOndrej Zajicek (work) <santiago@crfreenet.org>2019-10-09 17:53:23 +0200
commit09ee846d9275b4cb0b77f7e458aba6a71b111c52 (patch)
tree21fd5d815e7f7b7dc7d6dd3a473ece834c2d024c /proto
parent759b204be33fa8485c9e28038ee029a49e2e9d3f (diff)
BGP: AIGP metric support (RFC 7311)
Diffstat (limited to 'proto')
-rw-r--r--proto/bgp/attrs.c265
-rw-r--r--proto/bgp/bgp.c13
-rw-r--r--proto/bgp/bgp.h27
-rw-r--r--proto/bgp/config.Y8
-rw-r--r--proto/bgp/packets.c3
5 files changed, 304 insertions, 12 deletions
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index 886ee6ba..b8921363 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -34,7 +34,7 @@
* are probably inadequate.
*
* Loop detection based on AS_PATH causes updates to be withdrawn. RFC
- * 4271 does not explicitly specifiy the behavior in that case.
+ * 4271 does not explicitly specify the behavior in that case.
*
* Loop detection related to route reflection (based on ORIGINATOR_ID
* and CLUSTER_LIST) causes updates to be withdrawn. RFC 4456 8
@@ -200,6 +200,179 @@ bgp_encode_raw(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size)
/*
+ * AIGP handling
+ */
+
+static int
+bgp_aigp_valid(byte *data, uint len, char *err, uint elen)
+{
+ byte *pos = data;
+ char *err_dsc = NULL;
+ uint err_val = 0;
+
+#define BAD(DSC,VAL) ({ err_dsc = DSC; err_val = VAL; goto bad; })
+ while (len)
+ {
+ if (len < 3)
+ BAD("TLV framing error", len);
+
+ /* Process one TLV */
+ uint ptype = pos[0];
+ uint plen = get_u16(pos + 1);
+
+ if (len < plen)
+ BAD("TLV framing error", plen);
+
+ if (plen < 3)
+ BAD("Bad TLV length", plen);
+
+ if ((ptype == BGP_AIGP_METRIC) && (plen != 11))
+ BAD("Bad AIGP TLV length", plen);
+
+ ADVANCE(pos, len, plen);
+ }
+#undef BAD
+
+ return 1;
+
+bad:
+ if (err)
+ if (bsnprintf(err, elen, "%s (%u) at %d", err_dsc, err_val, (int) (pos - data)) < 0)
+ err[0] = 0;
+
+ return 0;
+}
+
+static const byte *
+bgp_aigp_get_tlv(const struct adata *ad, uint type)
+{
+ if (!ad)
+ return NULL;
+
+ uint len = ad->length;
+ const byte *pos = ad->data;
+
+ while (len)
+ {
+ uint ptype = pos[0];
+ uint plen = get_u16(pos + 1);
+
+ if (ptype == type)
+ return pos;
+
+ ADVANCE(pos, len, plen);
+ }
+
+ return NULL;
+}
+
+static const struct adata *
+bgp_aigp_set_tlv(struct linpool *pool, const struct adata *ad, uint type, byte *data, uint dlen)
+{
+ uint len = ad ? ad->length : 0;
+ const byte *pos = ad ? ad->data : NULL;
+ struct adata *res = lp_alloc_adata(pool, len + 3 + dlen);
+ byte *dst = res->data;
+ byte *tlv = NULL;
+ int del = 0;
+
+ while (len)
+ {
+ uint ptype = pos[0];
+ uint plen = get_u16(pos + 1);
+
+ /* Find position for new TLV */
+ if ((ptype >= type) && !tlv)
+ {
+ tlv = dst;
+ dst += 3 + dlen;
+ }
+
+ /* Skip first matching TLV, copy others */
+ if ((ptype == type) && !del)
+ del = 1;
+ else
+ {
+ memcpy(dst, pos, plen);
+ dst += plen;
+ }
+
+ ADVANCE(pos, len, plen);
+ }
+
+ if (!tlv)
+ {
+ tlv = dst;
+ dst += 3 + dlen;
+ }
+
+ /* Store the TLD */
+ put_u8(tlv + 0, type);
+ put_u16(tlv + 1, 3 + dlen);
+ memcpy(tlv + 3, data, dlen);
+
+ /* Update length */
+ res->length = dst - res->data;
+
+ return res;
+}
+
+static u64 UNUSED
+bgp_aigp_get_metric(const struct adata *ad, u64 def)
+{
+ const byte *b = bgp_aigp_get_tlv(ad, BGP_AIGP_METRIC);
+ return b ? get_u64(b + 3) : def;
+}
+
+static const struct adata *
+bgp_aigp_set_metric(struct linpool *pool, const struct adata *ad, u64 metric)
+{
+ byte data[8];
+ put_u64(data, metric);
+ return bgp_aigp_set_tlv(pool, ad, BGP_AIGP_METRIC, data, 8);
+}
+
+int
+bgp_total_aigp_metric_(rte *e, u64 *metric, const struct adata **ad)
+{
+ eattr *a = ea_find(e->attrs->eattrs, EA_CODE(PROTOCOL_BGP, BA_AIGP));
+ if (!a)
+ return 0;
+
+ const byte *b = bgp_aigp_get_tlv(a->u.ptr, BGP_AIGP_METRIC);
+ if (!b)
+ return 0;
+
+ u64 aigp = get_u64(b + 3);
+ u64 step = e->attrs->igp_metric;
+
+ if (!rte_resolvable(e) || (step >= IGP_METRIC_UNKNOWN))
+ step = BGP_AIGP_MAX;
+
+ if (!step)
+ step = 1;
+
+ *ad = a->u.ptr;
+ *metric = aigp + step;
+ if (*metric < aigp)
+ *metric = BGP_AIGP_MAX;
+
+ return 1;
+}
+
+static inline int
+bgp_init_aigp_metric(rte *e, u64 *metric, const struct adata **ad)
+{
+ if (e->attrs->source == RTS_BGP)
+ return 0;
+
+ *metric = rt_get_igp_metric(e);
+ *ad = NULL;
+ return *metric < IGP_METRIC_UNKNOWN;
+}
+
+
+/*
* Attribute hooks
*/
@@ -604,6 +777,42 @@ bgp_decode_as4_path(struct bgp_parse_state *s, uint code UNUSED, uint flags, byt
bgp_set_attr_ptr(to, s->pool, BA_AS4_PATH, flags, a);
}
+
+static void
+bgp_export_aigp(struct bgp_export_state *s, eattr *a)
+{
+ if (!s->channel->cf->aigp)
+ UNSET(a);
+}
+
+static void
+bgp_decode_aigp(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
+{
+ char err[128];
+
+ /* Acceptability test postponed to bgp_finish_attrs() */
+
+ if ((flags ^ bgp_attr_table[BA_AIGP].flags) & (BAF_OPTIONAL | BAF_TRANSITIVE))
+ DISCARD("Malformed AIGP attribute - conflicting flags (%02x)", flags);
+
+ if (!bgp_aigp_valid(data, len, err, sizeof(err)))
+ DISCARD("Malformed AIGP attribute - %s", err);
+
+ bgp_set_attr_data(to, s->pool, BA_AIGP, flags, data, len);
+}
+
+static void
+bgp_format_aigp(eattr *a, byte *buf, uint size UNUSED)
+{
+ const byte *b = bgp_aigp_get_tlv(a->u.ptr, BGP_AIGP_METRIC);
+
+ if (!b)
+ bsprintf(buf, "?");
+ else
+ bsprintf(buf, "%lu", get_u64(b + 3));
+}
+
+
static void
bgp_export_large_community(struct bgp_export_state *s, eattr *a)
{
@@ -820,6 +1029,15 @@ static const struct bgp_attr_desc bgp_attr_table[] = {
.decode = bgp_decode_as4_aggregator,
.format = bgp_format_aggregator,
},
+ [BA_AIGP] = {
+ .name = "aigp",
+ .type = EAF_TYPE_OPAQUE,
+ .flags = BAF_OPTIONAL | BAF_DECODE_FLAGS,
+ .export = bgp_export_aigp,
+ .encode = bgp_encode_raw,
+ .decode = bgp_decode_aigp,
+ .format = bgp_format_aigp,
+ },
[BA_LARGE_COMMUNITY] = {
.name = "large_community",
.type = EAF_TYPE_LC_SET,
@@ -1021,7 +1239,8 @@ bgp_decode_attr(struct bgp_parse_state *s, uint code, uint flags, byte *data, ui
const struct bgp_attr_desc *desc = &bgp_attr_table[code];
/* Handle conflicting flags; RFC 7606 3 (c) */
- if ((flags ^ desc->flags) & (BAF_OPTIONAL | BAF_TRANSITIVE))
+ if (((flags ^ desc->flags) & (BAF_OPTIONAL | BAF_TRANSITIVE)) &&
+ !(desc->flags & BAF_DECODE_FLAGS))
WITHDRAW("Malformed %s attribute - conflicting flags (%02x)", desc->name, flags);
desc->decode(s, code, flags, data, len, to);
@@ -1150,6 +1369,17 @@ withdraw:
return NULL;
}
+void
+bgp_finish_attrs(struct bgp_parse_state *s, rta *a)
+{
+ /* AIGP test here instead of in bgp_decode_aigp() - we need to know channel */
+ if (BIT32_TEST(s->attrs_seen, BA_AIGP) && !s->channel->cf->aigp)
+ {
+ REPORT("Discarding AIGP attribute received on non-AIGP session");
+ bgp_unset_attr(&a->eattrs, s->pool, BA_AIGP);
+ }
+}
+
/*
* Route bucket hash table
@@ -1481,6 +1711,16 @@ bgp_update_attrs(struct bgp_proto *p, struct bgp_channel *c, rte *e, ea_list *at
if (p->is_interior && ! bgp_find_attr(attrs0, BA_LOCAL_PREF))
bgp_set_attr_u32(&attrs, pool, BA_LOCAL_PREF, 0, p->cf->default_local_pref);
+ /* AIGP attribute - accumulate local metric or originate new one */
+ u64 metric;
+ if (s.local_next_hop &&
+ (bgp_total_aigp_metric_(e, &metric, &ad) ||
+ (c->cf->aigp_originate && bgp_init_aigp_metric(e, &metric, &ad))))
+ {
+ ad = bgp_aigp_set_metric(pool, ad, metric);
+ bgp_set_attr_ptr(&attrs, pool, BA_AIGP, 0, ad);
+ }
+
/* IBGP route reflection, RFC 4456 */
if (src && src->is_internal && p->is_internal && (src->local_as == p->local_as))
{
@@ -1579,12 +1819,6 @@ bgp_get_neighbor(rte *r)
}
static inline int
-rte_resolvable(rte *rt)
-{
- return rt->attrs->dest == RTD_UNICAST;
-}
-
-static inline int
rte_stale(rte *r)
{
if (r->u.bgp.stale < 0)
@@ -1639,6 +1873,14 @@ bgp_rte_better(rte *new, rte *old)
if (n < o)
return 0;
+ /* RFC 7311 4.1 - Apply AIGP metric */
+ u64 n2 = bgp_total_aigp_metric(new);
+ u64 o2 = bgp_total_aigp_metric(old);
+ if (n2 < o2)
+ return 1;
+ if (n2 > o2)
+ return 0;
+
/* RFC 4271 9.1.2.2. a) Use AS path lengths */
if (new_bgp->cf->compare_path_lengths || old_bgp->cf->compare_path_lengths)
{
@@ -2062,7 +2304,12 @@ bgp_get_route_info(rte *e, byte *buf)
if (rte_stale(e))
buf += bsprintf(buf, "s");
- if (e->attrs->hostentry)
+ u64 metric = bgp_total_aigp_metric(e);
+ if (metric < BGP_AIGP_MAX)
+ {
+ buf += bsprintf(buf, "/%lu", metric);
+ }
+ else if (e->attrs->igp_metric)
{
if (!rte_resolvable(e))
buf += bsprintf(buf, "/-");
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index d6c2b7e4..68b716b2 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -92,6 +92,7 @@
* RFC 6286 - AS-Wide Unique BGP Identifier
* RFC 6608 - Subcodes for BGP Finite State Machine Error
* RFC 6793 - BGP Support for 4-Octet AS Numbers
+ * RFC 7311 - Accumulated IGP Metric Attribute for BGP
* RFC 7313 - Enhanced Route Refresh Capability for BGP
* RFC 7606 - Revised Error Handling for BGP UPDATE Messages
* RFC 7911 - Advertisement of Multiple Paths in BGP
@@ -1979,6 +1980,10 @@ bgp_postconfig(struct proto_config *CF)
if (cc->llgr_time == ~0U)
cc->llgr_time = cf->llgr_time;
+ /* AIGP enabled by default on interior sessions */
+ if (cc->aigp == 0xff)
+ cc->aigp = interior;
+
/* Default values of IGP tables */
if ((cc->gw_mode == GW_RECURSIVE) && !cc->desc->no_igp)
{
@@ -2087,13 +2092,17 @@ bgp_channel_reconfigure(struct channel *C, struct channel_config *CC, int *impor
if (new->mandatory && !old->mandatory && (C->channel_state != CS_UP))
return 0;
- if (new->gw_mode != old->gw_mode)
+ if ((new->gw_mode != old->gw_mode) ||
+ (new->aigp != old->aigp) ||
+ (new->cost != old->cost))
*import_changed = 1;
if (!ipa_equal(new->next_hop_addr, old->next_hop_addr) ||
(new->next_hop_self != old->next_hop_self) ||
(new->next_hop_keep != old->next_hop_keep) ||
- (new->missing_lladdr != old->missing_lladdr))
+ (new->missing_lladdr != old->missing_lladdr) ||
+ (new->aigp != old->aigp) ||
+ (new->aigp_originate != old->aigp_originate))
*export_changed = 1;
c->cf = new;
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index f9a19d53..d3e8f2ff 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -149,6 +149,9 @@ struct bgp_channel_config {
uint llgr_time; /* Long-lived graceful restart stale time */
u8 ext_next_hop; /* Allow both IPv4 and IPv6 next hops */
u8 add_path; /* Use ADD-PATH extension [RFC 7911] */
+ u8 aigp; /* AIGP is allowed on this session */
+ u8 aigp_originate; /* AIGP is originated automatically */
+ u32 cost; /* IGP cost for direct next hops */
u8 import_table; /* Use c.in_table as Adj-RIB-In */
u8 export_table; /* Use c.out_table as Adj-RIB-Out */
@@ -379,6 +382,7 @@ struct bgp_export_state {
u32 attrs_seen[1];
uint err_withdraw;
+ uint local_next_hop;
};
struct bgp_write_state {
@@ -493,6 +497,11 @@ void bgp_stop(struct bgp_proto *p, int subcode, byte *data, uint len);
struct rte_source *bgp_find_source(struct bgp_proto *p, u32 path_id);
struct rte_source *bgp_get_source(struct bgp_proto *p, u32 path_id);
+static inline int
+rte_resolvable(rte *rt)
+{
+ return rt->attrs->dest == RTD_UNICAST;
+}
#ifdef LOCAL_DEBUG
@@ -541,6 +550,7 @@ bgp_unset_attr(ea_list **to, struct linpool *pool, uint code)
int bgp_encode_attrs(struct bgp_write_state *s, ea_list *attrs, byte *buf, byte *end);
ea_list * bgp_decode_attrs(struct bgp_parse_state *s, byte *data, uint len);
+void bgp_finish_attrs(struct bgp_parse_state *s, rta *a);
void bgp_init_bucket_table(struct bgp_channel *c);
void bgp_free_bucket_table(struct bgp_channel *c);
@@ -560,6 +570,20 @@ void bgp_rt_notify(struct proto *P, struct channel *C, net *n, rte *new, rte *ol
int bgp_preexport(struct proto *, struct rte **, struct linpool *);
int bgp_get_attr(struct eattr *e, byte *buf, int buflen);
void bgp_get_route_info(struct rte *, byte *buf);
+int bgp_total_aigp_metric_(rte *e, u64 *metric, const struct adata **ad);
+
+#define BGP_AIGP_METRIC 1
+#define BGP_AIGP_MAX U64(0xffffffffffffffff)
+
+static inline u64
+bgp_total_aigp_metric(rte *r)
+{
+ u64 metric = BGP_AIGP_MAX;
+ const struct adata *ad;
+
+ bgp_total_aigp_metric_(r, &metric, &ad);
+ return metric;
+}
/* packets.c */
@@ -595,6 +619,8 @@ void bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to);
#define BAF_PARTIAL 0x20
#define BAF_EXT_LEN 0x10
+#define BAF_DECODE_FLAGS 0x0100 /* Private flag - attribute flags are handled by the decode hook */
+
#define BA_ORIGIN 0x01 /* RFC 4271 */ /* WM */
#define BA_AS_PATH 0x02 /* WM */
#define BA_NEXT_HOP 0x03 /* WM */
@@ -610,6 +636,7 @@ void bgp_update_next_hop(struct bgp_export_state *s, eattr *a, ea_list **to);
#define BA_EXT_COMMUNITY 0x10 /* RFC 4360 */
#define BA_AS4_PATH 0x11 /* RFC 6793 */
#define BA_AS4_AGGREGATOR 0x12 /* RFC 6793 */
+#define BA_AIGP 0x1a /* RFC 7311 */
#define BA_LARGE_COMMUNITY 0x20 /* RFC 8092 */
/* Bird's private internal BGP attributes */
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index 8222024d..692854cf 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -30,7 +30,7 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE,
GRACEFUL, RESTART, AWARE, CHECK, LINK, PORT, EXTENDED, MESSAGES, SETKEY,
STRICT, BIND, CONFEDERATION, MEMBER, MULTICAST, FLOW4, FLOW6, LONG,
LIVED, STALE, IMPORT, IBGP, EBGP, MANDATORY, INTERNAL, EXTERNAL,
- DYNAMIC, RANGE, NAME, DIGITS)
+ DYNAMIC, RANGE, NAME, DIGITS, BGP_AIGP, AIGP, ORIGINATE, COST)
%type <i> bgp_nh
%type <i32> bgp_afi
@@ -227,6 +227,7 @@ bgp_channel_start: bgp_afi
BGP_CC->gr_able = 0xff; /* undefined */
BGP_CC->llgr_able = 0xff; /* undefined */
BGP_CC->llgr_time = ~0U; /* undefined */
+ BGP_CC->aigp = 0xff; /* undefined */
}
};
@@ -256,6 +257,9 @@ bgp_channel_item:
| ADD PATHS bool { BGP_CC->add_path = $3 ? BGP_ADD_PATH_FULL : 0; }
| IMPORT TABLE bool { BGP_CC->import_table = $3; }
| EXPORT TABLE bool { BGP_CC->export_table = $3; }
+ | AIGP bool { BGP_CC->aigp = $2; BGP_CC->aigp_originate = 0; }
+ | AIGP ORIGINATE { BGP_CC->aigp = 1; BGP_CC->aigp_originate = 1; }
+ | COST expr { BGP_CC->cost = $2; if ($2 < 1) cf_error("Cost must be positive"); }
| IGP TABLE rtable {
if (BGP_CC->desc->no_igp)
cf_error("IGP table not allowed here");
@@ -312,6 +316,8 @@ dynamic_attr: BGP_CLUSTER_LIST
{ $$ = f_new_dynamic_attr(EAF_TYPE_INT_SET, T_CLIST, EA_CODE(PROTOCOL_BGP, BA_CLUSTER_LIST)); } ;
dynamic_attr: BGP_EXT_COMMUNITY
{ $$ = f_new_dynamic_attr(EAF_TYPE_EC_SET, T_ECLIST, EA_CODE(PROTOCOL_BGP, BA_EXT_COMMUNITY)); } ;
+dynamic_attr: BGP_AIGP
+ { $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_AIGP)); } ;
dynamic_attr: BGP_LARGE_COMMUNITY
{ $$ = f_new_dynamic_attr(EAF_TYPE_LC_SET, T_LCLIST, EA_CODE(PROTOCOL_BGP, BA_LARGE_COMMUNITY)); } ;
diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c
index 4632e4ad..c3bd600a 100644
--- a/proto/bgp/packets.c
+++ b/proto/bgp/packets.c
@@ -919,6 +919,7 @@ bgp_apply_next_hop(struct bgp_parse_state *s, rta *a, ip_addr gw, ip_addr ll)
a->dest = RTD_UNICAST;
a->nh.gw = nbr->addr;
a->nh.iface = nbr->iface;
+ a->igp_metric = c->cf->cost;
}
else /* GW_RECURSIVE */
{
@@ -1063,6 +1064,7 @@ bgp_update_next_hop_ip(struct bgp_export_state *s, eattr *a, ea_list **to)
{
ip_addr nh[2] = { s->channel->next_hop_addr, s->channel->link_addr };
bgp_set_attr_data(to, s->pool, BA_NEXT_HOP, 0, nh, ipa_nonzero(nh[1]) ? 32 : 16);
+ s->local_next_hop = 1;
/* TODO: Use local MPLS assigned label */
if (s->mpls)
@@ -2401,6 +2403,7 @@ bgp_decode_nlri(struct bgp_parse_state *s, u32 afi, byte *nlri, uint len, ea_lis
a->eattrs = ea;
c->desc->decode_next_hop(s, nh, nh_len, a);
+ bgp_finish_attrs(s, a);
/* Handle withdraw during next hop decoding */
if (s->err_withdraw)