bgpd: encap: add attribute handling
Signed-off-by: Lou Berger <lberger@labn.net>
Reviewed-by: David Lamparter <equinox@opensourcerouting.org>
diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c
index a25ea76..b019347 100644
--- a/bgpd/bgp_attr.c
+++ b/bgpd/bgp_attr.c
@@ -39,6 +39,8 @@
#include "bgpd/bgp_debug.h"
#include "bgpd/bgp_packet.h"
#include "bgpd/bgp_ecommunity.h"
+#include "table.h"
+#include "bgp_encap_types.h"
/* Attribute strings for logging. */
static const struct message attr_str [] =
@@ -62,6 +64,7 @@
{ BGP_ATTR_AS4_PATH, "AS4_PATH" },
{ BGP_ATTR_AS4_AGGREGATOR, "AS4_AGGREGATOR" },
{ BGP_ATTR_AS_PATHLIMIT, "AS_PATHLIMIT" },
+ { BGP_ATTR_ENCAP, "ENCAP" },
};
static const int attr_str_max = array_size(attr_str);
@@ -209,6 +212,105 @@
cluster_hash = NULL;
}
+struct bgp_attr_encap_subtlv *
+encap_tlv_dup(struct bgp_attr_encap_subtlv *orig)
+{
+ struct bgp_attr_encap_subtlv *new;
+ struct bgp_attr_encap_subtlv *tail;
+ struct bgp_attr_encap_subtlv *p;
+
+ for (p = orig, tail = new = NULL; p; p = p->next) {
+ int size = sizeof(struct bgp_attr_encap_subtlv) - 1 + p->length;
+ if (tail) {
+ tail->next = XCALLOC(MTYPE_ENCAP_TLV, size);
+ tail = tail->next;
+ } else {
+ tail = new = XCALLOC(MTYPE_ENCAP_TLV, size);
+ }
+ assert(tail);
+ memcpy(tail, p, size);
+ tail->next = NULL;
+ }
+
+ return new;
+}
+
+static void
+encap_free(struct bgp_attr_encap_subtlv *p)
+{
+ struct bgp_attr_encap_subtlv *next;
+ while (p) {
+ next = p->next;
+ p->next = NULL;
+ XFREE(MTYPE_ENCAP_TLV, p);
+ p = next;
+ }
+}
+
+void
+bgp_attr_flush_encap(struct attr *attr)
+{
+ if (!attr || !attr->extra)
+ return;
+
+ if (attr->extra->encap_subtlvs) {
+ encap_free(attr->extra->encap_subtlvs);
+ attr->extra->encap_subtlvs = NULL;
+ }
+}
+
+/*
+ * Compare encap sub-tlv chains
+ *
+ * 1 = equivalent
+ * 0 = not equivalent
+ *
+ * This algorithm could be made faster if needed
+ */
+static int
+encap_same(struct bgp_attr_encap_subtlv *h1, struct bgp_attr_encap_subtlv *h2)
+{
+ struct bgp_attr_encap_subtlv *p;
+ struct bgp_attr_encap_subtlv *q;
+
+ if (!h1 && !h2)
+ return 1;
+ if (h1 && !h2)
+ return 0;
+ if (!h1 && h2)
+ return 0;
+ if (h1 == h2)
+ return 1;
+
+ for (p = h1; p; p = p->next) {
+ for (q = h2; q; q = q->next) {
+ if ((p->type == q->type) &&
+ (p->length == q->length) &&
+ !memcmp(p->value, q->value, p->length)) {
+
+ break;
+ }
+ }
+ if (!q)
+ return 0;
+ }
+
+ for (p = h2; p; p = p->next) {
+ for (q = h1; q; q = q->next) {
+ if ((p->type == q->type) &&
+ (p->length == q->length) &&
+ !memcmp(p->value, q->value, p->length)) {
+
+ break;
+ }
+ }
+ if (!q)
+ return 0;
+ }
+
+ return 1;
+}
+
/* Unknown transit attribute. */
static struct hash *transit_hash;
@@ -300,6 +402,10 @@
{
if (attr->extra)
{
+ if (attr->extra->encap_subtlvs) {
+ encap_free(attr->extra->encap_subtlvs);
+ attr->extra->encap_subtlvs = NULL;
+ }
XFREE (MTYPE_ATTR_EXTRA, attr->extra);
attr->extra = NULL;
}
@@ -335,13 +441,20 @@
{
new->extra = extra;
memset(new->extra, 0, sizeof(struct attr_extra));
- if (orig->extra)
+ if (orig->extra) {
*new->extra = *orig->extra;
+ if (orig->extra->encap_subtlvs) {
+ new->extra->encap_subtlvs = encap_tlv_dup(orig->extra->encap_subtlvs);
+ }
+ }
}
else if (orig->extra)
{
new->extra = bgp_attr_extra_new();
*new->extra = *orig->extra;
+ if (orig->extra->encap_subtlvs) {
+ new->extra->encap_subtlvs = encap_tlv_dup(orig->extra->encap_subtlvs);
+ }
}
}
@@ -438,6 +551,8 @@
&& ae1->ecommunity == ae2->ecommunity
&& ae1->cluster == ae2->cluster
&& ae1->transit == ae2->transit
+ && (ae1->encap_tunneltype == ae2->encap_tunneltype)
+ && encap_same(ae1->encap_subtlvs, ae2->encap_subtlvs)
&& IPV4_ADDR_SAME (&ae1->originator_id, &ae2->originator_id))
return 1;
else if (ae1 || ae2)
@@ -503,6 +618,10 @@
{
attr->extra = bgp_attr_extra_new ();
*attr->extra = *val->extra;
+
+ if (attr->extra->encap_subtlvs) {
+ attr->extra->encap_subtlvs = encap_tlv_dup(attr->extra->encap_subtlvs);
+ }
}
attr->refcnt = 0;
return attr;
@@ -592,6 +711,9 @@
struct attr attr;
struct attr *new;
+ memset (&attr, 0, sizeof (struct attr));
+ bgp_attr_extra_get (&attr);
+
bgp_attr_default_set(&attr, origin);
new = bgp_attr_intern (&attr);
@@ -731,6 +853,8 @@
cluster_free (attre->cluster);
if (attre->transit && ! attre->transit->refcnt)
transit_free (attre->transit);
+ encap_free(attre->encap_subtlvs);
+ attre->encap_subtlvs = NULL;
}
}
@@ -1710,6 +1834,122 @@
return BGP_ATTR_PARSE_PROCEED;
}
+/* Parse Tunnel Encap attribute in an UPDATE */
+static int
+bgp_attr_encap(
+ uint8_t type,
+ struct peer *peer, /* IN */
+ bgp_size_t length, /* IN: attr's length field */
+ struct attr *attr, /* IN: caller already allocated */
+ u_char flag, /* IN: attr's flags field */
+ u_char *startp)
+{
+ bgp_size_t total;
+ struct attr_extra *attre = NULL;
+ struct bgp_attr_encap_subtlv *stlv_last = NULL;
+ uint16_t tunneltype;
+
+ total = length + (CHECK_FLAG (flag, BGP_ATTR_FLAG_EXTLEN) ? 4 : 3);
+
+ if (!CHECK_FLAG(flag, BGP_ATTR_FLAG_TRANS)
+ || !CHECK_FLAG(flag, BGP_ATTR_FLAG_OPTIONAL))
+ {
+ zlog (peer->log, LOG_ERR,
+ "Tunnel Encap attribute flag isn't optional and transitive %d", flag);
+ bgp_notify_send_with_data (peer,
+ BGP_NOTIFY_UPDATE_ERR,
+ BGP_NOTIFY_UPDATE_ATTR_FLAG_ERR,
+ startp, total);
+ return -1;
+ }
+
+ if (BGP_ATTR_ENCAP == type) {
+ /* read outer TLV type and length */
+ uint16_t tlv_length;
+
+ if (length < 4) {
+ zlog (peer->log, LOG_ERR,
+ "Tunnel Encap attribute not long enough to contain outer T,L");
+ bgp_notify_send_with_data(peer,
+ BGP_NOTIFY_UPDATE_ERR,
+ BGP_NOTIFY_UPDATE_OPT_ATTR_ERR,
+ startp, total);
+ return -1;
+ }
+ tunneltype = stream_getw (BGP_INPUT (peer));
+ tlv_length = stream_getw (BGP_INPUT (peer));
+ length -= 4;
+
+ if (tlv_length != length) {
+ zlog (peer->log, LOG_ERR, "%s: tlv_length(%d) != length(%d)",
+ __func__, tlv_length, length);
+ }
+ }
+
+ while (length >= 4) {
+ uint16_t subtype;
+ uint16_t sublength;
+ struct bgp_attr_encap_subtlv *tlv;
+
+ subtype = stream_getw (BGP_INPUT (peer));
+ sublength = stream_getw (BGP_INPUT (peer));
+ length -= 4;
+
+ if (sublength > length) {
+ zlog (peer->log, LOG_ERR,
+ "Tunnel Encap attribute sub-tlv length %d exceeds remaining length %d",
+ sublength, length);
+ bgp_notify_send_with_data (peer,
+ BGP_NOTIFY_UPDATE_ERR,
+ BGP_NOTIFY_UPDATE_OPT_ATTR_ERR,
+ startp, total);
+ return -1;
+ }
+
+ /* alloc and copy sub-tlv */
+ /* TBD make sure these are freed when attributes are released */
+ tlv = XCALLOC (MTYPE_ENCAP_TLV, sizeof(struct bgp_attr_encap_subtlv)-1+sublength);
+ tlv->type = subtype;
+ tlv->length = sublength;
+ stream_get(tlv->value, peer->ibuf, sublength);
+ length -= sublength;
+
+ /* attach tlv to encap chain */
+ if (!attre) {
+ attre = bgp_attr_extra_get(attr);
+ if (BGP_ATTR_ENCAP == type) {
+ for (stlv_last = attre->encap_subtlvs; stlv_last && stlv_last->next;
+ stlv_last = stlv_last->next);
+ if (stlv_last) {
+ stlv_last->next = tlv;
+ } else {
+ attre->encap_subtlvs = tlv;
+ }
+ }
+ } else {
+ stlv_last->next = tlv;
+ }
+ stlv_last = tlv;
+ }
+
+ if (attre && (BGP_ATTR_ENCAP == type)) {
+ attre->encap_tunneltype = tunneltype;
+ }
+
+ if (length) {
+ /* spurious leftover data */
+ zlog (peer->log, LOG_ERR,
+ "Tunnel Encap attribute length is bad: %d leftover octets", length);
+ bgp_notify_send_with_data (peer,
+ BGP_NOTIFY_UPDATE_ERR,
+ BGP_NOTIFY_UPDATE_OPT_ATTR_ERR,
+ startp, total);
+ return -1;
+ }
+
+ return 0;
+}
+
/* BGP unknown attribute treatment. */
static bgp_attr_parse_ret_t
bgp_attr_unknown (struct bgp_attr_parser_args *args)
@@ -2008,6 +2248,9 @@
case BGP_ATTR_EXT_COMMUNITIES:
ret = bgp_attr_ext_communities (&attr_args);
break;
+ case BGP_ATTR_ENCAP:
+ ret = bgp_attr_encap (type, peer, length, attr, flag, startp);
+ break;
default:
ret = bgp_attr_unknown (&attr_args);
break;
@@ -2262,6 +2505,88 @@
return size;
}
+/*
+ * Encodes the tunnel encapsulation attribute
+ */
+static void
+bgp_packet_mpattr_tea(
+ struct bgp *bgp,
+ struct peer *peer,
+ struct stream *s,
+ struct attr *attr,
+ uint8_t attrtype)
+{
+ unsigned int attrlenfield = 0;
+ struct bgp_attr_encap_subtlv *subtlvs;
+ struct bgp_attr_encap_subtlv *st;
+ const char *attrname;
+
+ if (!attr || !attr->extra)
+ return;
+
+ switch (attrtype) {
+ case BGP_ATTR_ENCAP:
+ attrname = "Tunnel Encap";
+ subtlvs = attr->extra->encap_subtlvs;
+
+ /*
+ * The tunnel encap attr has an "outer" tlv.
+ * T = tunneltype,
+ * L = total length of subtlvs,
+ * V = concatenated subtlvs.
+ */
+ attrlenfield = 2 + 2; /* T + L */
+ break;
+
+ default:
+ assert(0);
+ }
+
+
+ /* compute attr length */
+ for (st = subtlvs; st; st = st->next) {
+ attrlenfield += (4 + st->length);
+ }
+
+ /* if no tlvs, don't make attr */
+ if (!attrlenfield)
+ return;
+
+ if (attrlenfield > 0xffff) {
+ zlog (peer->log, LOG_ERR,
+ "%s attribute is too long (length=%d), can't send it",
+ attrname,
+ attrlenfield);
+ return;
+ }
+
+ if (attrlenfield > 0xff) {
+ /* 2-octet length field */
+ stream_putc (s,
+ BGP_ATTR_FLAG_TRANS|BGP_ATTR_FLAG_OPTIONAL|BGP_ATTR_FLAG_EXTLEN);
+ stream_putc (s, attrtype);
+ stream_putw (s, attrlenfield & 0xffff);
+ } else {
+ /* 1-octet length field */
+ stream_putc (s, BGP_ATTR_FLAG_TRANS|BGP_ATTR_FLAG_OPTIONAL);
+ stream_putc (s, attrtype);
+ stream_putc (s, attrlenfield & 0xff);
+ }
+
+ if (attrtype == BGP_ATTR_ENCAP) {
+ /* write outer T+L */
+ stream_putw(s, attr->extra->encap_tunneltype);
+ stream_putw(s, attrlenfield - 4);
+ }
+
+ /* write each sub-tlv */
+ for (st = subtlvs; st; st = st->next) {
+ stream_putw (s, st->type);
+ stream_putw (s, st->length);
+ stream_put (s, st->value, st->length);
+ }
+}
+
void
bgp_packet_mpattr_end (struct stream *s, size_t sizep)
{