#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <limits.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <poll.h>
#include <pwd.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 "log.h"
#define PFD_PIPE_MAIN 0
#define PFD_PIPE_ROUTE 1
#define PFD_PIPE_ROUTE_CTL 2
#define PFD_SOCK_CTL 3
#define PFD_SOCK_RCTL 4
#define PFD_LISTENERS_START 5
#define MAX_TIMEOUT 240
void session_sighdlr(int);
int setup_listeners(u_int *);
void init_peer(struct peer *, struct bgpd_config *);
int session_setup_socket(struct peer *);
void session_accept(int);
void session_graceful_stop(struct peer *);
void session_dispatch_imsg(struct imsgbuf *, int, u_int *);
void imsg_rde(int, uint32_t, void *, uint16_t);
void merge_peers(struct bgpd_config *, struct bgpd_config *);
void session_template_clone(struct peer *, struct sockaddr *,
uint32_t, uint32_t);
int session_match_mask(struct peer *, struct bgpd_addr *);
static struct bgpd_config *conf, *nconf;
static struct imsgbuf *ibuf_rde;
static struct imsgbuf *ibuf_rde_ctl;
static struct imsgbuf *ibuf_main;
struct bgpd_sysdep sysdep;
volatile sig_atomic_t session_quit;
int pending_reconf;
int csock = -1, rcsock = -1;
u_int peer_cnt;
struct mrt_head mrthead;
monotime_t pauseaccept;
static inline int
peer_compare(const struct peer *a, const struct peer *b)
{
return a->conf.id - b->conf.id;
}
RB_GENERATE(peer_head, peer, entry, peer_compare);
void
session_sighdlr(int sig)
{
switch (sig) {
case SIGINT:
case SIGTERM:
session_quit = 1;
break;
}
}
int
setup_listeners(u_int *la_cnt)
{
int ttl = 255;
struct listen_addr *la, *next;
u_int cnt = 0;
TAILQ_CONCAT(conf->listen_addrs, nconf->listen_addrs, entry);
TAILQ_FOREACH_SAFE(la, conf->listen_addrs, entry, next) {
if (la->reconf == RECONF_NONE) {
log_info("not listening on %s any more",
log_sockaddr((struct sockaddr *)
&la->sa, la->sa_len));
TAILQ_REMOVE(conf->listen_addrs, la, entry);
close(la->fd);
free(la);
continue;
}
la->reconf = RECONF_NONE;
cnt++;
if (la->flags & LISTENER_LISTENING)
continue;
if (la->fd == -1) {
log_warn("cannot establish listener on %s: invalid fd",
log_sockaddr((struct sockaddr *)&la->sa,
la->sa_len));
continue;
}
if (tcp_md5_prep_listener(la, &conf->peers) == -1)
fatal("tcp_md5_prep_listener");
if (la->sa.ss_family == AF_INET && setsockopt(la->fd,
IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) == -1) {
log_warn("setup_listeners setsockopt TTL");
continue;
}
if (la->sa.ss_family == AF_INET6 && setsockopt(la->fd,
IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)) == -1) {
log_warn("setup_listeners setsockopt hoplimit");
continue;
}
if (listen(la->fd, MAX_BACKLOG)) {
close(la->fd);
fatal("listen");
}
la->flags |= LISTENER_LISTENING;
log_info("listening on %s",
log_sockaddr((struct sockaddr *)&la->sa, la->sa_len));
}
*la_cnt = cnt;
return (0);
}
void
session_main(int debug, int verbose)
{
unsigned int i, j, idx_peers, idx_listeners, idx_mrts;
u_int pfd_elms = 0, peer_l_elms = 0, mrt_l_elms = 0;
u_int listener_cnt, ctl_cnt, mrt_cnt;
u_int new_cnt;
struct passwd *pw;
struct peer *p, **peer_l = NULL, *next;
struct mrt *m, *xm, **mrt_l = NULL;
struct pollfd *pfd = NULL;
struct listen_addr *la;
void *newp;
monotime_t now, timeout;
short events;
log_init(debug, LOG_DAEMON);
log_setverbose(verbose);
log_procinit(log_procnames[PROC_SE]);
if ((pw = getpwnam(BGPD_USER)) == NULL)
fatal(NULL);
if (chroot(pw->pw_dir) == -1)
fatal("chroot");
if (chdir("/") == -1)
fatal("chdir(\"/\")");
setproctitle("session 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 inet recvfd", NULL) == -1)
fatal("pledge");
signal(SIGTERM, session_sighdlr);
signal(SIGINT, session_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);
LIST_INIT(&mrthead);
listener_cnt = 0;
peer_cnt = 0;
ctl_cnt = 0;
conf = new_config();
log_info("session engine ready");
while (session_quit == 0) {
if (!pending_reconf) {
RB_FOREACH_SAFE(p, peer_head, &conf->peers, next) {
if (p->state == STATE_NONE)
init_peer(p, conf);
if (p->reconf_action == RECONF_DELETE) {
if (p->demoted)
session_demote(p, -1);
p->conf.demote_group[0] = 0;
session_stop(p, ERR_CEASE_PEER_UNCONF,
NULL);
timer_remove_all(&p->timers);
tcp_md5_del_listener(conf, p);
imsg_rde(IMSG_SESSION_DELETE,
p->conf.id, NULL, 0);
msgbuf_free(p->wbuf);
RB_REMOVE(peer_head, &conf->peers, p);
log_peer_warnx(&p->conf, "removed");
free(p);
peer_cnt--;
continue;
}
p->reconf_action = RECONF_NONE;
}
}
if (peer_cnt > peer_l_elms) {
if ((newp = reallocarray(peer_l, peer_cnt,
sizeof(struct peer *))) == NULL) {
log_warn("could not resize peer_l from %u -> %u"
" entries", peer_l_elms, peer_cnt);
fatalx("exiting");
}
peer_l = newp;
peer_l_elms = peer_cnt;
}
mrt_cnt = 0;
LIST_FOREACH_SAFE(m, &mrthead, entry, xm) {
if (m->state == MRT_STATE_REMOVE) {
mrt_clean(m);
LIST_REMOVE(m, entry);
free(m);
continue;
}
if (msgbuf_queuelen(m->wbuf) > 0)
mrt_cnt++;
}
if (mrt_cnt > mrt_l_elms) {
if ((newp = reallocarray(mrt_l, mrt_cnt,
sizeof(struct mrt *))) == NULL) {
log_warn("could not resize mrt_l from %u -> %u"
" entries", mrt_l_elms, mrt_cnt);
fatalx("exiting");
}
mrt_l = newp;
mrt_l_elms = mrt_cnt;
}
new_cnt = PFD_LISTENERS_START + listener_cnt + peer_cnt +
ctl_cnt + mrt_cnt;
if (new_cnt > pfd_elms) {
if ((newp = reallocarray(pfd, new_cnt,
sizeof(struct pollfd))) == NULL) {
log_warn("could not resize pfd from %u -> %u"
" entries", pfd_elms, new_cnt);
fatalx("exiting");
}
pfd = newp;
pfd_elms = new_cnt;
}
memset(pfd, 0, sizeof(struct pollfd) * pfd_elms);
set_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main);
set_pollfd(&pfd[PFD_PIPE_ROUTE], ibuf_rde);
set_pollfd(&pfd[PFD_PIPE_ROUTE_CTL], ibuf_rde_ctl);
if (!monotime_valid(pauseaccept)) {
pfd[PFD_SOCK_CTL].fd = csock;
pfd[PFD_SOCK_CTL].events = POLLIN;
pfd[PFD_SOCK_RCTL].fd = rcsock;
pfd[PFD_SOCK_RCTL].events = POLLIN;
} else {
pfd[PFD_SOCK_CTL].fd = -1;
pfd[PFD_SOCK_RCTL].fd = -1;
}
i = PFD_LISTENERS_START;
TAILQ_FOREACH(la, conf->listen_addrs, entry) {
if (!monotime_valid(pauseaccept)) {
pfd[i].fd = la->fd;
pfd[i].events = POLLIN;
} else
pfd[i].fd = -1;
i++;
}
idx_listeners = i;
now = getmonotime();
timeout = monotime_add(now, monotime_from_sec(MAX_TIMEOUT));
RB_FOREACH(p, peer_head, &conf->peers) {
monotime_t nextaction;
struct timer *pt;
if ((pt = timer_nextisdue(&p->timers, now)) != NULL) {
switch (pt->type) {
case Timer_Hold:
bgp_fsm(p, EVNT_TIMER_HOLDTIME, NULL);
break;
case Timer_SendHold:
bgp_fsm(p, EVNT_TIMER_SENDHOLD, NULL);
break;
case Timer_ConnectRetry:
bgp_fsm(p, EVNT_TIMER_CONNRETRY, NULL);
break;
case Timer_Keepalive:
bgp_fsm(p, EVNT_TIMER_KEEPALIVE, NULL);
break;
case Timer_IdleHold:
bgp_fsm(p, EVNT_START, NULL);
break;
case Timer_IdleHoldReset:
p->IdleHoldTime = 0;
timer_stop(&p->timers,
Timer_IdleHoldReset);
break;
case Timer_CarpUndemote:
timer_stop(&p->timers,
Timer_CarpUndemote);
if (p->demoted &&
p->state == STATE_ESTABLISHED)
session_demote(p, -1);
break;
case Timer_RestartTimeout:
timer_stop(&p->timers,
Timer_RestartTimeout);
session_graceful_stop(p);
break;
case Timer_SessionDown:
timer_stop(&p->timers,
Timer_SessionDown);
imsg_rde(IMSG_SESSION_DELETE,
p->conf.id, NULL, 0);
p->rdesession = 0;
if (p->template)
p->reconf_action =
RECONF_DELETE;
break;
default:
fatalx("King Bula lost in time");
}
}
nextaction = timer_nextduein(&p->timers);
if (monotime_valid(nextaction)) {
if (monotime_cmp(nextaction, timeout) < 0)
timeout = nextaction;
}
if (!p->throttled &&
msgbuf_queuelen(p->wbuf) > SESS_MSG_HIGH_MARK) {
imsg_rde(IMSG_XOFF, p->conf.id, NULL, 0);
p->throttled = 1;
}
if (p->throttled &&
msgbuf_queuelen(p->wbuf) < SESS_MSG_LOW_MARK) {
imsg_rde(IMSG_XON, p->conf.id, NULL, 0);
p->throttled = 0;
}
events = POLLIN;
if (msgbuf_queuelen(p->wbuf) > 0 ||
p->state == STATE_CONNECT)
events |= POLLOUT;
if (p->rpending)
timeout = monotime_clear();
if (p->fd != -1 && events != 0) {
pfd[i].fd = p->fd;
pfd[i].events = events;
peer_l[i - idx_listeners] = p;
i++;
}
}
idx_peers = i;
LIST_FOREACH(m, &mrthead, entry)
if (msgbuf_queuelen(m->wbuf) > 0) {
pfd[i].fd = m->fd;
pfd[i].events = POLLOUT;
mrt_l[i - idx_peers] = m;
i++;
}
idx_mrts = i;
i += control_fill_pfds(pfd + i, pfd_elms -i);
if (i > pfd_elms)
fatalx("poll pfd overflow");
if (monotime_valid(pauseaccept) &&
monotime_cmp(timeout, pauseaccept) > 0)
timeout = pauseaccept;
timeout = monotime_sub(timeout, getmonotime());
if (!monotime_valid(timeout))
timeout = monotime_clear();
if (poll(pfd, i, monotime_to_msec(timeout)) == -1) {
if (errno == EINTR)
continue;
fatal("poll error");
}
if (monotime_valid(pauseaccept) &&
monotime_cmp(getmonotime(), pauseaccept) > 0)
pauseaccept = monotime_clear();
if (handle_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main) == -1) {
log_warnx("SE: Lost connection to parent");
session_quit = 1;
continue;
} else
session_dispatch_imsg(ibuf_main, PFD_PIPE_MAIN,
&listener_cnt);
if (handle_pollfd(&pfd[PFD_PIPE_ROUTE], ibuf_rde) == -1) {
log_warnx("SE: Lost connection to RDE");
imsgbuf_clear(ibuf_rde);
free(ibuf_rde);
ibuf_rde = NULL;
} else
session_dispatch_imsg(ibuf_rde, PFD_PIPE_ROUTE,
&listener_cnt);
if (handle_pollfd(&pfd[PFD_PIPE_ROUTE_CTL], ibuf_rde_ctl) ==
-1) {
log_warnx("SE: Lost connection to RDE control");
imsgbuf_clear(ibuf_rde_ctl);
free(ibuf_rde_ctl);
ibuf_rde_ctl = NULL;
} else
session_dispatch_imsg(ibuf_rde_ctl, PFD_PIPE_ROUTE_CTL,
&listener_cnt);
if (pfd[PFD_SOCK_CTL].revents & POLLIN)
ctl_cnt += control_accept(csock, 0);
if (pfd[PFD_SOCK_RCTL].revents & POLLIN)
ctl_cnt += control_accept(rcsock, 1);
for (j = PFD_LISTENERS_START; j < idx_listeners; j++)
if (pfd[j].revents & POLLIN)
session_accept(pfd[j].fd);
for (; j < idx_peers; j++)
session_dispatch_msg(&pfd[j],
peer_l[j - idx_listeners]);
RB_FOREACH(p, peer_head, &conf->peers)
session_process_msg(p);
for (; j < idx_mrts; j++)
if (pfd[j].revents & POLLOUT)
mrt_write(mrt_l[j - idx_peers]);
for (; j < i; j++)
ctl_cnt -= control_dispatch_msg(&pfd[j], &conf->peers);
}
RB_FOREACH_SAFE(p, peer_head, &conf->peers, next) {
session_stop(p, ERR_CEASE_ADMIN_DOWN, "bgpd shutting down");
timer_remove_all(&p->timers);
tcp_md5_del_listener(conf, p);
RB_REMOVE(peer_head, &conf->peers, p);
free(p);
}
while ((m = LIST_FIRST(&mrthead)) != NULL) {
mrt_clean(m);
LIST_REMOVE(m, entry);
free(m);
}
free_config(conf);
free(peer_l);
free(mrt_l);
free(pfd);
if (ibuf_rde) {
imsgbuf_write(ibuf_rde);
imsgbuf_clear(ibuf_rde);
close(ibuf_rde->fd);
free(ibuf_rde);
}
if (ibuf_rde_ctl) {
imsgbuf_clear(ibuf_rde_ctl);
close(ibuf_rde_ctl->fd);
free(ibuf_rde_ctl);
}
imsgbuf_write(ibuf_main);
imsgbuf_clear(ibuf_main);
close(ibuf_main->fd);
free(ibuf_main);
control_shutdown(csock);
control_shutdown(rcsock);
log_info("session engine exiting");
exit(0);
}
void
init_peer(struct peer *p, struct bgpd_config *c)
{
TAILQ_INIT(&p->timers);
p->fd = -1;
if (p->wbuf != NULL)
fatalx("%s: msgbuf already set", __func__);
if ((p->wbuf = msgbuf_new_reader(MSGSIZE_HEADER, parse_header, p)) ==
NULL)
fatal(NULL);
if (p->conf.if_depend[0])
imsg_compose(ibuf_main, IMSG_SESSION_DEPENDON, 0, 0, -1,
p->conf.if_depend, sizeof(p->conf.if_depend));
else
p->depend_ok = 1;
if (p->conf.holdtime == 0)
p->conf.holdtime = c->holdtime;
if (p->conf.min_holdtime == 0)
p->conf.min_holdtime = c->min_holdtime;
if (p->conf.connectretry == 0)
p->conf.connectretry = c->connectretry;
p->local_bgpid = c->bgpid;
peer_cnt++;
change_state(p, STATE_IDLE, EVNT_NONE);
if (!p->conf.down)
bgp_fsm(p, EVNT_START, NULL);
p->stats.last_updown = getmonotime();
if (p->reconf_action != RECONF_REINIT && p->conf.demote_group[0])
session_demote(p, +1);
}
int
session_dispatch_msg(struct pollfd *pfd, struct peer *p)
{
socklen_t len;
int error;
if (p->state == STATE_CONNECT) {
if (pfd->revents & POLLOUT) {
if (pfd->revents & POLLIN) {
len = sizeof(error);
if (getsockopt(pfd->fd, SOL_SOCKET, SO_ERROR,
&error, &len) == -1 || error) {
if (error)
errno = error;
if (errno != p->lasterr) {
log_peer_warn(&p->conf,
"socket error");
p->lasterr = errno;
}
bgp_fsm(p, EVNT_CON_OPENFAIL, NULL);
return (1);
}
}
bgp_fsm(p, EVNT_CON_OPEN, NULL);
return (1);
}
if (pfd->revents & POLLHUP) {
bgp_fsm(p, EVNT_CON_OPENFAIL, NULL);
return (1);
}
if (pfd->revents & (POLLERR|POLLNVAL)) {
bgp_fsm(p, EVNT_CON_FATAL, NULL);
return (1);
}
return (0);
}
if (pfd->revents & POLLHUP) {
bgp_fsm(p, EVNT_CON_CLOSED, NULL);
return (1);
}
if (pfd->revents & (POLLERR|POLLNVAL)) {
bgp_fsm(p, EVNT_CON_FATAL, NULL);
return (1);
}
if (pfd->revents & POLLOUT && msgbuf_queuelen(p->wbuf) > 0) {
if (ibuf_write(p->fd, p->wbuf) == -1) {
if (errno == EPIPE)
log_peer_warnx(&p->conf, "Connection closed");
else
log_peer_warn(&p->conf, "write error");
bgp_fsm(p, EVNT_CON_FATAL, NULL);
return (1);
}
p->stats.last_write = getmonotime();
start_timer_sendholdtime(p);
if (!(pfd->revents & POLLIN))
return (1);
}
if (p->fd != -1 && pfd->revents & POLLIN) {
switch (ibuf_read(p->fd, p->wbuf)) {
case -1:
if (p->state == STATE_IDLE)
return (1);
log_peer_warn(&p->conf, "read error");
bgp_fsm(p, EVNT_CON_FATAL, NULL);
return (1);
case 0:
bgp_fsm(p, EVNT_CON_CLOSED, NULL);
return (1);
}
p->stats.last_read = getmonotime();
return (1);
}
return (0);
}
void
session_accept(int listenfd)
{
int connfd;
socklen_t len;
struct sockaddr_storage cliaddr;
struct peer *p = NULL;
len = sizeof(cliaddr);
if ((connfd = accept4(listenfd,
(struct sockaddr *)&cliaddr, &len,
SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) {
if (errno == ENFILE || errno == EMFILE)
pauseaccept = monotime_add(getmonotime(),
monotime_from_sec(PAUSEACCEPT_TIMEOUT));
else if (errno != EWOULDBLOCK && errno != EINTR &&
errno != ECONNABORTED)
log_warn("accept");
return;
}
p = getpeerbyip(conf, (struct sockaddr *)&cliaddr);
if (p != NULL &&
(p->state == STATE_CONNECT || p->state == STATE_ACTIVE)) {
if (p->fd != -1) {
if (p->state == STATE_CONNECT)
session_close(p);
else {
close(connfd);
return;
}
}
open:
if (p->auth_conf.method != AUTH_NONE && sysdep.no_pfkey) {
log_peer_warnx(&p->conf,
"ipsec or md5sig configured but not available");
close(connfd);
return;
}
if (tcp_md5_check(connfd, &p->auth_conf) == -1) {
log_peer_warn(&p->conf, "check md5sig");
close(connfd);
return;
}
p->fd = connfd;
if (session_setup_socket(p)) {
close(connfd);
return;
}
bgp_fsm(p, EVNT_CON_OPEN, NULL);
return;
} else if (p != NULL && p->state == STATE_ESTABLISHED &&
p->capa.neg.grestart.restart == 2) {
change_state(p, STATE_CONNECT, EVNT_CON_CLOSED);
goto open;
} else {
log_conn_attempt(p, (struct sockaddr *)&cliaddr, len);
close(connfd);
}
}
int
session_connect(struct peer *peer)
{
struct sockaddr *sa;
struct bgpd_addr *bind_addr;
socklen_t sa_len;
if (peer->fd != -1)
return (-1);
if ((peer->fd = socket(aid2af(peer->conf.remote_addr.aid),
SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_TCP)) == -1) {
log_peer_warn(&peer->conf, "session_connect socket");
bgp_fsm(peer, EVNT_CON_OPENFAIL, NULL);
return (-1);
}
if (peer->auth_conf.method != AUTH_NONE && sysdep.no_pfkey) {
log_peer_warnx(&peer->conf,
"ipsec or md5sig configured but not available");
bgp_fsm(peer, EVNT_CON_OPENFAIL, NULL);
return (-1);
}
if (tcp_md5_set(peer->fd, &peer->auth_conf,
&peer->conf.remote_addr) == -1) {
log_peer_warn(&peer->conf, "setting md5sig");
bgp_fsm(peer, EVNT_CON_OPENFAIL, NULL);
return (-1);
}
bind_addr = session_localaddr(peer);
if ((sa = addr2sa(bind_addr, 0, &sa_len)) != NULL) {
if (bind(peer->fd, sa, sa_len) == -1) {
log_peer_warn(&peer->conf, "session_connect bind");
bgp_fsm(peer, EVNT_CON_OPENFAIL, NULL);
return (-1);
}
}
if (session_setup_socket(peer)) {
bgp_fsm(peer, EVNT_CON_OPENFAIL, NULL);
return (-1);
}
sa = addr2sa(&peer->conf.remote_addr, peer->conf.remote_port, &sa_len);
if (connect(peer->fd, sa, sa_len) == -1) {
if (errno == EINPROGRESS)
return (0);
if (errno != peer->lasterr)
log_peer_warn(&peer->conf, "connect");
peer->lasterr = errno;
bgp_fsm(peer, EVNT_CON_OPENFAIL, NULL);
return (-1);
}
bgp_fsm(peer, EVNT_CON_OPEN, NULL);
return (0);
}
int
session_setup_socket(struct peer *p)
{
int ttl = p->conf.distance;
int pre = IPTOS_PREC_INTERNETCONTROL;
int nodelay = 1;
int bsize;
switch (p->conf.remote_addr.aid) {
case AID_INET:
if (setsockopt(p->fd, IPPROTO_IP, IP_TOS, &pre, sizeof(pre)) ==
-1) {
log_peer_warn(&p->conf,
"session_setup_socket setsockopt TOS");
return (-1);
}
if (p->conf.ebgp) {
if (p->conf.ttlsec) {
ttl = 256 - p->conf.distance;
if (setsockopt(p->fd, IPPROTO_IP, IP_MINTTL,
&ttl, sizeof(ttl)) == -1) {
log_peer_warn(&p->conf,
"session_setup_socket: "
"setsockopt MINTTL");
return (-1);
}
ttl = 255;
}
if (setsockopt(p->fd, IPPROTO_IP, IP_TTL, &ttl,
sizeof(ttl)) == -1) {
log_peer_warn(&p->conf,
"session_setup_socket setsockopt TTL");
return (-1);
}
}
break;
case AID_INET6:
if (setsockopt(p->fd, IPPROTO_IPV6, IPV6_TCLASS, &pre,
sizeof(pre)) == -1) {
log_peer_warn(&p->conf, "session_setup_socket "
"setsockopt TCLASS");
return (-1);
}
if (p->conf.ebgp) {
if (p->conf.ttlsec) {
ttl = 256 - p->conf.distance;
if (setsockopt(p->fd, IPPROTO_IPV6,
IPV6_MINHOPCOUNT, &ttl, sizeof(ttl))
== -1) {
log_peer_warn(&p->conf,
"session_setup_socket: "
"setsockopt MINHOPCOUNT");
return (-1);
}
ttl = 255;
}
if (setsockopt(p->fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
&ttl, sizeof(ttl)) == -1) {
log_peer_warn(&p->conf,
"session_setup_socket setsockopt hoplimit");
return (-1);
}
}
break;
}
if (setsockopt(p->fd, IPPROTO_TCP, TCP_NODELAY, &nodelay,
sizeof(nodelay)) == -1) {
log_peer_warn(&p->conf,
"session_setup_socket setsockopt TCP_NODELAY");
return (-1);
}
bsize = 65535;
setsockopt(p->fd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(bsize));
setsockopt(p->fd, SOL_SOCKET, SO_SNDBUF, &bsize, sizeof(bsize));
return (0);
}
void
session_close(struct peer *peer)
{
if (peer->fd != -1) {
close(peer->fd);
pauseaccept = monotime_clear();
}
peer->fd = -1;
}
static int
sa_equal(struct bgpd_addr *ba, struct sockaddr *b)
{
struct bgpd_addr bb;
sa2addr(b, &bb, NULL);
return (memcmp(ba, &bb, sizeof(*ba)) == 0);
}
void
get_alternate_addr(struct bgpd_addr *local, struct bgpd_addr *remote,
struct bgpd_addr *alt, unsigned int *scope)
{
struct ifaddrs *ifap, *ifa, *match;
int connected = 0;
u_int8_t plen;
if (getifaddrs(&ifap) == -1)
fatal("getifaddrs");
for (match = ifap; match != NULL; match = match->ifa_next) {
if (match->ifa_addr == NULL)
continue;
if (match->ifa_addr->sa_family != AF_INET &&
match->ifa_addr->sa_family != AF_INET6)
continue;
if (sa_equal(local, match->ifa_addr)) {
if (remote->aid == AID_INET6 &&
IN6_IS_ADDR_LINKLOCAL(&remote->v6)) {
connected = 1;
} else if (match->ifa_flags & IFF_POINTOPOINT &&
match->ifa_dstaddr != NULL) {
if (sa_equal(remote, match->ifa_dstaddr))
connected = 1;
} else if (match->ifa_netmask != NULL) {
plen = mask2prefixlen(
match->ifa_addr->sa_family,
match->ifa_netmask);
if (prefix_compare(local, remote, plen) == 0)
connected = 1;
}
break;
}
}
if (match == NULL) {
log_warnx("%s: local address not found", __func__);
return;
}
if (connected)
*scope = if_nametoindex(match->ifa_name);
else
*scope = 0;
switch (local->aid) {
case AID_INET6:
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr != NULL &&
ifa->ifa_addr->sa_family == AF_INET &&
strcmp(ifa->ifa_name, match->ifa_name) == 0) {
sa2addr(ifa->ifa_addr, alt, NULL);
break;
}
}
break;
case AID_INET:
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr != NULL &&
ifa->ifa_addr->sa_family == AF_INET6 &&
strcmp(ifa->ifa_name, match->ifa_name) == 0) {
struct sockaddr_in6 *s =
(struct sockaddr_in6 *)ifa->ifa_addr;
if (IN6_IS_ADDR_LINKLOCAL(&s->sin6_addr) ||
IN6_IS_ADDR_SITELOCAL(&s->sin6_addr))
continue;
sa2addr(ifa->ifa_addr, alt, NULL);
break;
}
}
break;
default:
log_warnx("%s: unsupported address family %s", __func__,
aid2str(local->aid));
break;
}
freeifaddrs(ifap);
}
void
session_handle_update(struct peer *peer, struct ibuf *msg)
{
imsg_rde(IMSG_UPDATE, peer->conf.id, ibuf_data(msg), ibuf_size(msg));
}
void
session_handle_rrefresh(struct peer *peer, struct route_refresh *rr)
{
imsg_rde(IMSG_REFRESH, peer->conf.id, rr, sizeof(*rr));
}
void
session_graceful_restart(struct peer *p)
{
u_int i;
u_int staletime = conf->staletime;
if (p->conf.staletime)
staletime = p->conf.staletime;
if (staletime > p->capa.neg.grestart.timeout)
staletime = p->capa.neg.grestart.timeout;
timer_set(&p->timers, Timer_RestartTimeout, staletime);
if (staletime < INTERVAL_SESSION_DOWN - INTERVAL_STALE)
staletime = INTERVAL_SESSION_DOWN - INTERVAL_STALE;
p->stats.last_updown = getmonotime();
timer_set(&p->timers, Timer_SessionDown,
staletime + INTERVAL_STALE);
for (i = AID_MIN; i < AID_MAX; i++) {
if (p->capa.neg.grestart.flags[i] & CAPA_GR_PRESENT) {
imsg_rde(IMSG_SESSION_STALE, p->conf.id,
&i, sizeof(i));
log_peer_warnx(&p->conf,
"graceful restart of %s, keeping routes",
aid2str(i));
p->capa.neg.grestart.flags[i] |= CAPA_GR_RESTARTING;
} else if (p->capa.neg.mp[i]) {
imsg_rde(IMSG_SESSION_NOGRACE, p->conf.id,
&i, sizeof(i));
log_peer_warnx(&p->conf,
"graceful restart of %s, flushing routes",
aid2str(i));
}
}
}
void
session_graceful_stop(struct peer *p)
{
u_int i;
for (i = AID_MIN; i < AID_MAX; i++) {
if (p->capa.neg.grestart.flags[i] & CAPA_GR_RESTARTING)
session_graceful_flush(p, i, "time-out");
p->capa.neg.grestart.flags[i] &= ~CAPA_GR_RESTARTING;
}
}
void
session_graceful_flush(struct peer *p, u_int aid, const char *why)
{
log_peer_warnx(&p->conf, "graceful restart of %s, %s, flushing",
aid2str(aid), why);
imsg_rde(IMSG_SESSION_FLUSH, p->conf.id, &aid, sizeof(aid));
}
void
session_mrt_dump_state(struct peer *p)
{
struct mrt *mrt;
LIST_FOREACH(mrt, &mrthead, entry) {
if (mrt->type != MRT_ALL_IN && mrt->type != MRT_ALL_OUT)
continue;
if ((mrt->peer_id == 0 && mrt->group_id == 0) ||
mrt->peer_id == p->conf.id || (mrt->group_id != 0 &&
mrt->group_id == p->conf.groupid))
mrt_dump_state(mrt, p);
}
}
void
session_mrt_dump_bgp_msg(struct peer *p, struct ibuf *msg,
enum msg_type msgtype, enum direction dir)
{
struct mrt *mrt;
LIST_FOREACH(mrt, &mrthead, entry) {
if (dir == DIR_IN) {
if (mrt->type != MRT_ALL_IN &&
(mrt->type != MRT_UPDATE_IN ||
msgtype != BGP_UPDATE))
continue;
} else {
if (mrt->type != MRT_ALL_OUT &&
(mrt->type != MRT_UPDATE_OUT ||
msgtype != BGP_UPDATE))
continue;
}
if ((mrt->peer_id == 0 && mrt->group_id == 0) ||
mrt->peer_id == p->conf.id || (mrt->group_id != 0 &&
mrt->group_id == p->conf.groupid))
mrt_dump_bgp_msg(mrt, msg, p, msgtype);
}
}
static int
la_cmp(struct listen_addr *a, struct listen_addr *b)
{
struct sockaddr_in *in_a, *in_b;
struct sockaddr_in6 *in6_a, *in6_b;
if (a->sa.ss_family != b->sa.ss_family)
return (1);
switch (a->sa.ss_family) {
case AF_INET:
in_a = (struct sockaddr_in *)&a->sa;
in_b = (struct sockaddr_in *)&b->sa;
if (in_a->sin_addr.s_addr != in_b->sin_addr.s_addr)
return (1);
if (in_a->sin_port != in_b->sin_port)
return (1);
break;
case AF_INET6:
in6_a = (struct sockaddr_in6 *)&a->sa;
in6_b = (struct sockaddr_in6 *)&b->sa;
if (memcmp(&in6_a->sin6_addr, &in6_b->sin6_addr,
sizeof(struct in6_addr)))
return (1);
if (in6_a->sin6_port != in6_b->sin6_port)
return (1);
break;
default:
fatal("king bula sez: unknown address family");
}
return (0);
}
void
session_dispatch_imsg(struct imsgbuf *imsgbuf, int idx, u_int *listener_cnt)
{
struct imsg imsg;
struct ibuf ibuf;
struct mrt xmrt;
struct route_refresh rr;
struct mrt *mrt;
struct imsgbuf *i;
struct peer *p;
struct listen_addr *la, nla;
struct session_dependon sdon;
uint32_t peerid;
int n, fd, depend_ok, restricted;
u_int aid;
uint16_t t;
uint8_t errcode, subcode;
while (imsgbuf) {
if ((n = imsgbuf_get(imsgbuf, &imsg)) == -1)
fatal("session_dispatch_imsg: imsgbuf_get error");
if (n == 0)
break;
peerid = imsg_get_id(&imsg);
switch (imsg_get_type(&imsg)) {
case IMSG_SOCKET_CONN:
case IMSG_SOCKET_CONN_CTL:
if (idx != PFD_PIPE_MAIN)
fatalx("reconf request not from parent");
if ((fd = imsg_get_fd(&imsg)) == -1) {
log_warnx("expected to receive imsg fd to "
"RDE 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);
if (imsg_get_type(&imsg) == IMSG_SOCKET_CONN) {
if (ibuf_rde) {
log_warnx("Unexpected imsg connection "
"to RDE received");
imsgbuf_clear(ibuf_rde);
free(ibuf_rde);
}
ibuf_rde = i;
} else {
if (ibuf_rde_ctl) {
log_warnx("Unexpected imsg ctl "
"connection to RDE received");
imsgbuf_clear(ibuf_rde_ctl);
free(ibuf_rde_ctl);
}
ibuf_rde_ctl = i;
}
break;
case IMSG_RECONF_CONF:
if (idx != PFD_PIPE_MAIN)
fatalx("reconf request not from parent");
nconf = new_config();
if (imsg_recv_config(&imsg, nconf) == -1)
fatal("imsg_recv_config");
pending_reconf = 1;
break;
case IMSG_RECONF_PEER:
if (idx != PFD_PIPE_MAIN)
fatalx("reconf request not from parent");
if ((p = calloc(1, sizeof(struct peer))) == NULL)
fatal("new_peer");
if (imsg_get_data(&imsg, &p->conf, sizeof(p->conf)) ==
-1)
fatal("imsg_get_data");
p->state = p->prev_state = STATE_NONE;
p->reconf_action = RECONF_REINIT;
if (RB_INSERT(peer_head, &nconf->peers, p) != NULL)
fatalx("%s: peer tree is corrupt", __func__);
break;
case IMSG_RECONF_PEER_AUTH:
if (idx != PFD_PIPE_MAIN)
fatalx("reconf request not from parent");
if ((p = getpeerbyid(nconf, peerid)) == NULL) {
log_warnx("%s: no such peer: id=%u",
"IMSG_RECONF_PEER_AUTH", peerid);
break;
}
if (pfkey_recv_conf(p, &imsg) == -1)
fatal("pfkey_recv_conf");
break;
case IMSG_RECONF_LISTENER:
if (idx != PFD_PIPE_MAIN)
fatalx("reconf request not from parent");
if (nconf == NULL)
fatalx("IMSG_RECONF_LISTENER but no config");
if (imsg_get_data(&imsg, &nla, sizeof(nla)) == -1)
fatal("imsg_get_data");
TAILQ_FOREACH(la, conf->listen_addrs, entry)
if (!la_cmp(la, &nla))
break;
if (la == NULL) {
if (nla.reconf != RECONF_REINIT)
fatalx("king bula sez: "
"expected REINIT");
if ((nla.fd = imsg_get_fd(&imsg)) == -1)
log_warnx("expected to receive fd for "
"%s but didn't receive any",
log_sockaddr((struct sockaddr *)
&nla.sa, nla.sa_len));
la = calloc(1, sizeof(struct listen_addr));
if (la == NULL)
fatal(NULL);
memcpy(&la->sa, &nla.sa, sizeof(la->sa));
la->flags = nla.flags;
la->fd = nla.fd;
la->reconf = RECONF_REINIT;
TAILQ_INSERT_TAIL(nconf->listen_addrs, la,
entry);
} else {
if (nla.reconf != RECONF_KEEP)
fatalx("king bula sez: expected KEEP");
la->reconf = RECONF_KEEP;
}
break;
case IMSG_RECONF_CTRL:
if (idx != PFD_PIPE_MAIN)
fatalx("reconf request not from parent");
if (imsg_get_data(&imsg, &restricted,
sizeof(restricted)) == -1)
fatal("imsg_get_data");
if ((fd = imsg_get_fd(&imsg)) == -1) {
log_warnx("expected to receive fd for control "
"socket but didn't receive any");
break;
}
if (restricted) {
control_shutdown(rcsock);
rcsock = fd;
} else {
control_shutdown(csock);
csock = fd;
}
break;
case IMSG_RECONF_DRAIN:
switch (idx) {
case PFD_PIPE_ROUTE:
if (nconf != NULL)
fatalx("got unexpected %s from RDE",
"IMSG_RECONF_DONE");
imsg_compose(ibuf_main, IMSG_RECONF_DONE, 0, 0,
-1, NULL, 0);
break;
case PFD_PIPE_MAIN:
if (nconf == NULL)
fatalx("got unexpected %s from parent",
"IMSG_RECONF_DONE");
imsg_compose(ibuf_main, IMSG_RECONF_DRAIN, 0, 0,
-1, NULL, 0);
break;
default:
fatalx("reconf request not from parent or RDE");
}
break;
case IMSG_RECONF_DONE:
if (idx != PFD_PIPE_MAIN)
fatalx("reconf request not from parent");
if (nconf == NULL)
fatalx("got IMSG_RECONF_DONE but no config");
copy_config(conf, nconf);
merge_peers(conf, nconf);
setup_listeners(listener_cnt);
free_config(nconf);
nconf = NULL;
pending_reconf = 0;
log_info("SE reconfigured");
break;
case IMSG_SESSION_DEPENDON:
if (idx != PFD_PIPE_MAIN)
fatalx("IFINFO message not from parent");
if (imsg_get_data(&imsg, &sdon, sizeof(sdon)) == -1)
fatalx("DEPENDON imsg with wrong len");
depend_ok = sdon.depend_state;
RB_FOREACH(p, peer_head, &conf->peers)
if (!strcmp(p->conf.if_depend, sdon.ifname)) {
if (depend_ok && !p->depend_ok) {
p->depend_ok = depend_ok;
bgp_fsm(p, EVNT_START, NULL);
} else if (!depend_ok && p->depend_ok) {
p->depend_ok = depend_ok;
session_stop(p,
ERR_CEASE_OTHER_CHANGE,
NULL);
}
}
break;
case IMSG_MRT_OPEN:
case IMSG_MRT_REOPEN:
if (idx != PFD_PIPE_MAIN)
fatalx("mrt request not from parent");
if (imsg_get_data(&imsg, &xmrt, sizeof(xmrt)) == -1) {
log_warnx("mrt open, wrong imsg len");
break;
}
if ((xmrt.fd = imsg_get_fd(&imsg)) == -1) {
log_warnx("expected to receive fd for mrt dump "
"but didn't receive any");
break;
}
mrt = mrt_get(&mrthead, &xmrt);
if (mrt == NULL) {
mrt = calloc(1, sizeof(struct mrt));
if (mrt == NULL)
fatal("session_dispatch_imsg");
memcpy(mrt, &xmrt, sizeof(struct mrt));
if ((mrt->wbuf = msgbuf_new()) == NULL)
fatal("session_dispatch_imsg");
LIST_INSERT_HEAD(&mrthead, mrt, entry);
} else {
close(mrt->fd);
}
mrt->fd = xmrt.fd;
break;
case IMSG_MRT_CLOSE:
if (idx != PFD_PIPE_MAIN)
fatalx("mrt request not from parent");
if (imsg_get_data(&imsg, &xmrt, sizeof(xmrt)) == -1) {
log_warnx("mrt close, wrong imsg len");
break;
}
mrt = mrt_get(&mrthead, &xmrt);
if (mrt != NULL)
mrt_done(mrt);
break;
case IMSG_CTL_KROUTE:
case IMSG_CTL_KROUTE_ADDR:
case IMSG_CTL_SHOW_NEXTHOP:
case IMSG_CTL_SHOW_INTERFACE:
case IMSG_CTL_SHOW_FIB_TABLES:
case IMSG_CTL_SHOW_RTR:
case IMSG_CTL_SHOW_TIMER:
if (idx != PFD_PIPE_MAIN)
fatalx("ctl kroute request not from parent");
if (control_imsg_relay(&imsg, NULL) == -1)
log_warn("control_imsg_relay");
break;
case IMSG_CTL_SHOW_NEIGHBOR:
if (idx != PFD_PIPE_ROUTE_CTL)
fatalx("ctl rib request not from RDE");
if ((p = getpeerbyid(conf, peerid)) == NULL) {
log_warnx("%s: no such peer: id=%u",
"IMSG_CTL_SHOW_NEIGHBOR", peerid);
break;
}
if (control_imsg_relay(&imsg, p) == -1)
log_warn("control_imsg_relay");
break;
case IMSG_CTL_SHOW_RIB:
case IMSG_CTL_SHOW_RIB_PREFIX:
case IMSG_CTL_SHOW_RIB_COMMUNITIES:
case IMSG_CTL_SHOW_RIB_ATTR:
case IMSG_CTL_SHOW_RIB_MEM:
case IMSG_CTL_SHOW_NETWORK:
case IMSG_CTL_SHOW_FLOWSPEC:
case IMSG_CTL_SHOW_SET:
if (idx != PFD_PIPE_ROUTE_CTL)
fatalx("ctl rib request not from RDE");
if (control_imsg_relay(&imsg, NULL) == -1)
log_warn("control_imsg_relay");
break;
case IMSG_CTL_END:
case IMSG_CTL_RESULT:
if (control_imsg_relay(&imsg, NULL) == -1)
log_warn("control_imsg_relay");
break;
case IMSG_UPDATE:
if (idx != PFD_PIPE_ROUTE)
fatalx("update request not from RDE");
if ((p = getpeerbyid(conf, peerid)) == NULL) {
log_warnx("%s: no such peer: id=%u",
"IMSG_UPDATE", peerid);
break;
}
if (imsg_get_ibuf(&imsg, &ibuf) == -1)
log_warn("RDE sent invalid update");
else
session_update(p, &ibuf);
break;
case IMSG_UPDATE_ERR:
if (idx != PFD_PIPE_ROUTE)
fatalx("update request not from RDE");
if ((p = getpeerbyid(conf, peerid)) == NULL) {
log_warnx("%s: no such peer: id=%u",
"IMSG_UPDATE_ERR", peerid);
break;
}
if (imsg_get_ibuf(&imsg, &ibuf) == -1 ||
ibuf_get_n8(&ibuf, &errcode) == -1 ||
ibuf_get_n8(&ibuf, &subcode) == -1) {
log_warnx("RDE sent invalid notification");
break;
}
session_notification(p, errcode, subcode, &ibuf);
switch (errcode) {
case ERR_CEASE:
switch (subcode) {
case ERR_CEASE_MAX_PREFIX:
case ERR_CEASE_MAX_SENT_PREFIX:
t = p->conf.max_out_prefix_restart;
if (subcode == ERR_CEASE_MAX_PREFIX)
t = p->conf.max_prefix_restart;
bgp_fsm(p, EVNT_STOP, NULL);
if (t)
timer_set(&p->timers,
Timer_IdleHold, 60 * t);
break;
default:
bgp_fsm(p, EVNT_CON_FATAL, NULL);
break;
}
break;
default:
bgp_fsm(p, EVNT_CON_FATAL, NULL);
break;
}
break;
case IMSG_REFRESH:
if (idx != PFD_PIPE_ROUTE)
fatalx("route refresh request not from RDE");
if ((p = getpeerbyid(conf, peerid)) == NULL) {
log_warnx("%s: no such peer: id=%u",
"IMSG_REFRESH", peerid);
break;
}
if (imsg_get_data(&imsg, &rr, sizeof(rr)) == -1) {
log_warnx("RDE sent invalid refresh msg");
break;
}
if (rr.aid < AID_MIN || rr.aid >= AID_MAX)
fatalx("IMSG_REFRESH: bad AID");
session_rrefresh(p, rr.aid, rr.subtype);
break;
case IMSG_SESSION_RESTARTED:
if (idx != PFD_PIPE_ROUTE)
fatalx("session restart not from RDE");
if ((p = getpeerbyid(conf, peerid)) == NULL) {
log_warnx("%s: no such peer: id=%u",
"IMSG_SESSION_RESTARTED", peerid);
break;
}
if (imsg_get_data(&imsg, &aid, sizeof(aid)) == -1) {
log_warnx("RDE sent invalid restart msg");
break;
}
if (aid < AID_MIN || aid >= AID_MAX)
fatalx("IMSG_SESSION_RESTARTED: bad AID");
if (p->capa.neg.grestart.flags[aid] &
CAPA_GR_RESTARTING) {
log_peer_warnx(&p->conf,
"graceful restart of %s finished",
aid2str(aid));
p->capa.neg.grestart.flags[aid] &=
~CAPA_GR_RESTARTING;
timer_stop(&p->timers, Timer_RestartTimeout);
imsg_rde(IMSG_SESSION_RESTARTED,
peerid, &aid, sizeof(aid));
}
break;
default:
break;
}
imsg_free(&imsg);
}
}
struct peer *
getpeerbydesc(struct bgpd_config *c, const char *descr)
{
struct peer *p, *res = NULL;
int match = 0;
RB_FOREACH(p, peer_head, &c->peers)
if (!strcmp(p->conf.descr, descr)) {
res = p;
match++;
}
if (match > 1)
log_info("neighbor description \"%s\" not unique, request "
"aborted", descr);
if (match == 1)
return (res);
else
return (NULL);
}
struct peer *
getpeerbyip(struct bgpd_config *c, struct sockaddr *ip)
{
struct bgpd_addr addr;
struct peer *p, *newpeer, *loose = NULL;
uint32_t id;
sa2addr(ip, &addr, NULL);
RB_FOREACH(p, peer_head, &c->peers)
if (!p->conf.template &&
!memcmp(&addr, &p->conf.remote_addr, sizeof(addr)))
return (p);
RB_FOREACH(p, peer_head, &c->peers)
if (p->conf.template &&
p->conf.remote_addr.aid == addr.aid &&
session_match_mask(p, &addr))
if (loose == NULL || loose->conf.remote_masklen <
p->conf.remote_masklen)
loose = p;
if (loose != NULL) {
if ((newpeer = malloc(sizeof(struct peer))) == NULL)
fatal(NULL);
memcpy(newpeer, loose, sizeof(struct peer));
for (id = PEER_ID_DYN_MAX; id > PEER_ID_STATIC_MAX; id--) {
if (getpeerbyid(c, id) == NULL)
break;
}
newpeer->template = loose;
session_template_clone(newpeer, ip, id, 0);
newpeer->state = newpeer->prev_state = STATE_NONE;
newpeer->reconf_action = RECONF_KEEP;
newpeer->rpending = 0;
newpeer->wbuf = NULL;
init_peer(newpeer, c);
timer_set(&newpeer->timers, Timer_SessionDown,
INTERVAL_SESSION_DOWN);
bgp_fsm(newpeer, EVNT_START, NULL);
if (RB_INSERT(peer_head, &c->peers, newpeer) != NULL)
fatalx("%s: peer tree is corrupt", __func__);
return (newpeer);
}
return (NULL);
}
struct peer *
getpeerbyid(struct bgpd_config *c, uint32_t peerid)
{
static struct peer lookup;
lookup.conf.id = peerid;
return RB_FIND(peer_head, &c->peers, &lookup);
}
int
peer_matched(struct 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;
}
void
session_template_clone(struct peer *p, struct sockaddr *ip, uint32_t id,
uint32_t as)
{
struct bgpd_addr remote_addr;
if (ip)
sa2addr(ip, &remote_addr, NULL);
else
memcpy(&remote_addr, &p->conf.remote_addr, sizeof(remote_addr));
memcpy(&p->conf, &p->template->conf, sizeof(struct peer_config));
p->conf.id = id;
if (as) {
p->conf.remote_as = as;
p->conf.ebgp = (p->conf.remote_as != p->conf.local_as);
if (!p->conf.ebgp)
p->conf.enforce_as = ENFORCE_AS_OFF;
}
memcpy(&p->conf.remote_addr, &remote_addr, sizeof(remote_addr));
switch (p->conf.remote_addr.aid) {
case AID_INET:
p->conf.remote_masklen = 32;
break;
case AID_INET6:
p->conf.remote_masklen = 128;
break;
}
p->conf.template = 0;
}
int
session_match_mask(struct peer *p, struct bgpd_addr *a)
{
struct bgpd_addr masked;
applymask(&masked, a, p->conf.remote_masklen);
if (memcmp(&masked, &p->conf.remote_addr, sizeof(masked)) == 0)
return (1);
return (0);
}
void
session_down(struct peer *peer)
{
memset(&peer->capa.neg, 0, sizeof(peer->capa.neg));
peer->stats.last_updown = getmonotime();
timer_set(&peer->timers, Timer_SessionDown, INTERVAL_SESSION_DOWN);
if (ibuf_rde == NULL)
return;
imsg_rde(IMSG_SESSION_DOWN, peer->conf.id, NULL, 0);
}
void
session_up(struct peer *p)
{
struct session_up sup;
p->stats.last_sent_errcode = 0;
p->stats.last_sent_suberr = 0;
p->stats.last_rcvd_errcode = 0;
p->stats.last_rcvd_suberr = 0;
memset(p->stats.last_reason, 0, sizeof(p->stats.last_reason));
timer_stop(&p->timers, Timer_SessionDown);
if (!p->rdesession) {
imsg_rde(IMSG_SESSION_ADD, p->conf.id,
&p->conf, sizeof(p->conf));
p->rdesession = 1;
}
if (p->local.aid == AID_INET) {
sup.local_v4_addr = p->local;
sup.local_v6_addr = p->local_alt;
} else {
sup.local_v6_addr = p->local;
sup.local_v4_addr = p->local_alt;
}
sup.remote_addr = p->remote;
sup.if_scope = p->if_scope;
sup.remote_bgpid = p->remote_bgpid;
sup.short_as = p->short_as;
memcpy(&sup.capa, &p->capa.neg, sizeof(sup.capa));
p->stats.last_updown = getmonotime();
imsg_rde(IMSG_SESSION_UP, p->conf.id, &sup, sizeof(sup));
}
int
imsg_ctl_parent(struct imsg *imsg)
{
return imsg_forward(ibuf_main, imsg);
}
int
imsg_ctl_rde(struct imsg *imsg)
{
if (ibuf_rde_ctl == NULL)
return (0);
return imsg_forward(ibuf_rde_ctl, imsg);
}
int
imsg_ctl_rde_msg(int type, uint32_t peerid, pid_t pid)
{
if (ibuf_rde_ctl == NULL)
return (0);
return imsg_compose(ibuf_rde_ctl, type, peerid, pid, -1, NULL, 0);
}
void
imsg_rde(int type, uint32_t peerid, void *data, uint16_t datalen)
{
if (ibuf_rde == NULL)
return;
if (imsg_compose(ibuf_rde, type, peerid, 0, -1, data, datalen) == -1)
fatal("imsg_compose");
}
void
session_demote(struct peer *p, int level)
{
struct demote_msg msg;
strlcpy(msg.demote_group, p->conf.demote_group,
sizeof(msg.demote_group));
msg.level = level;
if (imsg_compose(ibuf_main, IMSG_DEMOTE, p->conf.id, 0, -1,
&msg, sizeof(msg)) == -1)
fatal("imsg_compose");
p->demoted += level;
}
void
session_md5_reload(struct peer *p)
{
if (!p->template)
if (imsg_compose(ibuf_main, IMSG_PFKEY_RELOAD,
p->conf.id, 0, -1, NULL, 0) == -1)
fatalx("imsg_compose error");
}
void
session_stop(struct peer *peer, uint8_t subcode, const char *reason)
{
struct ibuf *ibuf;
if (reason != NULL)
strlcpy(peer->conf.reason, reason, sizeof(peer->conf.reason));
ibuf = ibuf_dynamic(0, REASON_LEN);
if ((subcode == ERR_CEASE_ADMIN_DOWN ||
subcode == ERR_CEASE_ADMIN_RESET) &&
reason != NULL && *reason != '\0' &&
ibuf != NULL) {
if (ibuf_add_n8(ibuf, strlen(reason)) == -1 ||
ibuf_add(ibuf, reason, strlen(reason))) {
log_peer_warnx(&peer->conf,
"trying to send overly long shutdown reason");
ibuf_free(ibuf);
ibuf = NULL;
}
}
switch (peer->state) {
case STATE_OPENSENT:
case STATE_OPENCONFIRM:
case STATE_ESTABLISHED:
session_notification(peer, ERR_CEASE, subcode, ibuf);
break;
default:
if (subcode >= nitems(suberr_cease_names) ||
suberr_cease_names[subcode] == NULL)
log_peer_warnx(&peer->conf, "session stop: %s, "
"unknown subcode %u", errnames[ERR_CEASE], subcode);
else
log_peer_warnx(&peer->conf, "session stop: %s, %s",
errnames[ERR_CEASE], suberr_cease_names[subcode]);
break;
}
ibuf_free(ibuf);
bgp_fsm(peer, EVNT_STOP, NULL);
}
struct bgpd_addr *
session_localaddr(struct peer *p)
{
switch (p->conf.remote_addr.aid) {
case AID_INET:
return &p->conf.local_addr_v4;
case AID_INET6:
return &p->conf.local_addr_v6;
}
fatalx("Unknown AID in %s", __func__);
}
void
merge_peers(struct bgpd_config *c, struct bgpd_config *nc)
{
struct peer *p, *np, *next;
RB_FOREACH(p, peer_head, &c->peers) {
if (p->template != NULL)
continue;
np = getpeerbyid(nc, p->conf.id);
if (np == NULL) {
p->reconf_action = RECONF_DELETE;
continue;
}
if (p->auth_conf.method == AUTH_MD5SIG &&
np->auth_conf.method != AUTH_MD5SIG)
tcp_md5_del_listener(c, p);
else if (np->auth_conf.method == AUTH_MD5SIG)
tcp_md5_add_listener(c, np);
memcpy(&p->conf, &np->conf, sizeof(p->conf));
memcpy(&p->auth_conf, &np->auth_conf, sizeof(p->auth_conf));
RB_REMOVE(peer_head, &nc->peers, np);
free(np);
p->reconf_action = RECONF_KEEP;
if (p->conf.holdtime == 0)
p->conf.holdtime = nc->holdtime;
if (p->conf.min_holdtime == 0)
p->conf.min_holdtime = nc->min_holdtime;
if (p->conf.connectretry == 0)
p->conf.connectretry = nc->connectretry;
p->local_bgpid = nc->bgpid;
if (p->demoted && !p->conf.demote_group[0])
session_demote(p, -1);
if (p->state < STATE_OPENSENT && !p->template)
imsg_compose(ibuf_main, IMSG_PFKEY_RELOAD,
p->conf.id, 0, -1, NULL, 0);
if (p->rdesession)
imsg_rde(IMSG_SESSION_ADD, p->conf.id,
&p->conf, sizeof(struct peer_config));
if (p->conf.template) {
struct peer *xp;
RB_FOREACH(xp, peer_head, &c->peers) {
if (xp->template != p)
continue;
session_template_clone(xp, NULL, xp->conf.id,
xp->conf.remote_as);
xp->local_bgpid = nc->bgpid;
if (xp->rdesession)
imsg_rde(IMSG_SESSION_ADD,
xp->conf.id, &xp->conf,
sizeof(xp->conf));
}
}
}
imsg_rde(IMSG_RECONF_DRAIN, 0, NULL, 0);
RB_FOREACH_SAFE(np, peer_head, &nc->peers, next) {
RB_REMOVE(peer_head, &nc->peers, np);
if (RB_INSERT(peer_head, &c->peers, np) != NULL)
fatalx("%s: peer tree is corrupt", __func__);
if (np->auth_conf.method == AUTH_MD5SIG)
tcp_md5_add_listener(c, np);
}
}