-/* $OpenBSD: bgpd.h,v 1.470 2023/04/03 10:48:00 claudio Exp $ */
+/* $OpenBSD: bgpd.h,v 1.471 2023/04/17 08:02:21 claudio Exp $ */
/*
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
extern const struct ext_comm_pairs iana_ext_comms[];
/* BGP flowspec defines RFC 8955 and 8956 */
-#define FLOWSPEC_LEN_LIMIT 0xf0
-#define FLOWSPEC_OP_EOL 0x80
-#define FLOWSPEC_OP_AND 0x40
-#define FLOWSPEC_OP_LEN_MASK 0x30
-#define FLOWSPEC_OP_LEN_SHIFT 4
+#define FLOWSPEC_LEN_LIMIT 0xf0
+#define FLOWSPEC_OP_EOL 0x80
+#define FLOWSPEC_OP_AND 0x40
+#define FLOWSPEC_OP_LEN_MASK 0x30
+#define FLOWSPEC_OP_LEN_SHIFT 4
#define FLOWSPEC_OP_LEN(op) \
(1 << (((op) & FLOWSPEC_OP_LEN_MASK) >> FLOWSPEC_OP_LEN_SHIFT))
-#define FLOWSPEC_OP_NUM_LT 0x04
-#define FLOWSPEC_OP_NUM_GT 0x02
-#define FLOWSPEC_OP_NUM_EQ 0x01
-#define FLOWSPEC_OP_BIT_NOT 0x02
-#define FLOWSPEC_OP_BIT_MATCH 0x01
+#define FLOWSPEC_OP_NUM_LT 0x04
+#define FLOWSPEC_OP_NUM_GT 0x02
+#define FLOWSPEC_OP_NUM_EQ 0x01
+#define FLOWSPEC_OP_NUM_LE (FLOWSPEC_OP_NUM_LT | FLOWSPEC_OP_NUM_EQ)
+#define FLOWSPEC_OP_NUM_GE (FLOWSPEC_OP_NUM_GT | FLOWSPEC_OP_NUM_EQ)
+#define FLOWSPEC_OP_NUM_NOT (FLOWSPEC_OP_NUM_GT | FLOWSPEC_OP_NUM_LT)
+#define FLOWSPEC_OP_NUM_MASK 0x07
+#define FLOWSPEC_OP_BIT_NOT 0x02
+#define FLOWSPEC_OP_BIT_MATCH 0x01
+#define FLOWSPEC_OP_BIT_MASK 0x03
#define FLOWSPEC_TYPE_MIN 1
#define FLOWSPEC_TYPE_DEST 1
#define FLOWSPEC_TYPE_DSCP 11
#define FLOWSPEC_TYPE_FRAG 12
#define FLOWSPEC_TYPE_FLOW 13
-#define FLOWSPEC_TYPE_MAX 13
+#define FLOWSPEC_TYPE_MAX 14
struct filter_prefix {
struct bgpd_addr addr;
#define AS_ERR_BAD -3
#define AS_ERR_SOFT -4
u_char *aspath_inflate(void *, uint16_t, uint16_t *);
+int extract_prefix(const u_char *, int, void *, uint8_t, uint8_t);
int nlri_get_prefix(u_char *, uint16_t, struct bgpd_addr *,
uint8_t *);
int nlri_get_prefix6(u_char *, uint16_t, struct bgpd_addr *,
void sa2addr(struct sockaddr *, struct bgpd_addr *, uint16_t *);
const char * get_baudrate(unsigned long long, char *);
+/* flowspec.c */
+int flowspec_valid(const uint8_t *, int, int);
+int flowspec_cmp(const uint8_t *, int, const uint8_t *, int, int);
+int flowspec_get_component(const uint8_t *, int, int, int,
+ const uint8_t **, int *);
+int flowspec_get_addr(const uint8_t *, int, int, int, struct bgpd_addr *,
+ uint8_t *, uint8_t *);
+const char *flowspec_fmt_label(int);
+const char *flowspec_fmt_num_op(const uint8_t *, int, int *);
+const char *flowspec_fmt_bin_op(const uint8_t *, int, int *, const char *);
+
static const char * const log_procnames[] = {
"parent",
"SE",
--- /dev/null
+/* $OpenBSD: flowspec.c,v 1.1 2023/04/17 08:02:21 claudio Exp $ */
+
+/*
+ * Copyright (c) 2023 Claudio Jeker <claudio@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include "bgpd.h"
+#include "rde.h"
+
+/*
+ * Extract the next component from a flowspec NLRI buffer.
+ * Returns the length of the component including type field or -1 on failure.
+ * Also checks that the prefix encoding is valid.
+ */
+static int
+flowspec_next_component(const uint8_t *buf, int len, int is_v6, int *type)
+{
+ int vlen = 0;
+ uint8_t plen, off, op;
+
+ *type = 0;
+ if (len < 1)
+ return -1;
+ *type = buf[vlen];
+ vlen++;
+ if (*type < FLOWSPEC_TYPE_MIN || *type >= FLOWSPEC_TYPE_MAX)
+ return -1;
+
+ switch (*type) {
+ case FLOWSPEC_TYPE_DEST:
+ case FLOWSPEC_TYPE_SOURCE:
+ if (!is_v6) {
+ /* regular RFC 4271 encoding of prefixes */
+ if (len < vlen + 1)
+ return -1;
+ plen = buf[vlen];
+ vlen += PREFIX_SIZE(plen);
+ if (plen > 32 || len < vlen)
+ return -1;
+ } else {
+ /* special RFC 8956 encoding with extra offset */
+ if (len < vlen + 2)
+ return -1;
+ plen = buf[vlen];
+ off = buf[vlen + 1];
+ if (plen > 128 || off >= plen)
+ return -1;
+ vlen += PREFIX_SIZE(plen - off) + 1; /* off is extra */
+ if (len < vlen)
+ return -1;
+ }
+ break;
+ case FLOWSPEC_TYPE_FLOW:
+ if (!is_v6)
+ return -1;
+ /* FALLTHROUGH */
+ default:
+ do {
+ if (len < vlen + 1)
+ return -1;
+ op = buf[vlen];
+ /* first component cannot have and flag set */
+ if (vlen == 1 && op & FLOWSPEC_OP_AND)
+ return -1;
+ vlen += FLOWSPEC_OP_LEN(op) + 1;
+
+ if (len < vlen)
+ return -1;
+ } while ((op & FLOWSPEC_OP_EOL) == 0);
+ break;
+ }
+ return vlen;
+}
+
+#define MINIMUM(a, b) ((a) < (b) ? (a) : (b))
+
+/*
+ * Compare two IPv4 flowspec prefix components.
+ * Returns 1 if first prefix is preferred, -1 if second, 0 if equal.
+ */
+static int
+flowspec_cmp_prefix4(const uint8_t *abuf, int ablen, const uint8_t *bbuf,
+ int bblen)
+{
+ uint8_t a[4] = { 0 }, b[4] = { 0 };
+ int alen, blen, clen, cmp;
+
+ alen = abuf[1];
+ blen = bbuf[1];
+ clen = MINIMUM(alen, blen);
+
+ /* only extract the common prefix */
+ if (extract_prefix(abuf + 2, ablen - 2, &a, clen, sizeof(a)) == -1)
+ fatalx("bad flowspec prefix encoding");
+ if (extract_prefix(bbuf + 2, bblen - 2, &b, clen, sizeof(b)) == -1)
+ fatalx("bad flowspec prefix encoding");
+
+ /* lowest IP value has precedence */
+ cmp = memcmp(a, b, sizeof(a));
+ if (cmp < 0)
+ return 1;
+ if (cmp > 0)
+ return -1;
+
+ /* if common prefix, more specific route has precedence */
+ if (alen > blen)
+ return 1;
+ if (alen < blen)
+ return -1;
+ return 0;
+}
+
+/*
+ * Compare two IPv6 flowspec prefix components.
+ * Returns 1 if first prefix is preferred, -1 if second, 0 if equal.
+ * As usual the encoding of IPv6 addresses is extra complex.
+ */
+static int
+flowspec_cmp_prefix6(const uint8_t *abuf, int ablen, const uint8_t *bbuf,
+ int bblen)
+{
+ uint8_t a[16] = { 0 }, b[16] = { 0 };
+ int alen, blen, clen, cmp;
+
+ /* lowest offset has precedence */
+ if (abuf[2] < bbuf[2])
+ return 1;
+ if (abuf[2] > bbuf[2])
+ return -1;
+
+ /* calculate actual pattern size (len - offset) */
+ alen = abuf[1] - abuf[2];
+ blen = bbuf[1] - bbuf[2];
+ clen = MINIMUM(alen, blen);
+
+ /* only extract the common prefix */
+ if (extract_prefix(abuf + 3, ablen - 3, &a, clen, sizeof(a)) == -1)
+ fatalx("bad flowspec prefix encoding");
+ if (extract_prefix(bbuf + 3, bblen - 3, &b, clen, sizeof(b)) == -1)
+ fatalx("bad flowspec prefix encoding");
+
+ /* lowest IP value has precedence */
+ cmp = memcmp(a, b, sizeof(a));
+ if (cmp < 0)
+ return 1;
+ if (cmp > 0)
+ return -1;
+
+ /* if common prefix, more specific route has precedence */
+ if (alen > blen)
+ return 1;
+ if (alen < blen)
+ return -1;
+ return 0;
+}
+
+/*
+ * Check if the flowspec NLRI is syntactically valid.
+ */
+int
+flowspec_valid(const uint8_t *buf, int len, int is_v6)
+{
+ int l, type, prev = 0;
+
+ /* empty NLRI is invalid */
+ if (len == 0)
+ return -1;
+
+ while (len > 0) {
+ l = flowspec_next_component(buf, len, is_v6, &type);
+ if (l == -1)
+ return -1;
+ /* ensure that types are ordered */
+ if (prev >= type)
+ return -1;
+ prev = type;
+ buf += l;
+ len -= l;
+ }
+ if (len < 0)
+ fatalx("flowspec overflow");
+ return 0;
+}
+
+/*
+ * Compare two valid flowspec NLRI objects according to RFC 8955 & 8956.
+ * Returns 1 if the first object has preference, -1 if not, and 0 if the
+ * two objects are equal.
+ */
+int
+flowspec_cmp(const uint8_t *a, int alen, const uint8_t *b, int blen, int is_v6)
+{
+ int atype, btype;
+ int acomplen, bcomplen;
+ int cmp;
+
+ while (alen > 0 && blen > 0) {
+ acomplen = flowspec_next_component(a, alen, is_v6, &atype);
+ if (acomplen == -1)
+ fatalx("bad flowspec component");
+ bcomplen = flowspec_next_component(b, blen, is_v6, &btype);
+ if (acomplen == -1)
+ fatalx("bad flowspec component");
+
+ /* If types differ, lowest type wins. */
+ if (atype < btype)
+ return 1;
+ if (atype > btype)
+ return -1;
+
+ switch (atype) {
+ case FLOWSPEC_TYPE_DEST:
+ case FLOWSPEC_TYPE_SOURCE:
+ if (!is_v6) {
+ cmp = flowspec_cmp_prefix4(a, acomplen,
+ b, bcomplen);
+ } else {
+ cmp = flowspec_cmp_prefix6(a, acomplen,
+ b, bcomplen);
+ }
+ if (cmp != 0)
+ return cmp;
+ break;
+ default:
+ cmp = memcmp(a, b, MINIMUM(acomplen, bcomplen));
+ /*
+ * Lowest common component prefix wins also
+ * if both commponents are same length also lowest
+ * string has precedence.
+ */
+ if (cmp < 0)
+ return 1;
+ if (cmp > 0)
+ return -1;
+ /*
+ * Longest component wins when common prefix is equal.
+ * This is not really possible because EOL encoding will
+ * always tie break on the memcmp but the RFC mandates
+ * it (and it is cheap).
+ */
+ if (acomplen > bcomplen)
+ return 1;
+ if (acomplen < bcomplen)
+ return -1;
+ break;
+ }
+ a += acomplen;
+ alen -= acomplen;
+ b += bcomplen;
+ blen -= bcomplen;
+
+ /* Rule with more components wins */
+ if (alen > 0 && blen <= 0)
+ return 1;
+ if (alen <= 0 && blen > 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void
+shift_right(uint8_t *dst, const uint8_t *src, int off, int len)
+{
+ uint8_t carry = 0;
+ int i;
+
+ dst += off / 8; /* go to inital start point */
+ off %= 8;
+ len = (len + 7) / 8; /* how much to copy in bytes */
+
+ for (i = 0; i < len; i++) {
+ dst[i] = carry | src[i] >> off;
+ if (off != 0)
+ carry = src[i] << (8 - off);
+ }
+ dst[i] = carry;
+}
+
+/*
+ * Extract a flowspec component and return its buffer and size.
+ * Returns 1 on success, 0 if component is not present and -1 on error.
+ */
+int
+flowspec_get_component(const uint8_t *flow, int flowlen, int type, int is_v6,
+ const uint8_t **buf, int *len)
+{
+ int complen, t;
+
+ *buf = NULL;
+ *len = 0;
+
+ do {
+ complen = flowspec_next_component(flow, flowlen, is_v6, &t);
+ if (complen == -1)
+ return -1;
+ if (type == t)
+ break;
+ if (type < t)
+ return 0;
+
+ flow += complen;
+ flowlen -= complen;
+ } while (1);
+
+ *buf = flow + 1;
+ *len = complen - 1;
+
+ return 1;
+}
+
+/*
+ * Extract source or destination address into provided bgpd_addr.
+ * Returns 1 on success, 0 if no address was present, -1 on error.
+ * Sets plen to the prefix len and olen to the offset for IPv6 case.
+ * If olen is set to NULL when an IPv6 prefix with offset is fetched
+ * the function will return -1.
+ */
+int
+flowspec_get_addr(const uint8_t *flow, int flowlen, int type, int is_v6,
+ struct bgpd_addr *addr, uint8_t *plen, uint8_t *olen)
+{
+ const uint8_t *comp;
+ int complen, rv;
+
+ memset(addr, 0, sizeof(*addr));
+ *plen = 0;
+ if (olen != NULL)
+ *olen = 0;
+
+ rv = flowspec_get_component(flow, flowlen, type, is_v6,
+ &comp, &complen);
+ if (rv != 1)
+ return rv;
+
+ /* flowspec_get_component only returns valid encodings */
+ if (!is_v6) {
+ addr->aid = AID_INET;
+ if (extract_prefix(comp + 1, complen - 1, &addr->v4, comp[0],
+ sizeof(addr->v4)) == -1)
+ return -1;
+ *plen = comp[0];
+ } else {
+ uint8_t buf[16] = { 0 };
+ int xlen, xoff = 0;
+
+ addr->aid = AID_INET6;
+ xlen = comp[0];
+ if (comp[1] != 0) {
+ if (olen == NULL)
+ return -1;
+ xoff = comp[1];
+ xlen -= xoff;
+ }
+ if (extract_prefix(comp + 2, complen - 2, buf, xlen,
+ sizeof(buf)) == -1)
+ return -1;
+ shift_right(addr->v6.s6_addr, buf, *olen, xlen);
+ *plen = comp[0];
+ if (olen != NULL)
+ *olen = comp[1];
+ }
+
+ return 1;
+}
+
+const char *
+flowspec_fmt_label(int type)
+{
+ switch (type) {
+ case FLOWSPEC_TYPE_DEST:
+ return "to";
+ case FLOWSPEC_TYPE_SOURCE:
+ return "from";
+ case FLOWSPEC_TYPE_PROTO:
+ return "proto";
+ case FLOWSPEC_TYPE_PORT:
+ case FLOWSPEC_TYPE_DST_PORT:
+ case FLOWSPEC_TYPE_SRC_PORT:
+ return "port";
+ case FLOWSPEC_TYPE_ICMP_TYPE:
+ return "icmp-type";
+ case FLOWSPEC_TYPE_ICMP_CODE:
+ return "icmp-code";
+ case FLOWSPEC_TYPE_TCP_FLAGS:
+ return "flags";
+ case FLOWSPEC_TYPE_PKT_LEN:
+ return "length";
+ case FLOWSPEC_TYPE_DSCP:
+ return "tos";
+ case FLOWSPEC_TYPE_FRAG:
+ return "fragment";
+ case FLOWSPEC_TYPE_FLOW:
+ return "flow";
+ default:
+ return "???";
+ }
+}
+
+static uint64_t
+extract_val(const uint8_t *comp, int len)
+{
+ uint64_t val = 0;
+
+ while (len-- > 0) {
+ val <<= 8;
+ val |= *comp++;
+ }
+ return val;
+}
+
+const char *
+flowspec_fmt_num_op(const uint8_t *comp, int complen, int *off)
+{
+ static char buf[32];
+ uint64_t val, val2 = 0;
+ uint8_t op, op2 = 0;
+ int len, len2 = 0;
+
+ if (*off == -1)
+ return "";
+ if (complen < *off + 1)
+ return "bad encoding";
+
+ op = comp[*off];
+ len = FLOWSPEC_OP_LEN(op) + 1;
+ if (complen < *off + len)
+ return "bad encoding";
+ val = extract_val(comp + *off + 1, FLOWSPEC_OP_LEN(op));
+
+ if ((op & FLOWSPEC_OP_EOL) == 0) {
+ if (complen < *off + len + 1)
+ return "bad encoding";
+ op2 = comp[*off + len];
+ /*
+ * Check if this is a range specification else fall back
+ * to basic rules.
+ */
+ if (op2 & FLOWSPEC_OP_AND &&
+ (op & FLOWSPEC_OP_NUM_MASK) == FLOWSPEC_OP_NUM_GE &&
+ (op2 & FLOWSPEC_OP_NUM_MASK) == FLOWSPEC_OP_NUM_LE) {
+ len2 = FLOWSPEC_OP_LEN(op2) + 1;
+ val2 = extract_val(comp + *off + len + 1,
+ FLOWSPEC_OP_LEN(op2));
+ } else
+ op2 = 0;
+ }
+
+ if (op2 & FLOWSPEC_OP_AND) {
+ /* binary range operation */
+ snprintf(buf, sizeof(buf), "%llu - %llu",
+ (unsigned long long)val, (unsigned long long)val2);
+ } else {
+ /* unary operation */
+ switch (op & FLOWSPEC_OP_NUM_MASK) {
+ case 0:
+ snprintf(buf, sizeof(buf), "%sfalse",
+ op & FLOWSPEC_OP_AND ? "&& " : "");
+ break;
+ case FLOWSPEC_OP_NUM_EQ:
+ snprintf(buf, sizeof(buf), "%s%llu",
+ op & FLOWSPEC_OP_AND ? "&& " : "",
+ (unsigned long long)val);
+ break;
+ case FLOWSPEC_OP_NUM_GT:
+ snprintf(buf, sizeof(buf), "%s> %llu",
+ op & FLOWSPEC_OP_AND ? "&& " : "",
+ (unsigned long long)val);
+ break;
+ case FLOWSPEC_OP_NUM_GE:
+ snprintf(buf, sizeof(buf), "%s>= %llu",
+ op & FLOWSPEC_OP_AND ? "&& " : "",
+ (unsigned long long)val);
+ break;
+ case FLOWSPEC_OP_NUM_LT:
+ snprintf(buf, sizeof(buf), "%s< %llu",
+ op & FLOWSPEC_OP_AND ? "&& " : "",
+ (unsigned long long)val);
+ break;
+ case FLOWSPEC_OP_NUM_LE:
+ snprintf(buf, sizeof(buf), "%s<= %llu",
+ op & FLOWSPEC_OP_AND ? "&& " : "",
+ (unsigned long long)val);
+ break;
+ case FLOWSPEC_OP_NUM_NOT:
+ snprintf(buf, sizeof(buf), "%s!= %llu",
+ op & FLOWSPEC_OP_AND ? "&& " : "",
+ (unsigned long long)val);
+ break;
+ case 0x7:
+ snprintf(buf, sizeof(buf), "%strue",
+ op & FLOWSPEC_OP_AND ? "&& " : "");
+ break;
+ }
+ }
+
+ if (op2 & FLOWSPEC_OP_EOL || op & FLOWSPEC_OP_EOL)
+ *off = -1;
+ else
+ *off += len + len2;
+
+ return buf;
+}
+
+static const char *
+fmt_flags(uint64_t val, const char *bits, char *buf, size_t blen)
+{
+ int i, bi;
+
+ for (i = 0, bi = 0; i < 64 && val != 0; i++) {
+ if (val & 1) {
+ if (bits[i] == '\0' || bits[i] == ' ')
+ goto fail;
+ buf[bi++] = bits[i];
+ }
+ val >>= 1;
+ }
+ buf[bi++] = '\0';
+ return buf;
+
+fail:
+ snprintf(buf, blen, "%llx", (unsigned long long)val);
+ return buf;
+}
+
+const char *
+flowspec_fmt_bin_op(const uint8_t *comp, int complen, int *off,
+ const char *bits)
+{
+ static char buf[36], bit[17], mask[17];
+ uint64_t val, val2 = 0;
+ uint8_t op, op2 = 0;
+ int len, len2 = 0;
+
+ if (*off == -1)
+ return "";
+ if (complen < *off + 1)
+ return "bad encoding";
+
+ op = comp[*off];
+ len = FLOWSPEC_OP_LEN(op) + 1;
+ if (complen < *off + len)
+ return "bad encoding";
+ val = extract_val(comp + *off + 1, FLOWSPEC_OP_LEN(op));
+
+ if ((op & FLOWSPEC_OP_EOL) == 0) {
+ if (complen < *off + len + 1)
+ return "bad encoding";
+ op2 = comp[*off + len];
+ /*
+ * Check if this is a mask specification else fall back
+ * to basic rules.
+ */
+ if (op2 & FLOWSPEC_OP_AND &&
+ (op & FLOWSPEC_OP_BIT_MASK) == FLOWSPEC_OP_BIT_MATCH &&
+ (op2 & FLOWSPEC_OP_BIT_MASK) == FLOWSPEC_OP_BIT_NOT) {
+ len2 = FLOWSPEC_OP_LEN(op2) + 1;
+ val2 = extract_val(comp + *off + len + 1,
+ FLOWSPEC_OP_LEN(op2));
+ } else
+ op2 = 0;
+ }
+
+ if (op2 & FLOWSPEC_OP_AND) {
+ val2 |= val;
+ snprintf(buf, sizeof(buf), "%s / %s",
+ fmt_flags(val, bits, bit, sizeof(bit)),
+ fmt_flags(val2, bits, mask, sizeof(mask)));
+ } else {
+ switch (op & FLOWSPEC_OP_BIT_MASK) {
+ case 0:
+ snprintf(buf, sizeof(buf), "%s",
+ fmt_flags(val, bits, bit, sizeof(bit)));
+ break;
+ case FLOWSPEC_OP_BIT_NOT:
+ snprintf(buf, sizeof(buf), "/ %s",
+ fmt_flags(val, bits, mask, sizeof(mask)));
+ break;
+ case FLOWSPEC_OP_BIT_MATCH:
+ snprintf(buf, sizeof(buf), "%s / %s",
+ fmt_flags(val, bits, bit, sizeof(bit)),
+ fmt_flags(val, bits, mask, sizeof(mask)));
+ break;
+ case FLOWSPEC_OP_BIT_NOT | FLOWSPEC_OP_BIT_MATCH:
+ snprintf(buf, sizeof(buf), "???");
+ break;
+ }
+ }
+
+ if (op2 & FLOWSPEC_OP_EOL || op & FLOWSPEC_OP_EOL)
+ *off = -1;
+ else
+ *off += len + len2;
+
+ return buf;
+}