#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <errno.h>
#include <pwd.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "bgpd.h"
#include "session.h"
#include "rde.h"
#include "log.h"
#define PFD_PIPE_MAIN 0
#define PFD_PIPE_SESSION 1
#define PFD_PIPE_SESSION_CTL 2
#define PFD_PIPE_ROA 3
#define PFD_PIPE_COUNT 4
void rde_sighdlr(int);
void rde_dispatch_imsg_session(struct imsgbuf *);
void rde_dispatch_imsg_parent(struct imsgbuf *);
void rde_dispatch_imsg_rtr(struct imsgbuf *);
void rde_dispatch_imsg_peer(struct rde_peer *, void *);
void rde_update_dispatch(struct rde_peer *, struct ibuf *);
int rde_update_update(struct rde_peer *, uint32_t,
struct filterstate *, struct bgpd_addr *, uint8_t);
void rde_update_withdraw(struct rde_peer *, uint32_t,
struct bgpd_addr *, uint8_t);
int rde_attr_parse(struct ibuf *, struct rde_peer *,
struct filterstate *, struct ibuf *, struct ibuf *);
int rde_attr_add(struct filterstate *, struct ibuf *);
uint8_t rde_attr_missing(struct rde_aspath *, int, uint16_t);
int rde_get_mp_nexthop(struct ibuf *, uint8_t,
struct rde_peer *, struct filterstate *);
void rde_as4byte_fixup(struct rde_peer *, struct rde_aspath *);
uint8_t rde_aspa_validity(struct rde_peer *, struct rde_aspath *,
uint8_t);
void rde_reflector(struct rde_peer *, struct rde_aspath *);
void rde_dump_ctx_new(struct ctl_show_rib_request *, pid_t,
enum imsg_type);
void rde_dump_ctx_throttle(pid_t, int);
void rde_dump_ctx_terminate(pid_t);
void rde_dump_mrt_new(struct mrt *, pid_t, int);
static void rde_commit_pftable(void);
void rde_reload_done(void);
static void rde_softreconfig_in_done(void *, uint8_t);
static void rde_softreconfig_out_done(void *, uint8_t);
static void rde_softreconfig_done(void);
static void rde_softreconfig_out(struct rib_entry *, void *);
static void rde_softreconfig_in(struct rib_entry *, void *);
static void rde_softreconfig_sync_reeval(struct rib_entry *, void *);
static void rde_softreconfig_sync_fib(struct rib_entry *, void *);
static void rde_softreconfig_sync_done(void *, uint8_t);
static void rde_rpki_reload(void);
static int rde_roa_reload(void);
static int rde_aspa_reload(void);
int rde_update_queue_pending(void);
void rde_update_queue_runner(uint8_t);
struct rde_prefixset *rde_find_prefixset(char *, struct rde_prefixset_head *);
void rde_mark_prefixsets_dirty(struct rde_prefixset_head *,
struct rde_prefixset_head *);
uint8_t rde_roa_validity(struct rde_prefixset *,
struct bgpd_addr *, uint8_t, uint32_t);
static void rde_peer_recv_eor(struct rde_peer *, uint8_t);
static void rde_peer_send_eor(struct rde_peer *, uint8_t);
void network_add(struct network_config *, struct filterstate *);
void network_delete(struct network_config *);
static void network_dump_upcall(struct rib_entry *, void *);
static void network_flush_upcall(struct rib_entry *, void *);
void flowspec_add(struct flowspec *, struct filterstate *,
struct rde_filter_set *);
void flowspec_delete(struct flowspec *);
static void flowspec_flush_upcall(struct rib_entry *, void *);
static void flowspec_dump_upcall(struct rib_entry *, void *);
static void flowspec_dump_done(void *, uint8_t);
void rde_shutdown(void);
static int ovs_match(uint8_t, uint32_t);
static int avs_match(uint8_t, uint32_t);
static struct imsgbuf *ibuf_se;
static struct imsgbuf *ibuf_se_ctl;
static struct imsgbuf *ibuf_rtr;
static struct imsgbuf *ibuf_main;
static struct bgpd_config *conf, *nconf;
static struct rde_prefixset rde_roa, roa_new;
static struct rde_aspa *rde_aspa, *aspa_new;
static uint8_t rde_aspa_generation;
volatile sig_atomic_t rde_quit = 0;
struct filter_head *out_rules, *out_rules_tmp;
struct rde_memstats rdemem;
int softreconfig;
static int rde_eval_all;
extern struct peer_tree peertable;
extern struct rde_peer *peerself;
struct rde_dump_ctx {
LIST_ENTRY(rde_dump_ctx) entry;
struct ctl_show_rib_request req;
uint32_t peerid;
uint8_t throttled;
};
LIST_HEAD(, rde_dump_ctx) rde_dump_h = LIST_HEAD_INITIALIZER(rde_dump_h);
struct rde_mrt_ctx {
LIST_ENTRY(rde_mrt_ctx) entry;
struct mrt mrt;
};
LIST_HEAD(, rde_mrt_ctx) rde_mrts = LIST_HEAD_INITIALIZER(rde_mrts);
u_int rde_mrt_cnt;
void
rde_sighdlr(int sig)
{
switch (sig) {
case SIGINT:
case SIGTERM:
rde_quit = 1;
break;
}
}
void
rde_main(int debug, int verbose)
{
struct passwd *pw;
struct pollfd *pfd = NULL;
struct rde_mrt_ctx *mctx, *xmctx;
monotime_t loop_start, io_end, peer_end, adjout_end,
dump_end, nh_end;
void *newp;
u_int pfd_elms = 0, i, j;
int timeout;
uint8_t aid;
log_init(debug, LOG_DAEMON);
log_setverbose(verbose);
log_procinit(log_procnames[PROC_RDE]);
if ((pw = getpwnam(BGPD_USER)) == NULL)
fatal("getpwnam");
if (chroot(pw->pw_dir) == -1)
fatal("chroot");
if (chdir("/") == -1)
fatal("chdir(\"/\")");
setproctitle("route decision engine");
if (setgroups(1, &pw->pw_gid) ||
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
fatal("can't drop privileges");
if (pledge("stdio recvfd", NULL) == -1)
fatal("pledge");
signal(SIGTERM, rde_sighdlr);
signal(SIGINT, rde_sighdlr);
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, SIG_IGN);
signal(SIGALRM, SIG_IGN);
signal(SIGUSR1, SIG_IGN);
if ((ibuf_main = malloc(sizeof(struct imsgbuf))) == NULL)
fatal(NULL);
if (imsgbuf_init(ibuf_main, 3) == -1 ||
imsgbuf_set_maxsize(ibuf_main, MAX_BGPD_IMSGSIZE) == -1)
fatal(NULL);
imsgbuf_allow_fdpass(ibuf_main);
if ((out_rules = calloc(1, sizeof(struct filter_head))) == NULL)
fatal(NULL);
TAILQ_INIT(out_rules);
pt_init();
attr_init();
path_init();
adjout_init();
communities_init();
peer_init(out_rules);
rib_new("Adj-RIB-In", 0, F_RIB_NOFIB | F_RIB_NOEVALUATE);
conf = new_config();
log_info("route decision engine ready");
loop_start = getmonotime();
while (rde_quit == 0) {
if (pfd_elms < PFD_PIPE_COUNT + rde_mrt_cnt) {
if ((newp = reallocarray(pfd,
PFD_PIPE_COUNT + rde_mrt_cnt,
sizeof(struct pollfd))) == NULL) {
log_warn("could not resize pfd from %u -> %u"
" entries", pfd_elms, PFD_PIPE_COUNT +
rde_mrt_cnt);
fatalx("exiting");
}
pfd = newp;
pfd_elms = PFD_PIPE_COUNT + rde_mrt_cnt;
}
timeout = -1;
memset(pfd, 0, sizeof(struct pollfd) * pfd_elms);
set_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main);
set_pollfd(&pfd[PFD_PIPE_SESSION], ibuf_se);
set_pollfd(&pfd[PFD_PIPE_SESSION_CTL], ibuf_se_ctl);
set_pollfd(&pfd[PFD_PIPE_ROA], ibuf_rtr);
i = PFD_PIPE_COUNT;
LIST_FOREACH_SAFE(mctx, &rde_mrts, entry, xmctx) {
if (i >= pfd_elms)
fatalx("poll pfd too small");
if (msgbuf_queuelen(mctx->mrt.wbuf) > 0) {
pfd[i].fd = mctx->mrt.fd;
pfd[i].events = POLLOUT;
i++;
} else if (mctx->mrt.state == MRT_STATE_REMOVE) {
mrt_clean(&mctx->mrt);
LIST_REMOVE(mctx, entry);
free(mctx);
rde_mrt_cnt--;
}
}
if (peer_work_pending() || rde_update_queue_pending() ||
nexthop_pending() || rib_dump_pending())
timeout = 0;
rdemem.rde_event_loop_usec +=
monotime_to_usec(monotime_sub(getmonotime(), loop_start));
if (poll(pfd, i, timeout) == -1) {
if (errno == EINTR)
continue;
fatal("poll error");
}
rdemem.rde_event_loop_count++;
loop_start = getmonotime();
if (handle_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main) == -1) {
log_warnx("RDE: Lost connection to parent");
rde_quit = 1;
continue;
} else
rde_dispatch_imsg_parent(ibuf_main);
if (handle_pollfd(&pfd[PFD_PIPE_SESSION], ibuf_se) == -1) {
log_warnx("RDE: Lost connection to SE");
imsgbuf_clear(ibuf_se);
free(ibuf_se);
ibuf_se = NULL;
} else
rde_dispatch_imsg_session(ibuf_se);
if (handle_pollfd(&pfd[PFD_PIPE_SESSION_CTL], ibuf_se_ctl) ==
-1) {
log_warnx("RDE: Lost connection to SE control");
imsgbuf_clear(ibuf_se_ctl);
free(ibuf_se_ctl);
ibuf_se_ctl = NULL;
} else
rde_dispatch_imsg_session(ibuf_se_ctl);
if (handle_pollfd(&pfd[PFD_PIPE_ROA], ibuf_rtr) == -1) {
log_warnx("RDE: Lost connection to ROA");
imsgbuf_clear(ibuf_rtr);
free(ibuf_rtr);
ibuf_rtr = NULL;
} else
rde_dispatch_imsg_rtr(ibuf_rtr);
for (j = PFD_PIPE_COUNT, mctx = LIST_FIRST(&rde_mrts);
j < i && mctx != NULL; j++) {
if (pfd[j].fd == mctx->mrt.fd &&
pfd[j].revents & POLLOUT)
mrt_write(&mctx->mrt);
mctx = LIST_NEXT(mctx, entry);
}
io_end = getmonotime();
rdemem.rde_event_io_usec +=
monotime_to_usec(monotime_sub(io_end, loop_start));
peer_foreach(rde_dispatch_imsg_peer, NULL);
peer_end = getmonotime();
rdemem.rde_event_peer_usec +=
monotime_to_usec(monotime_sub(peer_end, io_end));
peer_foreach(peer_process_updates, NULL);
adjout_end = getmonotime();
rdemem.rde_event_adjout_usec +=
monotime_to_usec(monotime_sub(adjout_end, peer_end));
rib_dump_runner();
dump_end = getmonotime();
rdemem.rde_event_ribdump_usec +=
monotime_to_usec(monotime_sub(dump_end, adjout_end));
nexthop_runner();
nh_end = getmonotime();
rdemem.rde_event_nexthop_usec +=
monotime_to_usec(monotime_sub(nh_end, dump_end));
if (ibuf_se && imsgbuf_queuelen(ibuf_se) < SESS_MSG_HIGH_MARK) {
for (aid = AID_MIN; aid < AID_MAX; aid++)
rde_update_queue_runner(aid);
}
rdemem.rde_event_update_usec +=
monotime_to_usec(monotime_sub(getmonotime(), nh_end));
rde_commit_pftable();
}
if (0 && debug)
rde_shutdown();
free_config(conf);
free(pfd);
if (ibuf_se) {
imsgbuf_clear(ibuf_se);
close(ibuf_se->fd);
free(ibuf_se);
}
if (ibuf_se_ctl) {
imsgbuf_clear(ibuf_se_ctl);
close(ibuf_se_ctl->fd);
free(ibuf_se_ctl);
}
if (ibuf_rtr) {
imsgbuf_clear(ibuf_rtr);
close(ibuf_rtr->fd);
free(ibuf_rtr);
}
imsgbuf_clear(ibuf_main);
close(ibuf_main->fd);
free(ibuf_main);
while ((mctx = LIST_FIRST(&rde_mrts)) != NULL) {
mrt_clean(&mctx->mrt);
LIST_REMOVE(mctx, entry);
free(mctx);
}
log_info("route decision engine exiting");
exit(0);
}
static void
rde_hash_stats(long long *cnt, long long *size, long long *refs)
{
struct ch_stats stats;
attr_stats(&stats);
*cnt = stats.cs_num_tables;
*size = stats.cs_size_tables + stats.cs_size_extendible;
*refs = stats.cs_num_elm;
path_stats(&stats);
*cnt += stats.cs_num_tables;
*size += stats.cs_size_tables + stats.cs_size_extendible;
*refs += stats.cs_num_elm;
communities_stats(&stats);
*cnt += stats.cs_num_tables;
*size += stats.cs_size_tables + stats.cs_size_extendible;
*refs += stats.cs_num_elm;
rde_filtertable_stats(&stats);
*cnt += stats.cs_num_tables;
*size += stats.cs_size_tables + stats.cs_size_extendible;
*refs += stats.cs_num_elm;
rde_filterset_stats(&stats);
*cnt += stats.cs_num_tables;
*size += stats.cs_size_tables + stats.cs_size_extendible;
*refs += stats.cs_num_elm;
pend_attr_stats(&stats);
*cnt += stats.cs_num_tables;
*size += stats.cs_size_tables + stats.cs_size_extendible;
*refs += stats.cs_num_elm;
pend_prefix_stats(&stats);
*cnt += stats.cs_num_tables;
*size += stats.cs_size_tables + stats.cs_size_extendible;
*refs += stats.cs_num_elm;
adjout_attr_stats(&stats);
*cnt += stats.cs_num_tables;
*size += stats.cs_size_tables + stats.cs_size_extendible;
*refs += stats.cs_num_elm;
}
struct network_config netconf_s, netconf_p;
struct filterstate netconf_state;
struct rde_filter_set *session_set, *parent_set;
void
rde_dispatch_imsg_session(struct imsgbuf *imsgbuf)
{
static struct flowspec *curflow;
struct imsg imsg;
struct ibuf ibuf;
struct rde_peer_stats stats;
struct ctl_show_set cset;
struct ctl_show_rib csr;
struct ctl_show_rib_request req;
struct session_up sup;
struct peer_config pconf;
struct rde_peer *peer;
struct rde_aspath *asp;
struct as_set *aset;
struct rde_prefixset *pset;
ssize_t n;
uint32_t peerid;
pid_t pid;
int verbose;
uint8_t aid;
while (imsgbuf) {
if ((n = imsg_get(imsgbuf, &imsg)) == -1)
fatal("rde_dispatch_imsg_session: imsg_get error");
if (n == 0)
break;
peerid = imsg_get_id(&imsg);
pid = imsg_get_pid(&imsg);
switch (imsg_get_type(&imsg)) {
case IMSG_UPDATE:
case IMSG_REFRESH:
if ((peer = peer_get(peerid)) == NULL) {
log_warnx("rde_dispatch: unknown peer id %d",
peerid);
break;
}
if (peer_is_up(peer))
peer_imsg_push(peer, &imsg);
break;
case IMSG_SESSION_ADD:
if (imsg_get_data(&imsg, &pconf, sizeof(pconf)) == -1)
fatalx("incorrect size of session request");
peer = peer_add(peerid, &pconf, out_rules);
if (peer->conf.flags & PEERFLAG_EVALUATE_ALL)
rde_eval_all = 1;
break;
case IMSG_SESSION_UP:
if ((peer = peer_get(peerid)) == NULL) {
log_warnx("%s: unknown peer id %d",
"IMSG_SESSION_UP", peerid);
break;
}
if (imsg_get_data(&imsg, &sup, sizeof(sup)) == -1)
fatalx("incorrect size of session request");
peer_up(peer, &sup);
if (peer_has_add_path(peer, AID_UNSPEC, CAPA_AP_SEND))
rde_eval_all = 1;
break;
case IMSG_SESSION_DOWN:
if ((peer = peer_get(peerid)) == NULL) {
log_warnx("%s: unknown peer id %d",
"IMSG_SESSION_DOWN", peerid);
break;
}
peer_down(peer);
break;
case IMSG_SESSION_DELETE:
if ((peer = peer_get(peerid)) == NULL)
break;
peer_delete(peer);
break;
case IMSG_SESSION_STALE:
case IMSG_SESSION_NOGRACE:
case IMSG_SESSION_FLUSH:
case IMSG_SESSION_RESTARTED:
if ((peer = peer_get(peerid)) == NULL) {
log_warnx("%s: unknown peer id %d",
"graceful restart", peerid);
break;
}
if (imsg_get_data(&imsg, &aid, sizeof(aid)) == -1) {
log_warnx("%s: wrong imsg len", __func__);
break;
}
if (aid < AID_MIN || aid >= AID_MAX) {
log_warnx("%s: bad AID", __func__);
break;
}
switch (imsg_get_type(&imsg)) {
case IMSG_SESSION_STALE:
peer_stale(peer, aid, 0);
break;
case IMSG_SESSION_NOGRACE:
peer_stale(peer, aid, 1);
break;
case IMSG_SESSION_FLUSH:
peer_flush(peer, aid, peer->staletime[aid]);
break;
case IMSG_SESSION_RESTARTED:
if (monotime_valid(peer->staletime[aid]))
peer_flush(peer, aid,
peer->staletime[aid]);
break;
}
break;
case IMSG_NETWORK_ADD:
if (imsg_get_data(&imsg, &netconf_s,
sizeof(netconf_s)) == -1) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
TAILQ_INIT(&netconf_s.attrset);
rde_filterstate_init(&netconf_state);
asp = &netconf_state.aspath;
asp->aspath = aspath_get(NULL, 0);
asp->origin = ORIGIN_IGP;
asp->flags = F_ATTR_ORIGIN | F_ATTR_ASPATH |
F_ATTR_LOCALPREF | F_PREFIX_ANNOUNCED |
F_ANN_DYNAMIC;
break;
case IMSG_NETWORK_ASPATH:
if (imsg_get_ibuf(&imsg, &ibuf) == -1) {
log_warnx("bad network aspath received");
memset(&netconf_s, 0, sizeof(netconf_s));
break;
}
if (ibuf_get(&ibuf, &csr, sizeof(csr)) == -1) {
log_warnx("bad network aspath received");
memset(&netconf_s, 0, sizeof(netconf_s));
break;
}
asp = &netconf_state.aspath;
asp->lpref = csr.local_pref;
asp->med = csr.med;
asp->weight = csr.weight;
asp->flags = csr.flags;
asp->origin = csr.origin;
asp->flags |= F_PREFIX_ANNOUNCED | F_ANN_DYNAMIC;
aspath_put(asp->aspath);
asp->aspath = aspath_get(ibuf_data(&ibuf),
ibuf_size(&ibuf));
break;
case IMSG_NETWORK_ATTR:
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
rde_attr_add(&netconf_state, &ibuf) == -1) {
log_warnx("bad network attribute received");
rde_filterstate_clean(&netconf_state);
memset(&netconf_s, 0, sizeof(netconf_s));
break;
}
break;
case IMSG_NETWORK_DONE:
if (session_set == NULL)
goto badnet;
netconf_s.rde_attrset = session_set;
session_set = NULL;
switch (netconf_s.prefix.aid) {
case AID_INET:
if (netconf_s.prefixlen > 32)
goto badnet;
network_add(&netconf_s, &netconf_state);
break;
case AID_INET6:
if (netconf_s.prefixlen > 128)
goto badnet;
network_add(&netconf_s, &netconf_state);
break;
case 0:
break;
default:
badnet:
log_warnx("request to insert invalid network");
break;
}
rde_filterstate_clean(&netconf_state);
break;
case IMSG_NETWORK_REMOVE:
if (imsg_get_data(&imsg, &netconf_s,
sizeof(netconf_s)) == -1) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
TAILQ_INIT(&netconf_s.attrset);
switch (netconf_s.prefix.aid) {
case AID_INET:
if (netconf_s.prefixlen > 32)
goto badnetdel;
network_delete(&netconf_s);
break;
case AID_INET6:
if (netconf_s.prefixlen > 128)
goto badnetdel;
network_delete(&netconf_s);
break;
default:
badnetdel:
log_warnx("request to remove invalid network");
break;
}
break;
case IMSG_NETWORK_FLUSH:
if (rib_dump_new(RIB_ADJ_IN, AID_UNSPEC,
RDE_RUNNER_ROUNDS, NULL, network_flush_upcall,
NULL, NULL) == -1)
log_warn("rde_dispatch: IMSG_NETWORK_FLUSH");
break;
case IMSG_FLOWSPEC_ADD:
if (curflow != NULL) {
log_warnx("rde_dispatch: "
"unexpected flowspec add");
break;
}
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
ibuf_size(&ibuf) <= FLOWSPEC_SIZE) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
curflow = malloc(ibuf_size(&ibuf));
if (curflow == NULL)
fatal(NULL);
memcpy(curflow, ibuf_data(&ibuf), ibuf_size(&ibuf));
if (curflow->len + FLOWSPEC_SIZE != ibuf_size(&ibuf)) {
free(curflow);
curflow = NULL;
log_warnx("rde_dispatch: wrong flowspec len");
break;
}
rde_filterstate_init(&netconf_state);
asp = &netconf_state.aspath;
asp->aspath = aspath_get(NULL, 0);
asp->origin = ORIGIN_IGP;
asp->flags = F_ATTR_ORIGIN | F_ATTR_ASPATH |
F_ATTR_LOCALPREF | F_PREFIX_ANNOUNCED |
F_ANN_DYNAMIC;
break;
case IMSG_FLOWSPEC_DONE:
if (curflow == NULL) {
log_warnx("rde_dispatch: "
"unexpected flowspec done");
goto badflow;
}
if (flowspec_valid(curflow->data, curflow->len,
curflow->aid == AID_FLOWSPECv6) == -1 ||
session_set == NULL) {
log_warnx("invalid flowspec update received "
"from bgpctl");
goto badflow;
}
flowspec_add(curflow, &netconf_state, session_set);
badflow:
rde_filterstate_clean(&netconf_state);
rde_filterset_unref(session_set);
session_set = NULL;
free(curflow);
curflow = NULL;
break;
case IMSG_FLOWSPEC_REMOVE:
if (curflow != NULL) {
log_warnx("rde_dispatch: "
"unexpected flowspec remove");
break;
}
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
ibuf_size(&ibuf) <= FLOWSPEC_SIZE) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
curflow = malloc(ibuf_size(&ibuf));
if (curflow == NULL)
fatal(NULL);
memcpy(curflow, ibuf_data(&ibuf), ibuf_size(&ibuf));
if (curflow->len + FLOWSPEC_SIZE != ibuf_size(&ibuf)) {
free(curflow);
curflow = NULL;
log_warnx("rde_dispatch: wrong flowspec len");
break;
}
if (flowspec_valid(curflow->data, curflow->len,
curflow->aid == AID_FLOWSPECv6) == -1)
log_warnx("invalid flowspec withdraw received "
"from bgpctl");
else
flowspec_delete(curflow);
free(curflow);
curflow = NULL;
break;
case IMSG_FLOWSPEC_FLUSH:
prefix_flowspec_dump(AID_UNSPEC, NULL,
flowspec_flush_upcall, NULL);
break;
case IMSG_FILTER_SET:
if (session_set != NULL) {
log_warnx("previous filterset not consumed.");
rde_filterset_unref(session_set);
session_set = NULL;
}
session_set = rde_filterset_imsg_recv(&imsg);
break;
case IMSG_CTL_SHOW_NETWORK:
case IMSG_CTL_SHOW_RIB:
case IMSG_CTL_SHOW_RIB_PREFIX:
if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
rde_dump_ctx_new(&req, pid, imsg_get_type(&imsg));
break;
case IMSG_CTL_SHOW_FLOWSPEC:
if (imsg_get_data(&imsg, &req, sizeof(req)) == -1) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
prefix_flowspec_dump(req.aid, &pid,
flowspec_dump_upcall, flowspec_dump_done);
break;
case IMSG_CTL_SHOW_NEIGHBOR:
peer = peer_get(peerid);
if (peer != NULL)
memcpy(&stats, &peer->stats, sizeof(stats));
else
memset(&stats, 0, sizeof(stats));
imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_NEIGHBOR,
peerid, pid, -1, &stats, sizeof(stats));
break;
case IMSG_CTL_SHOW_RIB_MEM:
bitmap_get_stats(&rdemem.bitmap_cnt,
&rdemem.bitmap_size);
rde_hash_stats(&rdemem.hash_cnt,
&rdemem.hash_size, &rdemem.hash_refs);
imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_RIB_MEM, 0,
pid, -1, &rdemem, sizeof(rdemem));
break;
case IMSG_CTL_SHOW_SET:
pset = &rde_roa;
memset(&cset, 0, sizeof(cset));
cset.type = ROA_SET;
strlcpy(cset.name, "RPKI ROA", sizeof(cset.name));
cset.lastchange = pset->lastchange;
cset.v4_cnt = pset->th.v4_cnt;
cset.v6_cnt = pset->th.v6_cnt;
imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_SET, 0,
pid, -1, &cset, sizeof(cset));
memset(&cset, 0, sizeof(cset));
cset.type = ASPA_SET;
strlcpy(cset.name, "RPKI ASPA", sizeof(cset.name));
aspa_table_stats(rde_aspa, &cset);
imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_SET, 0,
pid, -1, &cset, sizeof(cset));
SIMPLEQ_FOREACH(aset, &conf->as_sets, entry) {
memset(&cset, 0, sizeof(cset));
cset.type = ASNUM_SET;
strlcpy(cset.name, aset->name,
sizeof(cset.name));
cset.lastchange = aset->lastchange;
cset.as_cnt = set_nmemb(aset->set);
imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_SET, 0,
pid, -1, &cset, sizeof(cset));
}
SIMPLEQ_FOREACH(pset, &conf->rde_prefixsets, entry) {
memset(&cset, 0, sizeof(cset));
cset.type = PREFIX_SET;
strlcpy(cset.name, pset->name,
sizeof(cset.name));
cset.lastchange = pset->lastchange;
cset.v4_cnt = pset->th.v4_cnt;
cset.v6_cnt = pset->th.v6_cnt;
imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_SET, 0,
pid, -1, &cset, sizeof(cset));
}
SIMPLEQ_FOREACH(pset, &conf->rde_originsets, entry) {
memset(&cset, 0, sizeof(cset));
cset.type = ORIGIN_SET;
strlcpy(cset.name, pset->name,
sizeof(cset.name));
cset.lastchange = pset->lastchange;
cset.v4_cnt = pset->th.v4_cnt;
cset.v6_cnt = pset->th.v6_cnt;
imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_SET, 0,
pid, -1, &cset, sizeof(cset));
}
imsg_compose(ibuf_se_ctl, IMSG_CTL_END, 0, pid,
-1, NULL, 0);
break;
case IMSG_CTL_LOG_VERBOSE:
if (imsg_get_data(&imsg, &verbose, sizeof(verbose)) ==
-1) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
log_setverbose(verbose);
break;
case IMSG_CTL_END:
imsg_compose(ibuf_se_ctl, IMSG_CTL_END, 0, pid,
-1, NULL, 0);
break;
case IMSG_CTL_TERMINATE:
rde_dump_ctx_terminate(pid);
break;
case IMSG_XON:
if (peerid) {
peer = peer_get(peerid);
if (peer)
peer->throttled = 0;
} else {
rde_dump_ctx_throttle(pid, 0);
}
break;
case IMSG_XOFF:
if (peerid) {
peer = peer_get(peerid);
if (peer)
peer->throttled = 1;
} else {
rde_dump_ctx_throttle(pid, 1);
}
break;
case IMSG_RECONF_DRAIN:
imsg_compose(ibuf_se, IMSG_RECONF_DRAIN, 0, 0,
-1, NULL, 0);
break;
default:
break;
}
imsg_free(&imsg);
}
}
void
rde_dispatch_imsg_parent(struct imsgbuf *imsgbuf)
{
static struct rde_prefixset *last_prefixset;
static struct as_set *last_as_set;
static struct l3vpn *vpn;
static struct flowspec *curflow;
struct imsg imsg;
struct ibuf ibuf;
struct filterstate state;
struct kroute_nexthop knext;
struct mrt xmrt;
struct prefixset_item psi;
struct rde_rib rr;
struct roa roa;
char name[SET_NAME_LEN];
struct imsgbuf *i;
struct filter_head *nr;
struct filter_rule *r;
struct rib *rib;
struct rde_prefixset *ps;
struct rde_aspath *asp;
size_t nmemb;
int n, fd;
uint16_t rid;
while (imsgbuf) {
if ((n = imsg_get(imsgbuf, &imsg)) == -1)
fatal("rde_dispatch_imsg_parent: imsg_get error");
if (n == 0)
break;
switch (imsg_get_type(&imsg)) {
case IMSG_SOCKET_CONN:
case IMSG_SOCKET_CONN_CTL:
case IMSG_SOCKET_CONN_RTR:
if ((fd = imsg_get_fd(&imsg)) == -1) {
log_warnx("expected to receive imsg fd "
"but didn't receive any");
break;
}
if ((i = malloc(sizeof(struct imsgbuf))) == NULL)
fatal(NULL);
if (imsgbuf_init(i, fd) == -1 ||
imsgbuf_set_maxsize(i, MAX_BGPD_IMSGSIZE) == -1)
fatal(NULL);
switch (imsg_get_type(&imsg)) {
case IMSG_SOCKET_CONN:
if (ibuf_se) {
log_warnx("Unexpected imsg connection "
"to SE received");
imsgbuf_clear(ibuf_se);
free(ibuf_se);
}
ibuf_se = i;
break;
case IMSG_SOCKET_CONN_CTL:
if (ibuf_se_ctl) {
log_warnx("Unexpected imsg ctl "
"connection to SE received");
imsgbuf_clear(ibuf_se_ctl);
free(ibuf_se_ctl);
}
ibuf_se_ctl = i;
break;
case IMSG_SOCKET_CONN_RTR:
if (ibuf_rtr) {
log_warnx("Unexpected imsg ctl "
"connection to ROA received");
imsgbuf_clear(ibuf_rtr);
free(ibuf_rtr);
}
ibuf_rtr = i;
break;
}
break;
case IMSG_NETWORK_ADD:
if (imsg_get_data(&imsg, &netconf_p,
sizeof(netconf_p)) == -1) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
TAILQ_INIT(&netconf_p.attrset);
break;
case IMSG_NETWORK_DONE:
if (parent_set == NULL)
fatalx("network done: filter_set missing");
netconf_p.rde_attrset = parent_set;
parent_set = NULL;
rde_filterstate_init(&state);
asp = &state.aspath;
asp->aspath = aspath_get(NULL, 0);
asp->origin = ORIGIN_IGP;
asp->flags = F_ATTR_ORIGIN | F_ATTR_ASPATH |
F_ATTR_LOCALPREF | F_PREFIX_ANNOUNCED;
network_add(&netconf_p, &state);
rde_filterstate_clean(&state);
break;
case IMSG_NETWORK_REMOVE:
if (imsg_get_data(&imsg, &netconf_p,
sizeof(netconf_p)) == -1) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
TAILQ_INIT(&netconf_p.attrset);
network_delete(&netconf_p);
break;
case IMSG_FLOWSPEC_ADD:
if (curflow != NULL) {
log_warnx("rde_dispatch: "
"unexpected flowspec add");
break;
}
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
ibuf_size(&ibuf) <= FLOWSPEC_SIZE) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
curflow = malloc(ibuf_size(&ibuf));
if (curflow == NULL)
fatal(NULL);
memcpy(curflow, ibuf_data(&ibuf), ibuf_size(&ibuf));
if (curflow->len + FLOWSPEC_SIZE != ibuf_size(&ibuf)) {
free(curflow);
curflow = NULL;
log_warnx("rde_dispatch: wrong flowspec len");
break;
}
break;
case IMSG_FLOWSPEC_DONE:
rde_filterstate_init(&state);
if (curflow == NULL) {
log_warnx("rde_dispatch: "
"unexpected flowspec done");
goto badflow;
}
asp = &state.aspath;
asp->aspath = aspath_get(NULL, 0);
asp->origin = ORIGIN_IGP;
asp->flags = F_ATTR_ORIGIN | F_ATTR_ASPATH |
F_ATTR_LOCALPREF | F_PREFIX_ANNOUNCED;
if (flowspec_valid(curflow->data, curflow->len,
curflow->aid == AID_FLOWSPECv6) == -1 ||
parent_set == NULL) {
log_warnx("invalid flowspec update received "
"from parent");
goto badflow;
}
flowspec_add(curflow, &state, parent_set);
badflow:
rde_filterstate_clean(&state);
rde_filterset_unref(parent_set);
parent_set = NULL;
free(curflow);
curflow = NULL;
break;
case IMSG_FLOWSPEC_REMOVE:
if (curflow != NULL) {
log_warnx("rde_dispatch: "
"unexpected flowspec remove");
break;
}
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
ibuf_size(&ibuf) <= FLOWSPEC_SIZE) {
log_warnx("rde_dispatch: wrong imsg len");
break;
}
curflow = malloc(ibuf_size(&ibuf));
if (curflow == NULL)
fatal(NULL);
memcpy(curflow, ibuf_data(&ibuf), ibuf_size(&ibuf));
if (curflow->len + FLOWSPEC_SIZE != ibuf_size(&ibuf)) {
free(curflow);
curflow = NULL;
log_warnx("rde_dispatch: wrong flowspec len");
break;
}
if (flowspec_valid(curflow->data, curflow->len,
curflow->aid == AID_FLOWSPECv6) == -1)
log_warnx("invalid flowspec withdraw received "
"from parent");
else
flowspec_delete(curflow);
free(curflow);
curflow = NULL;
break;
case IMSG_RECONF_CONF:
nconf = new_config();
if (imsg_recv_config(&imsg, nconf) == -1)
fatal("imsg_recv_config");
out_rules_tmp = calloc(1, sizeof(struct filter_head));
if (out_rules_tmp == NULL)
fatal(NULL);
TAILQ_INIT(out_rules_tmp);
for (rid = 0; rid < rib_size; rid++) {
if ((rib = rib_byid(rid)) == NULL)
continue;
rib->state = RECONF_DELETE;
rib->fibstate = RECONF_NONE;
}
break;
case IMSG_RECONF_RIB:
if (imsg_get_data(&imsg, &rr, sizeof(rr)) == -1)
fatalx("IMSG_RECONF_RIB bad len");
rib = rib_byid(rib_find(rr.name));
if (rib == NULL) {
rib = rib_new(rr.name, rr.rtableid, rr.flags);
} else if (rib->flags == rr.flags &&
rib->rtableid == rr.rtableid) {
rib->state = RECONF_KEEP;
} else {
rib->flags_tmp = rr.flags;
rib->rtableid_tmp = rr.rtableid;
rib->state = RECONF_RELOAD;
}
break;
case IMSG_RECONF_FILTER:
if ((r = malloc(sizeof(struct filter_rule))) == NULL)
fatal(NULL);
if (imsg_get_data(&imsg, r, sizeof(*r)) == -1)
fatalx("IMSG_RECONF_FILTER bad len");
if (r->match.prefixset.name[0] != '\0') {
r->match.prefixset.ps =
rde_find_prefixset(r->match.prefixset.name,
&nconf->rde_prefixsets);
if (r->match.prefixset.ps == NULL)
log_warnx("%s: no prefixset for %s",
__func__, r->match.prefixset.name);
}
if (r->match.originset.name[0] != '\0') {
r->match.originset.ps =
rde_find_prefixset(r->match.originset.name,
&nconf->rde_originsets);
if (r->match.originset.ps == NULL)
log_warnx("%s: no origin-set for %s",
__func__, r->match.originset.name);
}
if (r->match.as.flags & AS_FLAG_AS_SET_NAME) {
struct as_set * aset;
aset = as_sets_lookup(&nconf->as_sets,
r->match.as.name);
if (aset == NULL) {
log_warnx("%s: no as-set for %s",
__func__, r->match.as.name);
} else {
r->match.as.flags = AS_FLAG_AS_SET;
r->match.as.aset = aset;
}
}
TAILQ_INIT(&r->set);
if (parent_set == NULL)
fatalx("IMSG_RECONF_FILTER: bad filter_set");
r->rde_set = parent_set;
parent_set = NULL;
if ((rib = rib_byid(rib_find(r->rib))) == NULL) {
log_warnx("IMSG_RECONF_FILTER: filter rule "
"for nonexistent rib %s", r->rib);
rde_filterset_unref(r->rde_set);
free(r);
break;
}
r->peer.ribid = rib->id;
if (r->dir == DIR_IN) {
nr = rib->in_rules_tmp;
if (nr == NULL) {
nr = calloc(1,
sizeof(struct filter_head));
if (nr == NULL)
fatal(NULL);
TAILQ_INIT(nr);
rib->in_rules_tmp = nr;
}
TAILQ_INSERT_TAIL(nr, r, entry);
} else {
TAILQ_INSERT_TAIL(out_rules_tmp, r, entry);
}
break;
case IMSG_RECONF_PREFIX_SET:
case IMSG_RECONF_ORIGIN_SET:
ps = calloc(1, sizeof(struct rde_prefixset));
if (ps == NULL)
fatal(NULL);
if (imsg_get_data(&imsg, ps->name, sizeof(ps->name)) ==
-1)
fatalx("IMSG_RECONF_PREFIX_SET bad len");
if (imsg_get_type(&imsg) == IMSG_RECONF_ORIGIN_SET) {
SIMPLEQ_INSERT_TAIL(&nconf->rde_originsets, ps,
entry);
} else {
SIMPLEQ_INSERT_TAIL(&nconf->rde_prefixsets, ps,
entry);
}
last_prefixset = ps;
break;
case IMSG_RECONF_ROA_ITEM:
if (imsg_get_data(&imsg, &roa, sizeof(roa)) == -1)
fatalx("IMSG_RECONF_ROA_ITEM bad len");
if (trie_roa_add(&last_prefixset->th, &roa) != 0) {
log_warnx("trie_roa_add %s failed",
log_roa(&roa));
}
break;
case IMSG_RECONF_PREFIX_SET_ITEM:
if (imsg_get_data(&imsg, &psi, sizeof(psi)) == -1)
fatalx("IMSG_RECONF_PREFIX_SET_ITEM bad len");
if (last_prefixset == NULL)
fatalx("King Bula has no prefixset");
if (trie_add(&last_prefixset->th,
&psi.p.addr, psi.p.len,
psi.p.len_min, psi.p.len_max) != 0) {
log_warnx("trie_add(%s) %s/%u failed",
last_prefixset->name, log_addr(&psi.p.addr),
psi.p.len);
}
break;
case IMSG_RECONF_AS_SET:
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
ibuf_get(&ibuf, &nmemb, sizeof(nmemb)) == -1 ||
ibuf_get(&ibuf, name, sizeof(name)) == -1)
fatalx("IMSG_RECONF_AS_SET bad len");
if (as_sets_lookup(&nconf->as_sets, name) != NULL)
fatalx("duplicate as-set %s", name);
last_as_set = as_sets_new(&nconf->as_sets, name, nmemb,
sizeof(uint32_t));
break;
case IMSG_RECONF_AS_SET_ITEMS:
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
ibuf_size(&ibuf) == 0 ||
ibuf_size(&ibuf) % sizeof(uint32_t) != 0)
fatalx("IMSG_RECONF_AS_SET_ITEMS bad len");
nmemb = ibuf_size(&ibuf) / sizeof(uint32_t);
if (set_add(last_as_set->set, ibuf_data(&ibuf),
nmemb) != 0)
fatal(NULL);
break;
case IMSG_RECONF_AS_SET_DONE:
set_prep(last_as_set->set);
last_as_set = NULL;
break;
case IMSG_RECONF_VPN:
if ((vpn = malloc(sizeof(*vpn))) == NULL)
fatal(NULL);
if (imsg_get_data(&imsg, vpn, sizeof(*vpn)) == -1)
fatalx("IMSG_RECONF_VPN bad len");
TAILQ_INIT(&vpn->import);
TAILQ_INIT(&vpn->export);
TAILQ_INIT(&vpn->net_l);
SIMPLEQ_INSERT_TAIL(&nconf->l3vpns, vpn, entry);
break;
case IMSG_RECONF_VPN_EXPORT:
if (vpn == NULL) {
log_warnx("rde_dispatch_imsg_parent: "
"IMSG_RECONF_VPN_EXPORT unexpected");
break;
}
if (parent_set == NULL)
fatalx("vpn export, filterset missing");
vpn->rde_export = parent_set;
parent_set = NULL;
break;
case IMSG_RECONF_VPN_IMPORT:
if (vpn == NULL) {
log_warnx("rde_dispatch_imsg_parent: "
"IMSG_RECONF_VPN_IMPORT unexpected");
break;
}
if (parent_set == NULL)
fatalx("vpn import, filterset missing");
vpn->rde_import = parent_set;
parent_set = NULL;
break;
case IMSG_RECONF_VPN_DONE:
break;
case IMSG_RECONF_DRAIN:
imsg_compose(ibuf_main, IMSG_RECONF_DRAIN, 0, 0,
-1, NULL, 0);
break;
case IMSG_RECONF_DONE:
if (nconf == NULL)
fatalx("got IMSG_RECONF_DONE but no config");
last_prefixset = NULL;
rde_reload_done();
break;
case IMSG_NEXTHOP_UPDATE:
if (imsg_get_data(&imsg, &knext, sizeof(knext)) == -1)
fatalx("IMSG_NEXTHOP_UPDATE bad len");
nexthop_update(&knext);
break;
case IMSG_FILTER_SET:
if (parent_set != NULL)
fatalx("previous filterset not consumed.");
parent_set = rde_filterset_imsg_recv(&imsg);
break;
case IMSG_MRT_OPEN:
case IMSG_MRT_REOPEN:
if (imsg_get_data(&imsg, &xmrt, sizeof(xmrt)) == -1) {
log_warnx("wrong imsg len");
break;
}
if ((fd = imsg_get_fd(&imsg)) == -1)
log_warnx("expected to receive fd for mrt dump "
"but didn't receive any");
else if (xmrt.type == MRT_TABLE_DUMP ||
xmrt.type == MRT_TABLE_DUMP_MP ||
xmrt.type == MRT_TABLE_DUMP_V2) {
rde_dump_mrt_new(&xmrt, imsg_get_pid(&imsg),
fd);
} else
close(fd);
break;
case IMSG_MRT_CLOSE:
break;
default:
fatalx("unhandled IMSG %u", imsg_get_type(&imsg));
}
imsg_free(&imsg);
}
}
void
rde_dispatch_imsg_rtr(struct imsgbuf *imsgbuf)
{
static struct aspa_set *aspa;
struct imsg imsg;
struct roa roa;
struct aspa_prep ap;
int n;
while (imsgbuf) {
if ((n = imsg_get(imsgbuf, &imsg)) == -1)
fatal("rde_dispatch_imsg_parent: imsg_get error");
if (n == 0)
break;
switch (imsg_get_type(&imsg)) {
case IMSG_RECONF_ROA_SET:
trie_free(&roa_new.th);
break;
case IMSG_RECONF_ROA_ITEM:
if (imsg_get_data(&imsg, &roa, sizeof(roa)) == -1)
fatalx("IMSG_RECONF_ROA_ITEM bad len");
if (trie_roa_add(&roa_new.th, &roa) != 0) {
log_warnx("trie_roa_add %s failed",
log_roa(&roa));
}
break;
case IMSG_RECONF_ASPA_PREP:
if (imsg_get_data(&imsg, &ap, sizeof(ap)) == -1)
fatalx("IMSG_RECONF_ASPA_PREP bad len");
if (aspa_new)
fatalx("unexpected IMSG_RECONF_ASPA_PREP");
aspa_new = aspa_table_prep(ap.entries, ap.datasize);
break;
case IMSG_RECONF_ASPA:
if (aspa_new == NULL)
fatalx("unexpected IMSG_RECONF_ASPA");
if (aspa != NULL)
fatalx("IMSG_RECONF_ASPA already sent");
if ((aspa = calloc(1, sizeof(*aspa))) == NULL)
fatal("IMSG_RECONF_ASPA");
if (imsg_get_data(&imsg, aspa,
offsetof(struct aspa_set, tas)) == -1)
fatal("IMSG_RECONF_ASPA bad len");
break;
case IMSG_RECONF_ASPA_TAS:
if (aspa == NULL)
fatalx("unexpected IMSG_RECONF_ASPA_TAS");
aspa->tas = reallocarray(NULL, aspa->num,
sizeof(uint32_t));
if (aspa->tas == NULL)
fatal("IMSG_RECONF_ASPA_TAS");
if (imsg_get_data(&imsg, aspa->tas,
aspa->num * sizeof(uint32_t)) == -1)
fatal("IMSG_RECONF_ASPA_TAS bad len");
break;
case IMSG_RECONF_ASPA_DONE:
if (aspa_new == NULL)
fatalx("unexpected IMSG_RECONF_ASPA");
aspa_add_set(aspa_new, aspa->as, aspa->tas,
aspa->num);
free_aspa(aspa);
aspa = NULL;
break;
case IMSG_RECONF_DONE:
if (rde_roa_reload() + rde_aspa_reload() != 0)
rde_rpki_reload();
break;
}
imsg_free(&imsg);
}
}
void
rde_dispatch_imsg_peer(struct rde_peer *peer, void *bula)
{
struct route_refresh rr;
struct imsg imsg;
struct ibuf ibuf;
if (!peer_is_up(peer)) {
peer_imsg_flush(peer);
return;
}
if (!peer_imsg_pop(peer, &imsg))
return;
switch (imsg_get_type(&imsg)) {
case IMSG_UPDATE:
if (imsg_get_ibuf(&imsg, &ibuf) == -1)
log_warn("update: bad imsg");
else
rde_update_dispatch(peer, &ibuf);
break;
case IMSG_REFRESH:
if (imsg_get_data(&imsg, &rr, sizeof(rr)) == -1) {
log_warnx("route refresh: wrong imsg len");
break;
}
if (rr.aid < AID_MIN || rr.aid >= AID_MAX) {
log_peer_warnx(&peer->conf,
"route refresh: bad AID %d", rr.aid);
break;
}
if (peer->capa.mp[rr.aid] == 0) {
log_peer_warnx(&peer->conf,
"route refresh: AID %s not negotiated",
aid2str(rr.aid));
break;
}
switch (rr.subtype) {
case ROUTE_REFRESH_REQUEST:
peer_blast(peer, rr.aid);
break;
case ROUTE_REFRESH_BEGIN_RR:
if ((peer->recv_eor & (1 << rr.aid)) == 0) {
log_peer_warnx(&peer->conf,
"received %s BoRR before EoR",
aid2str(rr.aid));
break;
}
peer_begin_rrefresh(peer, rr.aid);
break;
case ROUTE_REFRESH_END_RR:
if ((peer->recv_eor & (1 << rr.aid)) != 0 &&
monotime_valid(peer->staletime[rr.aid]))
peer_flush(peer, rr.aid,
peer->staletime[rr.aid]);
else
log_peer_warnx(&peer->conf,
"received unexpected %s EoRR",
aid2str(rr.aid));
break;
default:
log_peer_warnx(&peer->conf,
"route refresh: bad subtype %d", rr.subtype);
break;
}
break;
default:
log_warnx("%s: unhandled imsg type %d", __func__,
imsg_get_type(&imsg));
break;
}
imsg_free(&imsg);
}
void
rde_update_dispatch(struct rde_peer *peer, struct ibuf *buf)
{
struct filterstate state;
struct bgpd_addr prefix;
struct ibuf wdbuf, attrbuf, nlribuf, reachbuf, unreachbuf;
uint16_t afi, len;
uint8_t aid, prefixlen, safi, subtype;
uint32_t fas, pathid;
if (ibuf_get_n16(buf, &len) == -1 ||
ibuf_get_ibuf(buf, len, &wdbuf) == -1 ||
ibuf_get_n16(buf, &len) == -1 ||
ibuf_get_ibuf(buf, len, &attrbuf) == -1 ||
ibuf_get_ibuf(buf, ibuf_size(buf), &nlribuf) == -1) {
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ATTRLIST, NULL);
return;
}
if (ibuf_size(&attrbuf) == 0) {
if (ibuf_size(&nlribuf) != 0) {
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ATTRLIST,
NULL);
return;
}
if (ibuf_size(&wdbuf) == 0) {
rde_peer_recv_eor(peer, AID_INET);
return;
}
}
ibuf_from_buffer(&reachbuf, NULL, 0);
ibuf_from_buffer(&unreachbuf, NULL, 0);
rde_filterstate_init(&state);
if (ibuf_size(&attrbuf) != 0) {
while (ibuf_size(&attrbuf) > 0) {
if (rde_attr_parse(&attrbuf, peer, &state, &reachbuf,
&unreachbuf) == -1)
goto done;
}
if ((subtype = rde_attr_missing(&state.aspath, peer->conf.ebgp,
ibuf_size(&nlribuf)))) {
struct ibuf sbuf;
ibuf_from_buffer(&sbuf, &subtype, sizeof(subtype));
rde_update_err(peer, ERR_UPDATE, ERR_UPD_MISSNG_WK_ATTR,
&sbuf);
goto done;
}
rde_as4byte_fixup(peer, &state.aspath);
if (state.aspath.flags & F_ATTR_ASPATH &&
peer->conf.enforce_as == ENFORCE_AS_ON) {
fas = aspath_neighbor(state.aspath.aspath);
if (peer->conf.remote_as != fas) {
log_peer_warnx(&peer->conf, "bad path, "
"starting with %s expected %u, "
"enforce neighbor-as enabled",
log_as(fas), peer->conf.remote_as);
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ASPATH,
NULL);
goto done;
}
}
if (state.aspath.flags & F_ATTR_ASPATH &&
peer->conf.ebgp &&
peer->conf.enforce_local_as == ENFORCE_AS_ON &&
!aspath_loopfree(state.aspath.aspath, peer->conf.local_as))
state.aspath.flags |= F_ATTR_LOOP;
rde_reflector(peer, &state.aspath);
if (state.aspath.flags & F_ATTR_ASPATH && peer->conf.ebgp) {
aspa_validation(rde_aspa, state.aspath.aspath,
&state.aspath.aspa_state);
state.aspath.aspa_generation = rde_aspa_generation;
}
}
if (ibuf_size(&wdbuf) > 0) {
if (peer->capa.mp[AID_INET] == 0) {
log_peer_warnx(&peer->conf,
"bad withdraw, %s disabled", aid2str(AID_INET));
rde_update_err(peer, ERR_UPDATE, ERR_UPD_NETWORK,
NULL);
goto done;
}
}
while (ibuf_size(&wdbuf) > 0) {
if (peer_has_add_path(peer, AID_INET, CAPA_AP_RECV)) {
if (ibuf_get_n32(&wdbuf, &pathid) == -1) {
log_peer_warnx(&peer->conf,
"bad withdraw prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_NETWORK, NULL);
goto done;
}
} else
pathid = 0;
if (nlri_get_prefix(&wdbuf, &prefix, &prefixlen) == -1) {
log_peer_warnx(&peer->conf, "bad withdraw prefix");
rde_update_err(peer, ERR_UPDATE, ERR_UPD_NETWORK,
NULL);
goto done;
}
rde_update_withdraw(peer, pathid, &prefix, prefixlen);
}
if (ibuf_size(&unreachbuf) != 0) {
if (ibuf_get_n16(&unreachbuf, &afi) == -1 ||
ibuf_get_n8(&unreachbuf, &safi) == -1 ||
afi2aid(afi, safi, &aid) == -1) {
log_peer_warnx(&peer->conf,
"bad AFI/SAFI pair in withdraw");
rde_update_err(peer, ERR_UPDATE, ERR_UPD_OPTATTR,
&unreachbuf);
goto done;
}
if (peer->capa.mp[aid] == 0) {
log_peer_warnx(&peer->conf,
"bad withdraw, %s disabled", aid2str(aid));
rde_update_err(peer, ERR_UPDATE, ERR_UPD_OPTATTR,
&unreachbuf);
goto done;
}
if ((state.aspath.flags & ~F_ATTR_MP_UNREACH) == 0 &&
ibuf_size(&unreachbuf) == 0) {
rde_peer_recv_eor(peer, aid);
}
while (ibuf_size(&unreachbuf) > 0) {
if (peer_has_add_path(peer, aid, CAPA_AP_RECV)) {
if (ibuf_get_n32(&unreachbuf,
&pathid) == -1) {
log_peer_warnx(&peer->conf,
"bad %s withdraw prefix",
aid2str(aid));
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &unreachbuf);
goto done;
}
} else
pathid = 0;
switch (aid) {
case AID_INET:
log_peer_warnx(&peer->conf,
"bad MP withdraw for %s", aid2str(aid));
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &unreachbuf);
goto done;
case AID_INET6:
if (nlri_get_prefix6(&unreachbuf,
&prefix, &prefixlen) == -1) {
log_peer_warnx(&peer->conf,
"bad IPv6 withdraw prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &unreachbuf);
goto done;
}
break;
case AID_VPN_IPv4:
if (nlri_get_vpn4(&unreachbuf,
&prefix, &prefixlen, 1) == -1) {
log_peer_warnx(&peer->conf,
"bad VPNv4 withdraw prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &unreachbuf);
goto done;
}
break;
case AID_VPN_IPv6:
if (nlri_get_vpn6(&unreachbuf,
&prefix, &prefixlen, 1) == -1) {
log_peer_warnx(&peer->conf,
"bad VPNv6 withdraw prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &unreachbuf);
goto done;
}
break;
case AID_EVPN:
if (nlri_get_evpn(&unreachbuf,
&prefix, &prefixlen) == -1) {
log_peer_warnx(&peer->conf,
"bad EVPN withdraw prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &unreachbuf);
goto done;
}
break;
case AID_FLOWSPECv4:
case AID_FLOWSPECv6:
default:
if (ibuf_skip(&unreachbuf,
ibuf_size(&unreachbuf)) == -1) {
log_peer_warnx(&peer->conf,
"bad withdraw prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &unreachbuf);
goto done;
}
continue;
}
rde_update_withdraw(peer, pathid, &prefix, prefixlen);
}
if ((state.aspath.flags & ~F_ATTR_MP_UNREACH) == 0)
goto done;
}
if (ibuf_size(&nlribuf) > 0) {
if (peer->capa.mp[AID_INET] == 0) {
log_peer_warnx(&peer->conf,
"bad update, %s disabled", aid2str(AID_INET));
rde_update_err(peer, ERR_UPDATE, ERR_UPD_NETWORK,
NULL);
goto done;
}
if ((state.aspath.flags & F_ATTR_OTC) == 0) {
uint32_t tmp;
switch (peer->role) {
case ROLE_CUSTOMER:
case ROLE_RS_CLIENT:
case ROLE_PEER:
tmp = htonl(peer->conf.remote_as);
if (attr_optadd(&state.aspath,
ATTR_OPTIONAL|ATTR_TRANSITIVE, ATTR_OTC,
&tmp, sizeof(tmp)) == -1) {
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_ATTRLIST, NULL);
goto done;
}
state.aspath.flags |= F_ATTR_OTC;
break;
default:
break;
}
}
}
while (ibuf_size(&nlribuf) > 0) {
if (peer_has_add_path(peer, AID_INET, CAPA_AP_RECV)) {
if (ibuf_get_n32(&nlribuf, &pathid) == -1) {
log_peer_warnx(&peer->conf,
"bad nlri prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_NETWORK, NULL);
goto done;
}
} else
pathid = 0;
if (nlri_get_prefix(&nlribuf, &prefix, &prefixlen) == -1) {
log_peer_warnx(&peer->conf, "bad nlri prefix");
rde_update_err(peer, ERR_UPDATE, ERR_UPD_NETWORK,
NULL);
goto done;
}
if (rde_update_update(peer, pathid, &state,
&prefix, prefixlen) == -1)
goto done;
}
if (ibuf_size(&reachbuf) != 0) {
if (ibuf_get_n16(&reachbuf, &afi) == -1 ||
ibuf_get_n8(&reachbuf, &safi) == -1 ||
afi2aid(afi, safi, &aid) == -1) {
log_peer_warnx(&peer->conf,
"bad AFI/SAFI pair in update");
rde_update_err(peer, ERR_UPDATE, ERR_UPD_OPTATTR,
&reachbuf);
goto done;
}
if (peer->capa.mp[aid] == 0) {
log_peer_warnx(&peer->conf,
"bad update, %s disabled", aid2str(aid));
rde_update_err(peer, ERR_UPDATE, ERR_UPD_OPTATTR,
&reachbuf);
goto done;
}
if (aid == AID_INET6 || aid == AID_INET) {
if ((state.aspath.flags & F_ATTR_OTC) == 0) {
uint32_t tmp;
switch (peer->role) {
case ROLE_CUSTOMER:
case ROLE_RS_CLIENT:
case ROLE_PEER:
tmp = htonl(peer->conf.remote_as);
if (attr_optadd(&state.aspath,
ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_OTC, &tmp,
sizeof(tmp)) == -1) {
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_ATTRLIST, NULL);
goto done;
}
state.aspath.flags |= F_ATTR_OTC;
break;
default:
break;
}
}
} else {
state.aspath.flags &= ~F_ATTR_OTC_LEAK;
}
nexthop_unref(state.nexthop);
state.nexthop = NULL;
if (rde_get_mp_nexthop(&reachbuf, aid, peer, &state) == -1) {
log_peer_warnx(&peer->conf, "bad nlri nexthop");
rde_update_err(peer, ERR_UPDATE, ERR_UPD_OPTATTR,
&reachbuf);
goto done;
}
while (ibuf_size(&reachbuf) > 0) {
if (peer_has_add_path(peer, aid, CAPA_AP_RECV)) {
if (ibuf_get_n32(&reachbuf, &pathid) == -1) {
log_peer_warnx(&peer->conf,
"bad %s nlri prefix", aid2str(aid));
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &reachbuf);
goto done;
}
} else
pathid = 0;
switch (aid) {
case AID_INET:
if (nlri_get_prefix(&reachbuf,
&prefix, &prefixlen) == -1) {
log_peer_warnx(&peer->conf,
"bad IPv4 MP nlri prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &reachbuf);
goto done;
}
break;
case AID_INET6:
if (nlri_get_prefix6(&reachbuf,
&prefix, &prefixlen) == -1) {
log_peer_warnx(&peer->conf,
"bad IPv6 nlri prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &reachbuf);
goto done;
}
break;
case AID_VPN_IPv4:
if (nlri_get_vpn4(&reachbuf,
&prefix, &prefixlen, 0) == -1) {
log_peer_warnx(&peer->conf,
"bad VPNv4 nlri prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &reachbuf);
goto done;
}
break;
case AID_VPN_IPv6:
if (nlri_get_vpn6(&reachbuf,
&prefix, &prefixlen, 0) == -1) {
log_peer_warnx(&peer->conf,
"bad VPNv6 nlri prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &reachbuf);
goto done;
}
break;
case AID_EVPN:
if (nlri_get_evpn(&reachbuf,
&prefix, &prefixlen) == -1) {
log_peer_warnx(&peer->conf,
"bad EVPN nlri prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &reachbuf);
goto done;
}
break;
case AID_FLOWSPECv4:
case AID_FLOWSPECv6:
default:
if (ibuf_skip(&reachbuf,
ibuf_size(&reachbuf)) == -1) {
log_peer_warnx(&peer->conf,
"bad nlri prefix");
rde_update_err(peer, ERR_UPDATE,
ERR_UPD_OPTATTR, &reachbuf);
goto done;
}
continue;
}
if (rde_update_update(peer, pathid, &state,
&prefix, prefixlen) == -1)
goto done;
}
}
done:
rde_filterstate_clean(&state);
}
static int
pathid_conflict(struct rib_entry *re, uint32_t pathid)
{
struct prefix *p;
if (re == NULL)
return 0;
TAILQ_FOREACH(p, &re->prefix_h, rib_l)
if (p->path_id_tx == pathid)
return 1;
return 0;
}
static uint32_t
pathid_assign(struct rde_peer *peer, uint32_t path_id,
struct bgpd_addr *prefix, uint8_t prefixlen)
{
struct rib_entry *re;
uint32_t path_id_tx;
if (!peer_has_add_path(peer, prefix->aid, CAPA_AP_RECV))
return peer->path_id_tx;
re = rib_get_addr(rib_byid(RIB_ADJ_IN), prefix, prefixlen);
if (re != NULL) {
struct prefix *p;
p = prefix_bypeer(re, peer, path_id);
if (p != NULL)
return p->path_id_tx;
}
do {
path_id_tx = arc4random() | 1;
} while (pathid_conflict(re, path_id_tx));
return path_id_tx;
}
int
rde_update_update(struct rde_peer *peer, uint32_t path_id,
struct filterstate *in, struct bgpd_addr *prefix, uint8_t prefixlen)
{
struct filterstate state;
enum filter_actions action;
uint32_t path_id_tx;
uint16_t i;
uint8_t roa_state, aspa_state;
const char *wmsg = "filtered, withdraw";
peer->stats.prefix_rcvd_update++;
roa_state = rde_roa_validity(&rde_roa, prefix, prefixlen,
aspath_origin(in->aspath.aspath));
aspa_state = rde_aspa_validity(peer, &in->aspath, prefix->aid);
rde_filterstate_set_vstate(in, roa_state, aspa_state);
path_id_tx = pathid_assign(peer, path_id, prefix, prefixlen);
if (prefix_update(rib_byid(RIB_ADJ_IN), peer, path_id, path_id_tx,
in, 0, prefix, prefixlen) == 1)
peer->stats.prefix_cnt++;
if (peer->conf.max_prefix &&
peer->stats.prefix_cnt > peer->conf.max_prefix) {
log_peer_warnx(&peer->conf, "prefix limit reached (>%u/%u)",
peer->stats.prefix_cnt, peer->conf.max_prefix);
rde_update_err(peer, ERR_CEASE, ERR_CEASE_MAX_PREFIX, NULL);
return (-1);
}
if (in->aspath.flags & F_ATTR_PARSE_ERR)
wmsg = "path invalid, withdraw";
for (i = RIB_LOC_START; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
rde_filterstate_copy(&state, in);
action = rde_filter(rib->in_rules, peer, peer, prefix,
prefixlen, &state);
if (action == ACTION_ALLOW) {
rde_update_log("update", i, peer,
&state.nexthop->exit_nexthop, prefix,
prefixlen);
prefix_update(rib, peer, path_id, path_id_tx, &state,
0, prefix, prefixlen);
} else if (conf->filtered_in_locrib && i == RIB_LOC_START) {
rde_update_log(wmsg, i, peer, NULL, prefix, prefixlen);
prefix_update(rib, peer, path_id, path_id_tx, &state,
1, prefix, prefixlen);
} else {
if (prefix_withdraw(rib, peer, path_id, prefix,
prefixlen))
rde_update_log(wmsg, i, peer,
NULL, prefix, prefixlen);
}
rde_filterstate_clean(&state);
}
return (0);
}
void
rde_update_withdraw(struct rde_peer *peer, uint32_t path_id,
struct bgpd_addr *prefix, uint8_t prefixlen)
{
uint16_t i;
for (i = RIB_LOC_START; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
if (prefix_withdraw(rib, peer, path_id, prefix, prefixlen))
rde_update_log("withdraw", i, peer, NULL, prefix,
prefixlen);
}
if (prefix_withdraw(rib_byid(RIB_ADJ_IN), peer, path_id,
prefix, prefixlen))
peer->stats.prefix_cnt--;
peer->stats.prefix_rcvd_withdraw++;
}
#define CHECK_FLAGS(s, t, m) \
(((s) & ~(ATTR_DEFMASK | (m))) == (t))
int
rde_attr_parse(struct ibuf *buf, struct rde_peer *peer,
struct filterstate *state, struct ibuf *reach, struct ibuf *unreach)
{
struct bgpd_addr nexthop;
struct rde_aspath *a = &state->aspath;
struct ibuf attrbuf, tmpbuf, *npath = NULL;
size_t alen, hlen;
uint32_t tmp32, zero = 0;
int error;
uint8_t flags, type;
ibuf_from_ibuf(&attrbuf, buf);
if (ibuf_get_n8(&attrbuf, &flags) == -1 ||
ibuf_get_n8(&attrbuf, &type) == -1)
goto bad_ibuf;
if (flags & ATTR_EXTLEN) {
uint16_t attr_len;
if (ibuf_get_n16(&attrbuf, &attr_len) == -1)
goto bad_ibuf;
alen = attr_len;
hlen = 4;
} else {
uint8_t attr_len;
if (ibuf_get_n8(&attrbuf, &attr_len) == -1)
goto bad_ibuf;
alen = attr_len;
hlen = 3;
}
if (ibuf_truncate(&attrbuf, alen) == -1)
goto bad_size;
if (ibuf_skip(buf, hlen + alen) == -1)
goto bad_ibuf;
switch (type) {
case ATTR_UNDEF:
break;
case ATTR_ORIGIN:
if (!CHECK_FLAGS(flags, ATTR_WELL_KNOWN, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) != 1)
goto bad_len;
if (a->flags & F_ATTR_ORIGIN)
goto bad_list;
if (ibuf_get_n8(&attrbuf, &a->origin) == -1)
goto bad_len;
if (a->origin > ORIGIN_INCOMPLETE) {
a->flags |= F_ATTR_PARSE_ERR;
log_peer_warnx(&peer->conf, "bad ORIGIN %u, "
"path invalidated and prefix withdrawn",
a->origin);
return (-1);
}
a->flags |= F_ATTR_ORIGIN;
break;
case ATTR_ASPATH:
if (!CHECK_FLAGS(flags, ATTR_WELL_KNOWN, 0))
goto bad_flags;
if (a->flags & F_ATTR_ASPATH)
goto bad_list;
error = aspath_verify(&attrbuf, peer_has_as4byte(peer),
peer_permit_as_set(peer));
if (error != 0 && error != AS_ERR_SOFT) {
log_peer_warnx(&peer->conf, "bad ASPATH, %s",
log_aspath_error(error));
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ASPATH,
NULL);
return (-1);
}
if (peer_has_as4byte(peer)) {
ibuf_from_ibuf(&tmpbuf, &attrbuf);
} else {
if ((npath = aspath_inflate(&attrbuf)) == NULL)
fatal("aspath_inflate");
ibuf_from_ibuf(&tmpbuf, npath);
}
if (error == AS_ERR_SOFT) {
char *str;
a->flags |= F_ATTR_PARSE_ERR;
aspath_asprint(&str, &tmpbuf);
log_peer_warnx(&peer->conf, "bad ASPATH %s, "
"path invalidated and prefix withdrawn",
str ? str : "(bad aspath)");
free(str);
}
a->flags |= F_ATTR_ASPATH;
a->aspath = aspath_get(ibuf_data(&tmpbuf), ibuf_size(&tmpbuf));
ibuf_free(npath);
break;
case ATTR_NEXTHOP:
if (!CHECK_FLAGS(flags, ATTR_WELL_KNOWN, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) != 4)
goto bad_len;
if (a->flags & F_ATTR_NEXTHOP)
goto bad_list;
a->flags |= F_ATTR_NEXTHOP;
memset(&nexthop, 0, sizeof(nexthop));
nexthop.aid = AID_INET;
if (ibuf_get_h32(&attrbuf, &nexthop.v4.s_addr) == -1)
goto bad_len;
tmp32 = ntohl(nexthop.v4.s_addr);
if (IN_MULTICAST(tmp32)) {
rde_update_err(peer, ERR_UPDATE, ERR_UPD_NEXTHOP,
&attrbuf);
return (-1);
}
nexthop_unref(state->nexthop);
state->nexthop = nexthop_get(&nexthop);
break;
case ATTR_MED:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) != 4)
goto bad_len;
if (a->flags & F_ATTR_MED)
goto bad_list;
if (ibuf_get_n32(&attrbuf, &a->med) == -1)
goto bad_len;
a->flags |= F_ATTR_MED;
break;
case ATTR_LOCALPREF:
if (!CHECK_FLAGS(flags, ATTR_WELL_KNOWN, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) != 4)
goto bad_len;
if (peer->conf.ebgp) {
break;
}
if (a->flags & F_ATTR_LOCALPREF)
goto bad_list;
if (ibuf_get_n32(&attrbuf, &a->lpref) == -1)
goto bad_len;
a->flags |= F_ATTR_LOCALPREF;
break;
case ATTR_ATOMIC_AGGREGATE:
if (!CHECK_FLAGS(flags, ATTR_WELL_KNOWN, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) != 0)
goto bad_len;
goto optattr;
case ATTR_AGGREGATOR:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_PARTIAL))
goto bad_flags;
if ((!peer_has_as4byte(peer) && ibuf_size(&attrbuf) != 6) ||
(peer_has_as4byte(peer) && ibuf_size(&attrbuf) != 8)) {
log_peer_warnx(&peer->conf, "bad AGGREGATOR, "
"attribute discarded");
break;
}
if (!peer_has_as4byte(peer)) {
u_char t[8];
t[0] = t[1] = 0;
if (ibuf_get(&attrbuf, &t[2], 6) == -1)
goto bad_ibuf;
if (memcmp(t, &zero, sizeof(uint32_t)) == 0) {
log_peer_warnx(&peer->conf, "bad AGGREGATOR, "
"AS 0 not allowed, attribute discarded");
break;
}
if (attr_optadd(a, flags, type, t, sizeof(t)) == -1)
goto bad_list;
break;
}
ibuf_from_ibuf(&tmpbuf, &attrbuf);
if (ibuf_get_n32(&tmpbuf, &tmp32) == -1)
goto bad_len;
if (tmp32 == 0) {
char *pfmt = log_fmt_peer(&peer->conf);
log_debug("%s: bad AGGREGATOR, "
"AS 0 not allowed, attribute discarded", pfmt);
free(pfmt);
break;
}
goto optattr;
case ATTR_COMMUNITIES:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_PARTIAL))
goto bad_flags;
if (community_add(&state->communities, flags,
&attrbuf) == -1) {
a->flags |= F_ATTR_PARSE_ERR;
log_peer_warnx(&peer->conf, "bad COMMUNITIES, "
"path invalidated and prefix withdrawn");
}
break;
case ATTR_LARGE_COMMUNITIES:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_PARTIAL))
goto bad_flags;
if (community_large_add(&state->communities, flags,
&attrbuf) == -1) {
a->flags |= F_ATTR_PARSE_ERR;
log_peer_warnx(&peer->conf, "bad LARGE COMMUNITIES, "
"path invalidated and prefix withdrawn");
}
break;
case ATTR_EXT_COMMUNITIES:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_PARTIAL))
goto bad_flags;
if (community_ext_add(&state->communities, flags,
peer->conf.ebgp, &attrbuf) == -1) {
a->flags |= F_ATTR_PARSE_ERR;
log_peer_warnx(&peer->conf, "bad EXT_COMMUNITIES, "
"path invalidated and prefix withdrawn");
}
break;
case ATTR_ORIGINATOR_ID:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) != 4)
goto bad_len;
goto optattr;
case ATTR_CLUSTER_LIST:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL, 0))
goto bad_flags;
if (peer->conf.ebgp) {
log_peer_warnx(&peer->conf, "bad CLUSTER_LIST, "
"received from external peer, attribute discarded");
break;
}
if (ibuf_size(&attrbuf) % 4 != 0 || ibuf_size(&attrbuf) == 0) {
a->flags |= F_ATTR_PARSE_ERR;
log_peer_warnx(&peer->conf, "bad CLUSTER_LIST, "
"path invalidated and prefix withdrawn");
break;
}
goto optattr;
case ATTR_MP_REACH_NLRI:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) < 5)
goto bad_len;
if (a->flags & F_ATTR_MP_REACH)
goto bad_list;
a->flags |= F_ATTR_MP_REACH;
*reach = attrbuf;
break;
case ATTR_MP_UNREACH_NLRI:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL, 0))
goto bad_flags;
if (ibuf_size(&attrbuf) < 3)
goto bad_len;
if (a->flags & F_ATTR_MP_UNREACH)
goto bad_list;
a->flags |= F_ATTR_MP_UNREACH;
*unreach = attrbuf;
break;
case ATTR_AS4_AGGREGATOR:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_PARTIAL))
goto bad_flags;
if (ibuf_size(&attrbuf) != 8) {
log_peer_warnx(&peer->conf, "bad AS4_AGGREGATOR, "
"attribute discarded");
break;
}
ibuf_from_ibuf(&tmpbuf, &attrbuf);
if (ibuf_get_n32(&tmpbuf, &tmp32) == -1)
goto bad_len;
if (tmp32 == 0) {
log_peer_warnx(&peer->conf, "bad AS4_AGGREGATOR, "
"AS 0 not allowed, attribute discarded");
break;
}
a->flags |= F_ATTR_AS4BYTE_NEW;
goto optattr;
case ATTR_AS4_PATH:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_PARTIAL))
goto bad_flags;
if ((error = aspath_verify(&attrbuf, 1,
peer_permit_as_set(peer))) != 0) {
log_peer_warnx(&peer->conf, "bad AS4_PATH, "
"attribute discarded");
break;
}
a->flags |= F_ATTR_AS4BYTE_NEW;
goto optattr;
case ATTR_OTC:
if (!CHECK_FLAGS(flags, ATTR_OPTIONAL|ATTR_TRANSITIVE,
ATTR_PARTIAL))
goto bad_flags;
if (ibuf_size(&attrbuf) != 4) {
a->flags |= F_ATTR_PARSE_ERR;
log_peer_warnx(&peer->conf, "bad OTC, "
"path invalidated and prefix withdrawn");
break;
}
switch (peer->role) {
case ROLE_PROVIDER:
case ROLE_RS:
a->flags |= F_ATTR_OTC_LEAK;
break;
case ROLE_PEER:
ibuf_from_ibuf(&tmpbuf, &attrbuf);
if (ibuf_get_n32(&tmpbuf, &tmp32) == -1)
goto bad_len;
if (tmp32 != peer->conf.remote_as)
a->flags |= F_ATTR_OTC_LEAK;
break;
default:
break;
}
a->flags |= F_ATTR_OTC;
goto optattr;
default:
if ((flags & ATTR_OPTIONAL) == 0) {
rde_update_err(peer, ERR_UPDATE, ERR_UPD_UNKNWN_WK_ATTR,
&attrbuf);
return (-1);
}
optattr:
if (attr_optadd(a, flags, type, ibuf_data(&attrbuf),
ibuf_size(&attrbuf)) == -1)
goto bad_list;
break;
}
return (0);
bad_len:
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ATTRLEN, &attrbuf);
return (-1);
bad_flags:
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ATTRFLAGS, &attrbuf);
return (-1);
bad_list:
log_peer_warnx(&peer->conf, "bad update attributes, "
"list error for attribute #%d", type);
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ATTRLIST, NULL);
return (-1);
bad_ibuf:
log_peer_warn(&peer->conf, "bad update attributes, "
"message parse error");
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ATTRLIST, NULL);
return (-1);
bad_size:
log_peer_warn(&peer->conf, "bad update attributes, "
"attribute #%d [%x] with size %zu overflowed", type, flags, alen);
rde_update_err(peer, ERR_UPDATE, ERR_UPD_ATTRLIST, NULL);
return (-1);
}
#undef CHECK_FLAGS
int
rde_attr_add(struct filterstate *state, struct ibuf *buf)
{
uint16_t attr_len;
uint8_t flags;
uint8_t type;
uint8_t tmp8;
if (ibuf_get_n8(buf, &flags) == -1 ||
ibuf_get_n8(buf, &type) == -1)
return (-1);
if (flags & ATTR_EXTLEN) {
if (ibuf_get_n16(buf, &attr_len) == -1)
return (-1);
} else {
if (ibuf_get_n8(buf, &tmp8) == -1)
return (-1);
attr_len = tmp8;
}
if (ibuf_size(buf) != attr_len)
return (-1);
switch (type) {
case ATTR_COMMUNITIES:
return community_add(&state->communities, flags, buf);
case ATTR_LARGE_COMMUNITIES:
return community_large_add(&state->communities, flags, buf);
case ATTR_EXT_COMMUNITIES:
return community_ext_add(&state->communities, flags, 0, buf);
}
if (attr_optadd(&state->aspath, flags, type, ibuf_data(buf),
attr_len) == -1)
return (-1);
return (0);
}
uint8_t
rde_attr_missing(struct rde_aspath *a, int ebgp, uint16_t nlrilen)
{
if (nlrilen == 0 && a->flags & F_ATTR_MP_UNREACH &&
(a->flags & F_ATTR_MP_REACH) == 0)
return (0);
if ((a->flags & F_ATTR_ORIGIN) == 0)
return (ATTR_ORIGIN);
if ((a->flags & F_ATTR_ASPATH) == 0)
return (ATTR_ASPATH);
if ((a->flags & F_ATTR_MP_REACH) == 0 &&
(a->flags & F_ATTR_NEXTHOP) == 0)
return (ATTR_NEXTHOP);
if (!ebgp)
if ((a->flags & F_ATTR_LOCALPREF) == 0)
return (ATTR_LOCALPREF);
return (0);
}
int
rde_get_mp_nexthop(struct ibuf *buf, uint8_t aid,
struct rde_peer *peer, struct filterstate *state)
{
struct bgpd_addr nexthop;
struct ibuf nhbuf;
uint8_t nhlen;
if (ibuf_get_n8(buf, &nhlen) == -1)
return (-1);
if (ibuf_get_ibuf(buf, nhlen, &nhbuf) == -1)
return (-1);
if (ibuf_skip(buf, 1) == -1)
return (-1);
if (aid == AID_INET && peer_has_ext_nexthop(peer, AID_INET) &&
(nhlen == 16 || nhlen == 32))
aid = AID_INET6;
if (aid == AID_VPN_IPv4 && peer_has_ext_nexthop(peer, AID_VPN_IPv4) &&
(nhlen == 24 || nhlen == 48))
aid = AID_VPN_IPv6;
memset(&nexthop, 0, sizeof(nexthop));
switch (aid) {
case AID_INET:
log_peer_warnx(&peer->conf, "bad multiprotocol nexthop, "
"IPv4 unexpected");
return (-1);
case AID_INET6:
if (nhlen != 16 && nhlen != 32) {
log_peer_warnx(&peer->conf, "bad %s nexthop, "
"bad size %d", aid2str(aid), nhlen);
return (-1);
}
if (ibuf_get(&nhbuf, &nexthop.v6, sizeof(nexthop.v6)) == -1)
return (-1);
nexthop.aid = AID_INET6;
if (IN6_IS_ADDR_LINKLOCAL(&nexthop.v6)) {
if (peer->local_if_scope != 0) {
nexthop.scope_id = peer->local_if_scope;
} else {
log_peer_warnx(&peer->conf,
"unexpected link-local nexthop: %s",
log_addr(&nexthop));
return (-1);
}
}
break;
case AID_VPN_IPv4:
if (nhlen != 12) {
log_peer_warnx(&peer->conf, "bad %s nexthop, "
"bad size %d", aid2str(aid), nhlen);
return (-1);
}
if (ibuf_skip(&nhbuf, sizeof(uint64_t)) == -1 ||
ibuf_get(&nhbuf, &nexthop.v4, sizeof(nexthop.v4)) == -1)
return (-1);
nexthop.aid = AID_INET;
break;
case AID_VPN_IPv6:
if (nhlen != 24 && nhlen != 48) {
log_peer_warnx(&peer->conf, "bad %s nexthop, "
"bad size %d", aid2str(aid), nhlen);
return (-1);
}
if (ibuf_skip(&nhbuf, sizeof(uint64_t)) == -1 ||
ibuf_get(&nhbuf, &nexthop.v6, sizeof(nexthop.v6)) == -1)
return (-1);
nexthop.aid = AID_INET6;
if (IN6_IS_ADDR_LINKLOCAL(&nexthop.v6)) {
if (peer->local_if_scope != 0) {
nexthop.scope_id = peer->local_if_scope;
} else {
log_peer_warnx(&peer->conf,
"unexpected link-local nexthop: %s",
log_addr(&nexthop));
return (-1);
}
}
break;
case AID_EVPN:
switch (nhlen) {
case 4:
if (ibuf_get_h32(&nhbuf, &nexthop.v4.s_addr) == -1)
return (-1);
nexthop.aid = AID_INET;
break;
case 16:
case 32:
if (ibuf_get(&nhbuf, &nexthop.v6,
sizeof(nexthop.v6)) == -1)
return (-1);
nexthop.aid = AID_INET6;
if (IN6_IS_ADDR_LINKLOCAL(&nexthop.v6)) {
if (peer->local_if_scope != 0) {
nexthop.scope_id = peer->local_if_scope;
} else {
log_peer_warnx(&peer->conf,
"unexpected link-local nexthop: %s",
log_addr(&nexthop));
return (-1);
}
}
break;
default:
log_peer_warnx(&peer->conf, "bad %s nexthop, "
"bad size %d", aid2str(aid), nhlen);
return (-1);
}
break;
case AID_FLOWSPECv4:
case AID_FLOWSPECv6:
if (nhlen != 0) {
log_peer_warnx(&peer->conf, "bad %s nexthop, "
"bad size %d", aid2str(aid), nhlen);
return (-1);
}
return (0);
default:
log_peer_warnx(&peer->conf, "bad multiprotocol nexthop, "
"bad AID");
return (-1);
}
state->nexthop = nexthop_get(&nexthop);
return (0);
}
void
rde_update_err(struct rde_peer *peer, uint8_t error, uint8_t suberr,
struct ibuf *opt)
{
struct ibuf *wbuf;
size_t size = 0;
if (opt != NULL) {
ibuf_rewind(opt);
size = ibuf_size(opt);
}
if ((wbuf = imsg_create(ibuf_se, IMSG_UPDATE_ERR, peer->conf.id, 0,
size + sizeof(error) + sizeof(suberr))) == NULL)
fatal("%s %d imsg_create error", __func__, __LINE__);
if (imsg_add(wbuf, &error, sizeof(error)) == -1 ||
imsg_add(wbuf, &suberr, sizeof(suberr)) == -1)
fatal("%s %d imsg_add error", __func__, __LINE__);
if (opt != NULL)
if (ibuf_add_ibuf(wbuf, opt) == -1)
fatal("%s %d ibuf_add_ibuf error", __func__, __LINE__);
imsg_close(ibuf_se, wbuf);
peer->state = PEER_ERR;
}
void
rde_update_log(const char *message, uint16_t rid,
const struct rde_peer *peer, const struct bgpd_addr *next,
const struct bgpd_addr *prefix, uint8_t prefixlen)
{
char *l = NULL;
char *n = NULL;
char *p = NULL;
if (!((conf->log & BGPD_LOG_UPDATES) ||
(peer->flags & PEERFLAG_LOG_UPDATES)))
return;
if (next != NULL)
if (asprintf(&n, " via %s", log_addr(next)) == -1)
n = NULL;
if (asprintf(&p, "%s/%u", log_addr(prefix), prefixlen) == -1)
p = NULL;
l = log_fmt_peer(&peer->conf);
log_info("Rib %s: %s AS%s: %s %s%s", rib_byid(rid)->name,
l, log_as(peer->conf.remote_as), message,
p ? p : "out of memory", n ? n : "");
free(l);
free(n);
free(p);
}
void
rde_as4byte_fixup(struct rde_peer *peer, struct rde_aspath *a)
{
struct attr *nasp, *naggr, *oaggr;
uint32_t as;
if (!(a->flags & F_ATTR_AS4BYTE_NEW) || a->flags & F_ATTR_PARSE_ERR)
return;
nasp = attr_optget(a, ATTR_AS4_PATH);
naggr = attr_optget(a, ATTR_AS4_AGGREGATOR);
if (peer_has_as4byte(peer)) {
if (nasp) {
log_peer_warnx(&peer->conf, "uses 4-byte ASN "
"but sent AS4_PATH attribute.");
attr_free(a, nasp);
}
if (naggr) {
log_peer_warnx(&peer->conf, "uses 4-byte ASN "
"but sent AS4_AGGREGATOR attribute.");
attr_free(a, naggr);
}
return;
}
if ((oaggr = attr_optget(a, ATTR_AGGREGATOR))) {
memcpy(&as, oaggr->data, sizeof(as));
if (ntohl(as) != AS_TRANS) {
if (nasp)
attr_free(a, nasp);
if (naggr)
attr_free(a, naggr);
return;
}
if (naggr) {
attr_free(a, oaggr);
if (attr_optadd(a, ATTR_OPTIONAL | ATTR_TRANSITIVE,
ATTR_AGGREGATOR, naggr->data, naggr->len) == -1)
fatalx("attr_optadd failed but impossible");
}
}
if (naggr)
attr_free(a, naggr);
if (nasp)
aspath_merge(a, nasp);
}
uint8_t
rde_aspa_validity(struct rde_peer *peer, struct rde_aspath *asp, uint8_t aid)
{
if (!peer->conf.ebgp)
return ASPA_NEVER_KNOWN;
if (aid != AID_INET && aid != AID_INET6)
return ASPA_NEVER_KNOWN;
#ifdef MAYBE
if (peer->role != ROLE_RS_CLIENT &&
peer->conf.enforce_as != ENFORCE_AS_ON) {
uint32_t fas;
fas = aspath_neighbor(asp->aspath);
if (peer->conf.remote_as != fas)
return ASPA_INVALID;
}
#endif
if (peer->role == ROLE_NONE)
return ASPA_UNKNOWN;
if (peer->role == ROLE_CUSTOMER)
return asp->aspa_state.downup;
else
return asp->aspa_state.onlyup;
}
void
rde_reflector(struct rde_peer *peer, struct rde_aspath *asp)
{
struct attr *a;
uint8_t *p;
uint16_t len;
uint32_t id;
if (asp->flags & F_ATTR_PARSE_ERR)
return;
if ((a = attr_optget(asp, ATTR_ORIGINATOR_ID)) != NULL) {
id = htonl(conf->bgpid);
if (memcmp(&id, a->data, sizeof(id)) == 0) {
asp->flags |= F_ATTR_LOOP;
return;
}
} else if (conf->flags & BGPD_FLAG_REFLECTOR) {
if (peer->conf.ebgp)
id = htonl(conf->bgpid);
else
id = htonl(peer->remote_bgpid);
if (attr_optadd(asp, ATTR_OPTIONAL, ATTR_ORIGINATOR_ID,
&id, sizeof(id)) == -1)
fatalx("attr_optadd failed but impossible");
}
if (conf->flags & BGPD_FLAG_REFLECTOR) {
id = htonl(conf->clusterid);
if ((a = attr_optget(asp, ATTR_CLUSTER_LIST)) != NULL) {
for (len = 0; len < a->len; len += sizeof(id))
if (memcmp(&id, a->data + len,
sizeof(id)) == 0) {
asp->flags |= F_ATTR_LOOP;
return;
}
len = a->len + sizeof(id);
if (len < a->len)
fatalx("rde_reflector: cluster-list overflow");
if ((p = malloc(len)) == NULL)
fatal("rde_reflector");
memcpy(p, &id, sizeof(id));
memcpy(p + sizeof(id), a->data, a->len);
attr_free(asp, a);
if (attr_optadd(asp, ATTR_OPTIONAL, ATTR_CLUSTER_LIST,
p, len) == -1)
fatalx("attr_optadd failed but impossible");
free(p);
} else if (attr_optadd(asp, ATTR_OPTIONAL, ATTR_CLUSTER_LIST,
&id, sizeof(id)) == -1)
fatalx("attr_optadd failed but impossible");
}
}
static void
rde_dump_rib_as(struct prefix *p, struct rde_aspath *asp, pid_t pid, int flags)
{
struct ctl_show_rib rib;
struct ibuf *wbuf;
struct attr *a;
struct nexthop *nexthop;
struct rib_entry *re;
struct prefix *xp;
struct rde_peer *peer;
monotime_t staletime;
size_t aslen;
uint8_t l;
nexthop = prefix_nexthop(p);
peer = prefix_peer(p);
memset(&rib, 0, sizeof(rib));
rib.lastchange = p->lastchange;
rib.local_pref = asp->lpref;
rib.med = asp->med;
rib.weight = asp->weight;
strlcpy(rib.descr, peer->conf.descr, sizeof(rib.descr));
memcpy(&rib.remote_addr, &peer->remote_addr,
sizeof(rib.remote_addr));
rib.remote_id = peer->remote_bgpid;
if (nexthop != NULL) {
rib.exit_nexthop = nexthop->exit_nexthop;
rib.true_nexthop = nexthop->true_nexthop;
} else {
rib.exit_nexthop.aid = p->pt->aid;
rib.true_nexthop.aid = p->pt->aid;
}
pt_getaddr(p->pt, &rib.prefix);
rib.prefixlen = p->pt->prefixlen;
rib.origin = asp->origin;
rib.roa_validation_state = prefix_roa_vstate(p);
rib.aspa_validation_state = prefix_aspa_vstate(p);
rib.dmetric = p->dmetric;
rib.flags = 0;
if (prefix_eligible(p)) {
re = prefix_re(p);
TAILQ_FOREACH(xp, &re->prefix_h, rib_l) {
switch (xp->dmetric) {
case PREFIX_DMETRIC_BEST:
if (xp == p)
rib.flags |= F_PREF_BEST;
break;
case PREFIX_DMETRIC_ECMP:
if (xp == p)
rib.flags |= F_PREF_ECMP;
break;
case PREFIX_DMETRIC_AS_WIDE:
if (xp == p)
rib.flags |= F_PREF_AS_WIDE;
break;
default:
xp = NULL;
break;
}
if (xp == NULL || xp == p)
break;
}
}
if (!peer->conf.ebgp)
rib.flags |= F_PREF_INTERNAL;
if (asp->flags & F_PREFIX_ANNOUNCED)
rib.flags |= F_PREF_ANNOUNCE;
if (prefix_eligible(p))
rib.flags |= F_PREF_ELIGIBLE;
if (prefix_filtered(p))
rib.flags |= F_PREF_FILTERED;
if (asp->flags & F_ATTR_OTC_LEAK)
rib.flags |= F_PREF_OTC_LEAK;
else if (asp->flags & F_ATTR_PARSE_ERR)
rib.flags |= F_PREF_INVALID;
staletime = peer->staletime[p->pt->aid];
if (monotime_valid(staletime) &&
monotime_cmp(p->lastchange, staletime) <= 0)
rib.flags |= F_PREF_STALE;
if (peer_has_add_path(peer, p->pt->aid, CAPA_AP_RECV)) {
rib.path_id = p->path_id;
rib.flags |= F_PREF_PATH_ID;
}
aslen = aspath_length(asp->aspath);
if ((wbuf = imsg_create(ibuf_se_ctl, IMSG_CTL_SHOW_RIB, 0, pid,
sizeof(rib) + aslen)) == NULL)
return;
if (imsg_add(wbuf, &rib, sizeof(rib)) == -1 ||
imsg_add(wbuf, aspath_dump(asp->aspath), aslen) == -1)
return;
imsg_close(ibuf_se_ctl, wbuf);
if (flags & F_CTL_DETAIL) {
struct rde_community *comm = prefix_communities(p);
size_t len = comm->nentries * sizeof(struct community);
if (comm->nentries > 0) {
if (imsg_compose(ibuf_se_ctl,
IMSG_CTL_SHOW_RIB_COMMUNITIES, 0, pid, -1,
comm->communities, len) == -1)
return;
}
for (l = 0; l < asp->others_len; l++) {
if ((a = asp->others[l]) == NULL)
break;
if ((wbuf = imsg_create(ibuf_se_ctl,
IMSG_CTL_SHOW_RIB_ATTR, 0, pid, 0)) == NULL)
return;
if (attr_writebuf(wbuf, a->flags, a->type, a->data,
a->len) == -1) {
ibuf_free(wbuf);
return;
}
imsg_close(ibuf_se_ctl, wbuf);
}
}
}
static void
rde_dump_adjout_as(struct rde_peer *peer, struct pt_entry *pte,
struct adjout_prefix *p, struct adjout_attr *attrs, pid_t pid, int flags)
{
struct ctl_show_rib rib;
struct ibuf *wbuf;
struct attr *a;
struct rde_aspath *asp;
struct nexthop *nexthop;
size_t aslen;
uint8_t l;
nexthop = attrs->nexthop;
asp = attrs->aspath;
memset(&rib, 0, sizeof(rib));
rib.local_pref = asp->lpref;
rib.med = asp->med;
rib.weight = asp->weight;
strlcpy(rib.descr, peer->conf.descr, sizeof(rib.descr));
memcpy(&rib.remote_addr, &peer->remote_addr,
sizeof(rib.remote_addr));
rib.remote_id = peer->remote_bgpid;
if (nexthop != NULL) {
rib.exit_nexthop = nexthop->exit_nexthop;
rib.true_nexthop = nexthop->true_nexthop;
} else {
rib.exit_nexthop.aid = pte->aid;
rib.true_nexthop.aid = pte->aid;
}
pt_getaddr(pte, &rib.prefix);
rib.prefixlen = pte->prefixlen;
rib.origin = asp->origin;
rib.flags = 0;
rib.flags |= F_PREF_ELIGIBLE;
if (!peer->conf.ebgp)
rib.flags |= F_PREF_INTERNAL;
if (asp->flags & F_PREFIX_ANNOUNCED)
rib.flags |= F_PREF_ANNOUNCE;
if (peer_has_add_path(peer, pte->aid, CAPA_AP_SEND)) {
rib.path_id = p->path_id_tx;
rib.flags |= F_PREF_PATH_ID;
}
aslen = aspath_length(asp->aspath);
if ((wbuf = imsg_create(ibuf_se_ctl, IMSG_CTL_SHOW_RIB, 0, pid,
sizeof(rib) + aslen)) == NULL)
return;
if (imsg_add(wbuf, &rib, sizeof(rib)) == -1 ||
imsg_add(wbuf, aspath_dump(asp->aspath), aslen) == -1)
return;
imsg_close(ibuf_se_ctl, wbuf);
if (flags & F_CTL_DETAIL) {
struct rde_community *comm = attrs->communities;
size_t len = comm->nentries * sizeof(struct community);
if (comm->nentries > 0) {
if (imsg_compose(ibuf_se_ctl,
IMSG_CTL_SHOW_RIB_COMMUNITIES, 0, pid, -1,
comm->communities, len) == -1)
return;
}
for (l = 0; l < asp->others_len; l++) {
if ((a = asp->others[l]) == NULL)
break;
if ((wbuf = imsg_create(ibuf_se_ctl,
IMSG_CTL_SHOW_RIB_ATTR, 0, pid, 0)) == NULL)
return;
if (attr_writebuf(wbuf, a->flags, a->type, a->data,
a->len) == -1) {
ibuf_free(wbuf);
return;
}
imsg_close(ibuf_se_ctl, wbuf);
}
}
}
int
rde_match_peer(struct rde_peer *p, struct ctl_neighbor *n)
{
char *s;
if (n && n->addr.aid) {
if (memcmp(&p->conf.remote_addr, &n->addr,
sizeof(p->conf.remote_addr)))
return 0;
} else if (n && n->descr[0]) {
s = n->is_group ? p->conf.group : p->conf.descr;
if (strncmp(s, n->descr, sizeof(n->descr)))
return 0;
}
return 1;
}
static void
rde_dump_filter(struct prefix *p, struct ctl_show_rib_request *req)
{
struct rde_aspath *asp;
if (!rde_match_peer(prefix_peer(p), &req->neighbor))
return;
asp = prefix_aspath(p);
if ((req->flags & F_CTL_BEST) && p->dmetric != PREFIX_DMETRIC_BEST)
return;
if ((req->flags & F_CTL_INVALID) &&
(asp->flags & F_ATTR_PARSE_ERR) == 0)
return;
if ((req->flags & F_CTL_FILTERED) && !prefix_filtered(p))
return;
if ((req->flags & F_CTL_INELIGIBLE) && prefix_eligible(p))
return;
if ((req->flags & F_CTL_LEAKED) &&
(asp->flags & F_ATTR_OTC_LEAK) == 0)
return;
if ((req->flags & F_CTL_HAS_PATHID)) {
if (req->path_id != p->path_id)
return;
}
if (req->as.type != AS_UNDEF &&
!aspath_match(asp->aspath, &req->as, 0))
return;
if (req->community.flags != 0) {
if (!community_match(prefix_communities(p), &req->community,
NULL))
return;
}
if (!ovs_match(prefix_roa_vstate(p), req->flags))
return;
if (!avs_match(prefix_aspa_vstate(p), req->flags))
return;
rde_dump_rib_as(p, asp, req->pid, req->flags);
}
static void
rde_dump_adjout_filter(struct rde_peer *peer, struct pt_entry *pte,
struct adjout_prefix *p, struct ctl_show_rib_request *req)
{
struct adjout_attr *attrs = p->attrs;
struct rde_aspath *asp = attrs->aspath;
if (!rde_match_peer(peer, &req->neighbor))
return;
if ((req->flags & F_CTL_HAS_PATHID)) {
if (req->path_id != p->path_id_tx)
return;
}
if (req->as.type != AS_UNDEF &&
!aspath_match(asp->aspath, &req->as, 0))
return;
if (req->community.flags != 0) {
if (!community_match(attrs->communities, &req->community, NULL))
return;
}
rde_dump_adjout_as(peer, pte, p, attrs, req->pid, req->flags);
}
static void
rde_dump_upcall(struct rib_entry *re, void *ptr)
{
struct rde_dump_ctx *ctx = ptr;
struct prefix *p;
if (re == NULL)
return;
TAILQ_FOREACH(p, &re->prefix_h, rib_l)
rde_dump_filter(p, &ctx->req);
}
static void
rde_dump_adjout_upcall(struct rde_peer *peer, struct pt_entry *pte,
struct adjout_prefix *p, void *ptr)
{
struct rde_dump_ctx *ctx = ptr;
rde_dump_adjout_filter(peer, pte, p, &ctx->req);
}
static int
rde_dump_throttled(void *arg)
{
struct rde_dump_ctx *ctx = arg;
return (ctx->throttled != 0);
}
static void
rde_dump_done(void *arg, uint8_t aid)
{
struct rde_dump_ctx *ctx = arg;
struct rde_peer *peer;
u_int error;
if (ctx->req.flags & F_CTL_ADJ_OUT) {
peer = peer_match(&ctx->req.neighbor, ctx->peerid);
if (peer == NULL)
goto done;
ctx->peerid = peer->conf.id;
switch (ctx->req.type) {
case IMSG_CTL_SHOW_RIB:
if (adjout_prefix_dump_new(peer, ctx->req.aid,
CTL_MSG_HIGH_MARK, ctx, rde_dump_adjout_upcall,
rde_dump_done, rde_dump_throttled) == -1)
goto nomem;
break;
case IMSG_CTL_SHOW_RIB_PREFIX:
if (adjout_prefix_dump_subtree(peer, &ctx->req.prefix,
ctx->req.prefixlen, CTL_MSG_HIGH_MARK, ctx,
rde_dump_adjout_upcall, rde_dump_done,
rde_dump_throttled) == -1)
goto nomem;
break;
default:
fatalx("%s: unsupported imsg type", __func__);
}
return;
}
done:
imsg_compose(ibuf_se_ctl, IMSG_CTL_END, 0, ctx->req.pid, -1, NULL, 0);
LIST_REMOVE(ctx, entry);
free(ctx);
return;
nomem:
log_warn(__func__);
error = CTL_RES_NOMEM;
imsg_compose(ibuf_se_ctl, IMSG_CTL_RESULT, 0, ctx->req.pid, -1, &error,
sizeof(error));
return;
}
void
rde_dump_ctx_new(struct ctl_show_rib_request *req, pid_t pid,
enum imsg_type type)
{
struct rde_dump_ctx *ctx;
struct rib_entry *re;
struct adjout_prefix *p;
u_int error;
uint8_t hostplen, plen;
uint16_t rid;
if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
nomem:
log_warn(__func__);
error = CTL_RES_NOMEM;
imsg_compose(ibuf_se_ctl, IMSG_CTL_RESULT, 0, pid, -1, &error,
sizeof(error));
free(ctx);
return;
}
if (strcmp(req->rib, "Adj-RIB-Out") == 0)
req->flags |= F_CTL_ADJ_OUT;
memcpy(&ctx->req, req, sizeof(struct ctl_show_rib_request));
ctx->req.pid = pid;
ctx->req.type = type;
if (req->flags & (F_CTL_ADJ_IN | F_CTL_INVALID)) {
rid = RIB_ADJ_IN;
} else if (req->flags & F_CTL_ADJ_OUT) {
struct rde_peer *peer;
peer = peer_match(&req->neighbor, 0);
if (peer == NULL) {
error = CTL_RES_NOSUCHPEER;
imsg_compose(ibuf_se_ctl, IMSG_CTL_RESULT, 0, pid, -1,
&error, sizeof(error));
free(ctx);
return;
}
ctx->peerid = peer->conf.id;
switch (ctx->req.type) {
case IMSG_CTL_SHOW_RIB:
if (adjout_prefix_dump_new(peer, ctx->req.aid,
CTL_MSG_HIGH_MARK, ctx, rde_dump_adjout_upcall,
rde_dump_done, rde_dump_throttled) == -1)
goto nomem;
break;
case IMSG_CTL_SHOW_RIB_PREFIX:
if (req->flags & F_LONGER) {
if (adjout_prefix_dump_subtree(peer,
&req->prefix, req->prefixlen,
CTL_MSG_HIGH_MARK, ctx,
rde_dump_adjout_upcall,
rde_dump_done, rde_dump_throttled) == -1)
goto nomem;
break;
}
switch (req->prefix.aid) {
case AID_INET:
case AID_VPN_IPv4:
hostplen = 32;
break;
case AID_INET6:
case AID_VPN_IPv6:
hostplen = 128;
break;
default:
fatalx("%s: unknown af", __func__);
}
do {
struct pt_entry *pte;
if (req->flags & F_SHORTER) {
for (plen = 0; plen <= req->prefixlen;
plen++) {
pte = pt_get(&req->prefix,
plen);
if (pte == NULL)
continue;
for (p = adjout_prefix_first(
peer, pte);
p != NULL;
p = adjout_prefix_next(
peer, pte, p)) {
rde_dump_adjout_upcall(
peer, pte, p, ctx);
}
}
continue;
} else if (req->prefixlen == hostplen) {
pte = pt_lookup(&req->prefix);
} else {
pte = pt_get(&req->prefix,
req->prefixlen);
}
if (pte == NULL)
continue;
for (p = adjout_prefix_first(peer, pte);
p != NULL;
p = adjout_prefix_next(peer, pte, p)) {
rde_dump_adjout_upcall(peer, pte, p,
ctx);
}
} while ((peer = peer_match(&req->neighbor,
peer->conf.id)));
imsg_compose(ibuf_se_ctl, IMSG_CTL_END, 0, ctx->req.pid,
-1, NULL, 0);
free(ctx);
return;
default:
fatalx("%s: unsupported imsg type", __func__);
}
LIST_INSERT_HEAD(&rde_dump_h, ctx, entry);
return;
} else if ((rid = rib_find(req->rib)) == RIB_NOTFOUND) {
log_warnx("%s: no such rib %s", __func__, req->rib);
error = CTL_RES_NOSUCHRIB;
imsg_compose(ibuf_se_ctl, IMSG_CTL_RESULT, 0, pid, -1, &error,
sizeof(error));
free(ctx);
return;
}
switch (ctx->req.type) {
case IMSG_CTL_SHOW_NETWORK:
if (rib_dump_new(rid, ctx->req.aid, CTL_MSG_HIGH_MARK, ctx,
network_dump_upcall, rde_dump_done,
rde_dump_throttled) == -1)
goto nomem;
break;
case IMSG_CTL_SHOW_RIB:
if (rib_dump_new(rid, ctx->req.aid, CTL_MSG_HIGH_MARK, ctx,
rde_dump_upcall, rde_dump_done, rde_dump_throttled) == -1)
goto nomem;
break;
case IMSG_CTL_SHOW_RIB_PREFIX:
if (req->flags & F_LONGER) {
if (rib_dump_subtree(rid, &req->prefix, req->prefixlen,
CTL_MSG_HIGH_MARK, ctx, rde_dump_upcall,
rde_dump_done, rde_dump_throttled) == -1)
goto nomem;
break;
}
switch (req->prefix.aid) {
case AID_INET:
case AID_VPN_IPv4:
hostplen = 32;
break;
case AID_INET6:
case AID_VPN_IPv6:
hostplen = 128;
break;
default:
fatalx("%s: unknown af", __func__);
}
if (req->flags & F_SHORTER) {
for (plen = 0; plen <= req->prefixlen; plen++) {
re = rib_get_addr(rib_byid(rid), &req->prefix,
plen);
rde_dump_upcall(re, ctx);
}
} else if (req->prefixlen == hostplen) {
re = rib_match(rib_byid(rid), &req->prefix);
rde_dump_upcall(re, ctx);
} else {
re = rib_get_addr(rib_byid(rid), &req->prefix,
req->prefixlen);
rde_dump_upcall(re, ctx);
}
imsg_compose(ibuf_se_ctl, IMSG_CTL_END, 0, ctx->req.pid,
-1, NULL, 0);
free(ctx);
return;
default:
fatalx("%s: unsupported imsg type", __func__);
}
LIST_INSERT_HEAD(&rde_dump_h, ctx, entry);
}
void
rde_dump_ctx_throttle(pid_t pid, int throttle)
{
struct rde_dump_ctx *ctx;
LIST_FOREACH(ctx, &rde_dump_h, entry) {
if (ctx->req.pid == pid) {
ctx->throttled = throttle;
return;
}
}
}
void
rde_dump_ctx_terminate(pid_t pid)
{
struct rde_dump_ctx *ctx;
LIST_FOREACH(ctx, &rde_dump_h, entry) {
if (ctx->req.pid == pid) {
rib_dump_terminate(ctx);
return;
}
}
}
static int
rde_mrt_throttled(void *arg)
{
struct mrt *mrt = arg;
return (msgbuf_queuelen(mrt->wbuf) > SESS_MSG_LOW_MARK);
}
static void
rde_mrt_done(void *ptr, uint8_t aid)
{
mrt_done(ptr);
}
void
rde_dump_mrt_new(struct mrt *mrt, pid_t pid, int fd)
{
struct rde_mrt_ctx *ctx;
uint16_t rid;
if ((ctx = calloc(1, sizeof(*ctx))) == NULL) {
log_warn("rde_dump_mrt_new");
return;
}
memcpy(&ctx->mrt, mrt, sizeof(struct mrt));
if ((ctx->mrt.wbuf = msgbuf_new()) == NULL) {
log_warn("rde_dump_mrt_new");
free(ctx);
return;
}
ctx->mrt.fd = fd;
ctx->mrt.state = MRT_STATE_RUNNING;
rid = rib_find(ctx->mrt.rib);
if (rid == RIB_NOTFOUND) {
log_warnx("non existing RIB %s for mrt dump", ctx->mrt.rib);
free(ctx);
return;
}
if (ctx->mrt.type == MRT_TABLE_DUMP_V2)
mrt_dump_v2_hdr(&ctx->mrt, conf);
if (rib_dump_new(rid, AID_UNSPEC, CTL_MSG_HIGH_MARK, &ctx->mrt,
mrt_dump_upcall, rde_mrt_done, rde_mrt_throttled) == -1)
fatal("%s: rib_dump_new", __func__);
LIST_INSERT_HEAD(&rde_mrts, ctx, entry);
rde_mrt_cnt++;
}
void
rde_send_kroute_flush(struct rib *rib)
{
if (imsg_compose(ibuf_main, IMSG_KROUTE_FLUSH, rib->rtableid, 0, -1,
NULL, 0) == -1)
fatal("%s %d imsg_compose error", __func__, __LINE__);
}
void
rde_send_kroute(struct rib *rib, struct prefix *new, struct prefix *old)
{
struct kroute_full kf;
struct prefix *p;
struct l3vpn *vpn;
enum imsg_type type;
if ((old == NULL || prefix_aspath(old)->flags & F_PREFIX_ANNOUNCED) &&
(new == NULL || prefix_aspath(new)->flags & F_PREFIX_ANNOUNCED))
return;
if (new == NULL || prefix_aspath(new)->flags & F_PREFIX_ANNOUNCED) {
type = IMSG_KROUTE_DELETE;
p = old;
} else {
type = IMSG_KROUTE_CHANGE;
p = new;
}
memset(&kf, 0, sizeof(kf));
pt_getaddr(p->pt, &kf.prefix);
kf.prefixlen = p->pt->prefixlen;
if (type == IMSG_KROUTE_CHANGE) {
if (prefix_nhflags(p) == NEXTHOP_REJECT)
kf.flags |= F_REJECT;
if (prefix_nhflags(p) == NEXTHOP_BLACKHOLE)
kf.flags |= F_BLACKHOLE;
kf.nexthop = prefix_nexthop(p)->exit_nexthop;
strlcpy(kf.label, rtlabel_id2name(prefix_aspath(p)->rtlabelid),
sizeof(kf.label));
}
switch (kf.prefix.aid) {
case AID_VPN_IPv4:
if (kf.nexthop.aid != AID_INET)
type = IMSG_KROUTE_DELETE;
case AID_VPN_IPv6:
if (!(rib->flags & F_RIB_LOCAL))
break;
SIMPLEQ_FOREACH(vpn, &conf->l3vpns, entry) {
if (!rde_l3vpn_import(prefix_communities(p), vpn))
continue;
kf.ifindex = if_nametoindex(vpn->ifmpe);
if (imsg_compose(ibuf_main, type, vpn->rtableid, 0, -1,
&kf, sizeof(kf)) == -1)
fatal("%s %d imsg_compose error", __func__,
__LINE__);
}
break;
case AID_INET:
if (kf.nexthop.aid != AID_INET)
type = IMSG_KROUTE_DELETE;
default:
if (imsg_compose(ibuf_main, type, rib->rtableid, 0, -1,
&kf, sizeof(kf)) == -1)
fatal("%s %d imsg_compose error", __func__, __LINE__);
break;
}
}
int
rde_evaluate_all(void)
{
return rde_eval_all;
}
static void
rde_up_flush_upcall(struct rde_peer *peer, struct pt_entry *pte,
struct adjout_prefix *p, void *ptr)
{
adjout_prefix_withdraw(peer, pte, p);
}
int
rde_update_queue_pending(void)
{
struct rde_peer *peer;
uint8_t aid;
if (ibuf_se && imsgbuf_queuelen(ibuf_se) >= SESS_MSG_HIGH_MARK)
return 0;
RB_FOREACH(peer, peer_tree, &peertable) {
if (peer->conf.id == 0)
continue;
if (!peer_is_up(peer))
continue;
if (peer->throttled)
continue;
for (aid = AID_MIN; aid < AID_MAX; aid++) {
if (!TAILQ_EMPTY(&peer->updates[aid]) ||
!TAILQ_EMPTY(&peer->withdraws[aid]))
return 1;
}
}
return 0;
}
void
rde_update_queue_runner(uint8_t aid)
{
struct rde_peer *peer;
int sent, max = RDE_RUNNER_ROUNDS;
do {
sent = 0;
RB_FOREACH(peer, peer_tree, &peertable) {
if (peer->conf.id == 0)
continue;
if (!peer_is_up(peer))
continue;
if (peer->throttled)
continue;
if (TAILQ_EMPTY(&peer->withdraws[aid]))
continue;
up_dump_withdraws(ibuf_se, peer, aid);
sent++;
}
max -= sent;
} while (sent != 0 && max > 0);
max = RDE_RUNNER_ROUNDS;
do {
sent = 0;
RB_FOREACH(peer, peer_tree, &peertable) {
if (peer->conf.id == 0)
continue;
if (!peer_is_up(peer))
continue;
if (peer->throttled)
continue;
if (TAILQ_EMPTY(&peer->updates[aid]))
continue;
if (up_is_eor(peer, aid)) {
int sent_eor = peer->sent_eor & (1 << aid);
if (peer->capa.grestart.restart && !sent_eor)
rde_peer_send_eor(peer, aid);
if (peer->capa.enhanced_rr && sent_eor)
rde_peer_send_rrefresh(peer, aid,
ROUTE_REFRESH_END_RR);
continue;
}
up_dump_update(ibuf_se, peer, aid);
sent++;
}
max -= sent;
} while (sent != 0 && max > 0);
}
struct rde_pftable_node {
RB_ENTRY(rde_pftable_node) entry;
struct pt_entry *prefix;
int refcnt;
uint16_t id;
};
RB_HEAD(rde_pftable_tree, rde_pftable_node);
static inline int
rde_pftable_cmp(struct rde_pftable_node *a, struct rde_pftable_node *b)
{
if (a->prefix > b->prefix)
return 1;
if (a->prefix < b->prefix)
return -1;
return (a->id - b->id);
}
RB_GENERATE_STATIC(rde_pftable_tree, rde_pftable_node, entry, rde_pftable_cmp);
struct rde_pftable_tree pftable_tree = RB_INITIALIZER(&pftable_tree);
int need_commit;
static void
rde_pftable_send(uint16_t id, struct pt_entry *pt, int del)
{
struct pftable_msg pfm;
if (id == 0)
return;
if (rde_quit)
return;
memset(&pfm, 0, sizeof(pfm));
strlcpy(pfm.pftable, pftable_id2name(id), sizeof(pfm.pftable));
pt_getaddr(pt, &pfm.addr);
pfm.len = pt->prefixlen;
if (imsg_compose(ibuf_main,
del ? IMSG_PFTABLE_REMOVE : IMSG_PFTABLE_ADD,
0, 0, -1, &pfm, sizeof(pfm)) == -1)
fatal("%s %d imsg_compose error", __func__, __LINE__);
need_commit = 1;
}
void
rde_pftable_add(uint16_t id, struct prefix *p)
{
struct rde_pftable_node *pfn, node;
memset(&node, 0, sizeof(node));
node.prefix = p->pt;
node.id = id;
pfn = RB_FIND(rde_pftable_tree, &pftable_tree, &node);
if (pfn == NULL) {
if ((pfn = calloc(1, sizeof(*pfn))) == NULL)
fatal("%s", __func__);
pfn->prefix = pt_ref(p->pt);
pfn->id = id;
if (RB_INSERT(rde_pftable_tree, &pftable_tree, pfn) != NULL)
fatalx("%s: tree corrupt", __func__);
rde_pftable_send(id, p->pt, 0);
}
pfn->refcnt++;
}
void
rde_pftable_del(uint16_t id, struct prefix *p)
{
struct rde_pftable_node *pfn, node;
memset(&node, 0, sizeof(node));
node.prefix = p->pt;
node.id = id;
pfn = RB_FIND(rde_pftable_tree, &pftable_tree, &node);
if (pfn == NULL)
return;
if (--pfn->refcnt <= 0) {
rde_pftable_send(id, p->pt, 1);
if (RB_REMOVE(rde_pftable_tree, &pftable_tree, pfn) == NULL)
fatalx("%s: tree corrupt", __func__);
pt_unref(pfn->prefix);
free(pfn);
}
}
void
rde_commit_pftable(void)
{
if (rde_quit)
return;
if (!need_commit)
return;
if (imsg_compose(ibuf_main, IMSG_PFTABLE_COMMIT, 0, 0, -1, NULL, 0) ==
-1)
fatal("%s %d imsg_compose error", __func__, __LINE__);
need_commit = 0;
}
void
rde_send_nexthop(struct bgpd_addr *next, int insert)
{
int type;
if (insert)
type = IMSG_NEXTHOP_ADD;
else
type = IMSG_NEXTHOP_REMOVE;
if (imsg_compose(ibuf_main, type, 0, 0, -1, next,
sizeof(struct bgpd_addr)) == -1)
fatal("%s %d imsg_compose error", __func__, __LINE__);
}
void
rde_reload_done(void)
{
struct rde_peer *peer;
struct filter_head *fh;
struct rde_filter *rf;
struct rde_prefixset_head prefixsets_old;
struct rde_prefixset_head originsets_old;
struct as_set_head as_sets_old;
uint16_t rid;
int reload = 0, force_locrib = 0;
softreconfig = 0;
SIMPLEQ_INIT(&prefixsets_old);
SIMPLEQ_INIT(&originsets_old);
SIMPLEQ_INIT(&as_sets_old);
SIMPLEQ_CONCAT(&prefixsets_old, &conf->rde_prefixsets);
SIMPLEQ_CONCAT(&originsets_old, &conf->rde_originsets);
SIMPLEQ_CONCAT(&as_sets_old, &conf->as_sets);
if (conf->filtered_in_locrib != nconf->filtered_in_locrib) {
log_debug("filter mode changed, reloading Loc-Rib");
force_locrib = 1;
}
copy_config(conf, nconf);
SIMPLEQ_CONCAT(&conf->rde_prefixsets, &nconf->rde_prefixsets);
SIMPLEQ_CONCAT(&conf->rde_originsets, &nconf->rde_originsets);
SIMPLEQ_CONCAT(&conf->as_sets, &nconf->as_sets);
free_l3vpns(&conf->l3vpns);
SIMPLEQ_CONCAT(&conf->l3vpns, &nconf->l3vpns);
free_config(nconf);
nconf = NULL;
peerself->remote_bgpid = conf->bgpid;
peerself->conf.local_as = conf->as;
peerself->conf.remote_as = conf->as;
peerself->conf.remote_addr.aid = AID_INET;
peerself->conf.remote_addr.v4.s_addr = htonl(conf->bgpid);
peerself->conf.remote_masklen = 32;
peerself->short_as = conf->short_as;
rde_mark_prefixsets_dirty(&prefixsets_old, &conf->rde_prefixsets);
rde_mark_prefixsets_dirty(&originsets_old, &conf->rde_originsets);
as_sets_mark_dirty(&as_sets_old, &conf->as_sets);
rde_eval_all = 0;
filterlist_free(out_rules);
out_rules = out_rules_tmp;
out_rules_tmp = NULL;
RB_FOREACH(peer, peer_tree, &peertable) {
if (peer->conf.id == 0)
continue;
peer->reconf_out = 0;
peer->reconf_rib = 0;
if (peer->conf.max_prefix &&
peer->stats.prefix_cnt > peer->conf.max_prefix) {
log_peer_warnx(&peer->conf,
"prefix limit reached (>%u/%u)",
peer->stats.prefix_cnt, peer->conf.max_prefix);
rde_update_err(peer, ERR_CEASE, ERR_CEASE_MAX_PREFIX,
NULL);
}
if (peer->conf.max_out_prefix &&
peer->stats.prefix_out_cnt > peer->conf.max_out_prefix) {
log_peer_warnx(&peer->conf,
"outbound prefix limit reached (>%u/%u)",
peer->stats.prefix_out_cnt,
peer->conf.max_out_prefix);
rde_update_err(peer, ERR_CEASE,
ERR_CEASE_MAX_SENT_PREFIX, NULL);
}
if (peer->export_type != peer->conf.export_type) {
log_peer_info(&peer->conf, "export type change, "
"reloading");
peer->reconf_rib = 1;
}
if ((peer->flags & PEERFLAG_EVALUATE_ALL) !=
(peer->conf.flags & PEERFLAG_EVALUATE_ALL)) {
log_peer_info(&peer->conf, "rde evaluate change, "
"reloading");
peer->reconf_rib = 1;
}
if ((peer->flags & PEERFLAG_TRANS_AS) !=
(peer->conf.flags & PEERFLAG_TRANS_AS)) {
log_peer_info(&peer->conf, "transparent-as change, "
"reloading");
peer->reconf_rib = 1;
}
if (peer->loc_rib_id != rib_find(peer->conf.rib)) {
log_peer_info(&peer->conf, "rib change, reloading");
peer->loc_rib_id = rib_find(peer->conf.rib);
if (peer->loc_rib_id == RIB_NOTFOUND)
fatalx("King Bula's peer met an unknown RIB");
peer->reconf_rib = 1;
}
if (peer_has_add_path(peer, AID_UNSPEC, CAPA_AP_SEND)) {
if (peer->conf.eval.mode != ADDPATH_EVAL_NONE &&
memcmp(&peer->eval, &peer->conf.eval,
sizeof(peer->eval)) != 0) {
log_peer_info(&peer->conf,
"addpath eval change, reloading");
peer->reconf_out = 1;
peer->eval = peer->conf.eval;
}
rde_eval_all = 1;
}
if (peer->role != peer->conf.role) {
if (reload == 0)
log_debug("peer role change: "
"reloading Adj-RIB-In");
peer->role = peer->conf.role;
reload++;
}
peer->export_type = peer->conf.export_type;
peer->flags = peer->conf.flags;
if (peer->flags & PEERFLAG_EVALUATE_ALL)
rde_eval_all = 1;
if (peer->reconf_rib) {
if (adjout_prefix_dump_new(peer, AID_UNSPEC,
RDE_RUNNER_ROUNDS, peer, rde_up_flush_upcall,
rde_softreconfig_in_done, NULL) == -1)
fatal("%s: prefix_dump_new", __func__);
log_peer_info(&peer->conf, "flushing Adj-RIB-Out");
softreconfig++;
continue;
}
rf = peer_apply_out_filter(peer, out_rules);
if (rf != peer->out_rules) {
char *p = log_fmt_peer(&peer->conf);
log_debug("out filter change: reloading peer %s", p);
free(p);
peer->reconf_out = 1;
}
rde_filter_unref(rf);
}
for (rid = RIB_LOC_START; rid < rib_size; rid++) {
struct rib *rib = rib_byid(rid);
if (rib == NULL)
continue;
rde_filter_calc_skip_steps(rib->in_rules_tmp);
fh = rib->in_rules;
rib->in_rules = rib->in_rules_tmp;
rib->in_rules_tmp = fh;
switch (rib->state) {
case RECONF_DELETE:
rib_free(rib);
rib = NULL;
continue;
case RECONF_RELOAD:
if (rib_update(rib)) {
RB_FOREACH(peer, peer_tree, &peertable) {
if (peer->conf.id == 0)
continue;
if (peer->loc_rib_id != rib->id)
continue;
if (peer->reconf_rib)
continue;
if (adjout_prefix_dump_new(peer,
AID_UNSPEC, RDE_RUNNER_ROUNDS, peer,
rde_up_flush_upcall,
rde_softreconfig_in_done,
NULL) == -1)
fatal("%s: prefix_dump_new",
__func__);
log_peer_info(&peer->conf,
"flushing Adj-RIB-Out");
softreconfig++;
}
}
rib->state = RECONF_KEEP;
case RECONF_KEEP:
if (!(force_locrib && rid == RIB_LOC_START) &&
rde_filter_equal(rib->in_rules, rib->in_rules_tmp))
break;
log_debug("filter change: reloading RIB %s",
rib->name);
rib->state = RECONF_RELOAD;
reload++;
break;
case RECONF_REINIT:
rib->state = RECONF_RELOAD;
reload++;
break;
case RECONF_NONE:
break;
}
filterlist_free(rib->in_rules_tmp);
rib->in_rules_tmp = NULL;
}
free_rde_prefixsets(&prefixsets_old);
free_rde_prefixsets(&originsets_old);
as_sets_free(&as_sets_old);
log_info("RDE reconfigured");
softreconfig++;
if (reload > 0) {
if (rib_dump_new(RIB_ADJ_IN, AID_UNSPEC, RDE_RUNNER_ROUNDS,
NULL, rde_softreconfig_in, rde_softreconfig_in_done,
NULL) == -1)
fatal("%s: rib_dump_new", __func__);
log_info("running softreconfig in");
} else {
rde_softreconfig_in_done((void *)1, AID_UNSPEC);
}
}
static void
rde_softreconfig_in_done(void *arg, uint8_t dummy)
{
struct rde_peer *peer;
uint16_t i;
softreconfig--;
if (softreconfig > 0)
return;
if (arg == NULL)
log_info("softreconfig in done");
softreconfig = 0;
for (i = 0; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
rib->state = RECONF_NONE;
if (rib->fibstate == RECONF_RELOAD) {
if (rib_dump_new(i, AID_UNSPEC, RDE_RUNNER_ROUNDS,
rib, rde_softreconfig_sync_fib,
rde_softreconfig_sync_done, NULL) == -1)
fatal("%s: rib_dump_new", __func__);
softreconfig++;
log_info("starting fib sync for rib %s",
rib->name);
} else if (rib->fibstate == RECONF_REINIT) {
if (rib_dump_new(i, AID_UNSPEC, RDE_RUNNER_ROUNDS,
rib, rde_softreconfig_sync_reeval,
rde_softreconfig_sync_done, NULL) == -1)
fatal("%s: rib_dump_new", __func__);
softreconfig++;
log_info("starting re-evaluation of rib %s",
rib->name);
}
}
RB_FOREACH(peer, peer_tree, &peertable) {
uint8_t aid;
if (peer->reconf_out) {
if (peer->export_type == EXPORT_NONE) {
peer->reconf_out = 0;
} else if (peer->export_type == EXPORT_DEFAULT_ROUTE) {
for (aid = AID_MIN; aid < AID_MAX; aid++) {
if (peer->capa.mp[aid])
up_generate_default(peer, aid);
}
peer->reconf_out = 0;
} else
rib_byid(peer->loc_rib_id)->state =
RECONF_RELOAD;
} else if (peer->reconf_rib) {
for (aid = AID_MIN; aid < AID_MAX; aid++) {
if (peer->capa.mp[aid])
peer_dump(peer, aid);
}
}
}
for (i = 0; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
if (rib->state == RECONF_RELOAD) {
if (rib_dump_new(i, AID_UNSPEC, RDE_RUNNER_ROUNDS,
rib, rde_softreconfig_out,
rde_softreconfig_out_done, NULL) == -1)
fatal("%s: rib_dump_new", __func__);
softreconfig++;
log_info("starting softreconfig out for rib %s",
rib->name);
}
}
if (softreconfig == 0)
rde_softreconfig_done();
}
static void
rde_softreconfig_out_done(void *arg, uint8_t aid)
{
struct rib *rib = arg;
log_info("softreconfig out done for %s", rib->name);
if (--softreconfig == 0)
rde_softreconfig_done();
}
static void
rde_softreconfig_done(void)
{
uint16_t i;
for (i = 0; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
rib->state = RECONF_NONE;
}
log_info("RDE soft reconfiguration done");
imsg_compose(ibuf_main, IMSG_RECONF_DONE, 0, 0,
-1, NULL, 0);
}
static void
rde_softreconfig_in(struct rib_entry *re, void *bula)
{
struct filterstate state;
struct rib *rib;
struct prefix *p;
struct pt_entry *pt;
struct rde_peer *peer;
struct rde_aspath *asp;
enum filter_actions action;
struct bgpd_addr prefix;
uint16_t i;
uint8_t aspa_vstate;
pt = re->prefix;
pt_getaddr(pt, &prefix);
TAILQ_FOREACH(p, &re->prefix_h, rib_l) {
asp = prefix_aspath(p);
peer = prefix_peer(p);
if (prefix_aspa_vstate(p) == ASPA_NEVER_KNOWN)
aspa_vstate = ASPA_NEVER_KNOWN;
else
aspa_vstate = rde_aspa_validity(peer, asp, pt->aid);
prefix_set_vstate(p, prefix_roa_vstate(p), aspa_vstate);
if (asp->flags & F_PREFIX_ANNOUNCED)
continue;
for (i = RIB_LOC_START; i < rib_size; i++) {
rib = rib_byid(i);
if (rib == NULL)
continue;
if (rib->state != RECONF_RELOAD)
continue;
rde_filterstate_prep(&state, p);
action = rde_filter(rib->in_rules, peer, peer, &prefix,
pt->prefixlen, &state);
if (action == ACTION_ALLOW) {
prefix_update(rib, peer, p->path_id,
p->path_id_tx, &state, 0,
&prefix, pt->prefixlen);
} else if (conf->filtered_in_locrib &&
i == RIB_LOC_START) {
prefix_update(rib, peer, p->path_id,
p->path_id_tx, &state, 1,
&prefix, pt->prefixlen);
} else {
prefix_withdraw(rib, peer, p->path_id, &prefix,
pt->prefixlen);
}
rde_filterstate_clean(&state);
}
}
}
static void
rde_softreconfig_out(struct rib_entry *re, void *arg)
{
if (prefix_best(re) == NULL)
return;
rde_generate_updates(re, NULL, 0, EVAL_RECONF);
}
static void
rde_softreconfig_sync_reeval(struct rib_entry *re, void *arg)
{
struct prefix_queue prefixes = TAILQ_HEAD_INITIALIZER(prefixes);
struct prefix *p, *next;
struct rib *rib = arg;
if (rib->flags & F_RIB_NOEVALUATE) {
TAILQ_FOREACH(p, &re->prefix_h, rib_l) {
if (p->flags & PREFIX_NEXTHOP_LINKED)
nexthop_unlink(p);
p->dmetric = PREFIX_DMETRIC_INVALID;
}
return;
}
TAILQ_CONCAT(&prefixes, &re->prefix_h, rib_l);
TAILQ_FOREACH_SAFE(p, &prefixes, rib_l, next) {
TAILQ_REMOVE(&prefixes, p, rib_l);
if ((p->flags & PREFIX_NEXTHOP_LINKED) == 0)
nexthop_link(p);
prefix_evaluate(re, p, NULL);
}
}
static void
rde_softreconfig_sync_fib(struct rib_entry *re, void *bula)
{
struct prefix *p;
if ((p = prefix_best(re)) != NULL)
rde_send_kroute(re_rib(re), p, NULL);
}
static void
rde_softreconfig_sync_done(void *arg, uint8_t aid)
{
struct rib *rib = arg;
if (rib->fibstate == RECONF_RELOAD)
log_info("fib sync done for %s", rib->name);
else
log_info("re-evaluation done for %s", rib->name);
rib->fibstate = RECONF_NONE;
if (--softreconfig == 0)
rde_softreconfig_done();
}
static void
rde_rpki_softreload(struct rib_entry *re, void *bula)
{
struct filterstate state;
struct rib *rib;
struct prefix *p;
struct pt_entry *pt;
struct rde_peer *peer;
struct rde_aspath *asp;
enum filter_actions action;
struct bgpd_addr prefix;
uint8_t roa_vstate, aspa_vstate;
uint16_t i;
pt = re->prefix;
pt_getaddr(pt, &prefix);
TAILQ_FOREACH(p, &re->prefix_h, rib_l) {
asp = prefix_aspath(p);
peer = prefix_peer(p);
roa_vstate = rde_roa_validity(&rde_roa,
&prefix, pt->prefixlen, aspath_origin(asp->aspath));
if (prefix_aspa_vstate(p) == ASPA_NEVER_KNOWN) {
aspa_vstate = ASPA_NEVER_KNOWN;
} else {
if (asp->aspa_generation != rde_aspa_generation) {
asp->aspa_generation = rde_aspa_generation;
aspa_validation(rde_aspa, asp->aspath,
&asp->aspa_state);
}
aspa_vstate = rde_aspa_validity(peer, asp, pt->aid);
}
if (roa_vstate == prefix_roa_vstate(p) &&
aspa_vstate == prefix_aspa_vstate(p))
continue;
prefix_set_vstate(p, roa_vstate, aspa_vstate);
if (asp->flags & F_PREFIX_ANNOUNCED)
continue;
for (i = RIB_LOC_START; i < rib_size; i++) {
rib = rib_byid(i);
if (rib == NULL)
continue;
rde_filterstate_prep(&state, p);
action = rde_filter(rib->in_rules, peer, peer, &prefix,
pt->prefixlen, &state);
if (action == ACTION_ALLOW) {
prefix_update(rib, peer, p->path_id,
p->path_id_tx, &state, 0,
&prefix, pt->prefixlen);
} else if (conf->filtered_in_locrib &&
i == RIB_LOC_START) {
prefix_update(rib, peer, p->path_id,
p->path_id_tx, &state, 1,
&prefix, pt->prefixlen);
} else {
prefix_withdraw(rib, peer, p->path_id, &prefix,
pt->prefixlen);
}
rde_filterstate_clean(&state);
}
}
}
static int rpki_update_pending;
static void
rde_rpki_softreload_done(void *arg, uint8_t aid)
{
log_info("RPKI softreload done");
rpki_update_pending = 0;
}
static void
rde_rpki_reload(void)
{
if (rpki_update_pending) {
log_info("RPKI softreload skipped, old still running");
return;
}
rpki_update_pending = 1;
if (rib_dump_new(RIB_ADJ_IN, AID_UNSPEC, RDE_RUNNER_ROUNDS,
rib_byid(RIB_ADJ_IN), rde_rpki_softreload,
rde_rpki_softreload_done, NULL) == -1)
fatal("%s: rib_dump_new", __func__);
}
static int
rde_roa_reload(void)
{
struct rde_prefixset roa_old;
if (rpki_update_pending) {
trie_free(&roa_new.th);
return 1;
}
roa_old = rde_roa;
rde_roa = roa_new;
memset(&roa_new, 0, sizeof(roa_new));
if (trie_equal(&rde_roa.th, &roa_old.th)) {
rde_roa.lastchange = roa_old.lastchange;
trie_free(&roa_old.th);
return 0;
}
rde_roa.lastchange = getmonotime();
trie_free(&roa_old.th);
log_debug("ROA change: reloading Adj-RIB-In");
return 1;
}
static int
rde_aspa_reload(void)
{
struct rde_aspa *aspa_old;
if (rpki_update_pending) {
aspa_table_free(aspa_new);
aspa_new = NULL;
return 1;
}
aspa_old = rde_aspa;
rde_aspa = aspa_new;
aspa_new = NULL;
if (aspa_table_equal(rde_aspa, aspa_old)) {
aspa_table_unchanged(rde_aspa, aspa_old);
aspa_table_free(aspa_old);
return 0;
}
aspa_table_free(aspa_old);
log_debug("ASPA change: reloading Adj-RIB-In");
rde_aspa_generation++;
return 1;
}
uint32_t
rde_local_as(void)
{
return (conf->as);
}
int
rde_decisionflags(void)
{
return (conf->flags & BGPD_FLAG_DECISION_MASK);
}
static void
rde_peer_recv_eor(struct rde_peer *peer, uint8_t aid)
{
peer->stats.prefix_rcvd_eor++;
peer->recv_eor |= 1 << aid;
if (imsg_compose(ibuf_se, IMSG_SESSION_RESTARTED, peer->conf.id,
0, -1, &aid, sizeof(aid)) == -1)
fatal("imsg_compose error while receiving EoR");
log_peer_info(&peer->conf, "received %s EOR marker",
aid2str(aid));
}
static void
rde_peer_send_eor(struct rde_peer *peer, uint8_t aid)
{
uint16_t afi;
uint8_t safi;
peer->stats.prefix_sent_eor++;
peer->sent_eor |= 1 << aid;
if (aid == AID_INET) {
u_char null[4];
memset(&null, 0, 4);
if (imsg_compose(ibuf_se, IMSG_UPDATE, peer->conf.id,
0, -1, &null, 4) == -1)
fatal("imsg_compose error while sending EoR");
} else {
uint16_t i;
u_char buf[10];
if (aid2afi(aid, &afi, &safi) == -1)
fatalx("%s: bad AID", __func__);
i = 0;
memcpy(&buf[0], &i, sizeof(i));
i = htons(6);
memcpy(&buf[2], &i, sizeof(i));
buf[4] = ATTR_OPTIONAL;
buf[5] = ATTR_MP_UNREACH_NLRI;
buf[6] = 3;
i = htons(afi);
memcpy(&buf[7], &i, sizeof(i));
buf[9] = safi;
if (imsg_compose(ibuf_se, IMSG_UPDATE, peer->conf.id,
0, -1, &buf, 10) == -1)
fatal("%s %d imsg_compose error", __func__, __LINE__);
}
log_peer_info(&peer->conf, "sending %s EOR marker",
aid2str(aid));
}
void
rde_peer_send_rrefresh(struct rde_peer *peer, uint8_t aid, uint8_t subtype)
{
struct route_refresh rr;
if (peer->capa.enhanced_rr == 0)
return;
switch (subtype) {
case ROUTE_REFRESH_END_RR:
case ROUTE_REFRESH_BEGIN_RR:
break;
default:
fatalx("%s unexpected subtype %d", __func__, subtype);
}
rr.aid = aid;
rr.subtype = subtype;
if (imsg_compose(ibuf_se, IMSG_REFRESH, peer->conf.id, 0, -1,
&rr, sizeof(rr)) == -1)
fatal("%s %d imsg_compose error", __func__, __LINE__);
log_peer_info(&peer->conf, "sending %s %s marker",
aid2str(aid), subtype == ROUTE_REFRESH_END_RR ? "EoRR" : "BoRR");
}
void
network_add(struct network_config *nc, struct filterstate *state)
{
struct l3vpn *vpn;
struct rde_filter_set *vpnset = NULL;
struct in_addr prefix4;
struct in6_addr prefix6;
uint32_t path_id_tx;
uint16_t i;
uint8_t vstate;
if (nc->rd != 0) {
SIMPLEQ_FOREACH(vpn, &conf->l3vpns, entry) {
if (vpn->rd != nc->rd)
continue;
switch (nc->prefix.aid) {
case AID_INET:
prefix4 = nc->prefix.v4;
memset(&nc->prefix, 0, sizeof(nc->prefix));
nc->prefix.aid = AID_VPN_IPv4;
nc->prefix.rd = vpn->rd;
nc->prefix.v4 = prefix4;
nc->prefix.labellen = 3;
nc->prefix.labelstack[0] =
(vpn->label >> 12) & 0xff;
nc->prefix.labelstack[1] =
(vpn->label >> 4) & 0xff;
nc->prefix.labelstack[2] =
(vpn->label << 4) & 0xf0;
nc->prefix.labelstack[2] |= BGP_MPLS_BOS;
vpnset = vpn->rde_export;
break;
case AID_INET6:
prefix6 = nc->prefix.v6;
memset(&nc->prefix, 0, sizeof(nc->prefix));
nc->prefix.aid = AID_VPN_IPv6;
nc->prefix.rd = vpn->rd;
nc->prefix.v6 = prefix6;
nc->prefix.labellen = 3;
nc->prefix.labelstack[0] =
(vpn->label >> 12) & 0xff;
nc->prefix.labelstack[1] =
(vpn->label >> 4) & 0xff;
nc->prefix.labelstack[2] =
(vpn->label << 4) & 0xf0;
nc->prefix.labelstack[2] |= BGP_MPLS_BOS;
vpnset = vpn->rde_export;
break;
default:
log_warnx("unable to VPNize prefix");
rde_filterset_unref(nc->rde_attrset);
return;
}
break;
}
if (vpn == NULL) {
log_warnx("network_add: "
"prefix %s/%u in non-existing l3vpn %s",
log_addr(&nc->prefix), nc->prefixlen,
log_rd(nc->rd));
return;
}
}
rde_apply_set(nc->rde_attrset, peerself, peerself, state,
nc->prefix.aid);
if (vpnset)
rde_apply_set(vpnset, peerself, peerself, state,
nc->prefix.aid);
vstate = rde_roa_validity(&rde_roa, &nc->prefix, nc->prefixlen,
aspath_origin(state->aspath.aspath));
rde_filterstate_set_vstate(state, vstate, ASPA_NEVER_KNOWN);
path_id_tx = pathid_assign(peerself, 0, &nc->prefix, nc->prefixlen);
if (prefix_update(rib_byid(RIB_ADJ_IN), peerself, 0, path_id_tx,
state, 0, &nc->prefix, nc->prefixlen) == 1)
peerself->stats.prefix_cnt++;
for (i = RIB_LOC_START; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
rde_update_log("announce", i, peerself,
state->nexthop ? &state->nexthop->exit_nexthop : NULL,
&nc->prefix, nc->prefixlen);
prefix_update(rib, peerself, 0, path_id_tx, state, 0,
&nc->prefix, nc->prefixlen);
}
rde_filterset_unref(nc->rde_attrset);
}
void
network_delete(struct network_config *nc)
{
struct l3vpn *vpn;
struct in_addr prefix4;
struct in6_addr prefix6;
uint32_t i;
if (nc->rd) {
SIMPLEQ_FOREACH(vpn, &conf->l3vpns, entry) {
if (vpn->rd != nc->rd)
continue;
switch (nc->prefix.aid) {
case AID_INET:
prefix4 = nc->prefix.v4;
memset(&nc->prefix, 0, sizeof(nc->prefix));
nc->prefix.aid = AID_VPN_IPv4;
nc->prefix.rd = vpn->rd;
nc->prefix.v4 = prefix4;
nc->prefix.labellen = 3;
nc->prefix.labelstack[0] =
(vpn->label >> 12) & 0xff;
nc->prefix.labelstack[1] =
(vpn->label >> 4) & 0xff;
nc->prefix.labelstack[2] =
(vpn->label << 4) & 0xf0;
nc->prefix.labelstack[2] |= BGP_MPLS_BOS;
break;
case AID_INET6:
prefix6 = nc->prefix.v6;
memset(&nc->prefix, 0, sizeof(nc->prefix));
nc->prefix.aid = AID_VPN_IPv6;
nc->prefix.rd = vpn->rd;
nc->prefix.v6 = prefix6;
nc->prefix.labellen = 3;
nc->prefix.labelstack[0] =
(vpn->label >> 12) & 0xff;
nc->prefix.labelstack[1] =
(vpn->label >> 4) & 0xff;
nc->prefix.labelstack[2] =
(vpn->label << 4) & 0xf0;
nc->prefix.labelstack[2] |= BGP_MPLS_BOS;
break;
default:
log_warnx("unable to VPNize prefix");
return;
}
}
}
for (i = RIB_LOC_START; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
if (prefix_withdraw(rib, peerself, 0, &nc->prefix,
nc->prefixlen))
rde_update_log("withdraw announce", i, peerself,
NULL, &nc->prefix, nc->prefixlen);
}
if (prefix_withdraw(rib_byid(RIB_ADJ_IN), peerself, 0, &nc->prefix,
nc->prefixlen))
peerself->stats.prefix_cnt--;
}
static void
network_dump_upcall(struct rib_entry *re, void *ptr)
{
struct prefix *p;
struct rde_aspath *asp;
struct kroute_full kf;
struct bgpd_addr addr;
struct rde_dump_ctx *ctx = ptr;
TAILQ_FOREACH(p, &re->prefix_h, rib_l) {
asp = prefix_aspath(p);
if (!(asp->flags & F_PREFIX_ANNOUNCED))
continue;
pt_getaddr(p->pt, &addr);
memset(&kf, 0, sizeof(kf));
kf.prefix = addr;
kf.prefixlen = p->pt->prefixlen;
if (prefix_nhvalid(p) && prefix_nexthop(p) != NULL)
kf.nexthop = prefix_nexthop(p)->true_nexthop;
else
kf.nexthop.aid = kf.prefix.aid;
if ((asp->flags & F_ANN_DYNAMIC) == 0)
kf.flags = F_STATIC;
if (imsg_compose(ibuf_se_ctl, IMSG_CTL_SHOW_NETWORK, 0,
ctx->req.pid, -1, &kf, sizeof(kf)) == -1)
log_warnx("%s: imsg_compose error", __func__);
}
}
static void
network_flush_upcall(struct rib_entry *re, void *ptr)
{
struct bgpd_addr addr;
struct prefix *p;
uint32_t i;
uint8_t prefixlen;
p = prefix_bypeer(re, peerself, 0);
if (p == NULL)
return;
if ((prefix_aspath(p)->flags & F_ANN_DYNAMIC) != F_ANN_DYNAMIC)
return;
pt_getaddr(re->prefix, &addr);
prefixlen = re->prefix->prefixlen;
for (i = RIB_LOC_START; i < rib_size; i++) {
struct rib *rib = rib_byid(i);
if (rib == NULL)
continue;
if (prefix_withdraw(rib, peerself, 0, &addr, prefixlen) == 1)
rde_update_log("flush announce", i, peerself,
NULL, &addr, prefixlen);
}
if (prefix_withdraw(rib_byid(RIB_ADJ_IN), peerself, 0, &addr,
prefixlen) == 1)
peerself->stats.prefix_cnt--;
}
void
flowspec_add(struct flowspec *f, struct filterstate *state,
struct rde_filter_set *attrset)
{
struct pt_entry *pte;
uint32_t path_id_tx;
rde_apply_set(attrset, peerself, peerself, state, f->aid);
rde_filterstate_set_vstate(state, ROA_NOTFOUND, ASPA_NEVER_KNOWN);
path_id_tx = peerself->path_id_tx;
pte = pt_get_flow(f);
if (pte == NULL)
pte = pt_add_flow(f);
if (prefix_flowspec_update(peerself, state, pte, path_id_tx) == 1)
peerself->stats.prefix_cnt++;
}
void
flowspec_delete(struct flowspec *f)
{
struct pt_entry *pte;
pte = pt_get_flow(f);
if (pte == NULL)
return;
if (prefix_flowspec_withdraw(peerself, pte) == 1)
peerself->stats.prefix_cnt--;
}
static void
flowspec_flush_upcall(struct rib_entry *re, void *ptr)
{
struct prefix *p;
p = prefix_bypeer(re, peerself, 0);
if (p == NULL)
return;
if ((prefix_aspath(p)->flags & F_ANN_DYNAMIC) != F_ANN_DYNAMIC)
return;
if (prefix_flowspec_withdraw(peerself, re->prefix) == 1)
peerself->stats.prefix_cnt--;
}
static void
flowspec_dump_upcall(struct rib_entry *re, void *ptr)
{
pid_t *pid = ptr;
struct prefix *p;
struct rde_aspath *asp;
struct rde_community *comm;
struct flowspec ff;
struct ibuf *ibuf;
uint8_t *flow;
int len;
TAILQ_FOREACH(p, &re->prefix_h, rib_l) {
asp = prefix_aspath(p);
if (!(asp->flags & F_PREFIX_ANNOUNCED))
continue;
comm = prefix_communities(p);
len = pt_getflowspec(p->pt, &flow);
memset(&ff, 0, sizeof(ff));
ff.aid = p->pt->aid;
ff.len = len;
if ((asp->flags & F_ANN_DYNAMIC) == 0)
ff.flags = F_STATIC;
if ((ibuf = imsg_create(ibuf_se_ctl, IMSG_CTL_SHOW_FLOWSPEC, 0,
*pid, FLOWSPEC_SIZE + len)) == NULL)
continue;
if (imsg_add(ibuf, &ff, FLOWSPEC_SIZE) == -1 ||
imsg_add(ibuf, flow, len) == -1)
continue;
imsg_close(ibuf_se_ctl, ibuf);
if (comm->nentries > 0) {
if (imsg_compose(ibuf_se_ctl,
IMSG_CTL_SHOW_RIB_COMMUNITIES, 0, *pid, -1,
comm->communities,
comm->nentries * sizeof(struct community)) == -1)
continue;
}
}
}
static void
flowspec_dump_done(void *ptr, uint8_t aid)
{
pid_t *pid = ptr;
imsg_compose(ibuf_se_ctl, IMSG_CTL_END, 0, *pid, -1, NULL, 0);
}
void
rde_shutdown(void)
{
peer_shutdown();
filterlist_free(out_rules);
filterlist_free(out_rules_tmp);
free_l3vpns(&conf->l3vpns);
rib_shutdown();
nexthop_shutdown();
pt_shutdown();
}
struct rde_prefixset *
rde_find_prefixset(char *name, struct rde_prefixset_head *p)
{
struct rde_prefixset *ps;
SIMPLEQ_FOREACH(ps, p, entry) {
if (!strcmp(ps->name, name))
return (ps);
}
return (NULL);
}
void
rde_mark_prefixsets_dirty(struct rde_prefixset_head *psold,
struct rde_prefixset_head *psnew)
{
struct rde_prefixset *new, *old;
SIMPLEQ_FOREACH(new, psnew, entry) {
if ((psold == NULL) ||
(old = rde_find_prefixset(new->name, psold)) == NULL) {
new->dirty = 1;
new->lastchange = getmonotime();
} else {
if (trie_equal(&new->th, &old->th) == 0) {
new->dirty = 1;
new->lastchange = getmonotime();
} else
new->lastchange = old->lastchange;
}
}
}
uint8_t
rde_roa_validity(struct rde_prefixset *ps, struct bgpd_addr *prefix,
uint8_t plen, uint32_t as)
{
int r;
r = trie_roa_check(&ps->th, prefix, plen, as);
return (r & ROA_MASK);
}
static int
ovs_match(uint8_t roa_vstate, uint32_t flag)
{
if (flag & (F_CTL_OVS_VALID|F_CTL_OVS_INVALID|F_CTL_OVS_NOTFOUND)) {
switch (roa_vstate) {
case ROA_VALID:
if (!(flag & F_CTL_OVS_VALID))
return 0;
break;
case ROA_INVALID:
if (!(flag & F_CTL_OVS_INVALID))
return 0;
break;
case ROA_NOTFOUND:
if (!(flag & F_CTL_OVS_NOTFOUND))
return 0;
break;
default:
break;
}
}
return 1;
}
static int
avs_match(uint8_t aspa_vstate, uint32_t flag)
{
if (flag & (F_CTL_AVS_VALID|F_CTL_AVS_INVALID|F_CTL_AVS_UNKNOWN)) {
switch (aspa_vstate & ASPA_MASK) {
case ASPA_VALID:
if (!(flag & F_CTL_AVS_VALID))
return 0;
break;
case ASPA_INVALID:
if (!(flag & F_CTL_AVS_INVALID))
return 0;
break;
case ASPA_UNKNOWN:
if (!(flag & F_CTL_AVS_UNKNOWN))
return 0;
break;
default:
break;
}
}
return 1;
}