#include <sys/types.h>
#include <sys/queue.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "bgpd.h"
#include "rde.h"
struct rib_pq {
LIST_ENTRY(rib_pq) entry;
struct prefix *new;
uint32_t old_pathid_tx;
};
struct peer_tree peertable = RB_INITIALIZER(&peertable);
struct peer_tree zombietable = RB_INITIALIZER(&zombietable);
struct rde_peer *peerself;
CTASSERT(sizeof(peerself->recv_eor) * 8 >= AID_MAX);
CTASSERT(sizeof(peerself->sent_eor) * 8 >= AID_MAX);
int
peer_has_as4byte(struct rde_peer *peer)
{
return peer->capa.as4byte;
}
int
peer_has_add_path(struct rde_peer *peer, uint8_t aid, int mode)
{
if (aid >= AID_MAX)
return 0;
return peer->capa.add_path[aid] & mode;
}
int
peer_has_ext_msg(struct rde_peer *peer)
{
return peer->capa.ext_msg;
}
int
peer_has_ext_nexthop(struct rde_peer *peer, uint8_t aid)
{
if (aid >= AID_MAX)
return 0;
return peer->capa.ext_nh[aid];
}
int
peer_permit_as_set(struct rde_peer *peer)
{
return peer->flags & PEERFLAG_PERMIT_AS_SET;
}
void
peer_init(struct filter_head *rules)
{
struct peer_config pc;
memset(&pc, 0, sizeof(pc));
snprintf(pc.descr, sizeof(pc.descr), "LOCAL");
pc.id = PEER_ID_SELF;
peerself = peer_add(PEER_ID_SELF, &pc, rules);
peerself->state = PEER_UP;
}
void
peer_shutdown(void)
{
struct rde_peer *peer, *np;
RB_FOREACH_SAFE(peer, peer_tree, &peertable, np)
peer_delete(peer);
if (!RB_EMPTY(&peertable))
log_warnx("%s: free non-free table", __func__);
}
void
peer_foreach(void (*callback)(struct rde_peer *, void *), void *arg)
{
struct rde_peer *peer, *np;
RB_FOREACH_SAFE(peer, peer_tree, &peertable, np)
callback(peer, arg);
}
struct rde_peer *
peer_get(uint32_t id)
{
struct rde_peer needle;
needle.conf.id = id;
return RB_FIND(peer_tree, &peertable, &needle);
}
struct rde_peer *
peer_match(struct ctl_neighbor *n, uint32_t peerid)
{
struct rde_peer *peer;
if (peerid != 0) {
peer = peer_get(peerid);
if (peer)
peer = RB_NEXT(peer_tree, &peertable, peer);
} else
peer = RB_MIN(peer_tree, &peertable);
for (; peer != NULL; peer = RB_NEXT(peer_tree, &peertable, peer)) {
if (rde_match_peer(peer, n))
return peer;
}
return NULL;
}
struct rde_peer *
peer_add(uint32_t id, struct peer_config *p_conf, struct filter_head *rules)
{
struct rde_peer *peer;
int conflict;
if ((peer = peer_get(id))) {
memcpy(&peer->conf, p_conf, sizeof(struct peer_config));
return peer;
}
peer = calloc(1, sizeof(struct rde_peer));
if (peer == NULL)
fatal("peer_add");
TAILQ_INIT(&peer->rib_pq_head);
memcpy(&peer->conf, p_conf, sizeof(struct peer_config));
peer->remote_bgpid = 0;
peer->loc_rib_id = rib_find(peer->conf.rib);
if (peer->loc_rib_id == RIB_NOTFOUND)
fatalx("King Bula's new peer met an unknown RIB");
peer->state = PEER_NONE;
peer->eval = peer->conf.eval;
peer->role = peer->conf.role;
peer->export_type = peer->conf.export_type;
peer->flags = peer->conf.flags;
if ((peer->ibufq = ibufq_new()) == NULL)
fatal(NULL);
adjout_peer_init(peer);
if (peer_apply_out_filter(peer, rules) != NULL)
fatalx("peer add: peer_apply_out_filter failed");
do {
struct rde_peer *p;
conflict = 0;
peer->path_id_tx = arc4random() << 1;
RB_FOREACH(p, peer_tree, &peertable) {
if (peer->path_id_tx == 0 ||
p->path_id_tx == peer->path_id_tx) {
conflict = 1;
break;
}
}
} while (conflict);
if (RB_INSERT(peer_tree, &peertable, peer) != NULL)
fatalx("rde peer table corrupted");
return peer;
}
struct rde_filter *
peer_apply_out_filter(struct rde_peer *peer, struct filter_head *rules)
{
struct rde_filter *old, *new;
struct filter_rule *fr;
size_t count = 0;
old = peer->out_rules;
TAILQ_FOREACH(fr, rules, entry) {
if (rde_filter_skip_rule(peer, fr))
continue;
count++;
}
new = rde_filter_new(count);
count = 0;
TAILQ_FOREACH(fr, rules, entry) {
if (rde_filter_skip_rule(peer, fr))
continue;
rde_filter_fill(new, count++, fr);
}
peer->out_rules = rde_filter_getcache(new);
return old;
}
static inline int
peer_cmp(struct rde_peer *a, struct rde_peer *b)
{
if (a->conf.id > b->conf.id)
return 1;
if (a->conf.id < b->conf.id)
return -1;
return 0;
}
RB_GENERATE(peer_tree, rde_peer, entry, peer_cmp);
static void
peer_generate_update(struct rde_peer *peer, struct rib_entry *re,
struct prefix *newpath, uint32_t old_pathid_tx, enum eval_mode mode)
{
uint8_t aid;
aid = re->prefix->aid;
if (peer == peerself)
return;
if (peer->state == PEER_NONE)
return;
if (peer->loc_rib_id != re->rib_id)
return;
if (peer->capa.mp[aid] == 0)
return;
if (peer->export_type == EXPORT_NONE ||
peer->export_type == EXPORT_DEFAULT_ROUTE)
return;
if (peer_has_add_path(peer, aid, CAPA_AP_SEND)) {
#if NOTYET
if (peer->eval.mode == ADDPATH_EVAL_ALL) {
up_generate_addpath_all(peer, re, newpath,
old_pathid_tx);
return;
}
#endif
up_generate_addpath(peer, re);
return;
}
if (mode == EVAL_ALL && (peer->flags & PEERFLAG_EVALUATE_ALL) == 0)
return;
up_generate_updates(peer, re);
}
void
rde_generate_updates(struct rib_entry *re, struct prefix *newpath,
uint32_t old_pathid_tx, enum eval_mode mode)
{
struct rde_peer *peer;
switch (mode) {
case EVAL_RECONF:
RB_FOREACH(peer, peer_tree, &peertable) {
if (peer->reconf_out == 0)
continue;
peer_generate_update(peer, re, NULL, 0, EVAL_RECONF);
}
return;
case EVAL_DEFAULT:
break;
case EVAL_ALL:
if (re->pq_mode == EVAL_DEFAULT)
return;
break;
case EVAL_NONE:
fatalx("bad eval mode in %s", __func__);
}
if (re->pq_mode != EVAL_NONE) {
peer = peer_get(re->pq_peer_id);
TAILQ_REMOVE(&peer->rib_pq_head, re, rib_queue);
rdemem.rde_rib_entry_count--;
peer->stats.rib_entry_count--;
}
if (newpath != NULL)
peer = prefix_peer(newpath);
else
peer = peerself;
re->pq_mode = mode;
re->pq_peer_id = peer->conf.id;
TAILQ_INSERT_TAIL(&peer->rib_pq_head, re, rib_queue);
rdemem.rde_rib_entry_count++;
peer->stats.rib_entry_count++;
}
void
peer_process_updates(struct rde_peer *peer, void *bula)
{
struct rib_entry *re;
struct rde_peer *p;
enum eval_mode mode;
re = TAILQ_FIRST(&peer->rib_pq_head);
if (re == NULL)
return;
TAILQ_REMOVE(&peer->rib_pq_head, re, rib_queue);
rdemem.rde_rib_entry_count--;
peer->stats.rib_entry_count--;
mode = re->pq_mode;
RB_FOREACH(p, peer_tree, &peertable)
peer_generate_update(p, re, NULL, 0, mode);
rib_dequeue(re);
}
struct peer_flush {
struct rde_peer *peer;
monotime_t staletime;
};
static void
peer_flush_upcall(struct rib_entry *re, void *arg)
{
struct rde_peer *peer = ((struct peer_flush *)arg)->peer;
struct rde_aspath *asp;
struct bgpd_addr addr;
struct prefix *p, *np, *rp;
monotime_t staletime = ((struct peer_flush *)arg)->staletime;
uint32_t i;
uint8_t prefixlen;
pt_getaddr(re->prefix, &addr);
prefixlen = re->prefix->prefixlen;
TAILQ_FOREACH_SAFE(p, &re->prefix_h, rib_l, np) {
if (peer != prefix_peer(p))
continue;
if (monotime_valid(staletime) &&
monotime_cmp(p->lastchange, staletime) > 0)
continue;
for (i = RIB_LOC_START; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
rp = prefix_get(rib, peer, p->path_id,
&addr, prefixlen);
if (rp) {
asp = prefix_aspath(rp);
if (asp && asp->pftableid)
rde_pftable_del(asp->pftableid, rp);
prefix_destroy(rp);
rde_update_log("flush", i, peer, NULL,
&addr, prefixlen);
}
}
prefix_destroy(p);
peer->stats.prefix_cnt--;
}
}
void
peer_up(struct rde_peer *peer, struct session_up *sup)
{
uint8_t i;
int force_sync = 1;
if (peer->state == PEER_ERR) {
peer_down(peer);
}
if (memcmp(&peer->remote_addr, &sup->remote_addr,
sizeof(sup->remote_addr)) == 0 &&
memcmp(&peer->local_v4_addr, &sup->local_v4_addr,
sizeof(sup->local_v4_addr)) == 0 &&
memcmp(&peer->local_v6_addr, &sup->local_v6_addr,
sizeof(sup->local_v6_addr)) == 0 &&
memcmp(&peer->capa, &sup->capa, sizeof(sup->capa)) == 0)
force_sync = 0;
peer->remote_addr = sup->remote_addr;
peer->local_v4_addr = sup->local_v4_addr;
peer->local_v6_addr = sup->local_v6_addr;
memcpy(&peer->capa, &sup->capa, sizeof(sup->capa));
peer->remote_bgpid = sup->remote_bgpid;
peer->local_if_scope = sup->if_scope;
peer->short_as = sup->short_as;
if (peer->capa.grestart.restart) {
peer->sent_eor = 0;
peer->recv_eor = 0;
} else {
peer->sent_eor = ~0;
peer->recv_eor = ~0;
}
peer->state = PEER_UP;
if (!force_sync) {
for (i = AID_MIN; i < AID_MAX; i++) {
if (peer->capa.mp[i])
peer_blast(peer, i);
}
} else {
for (i = AID_MIN; i < AID_MAX; i++) {
if (peer->capa.mp[i])
peer_dump(peer, i);
}
}
}
void
peer_down(struct rde_peer *peer)
{
peer->remote_bgpid = 0;
peer->state = PEER_DOWN;
rib_dump_terminate(peer);
adjout_peer_flush_pending(peer);
peer_imsg_flush(peer);
peer_flush(peer, AID_UNSPEC, monotime_clear());
peer->stats.prefix_cnt = 0;
}
static void
peer_reaper_upcall(struct rde_peer *peer, struct pt_entry *pte,
struct adjout_prefix *p, void *ptr)
{
adjout_prefix_withdraw(peer, pte, p);
}
static void
peer_reaper_done(void *ptr, uint8_t aid)
{
struct rde_peer *peer = ptr;
adjout_prefix_reaper(peer);
ibufq_free(peer->ibufq);
free(peer);
}
void
peer_delete(struct rde_peer *peer)
{
if (peer->state != PEER_DOWN)
peer_down(peer);
rde_filter_unref(peer->out_rules);
adjout_peer_free(peer);
TAILQ_CONCAT(&peerself->rib_pq_head, &peer->rib_pq_head, rib_queue);
peerself->stats.rib_entry_count += peer->stats.rib_entry_count;
peer->stats.rib_entry_count = 0;
RB_REMOVE(peer_tree, &peertable, peer);
if (adjout_prefix_dump_new(peer, AID_UNSPEC, RDE_RUNNER_ROUNDS, peer,
peer_reaper_upcall, peer_reaper_done, NULL) == -1)
fatal("%s: adjout_prefix_dump_new", __func__);
}
void
peer_flush(struct rde_peer *peer, uint8_t aid, monotime_t staletime)
{
struct peer_flush pf = { peer, staletime };
if (rib_dump_new(RIB_ADJ_IN, aid, 0, &pf, peer_flush_upcall,
NULL, NULL) == -1)
fatal("%s: rib_dump_new", __func__);
if (aid == AID_UNSPEC) {
uint8_t i;
for (i = AID_MIN; i < AID_MAX; i++)
peer->staletime[i] = monotime_clear();
} else {
peer->staletime[aid] = monotime_clear();
}
}
void
peer_stale(struct rde_peer *peer, uint8_t aid, int flushall)
{
monotime_t now;
if (monotime_valid(peer->staletime[aid]))
peer_flush(peer, aid, peer->staletime[aid]);
peer->staletime[aid] = now = getmonotime();
peer->state = PEER_DOWN;
rib_dump_terminate(peer);
adjout_peer_flush_pending(peer);
peer_imsg_flush(peer);
if (flushall)
peer_flush(peer, aid, monotime_clear());
while (monotime_cmp(now, getmonotime()) >= 0) {
struct timespec ts = { .tv_nsec = 1000 * 1000 };
nanosleep(&ts, NULL);
}
}
static void
peer_blast_upcall(struct rde_peer *peer, struct pt_entry *pte,
struct adjout_prefix *p, void *ptr)
{
pend_prefix_add(peer, p->attrs, pte, p->path_id_tx);
}
static void
peer_blast_done(void *ptr, uint8_t aid)
{
struct rde_peer *peer = ptr;
peer->throttled = 0;
if (peer->capa.grestart.restart)
pend_eor_add(peer, aid);
}
void
peer_blast(struct rde_peer *peer, uint8_t aid)
{
if (peer->capa.enhanced_rr && (peer->sent_eor & (1 << aid)))
rde_peer_send_rrefresh(peer, aid, ROUTE_REFRESH_BEGIN_RR);
if (adjout_prefix_dump_new(peer, aid, RDE_RUNNER_ROUNDS, peer,
peer_blast_upcall, peer_blast_done, NULL) == -1)
fatal("%s: adjout_prefix_dump_new", __func__);
}
static void
peer_dump_upcall(struct rib_entry *re, void *ptr)
{
struct rde_peer *peer = ptr;
struct prefix *p;
if ((p = prefix_best(re)) == NULL)
return;
peer_generate_update(peer, re, NULL, 0, EVAL_DEFAULT);
}
static void
peer_dump_done(void *ptr, uint8_t aid)
{
peer_blast_done(ptr, aid);
}
void
peer_dump(struct rde_peer *peer, uint8_t aid)
{
peer->throttled = 1;
if (peer->export_type == EXPORT_NONE) {
peer_blast(peer, aid);
} else if (peer->export_type == EXPORT_DEFAULT_ROUTE) {
up_generate_default(peer, aid);
peer_blast(peer, aid);
} else if (aid == AID_FLOWSPECv4 || aid == AID_FLOWSPECv6) {
prefix_flowspec_dump(aid, peer, peer_dump_upcall,
peer_dump_done);
} else {
if (rib_dump_new(peer->loc_rib_id, aid, RDE_RUNNER_ROUNDS, peer,
peer_dump_upcall, peer_dump_done, NULL) == -1)
fatal("%s: rib_dump_new", __func__);
}
}
void
peer_begin_rrefresh(struct rde_peer *peer, uint8_t aid)
{
monotime_t now;
if (monotime_valid(peer->staletime[aid]))
peer_flush(peer, aid, peer->staletime[aid]);
peer->staletime[aid] = now = getmonotime();
while (monotime_cmp(now, getmonotime()) >= 0) {
struct timespec ts = { .tv_nsec = 1000 * 1000 };
nanosleep(&ts, NULL);
}
}
int
peer_work_pending(void)
{
struct rde_peer *p;
if (!RB_EMPTY(&zombietable))
return 1;
RB_FOREACH(p, peer_tree, &peertable) {
if (ibufq_queuelen(p->ibufq) != 0)
return 1;
if (!TAILQ_EMPTY(&p->rib_pq_head))
return 1;
}
return 0;
}
void
peer_imsg_push(struct rde_peer *peer, struct imsg *imsg)
{
peer->stats.ibufq_msg_count++;
rdemem.rde_ibufq_msg_count++;
peer->stats.ibufq_payload_size += imsg_get_len(imsg);
rdemem.rde_ibufq_payload_size += imsg_get_len(imsg);
imsg_ibufq_push(peer->ibufq, imsg);
}
int
peer_imsg_pop(struct rde_peer *peer, struct imsg *imsg)
{
switch (imsg_ibufq_pop(peer->ibufq, imsg)) {
case 0:
return 0;
case 1:
peer->stats.ibufq_msg_count--;
rdemem.rde_ibufq_msg_count--;
peer->stats.ibufq_payload_size -= imsg_get_len(imsg);
rdemem.rde_ibufq_payload_size -= imsg_get_len(imsg);
return 1;
default:
fatal("imsg_ibufq_pop");
}
}
void
peer_imsg_flush(struct rde_peer *peer)
{
ibufq_flush(peer->ibufq);
rdemem.rde_ibufq_msg_count -= peer->stats.ibufq_msg_count;
rdemem.rde_ibufq_payload_size -= peer->stats.ibufq_payload_size;
peer->stats.ibufq_msg_count = 0;
peer->stats.ibufq_payload_size = 0;
}