#include <sys/queue.h>
#include <endian.h>
#include <limits.h>
#include <siphash.h>
#include <stdlib.h>
#include <string.h>
#include "bgpd.h"
#include "rde.h"
#include "log.h"
#include "chash.h"
int
attr_writebuf(struct ibuf *buf, uint8_t flags, uint8_t type, void *data,
uint16_t data_len)
{
u_char hdr[4];
flags &= ~ATTR_DEFMASK;
if (data_len > 255) {
flags |= ATTR_EXTLEN;
hdr[2] = (data_len >> 8) & 0xff;
hdr[3] = data_len & 0xff;
} else {
hdr[2] = data_len & 0xff;
}
hdr[0] = flags;
hdr[1] = type;
if (ibuf_add(buf, hdr, flags & ATTR_EXTLEN ? 4 : 3) == -1)
return (-1);
if (data != NULL && ibuf_add(buf, data, data_len) == -1)
return (-1);
return (0);
}
static struct attr *attr_alloc(uint8_t, uint8_t, void *, uint16_t, uint64_t);
static struct attr *attr_lookup(uint8_t, uint8_t, void *, uint16_t, uint64_t);
static void attr_put(struct attr *);
static SIPHASH_KEY attrkey;
static inline uint64_t
attr_hash(const struct attr *a)
{
return a->hash;
}
static uint64_t
attr_calc_hash(uint8_t type, const void *data, uint16_t len)
{
SIPHASH_CTX ctx;
SipHash24_Init(&ctx, &attrkey);
SipHash24_Update(&ctx, data, len);
SipHash24_Update(&ctx, &type, sizeof(type));
return SipHash24_End(&ctx);
}
CH_HEAD(attr_tree, attr);
CH_PROTOTYPE(attr_tree, attr, attr_hash);
static struct attr_tree attrtable = CH_INITIALIZER(&attrtable);
void
attr_init(void)
{
arc4random_buf(&attrkey, sizeof(attrkey));
}
int
attr_optadd(struct rde_aspath *asp, uint8_t flags, uint8_t type,
void *data, uint16_t len)
{
uint8_t l;
struct attr *a, *t;
struct attr **p;
uint64_t h;
for (l = 0; l < asp->others_len; l++) {
if (asp->others[l] == NULL)
break;
if (type == asp->others[l]->type)
return (-1);
if (type < asp->others[l]->type)
break;
}
h = attr_calc_hash(type, data, len);
if ((a = attr_lookup(flags, type, data, len, h)) == NULL)
a = attr_alloc(flags, type, data, len, h);
a->refcnt++;
rdemem.attr_refs++;
for (l = 0; l < asp->others_len; l++) {
if (asp->others[l] == NULL) {
asp->others[l] = a;
return (0);
}
if (a->type < asp->others[l]->type) {
t = asp->others[l];
asp->others[l] = a;
a = t;
}
}
if (asp->others_len == UCHAR_MAX)
fatalx("attr_optadd: attribute overflow");
l = bin_of_attrs(asp->others_len + 1);
if ((p = reallocarray(asp->others, l, sizeof(struct attr *))) == NULL)
fatal("%s", __func__);
memset(&p[asp->others_len], 0, (l - asp->others_len) * sizeof(*p));
asp->others = p;
asp->others[asp->others_len] = a;
asp->others_len = l;
return (0);
}
struct attr *
attr_optget(const struct rde_aspath *asp, uint8_t type)
{
uint8_t l;
for (l = 0; l < asp->others_len; l++) {
if (asp->others[l] == NULL)
break;
if (type == asp->others[l]->type)
return (asp->others[l]);
if (type < asp->others[l]->type)
break;
}
return (NULL);
}
void
attr_copy(struct rde_aspath *t, const struct rde_aspath *s)
{
uint8_t l;
if (t->others != NULL)
attr_freeall(t);
t->others_len = s->others_len;
if (t->others_len == 0) {
t->others = NULL;
return;
}
if ((t->others = calloc(s->others_len, sizeof(struct attr *))) == 0)
fatal("%s", __func__);
for (l = 0; l < t->others_len; l++) {
if (s->others[l] == NULL)
break;
s->others[l]->refcnt++;
rdemem.attr_refs++;
t->others[l] = s->others[l];
}
}
static inline int
attr_eq(const struct attr *oa, const struct attr *ob)
{
if (oa->hash != ob->hash)
return 0;
if (oa->type != ob->type)
return 0;
if (oa->flags != ob->flags)
return 0;
if (oa->len != ob->len)
return 0;
return (oa->len == 0 || memcmp(oa->data, ob->data, oa->len) == 0);
}
int
attr_equal(const struct rde_aspath *a, const struct rde_aspath *b)
{
uint8_t l;
if (a->others_len != b->others_len)
return (0);
for (l = 0; l < a->others_len; l++)
if (a->others[l] != b->others[l])
return (0);
return (1);
}
void
attr_free(struct rde_aspath *asp, struct attr *attr)
{
uint8_t l;
for (l = 0; l < asp->others_len; l++)
if (asp->others[l] == attr) {
attr_put(asp->others[l]);
for (++l; l < asp->others_len; l++)
asp->others[l - 1] = asp->others[l];
asp->others[asp->others_len - 1] = NULL;
return;
}
}
void
attr_freeall(struct rde_aspath *asp)
{
uint8_t l;
for (l = 0; l < asp->others_len; l++)
attr_put(asp->others[l]);
free(asp->others);
asp->others = NULL;
asp->others_len = 0;
}
struct attr *
attr_alloc(uint8_t flags, uint8_t type, void *data, uint16_t len, uint64_t hash)
{
struct attr *a;
a = calloc(1, sizeof(struct attr));
if (a == NULL)
fatal("%s", __func__);
rdemem.attr_cnt++;
flags &= ~ATTR_DEFMASK;
a->flags = flags;
a->type = type;
a->len = len;
if (len != 0) {
if ((a->data = malloc(len)) == NULL)
fatal("%s", __func__);
rdemem.attr_dcnt++;
rdemem.attr_data += len;
memcpy(a->data, data, len);
} else
a->data = NULL;
a->hash = hash;
if (CH_INSERT(attr_tree, &attrtable, a, NULL) != 1)
fatalx("corrupted attr tree");
return (a);
}
struct attr *
attr_lookup(uint8_t flags, uint8_t type, void *data, uint16_t len,
uint64_t hash)
{
struct attr needle;
flags &= ~ATTR_DEFMASK;
needle.flags = flags;
needle.type = type;
needle.len = len;
needle.data = data;
needle.hash = hash;
return CH_FIND(attr_tree, &attrtable, &needle);
}
void
attr_put(struct attr *a)
{
if (a == NULL)
return;
rdemem.attr_refs--;
if (--a->refcnt > 0)
return;
CH_REMOVE(attr_tree, &attrtable, a);
if (a->len != 0)
rdemem.attr_dcnt--;
rdemem.attr_data -= a->len;
rdemem.attr_cnt--;
free(a->data);
free(a);
}
void
attr_stats(struct ch_stats *stats)
{
CH_GLOBAL_STATS(attr_tree, stats);
}
CH_GENERATE(attr_tree, attr, attr_eq, attr_hash);
static uint16_t aspath_count(const void *, uint16_t);
static uint32_t aspath_extract_origin(const void *, uint16_t);
static uint16_t aspath_countlength(struct aspath *, uint16_t, int);
static void aspath_countcopy(struct aspath *, uint16_t, uint8_t *,
uint16_t, int);
int
aspath_compare(const struct aspath *a1, const struct aspath *a2)
{
int r;
if (a1->len > a2->len)
return (1);
if (a1->len < a2->len)
return (-1);
r = memcmp(a1->data, a2->data, a1->len);
if (r > 0)
return (1);
if (r < 0)
return (-1);
return (0);
}
struct aspath *
aspath_get(void *data, uint16_t len)
{
struct aspath *aspath;
aspath = malloc(ASPATH_HEADER_SIZE + len);
if (aspath == NULL)
fatal("%s", __func__);
rdemem.aspath_cnt++;
rdemem.aspath_size += ASPATH_HEADER_SIZE + len;
aspath->len = len;
aspath->ascnt = aspath_count(data, len);
aspath->source_as = aspath_extract_origin(data, len);
if (len != 0)
memcpy(aspath->data, data, len);
return (aspath);
}
struct aspath *
aspath_copy(struct aspath *a)
{
struct aspath *aspath;
aspath = malloc(ASPATH_HEADER_SIZE + a->len);
if (aspath == NULL)
fatal("%s", __func__);
rdemem.aspath_cnt++;
rdemem.aspath_size += ASPATH_HEADER_SIZE + a->len;
memcpy(aspath, a, ASPATH_HEADER_SIZE + a->len);
return (aspath);
}
void
aspath_put(struct aspath *aspath)
{
if (aspath == NULL)
return;
rdemem.aspath_cnt--;
rdemem.aspath_size -= ASPATH_HEADER_SIZE + aspath->len;
free(aspath);
}
u_char *
aspath_deflate(u_char *data, uint16_t *len, int *flagnew)
{
uint8_t *seg, *nseg, *ndata = NULL;
uint32_t as;
int i;
uint16_t seg_size, olen, nlen;
uint8_t seg_len;
nlen = 0;
seg = data;
olen = *len;
for (; olen > 0; olen -= seg_size, seg += seg_size) {
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
nlen += 2 + sizeof(uint16_t) * seg_len;
if (seg_size > olen)
fatalx("%s: would overflow", __func__);
}
if (nlen == 0)
goto done;
if ((ndata = malloc(nlen)) == NULL)
fatal("%s", __func__);
seg = data;
olen = *len;
for (nseg = ndata; seg < data + olen; seg += seg_size) {
*nseg++ = seg[0];
*nseg++ = seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
for (i = 0; i < seg_len; i++) {
as = aspath_extract(seg, i);
if (as > USHRT_MAX) {
as = AS_TRANS;
*flagnew = 1;
}
*nseg++ = (as >> 8) & 0xff;
*nseg++ = as & 0xff;
}
}
done:
*len = nlen;
return (ndata);
}
void
aspath_merge(struct rde_aspath *a, struct attr *attr)
{
uint8_t *np;
uint16_t ascnt, diff, nlen, difflen;
int hroom = 0;
ascnt = aspath_count(attr->data, attr->len);
if (ascnt > a->aspath->ascnt) {
attr_free(a, attr);
return;
}
diff = a->aspath->ascnt - ascnt;
if (diff && attr->len > 2 && attr->data[0] == AS_SEQUENCE)
hroom = attr->data[1];
difflen = aspath_countlength(a->aspath, diff, hroom);
nlen = attr->len + difflen;
if ((np = malloc(nlen)) == NULL)
fatal("%s", __func__);
aspath_countcopy(a->aspath, diff, np, difflen, hroom);
if (hroom > 0)
memcpy(np + nlen - attr->len + 2, attr->data + 2,
attr->len - 2);
else
memcpy(np + nlen - attr->len, attr->data, attr->len);
aspath_put(a->aspath);
a->aspath = aspath_get(np, nlen);
free(np);
attr_free(a, attr);
}
uint32_t
aspath_neighbor(struct aspath *aspath)
{
if (aspath->len == 0 ||
aspath->data[0] != AS_SEQUENCE)
return (rde_local_as());
return (aspath_extract(aspath->data, 0));
}
static uint16_t
aspath_count(const void *data, uint16_t len)
{
const uint8_t *seg;
uint16_t cnt, seg_size;
uint8_t seg_type, seg_len;
cnt = 0;
seg = data;
for (; len > 0; len -= seg_size, seg += seg_size) {
seg_type = seg[0];
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
if (seg_type == AS_SET)
cnt += 1;
else
cnt += seg_len;
if (seg_size > len)
fatalx("%s: would overflow", __func__);
}
return (cnt);
}
static uint32_t
aspath_extract_origin(const void *data, uint16_t len)
{
const uint8_t *seg;
uint32_t as = AS_NONE;
uint16_t seg_size;
uint8_t seg_len;
if (len == 0)
return (rde_local_as());
seg = data;
for (; len > 0; len -= seg_size, seg += seg_size) {
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
if (len == seg_size && seg[0] == AS_SEQUENCE) {
as = aspath_extract(seg, seg_len - 1);
}
if (seg_size > len)
fatalx("%s: would overflow", __func__);
}
return (as);
}
static uint16_t
aspath_countlength(struct aspath *aspath, uint16_t cnt, int headcnt)
{
const uint8_t *seg;
uint16_t seg_size, len, clen;
uint8_t seg_type = 0, seg_len = 0;
seg = aspath->data;
clen = 0;
for (len = aspath->len; len > 0 && cnt > 0;
len -= seg_size, seg += seg_size) {
seg_type = seg[0];
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
if (seg_type == AS_SET)
cnt -= 1;
else if (seg_len > cnt) {
seg_len = cnt;
clen += 2 + sizeof(uint32_t) * cnt;
break;
} else
cnt -= seg_len;
clen += seg_size;
if (seg_size > len)
fatalx("%s: would overflow", __func__);
}
if (headcnt > 0 && seg_type == AS_SEQUENCE && headcnt + seg_len < 256)
clen -= 2;
return (clen);
}
static void
aspath_countcopy(struct aspath *aspath, uint16_t cnt, uint8_t *buf,
uint16_t size, int headcnt)
{
const uint8_t *seg;
uint16_t seg_size, len;
uint8_t seg_type, seg_len;
if (headcnt > 0)
size += 2;
seg = aspath->data;
for (len = aspath->len; len > 0 && cnt > 0;
len -= seg_size, seg += seg_size) {
seg_type = seg[0];
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
if (seg_type == AS_SET)
cnt -= 1;
else if (seg_len > cnt) {
seg_len = cnt + headcnt;
seg_size = 2 + sizeof(uint32_t) * cnt;
cnt = 0;
} else {
cnt -= seg_len;
if (cnt == 0)
seg_len += headcnt;
}
memcpy(buf, seg, seg_size);
buf[0] = seg_type;
buf[1] = seg_len;
buf += seg_size;
if (size < seg_size)
fatalx("%s: would overflow", __func__);
size -= seg_size;
}
}
int
aspath_loopfree(struct aspath *aspath, uint32_t myAS)
{
uint8_t *seg;
uint16_t len, seg_size;
uint8_t i, seg_len;
seg = aspath->data;
for (len = aspath->len; len > 0; len -= seg_size, seg += seg_size) {
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
for (i = 0; i < seg_len; i++) {
if (myAS == aspath_extract(seg, i))
return (0);
}
if (seg_size > len)
fatalx("%s: would overflow", __func__);
}
return (1);
}
static int
as_compare(struct filter_as *f, uint32_t as, uint32_t neighas)
{
uint32_t match;
if (f->flags & AS_FLAG_AS_SET_NAME)
return (0);
if (f->flags & AS_FLAG_AS_SET)
return (as_set_match(f->aset, as));
if (f->flags & AS_FLAG_NEIGHBORAS)
match = neighas;
else
match = f->as_min;
switch (f->op) {
case OP_NONE:
case OP_EQ:
if (as == match)
return (1);
break;
case OP_NE:
if (as != match)
return (1);
break;
case OP_RANGE:
if (as >= f->as_min && as <= f->as_max)
return (1);
break;
case OP_XRANGE:
if (as < f->as_min || as > f->as_max)
return (1);
break;
}
return (0);
}
int
aspath_match(struct aspath *aspath, struct filter_as *f, uint32_t neighas)
{
const uint8_t *seg;
int final;
uint16_t len, seg_size;
uint8_t i, seg_len;
uint32_t as = AS_NONE;
if (f->type == AS_EMPTY) {
if (aspath_length(aspath) == 0)
return (1);
else
return (0);
}
if (f->type == AS_PEER) {
as = aspath_neighbor(aspath);
if (as_compare(f, as, neighas))
return (1);
else
return (0);
}
seg = aspath->data;
len = aspath->len;
for (; len >= 6; len -= seg_size, seg += seg_size) {
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
final = (len == seg_size);
if (f->type == AS_SOURCE) {
if (seg[0] == AS_SEQUENCE)
as = aspath_extract(seg, seg_len - 1);
if (!final)
continue;
if (as_compare(f, as, neighas))
return (1);
else
return (0);
}
for (i = 0; i < seg_len; i++) {
if (final && i == seg_len - 1 && f->type == AS_TRANSIT)
return (0);
as = aspath_extract(seg, i);
if (as_compare(f, as, neighas))
return (1);
}
if (seg_size > len)
fatalx("%s: would overflow", __func__);
}
return (0);
}
u_char *
aspath_prepend(struct aspath *asp, uint32_t as, int quantum, uint16_t *len)
{
u_char *p;
int l, overflow = 0, shift = 0, size, wpos = 0;
uint8_t type;
if (asp->len > 0) {
if (asp->len < 2)
fatalx("aspath_prepend: bad aspath length");
type = asp->data[0];
size = asp->data[1];
} else {
type = AS_SET;
size = 0;
}
if (quantum > 255)
fatalx("aspath_prepend: preposterous prepend");
if (quantum == 0) {
if (asp->len == 0) {
*len = 0;
return (NULL);
}
p = malloc(asp->len);
if (p == NULL)
fatal("%s", __func__);
memcpy(p, asp->data, asp->len);
*len = asp->len;
return (p);
} else if (type == AS_SET || size + quantum > 255) {
l = 2 + quantum * sizeof(uint32_t) + asp->len;
if (type == AS_SET)
overflow = quantum;
else
overflow = size + quantum - 255;
} else
l = quantum * sizeof(uint32_t) + asp->len;
quantum -= overflow;
p = malloc(l);
if (p == NULL)
fatal("%s", __func__);
as = htonl(as);
if (overflow > 0) {
p[wpos++] = AS_SEQUENCE;
p[wpos++] = overflow;
for (; overflow > 0; overflow--) {
memcpy(p + wpos, &as, sizeof(uint32_t));
wpos += sizeof(uint32_t);
}
}
if (quantum > 0) {
shift = 2;
p[wpos++] = AS_SEQUENCE;
p[wpos++] = quantum + size;
for (; quantum > 0; quantum--) {
memcpy(p + wpos, &as, sizeof(uint32_t));
wpos += sizeof(uint32_t);
}
}
if (asp->len > shift)
memcpy(p + wpos, asp->data + shift, asp->len - shift);
*len = l;
return (p);
}
u_char *
aspath_override(struct aspath *asp, uint32_t neighbor_as, uint32_t local_as,
uint16_t *len)
{
u_char *p, *seg, *nseg;
uint32_t as;
uint16_t l, seg_size;
uint8_t i, seg_len, seg_type;
if (asp->len == 0) {
*len = 0;
return (NULL);
}
p = malloc(asp->len);
if (p == NULL)
fatal("%s", __func__);
seg = asp->data;
nseg = p;
for (l = asp->len; l > 0; l -= seg_size, seg += seg_size) {
*nseg++ = seg_type = seg[0];
*nseg++ = seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
for (i = 0; i < seg_len; i++) {
as = aspath_extract(seg, i);
if (as == neighbor_as)
as = local_as;
as = htonl(as);
memcpy(nseg, &as, sizeof(as));
nseg += sizeof(as);
}
if (seg_size > l)
fatalx("%s: would overflow", __func__);
}
*len = asp->len;
return (p);
}
int
aspath_lenmatch(struct aspath *a, enum aslen_spec type, u_int aslen)
{
uint8_t *seg;
uint32_t as, lastas = 0;
u_int count = 0;
uint16_t len, seg_size;
uint8_t i, seg_len, seg_type;
if (type == ASLEN_MAX) {
if (aslen < aspath_count(a->data, a->len))
return (1);
else
return (0);
}
seg = a->data;
for (len = a->len; len > 0; len -= seg_size, seg += seg_size) {
seg_type = seg[0];
seg_len = seg[1];
seg_size = 2 + sizeof(uint32_t) * seg_len;
for (i = 0; i < seg_len; i++) {
as = aspath_extract(seg, i);
if (as == lastas) {
if (aslen < ++count)
return (1);
} else if (seg_type == AS_SET) {
continue;
} else
count = 1;
lastas = as;
}
if (seg_size > len)
fatalx("%s: would overflow", __func__);
}
return (0);
}