#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "bgpd.h"
#include "rde.h"
enum cp_state {
UNKNOWN,
NOT_PROVIDER,
PROVIDER,
};
struct rde_aspa_set {
uint32_t as;
uint32_t num;
uint32_t *pas;
int next;
};
struct rde_aspa {
struct rde_aspa_set **table;
uint32_t mask;
uint32_t maxset;
struct rde_aspa_set *sets;
uint32_t *data;
size_t maxdata;
size_t curdata;
uint32_t curset;
monotime_t lastchange;
};
struct aspa_state {
int nhops;
int nup_p;
int nup_u;
int nup_np;
int ndown_p;
int ndown_u;
int ndown_np;
};
static inline uint32_t
hash(uint32_t h)
{
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
static struct rde_aspa_set *
aspa_lookup(struct rde_aspa *ra, uint32_t asnum)
{
struct rde_aspa_set *aspa;
uint32_t h;
h = hash(asnum) & ra->mask;
aspa = ra->table[h];
if (aspa == NULL)
return NULL;
while (aspa->as < asnum) {
if (!aspa->next)
break;
aspa++;
}
if (aspa->as == asnum)
return aspa;
return NULL;
}
static enum cp_state
aspa_cp_lookup(struct rde_aspa *ra, uint32_t cas, uint32_t pas)
{
struct rde_aspa_set *aspa;
uint32_t i;
aspa = aspa_lookup(ra, cas);
if (aspa == NULL)
return UNKNOWN;
if (aspa->num < 16) {
for (i = 0; i < aspa->num; i++) {
if (aspa->pas[i] == pas)
break;
if (aspa->pas[i] > pas)
return NOT_PROVIDER;
}
if (i == aspa->num)
return NOT_PROVIDER;
} else {
uint32_t lim, x;
for (i = 0, lim = aspa->num; lim != 0; lim /= 2) {
x = lim / 2;
i += x;
if (aspa->pas[i] == pas) {
break;
} else if (aspa->pas[i] < pas) {
i++;
lim--;
} else {
i -= x;
}
}
if (lim == 0)
return NOT_PROVIDER;
}
return PROVIDER;
}
static int
aspa_check_aspath(struct rde_aspa *ra, struct aspath *a, struct aspa_state *s)
{
uint8_t *seg;
uint32_t as, prevas = 0;
uint16_t len, seg_size;
uint8_t i, seg_type, seg_len;
s->ndown_p = 1;
seg = aspath_dump(a);
len = aspath_length(a);
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_SEQUENCE)
return -1;
for (i = 0; i < seg_len; i++) {
as = aspath_extract(seg, i);
if (as == prevas)
continue;
s->nhops++;
if (prevas == 0) {
prevas = as;
continue;
}
switch (aspa_cp_lookup(ra, prevas, as)) {
case UNKNOWN:
if (s->ndown_u == 0)
s->ndown_u = s->nhops;
break;
case PROVIDER:
if (s->ndown_p + 1 == s->nhops)
s->ndown_p = s->nhops;
break;
case NOT_PROVIDER:
if (s->ndown_np == 0)
s->ndown_np = s->nhops;
break;
}
switch (aspa_cp_lookup(ra, as, prevas)) {
case UNKNOWN:
s->nup_p = 0;
s->nup_u = s->nhops - 1;
break;
case PROVIDER:
if (s->nup_p == 0)
s->nup_p = s->nhops - 1;
break;
case NOT_PROVIDER:
s->nup_p = 0;
s->nup_np = s->nhops - 1;
break;
}
prevas = as;
}
}
if (s->nup_p == 0)
s->nup_p = s->nhops;
return 0;
}
static void
aspa_check_finalize(struct aspa_state *state, uint8_t *onlyup, uint8_t *downup)
{
if (state->nup_np != 0)
*onlyup = ASPA_INVALID;
else if (state->nup_u != 0)
*onlyup = ASPA_UNKNOWN;
else
*onlyup = ASPA_VALID;
if (state->nhops <= 2)
*downup = ASPA_VALID;
else if (state->nup_p - state->ndown_p <= 1)
*downup = ASPA_VALID;
else if (state->nup_np != 0 && state->ndown_np != 0 &&
state->nup_np - state->ndown_np >= 0)
*downup = ASPA_INVALID;
else
*downup = ASPA_UNKNOWN;
}
void
aspa_validation(struct rde_aspa *ra, struct aspath *a,
struct rde_aspa_state *vstate)
{
struct aspa_state state = { 0 };
if (ra == NULL) {
memset(vstate, ASPA_UNKNOWN, sizeof(*vstate));
return;
}
if (aspath_length(a) == 0) {
memset(vstate, ASPA_INVALID, sizeof(*vstate));
return;
}
if (aspa_check_aspath(ra, a, &state) == -1) {
memset(vstate, ASPA_INVALID, sizeof(*vstate));
return;
}
aspa_check_finalize(&state, &vstate->onlyup, &vstate->downup);
}
struct rde_aspa *
aspa_table_prep(uint32_t entries, size_t datasize)
{
struct rde_aspa *ra;
uint32_t hsize = 1024;
if (entries == 0)
return NULL;
if (entries > UINT32_MAX / 2)
fatalx("aspa_table_prep overflow");
while (hsize < entries)
hsize *= 2;
if ((ra = calloc(1, sizeof(*ra))) == NULL)
fatal("aspa table prep");
if ((ra->table = calloc(hsize, sizeof(ra->table[0]))) == NULL)
fatal("aspa table prep");
if ((ra->sets = calloc(entries, sizeof(ra->sets[0]))) == NULL)
fatal("aspa table prep");
if ((ra->data = malloc(datasize)) == NULL)
fatal("aspa table prep");
ra->mask = hsize - 1;
ra->maxset = entries;
ra->maxdata = datasize / sizeof(ra->data[0]);
ra->lastchange = getmonotime();
rdemem.aspa_cnt += ra->maxset;
rdemem.aspa_size += ra->maxset * sizeof(ra->sets[0]) +
ra->maxdata * sizeof(ra->data[0]);
return ra;
}
void
aspa_add_set(struct rde_aspa *ra, uint32_t cas, const uint32_t *pas,
uint32_t pascnt)
{
struct rde_aspa_set *aspa;
uint32_t h, i;
if (ra->curset >= ra->maxset)
fatalx("aspa set overflow");
h = hash(cas) & ra->mask;
aspa = ra->table[h];
if (aspa == NULL) {
aspa = &ra->sets[ra->curset++];
} else {
if (aspa->as <= cas)
fatalx("%s: bad order of adds", __func__);
memmove(aspa + 1, aspa,
(ra->sets + ra->curset - aspa) * sizeof(*aspa));
ra->curset++;
memset(aspa, 0, sizeof(*aspa));
aspa->next = 1;
for (i = 0; i <= ra->mask; i++) {
if (ra->table[i] > aspa)
ra->table[i]++;
}
}
aspa->as = cas;
ra->table[h] = aspa;
if (ra->maxdata - ra->curdata < pascnt)
fatalx("aspa set data overflow");
aspa->num = pascnt;
aspa->pas = ra->data + ra->curdata;
for (i = 0; i < pascnt; i++)
ra->data[ra->curdata++] = pas[i];
}
void
aspa_table_free(struct rde_aspa *ra)
{
if (ra == NULL)
return;
rdemem.aspa_cnt -= ra->maxset;
rdemem.aspa_size -= ra->maxset * sizeof(ra->sets[0]) +
ra->maxdata * sizeof(ra->data[0]);
free(ra->table);
free(ra->sets);
free(ra->data);
free(ra);
}
void
aspa_table_stats(const struct rde_aspa *ra, struct ctl_show_set *cset)
{
if (ra == NULL)
return;
cset->lastchange = ra->lastchange;
cset->as_cnt = ra->maxset;
}
int
aspa_table_equal(const struct rde_aspa *ra, const struct rde_aspa *rb)
{
uint32_t i;
if (ra == NULL && rb == NULL)
return 1;
if (ra == NULL || rb == NULL)
return 0;
if (ra->maxset != rb->maxset ||
ra->maxdata != rb->maxdata)
return 0;
for (i = 0; i < ra->maxset; i++)
if (ra->sets[i].as != rb->sets[i].as)
return 0;
if (memcmp(ra->data, rb->data, ra->maxdata * sizeof(ra->data[0])) != 0)
return 0;
return 1;
}
void
aspa_table_unchanged(struct rde_aspa *ra, const struct rde_aspa *old)
{
if (ra == NULL || old == NULL)
return;
ra->lastchange = old->lastchange;
}