On Tue, Jan 26, 2021 at 10:31:40AM +0100, Claudio Jeker wrote:
> This diff adds initial RTR (RPKI to Router) support to bgpd.
> Instead of loading the roa-set table via the configuration bgpd will use
> RTR to load the RPKI table from one or multiple RTR servers.
> This has the benefit that in large setups only a few systems need to run
> rpki-client instead of running it on every router.
> 
> Currently only RTR via TCP is supported. Basic 'bgpctl show rtr' output is
> available to monitor sessions and 'bgpctl show sets' also shows the right
> info. There is a lot more that can be added here but this diff is already
> big enough.
> 
> Enjoy

I know this is a large diff but I would like to have somebody review it
before commit. This will affect any kind of RPKI usage via roa-set or rtr
session.

-- 
:wq Claudio

Index: bgpctl/bgpctl.8
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.8,v
retrieving revision 1.95
diff -u -p -r1.95 bgpctl.8
--- bgpctl/bgpctl.8     10 May 2020 13:38:46 -0000      1.95
+++ bgpctl/bgpctl.8     26 Jan 2021 08:42:39 -0000
@@ -33,7 +33,7 @@ program controls the
 .Xr bgpd 8
 daemon.
 Commands may be abbreviated to the minimum unambiguous prefix; for example,
-.Cm s s
+.Cm s su
 for
 .Cm show summary .
 .Pp
@@ -409,6 +409,18 @@ or
 Multiple options can be used at the same time and the
 .Ar neighbor
 filter can be combined with other filters.
+.It Cm show rtr
+Show a list of all
+.Em RTR
+sessions, including information about the session state.
+.It Cm show sets
+Show a list summarizing all
+.Em roa-set ,
+.Em as-set ,
+.Em prefix-set ,
+and
+.Em origin-set
+tables.
 .It Cm show summary
 Show a list of all neighbors, including information about the session state
 and message counters:
Index: bgpctl/bgpctl.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.c,v
retrieving revision 1.264
diff -u -p -r1.264 bgpctl.c
--- bgpctl/bgpctl.c     30 Dec 2020 07:31:19 -0000      1.264
+++ bgpctl/bgpctl.c     25 Jan 2021 18:06:13 -0000
@@ -216,6 +216,9 @@ main(int argc, char *argv[])
        case SHOW_SET:
                imsg_compose(ibuf, IMSG_CTL_SHOW_SET, 0, 0, -1, NULL, 0);
                break;
+       case SHOW_RTR:
+               imsg_compose(ibuf, IMSG_CTL_SHOW_RTR, 0, 0, -1, NULL, 0);
+               break;
        case SHOW_NEIGHBOR:
        case SHOW_NEIGHBOR_TIMERS:
        case SHOW_NEIGHBOR_TERSE:
@@ -393,18 +396,19 @@ int
 show(struct imsg *imsg, struct parse_result *res)
 {
        struct peer             *p;
-       struct ctl_timer        *t;
+       struct ctl_timer         t;
        struct ctl_show_interface       *iface;
        struct ctl_show_nexthop *nh;
-       struct ctl_show_set     *set;
+       struct ctl_show_set      set;
+       struct ctl_show_rtr      rtr;
        struct kroute_full      *kf;
        struct ktable           *kt;
        struct ctl_show_rib      rib;
+       struct rde_memstats      stats;
+       struct rde_hashstats     hash;
        u_char                  *asdata;
-       struct rde_memstats     stats;
-       struct rde_hashstats    hash;
-       u_int                   rescode, ilen;
-       size_t                  aslen;
+       u_int                    rescode, ilen;
+       size_t                   aslen;
 
        switch (imsg->hdr.type) {
        case IMSG_CTL_SHOW_NEIGHBOR:
@@ -412,9 +416,11 @@ show(struct imsg *imsg, struct parse_res
                output->neighbor(p, res);
                break;
        case IMSG_CTL_SHOW_TIMER:
-               t = imsg->data;
-               if (t->type > 0 && t->type < Timer_Max)
-                       output->timer(t);
+               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(t))
+                       errx(1, "wrong imsg len");
+               memcpy(&t, imsg->data, sizeof(t));
+               if (t.type > 0 && t.type < Timer_Max)
+                       output->timer(&t);
                break;
        case IMSG_CTL_SHOW_INTERFACE:
                iface = imsg->data;
@@ -463,16 +469,28 @@ show(struct imsg *imsg, struct parse_res
                output->attr(imsg->data, ilen, res);
                break;
        case IMSG_CTL_SHOW_RIB_MEM:
+               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(stats))
+                       errx(1, "wrong imsg len");
                memcpy(&stats, imsg->data, sizeof(stats));
                output->rib_mem(&stats);
                break;
        case IMSG_CTL_SHOW_RIB_HASH:
+               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(hash))
+                       errx(1, "wrong imsg len");
                memcpy(&hash, imsg->data, sizeof(hash));
                output->rib_hash(&hash);
                break;
        case IMSG_CTL_SHOW_SET:
-               set = imsg->data;
-               output->set(set);
+               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(set))
+                       errx(1, "wrong imsg len");
+               memcpy(&set, imsg->data, sizeof(set));
+               output->set(&set);
+               break;
+       case IMSG_CTL_SHOW_RTR:
+               if (imsg->hdr.len < IMSG_HEADER_SIZE + sizeof(rtr))
+                       errx(1, "wrong imsg len");
+               memcpy(&rtr, imsg->data, sizeof(rtr));
+               output->rtr(&rtr);
                break;
        case IMSG_CTL_RESULT:
                if (imsg->hdr.len != IMSG_HEADER_SIZE + sizeof(rescode)) {
Index: bgpctl/bgpctl.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.h,v
retrieving revision 1.8
diff -u -p -r1.8 bgpctl.h
--- bgpctl/bgpctl.h     30 Dec 2020 07:31:19 -0000      1.8
+++ bgpctl/bgpctl.h     25 Jan 2021 18:06:25 -0000
@@ -31,6 +31,7 @@ struct output {
        void    (*rib_hash)(struct rde_hashstats *);
        void    (*rib_mem)(struct rde_memstats *);
        void    (*set)(struct ctl_show_set *);
+       void    (*rtr)(struct ctl_show_rtr *);
        void    (*result)(u_int);
        void    (*tail)(void);
 };
Index: bgpctl/output.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/output.c,v
retrieving revision 1.12
diff -u -p -r1.12 output.c
--- bgpctl/output.c     25 Jan 2021 09:17:33 -0000      1.12
+++ bgpctl/output.c     26 Jan 2021 09:21:43 -0000
@@ -266,7 +266,7 @@ show_neighbor_full(struct peer *p, struc
        if (p->conf.down) {
                printf(", marked down");
        }
-       if (*(p->conf.reason)) {
+       if (p->conf.reason[0]) {
                printf(" with shutdown reason \"%s\"",
                    log_reason(p->conf.reason));
        }
@@ -301,7 +301,7 @@ show_neighbor_full(struct peer *p, struc
 
        show_neighbor_msgstats(p);
        printf("\n");
-       if (*(p->stats.last_reason)) {
+       if (p->stats.last_reason[0]) {
                printf("  Last received shutdown reason: \"%s\"\n",
                    log_reason(p->stats.last_reason));
        }
@@ -978,6 +978,45 @@ show_rib_set(struct ctl_show_set *set)
 }
 
 static void
+show_rtr(struct ctl_show_rtr *rtr)
+{
+       static int not_first;
+
+       if (not_first)
+               printf("\n");
+       not_first = 1;
+
+       printf("RTR neighbor is %s, port %u\n",
+           log_addr(&rtr->remote_addr), rtr->remote_port);
+       if (rtr->descr[0])
+               printf(" Description: %s\n", rtr->descr);
+       if (rtr->local_addr.aid != AID_UNSPEC)
+               printf(" Local Address: %s\n", log_addr(&rtr->local_addr));
+       if (rtr->session_id != -1)
+               printf (" Session ID: %d Serial #: %u\n",
+                   rtr->session_id, rtr->serial);
+       printf(" Refresh: %u, Retry: %u, Expire: %u\n",
+           rtr->refresh, rtr->retry, rtr->expire);
+
+       if (rtr->last_sent_error != NO_ERROR) {
+               printf(" Last sent error: %s\n",
+                 log_rtr_error(rtr->last_sent_error));
+               if (rtr->last_sent_msg[0])
+                       printf(" with reason \"%s\"",
+                           log_reason(rtr->last_sent_msg));
+       }
+       if (rtr->last_recv_error != NO_ERROR) {
+               printf("Last received error: %s\n",
+                 log_rtr_error(rtr->last_recv_error));
+               if (rtr->last_recv_msg[0])
+                       printf(" with reason \"%s\"",
+                           log_reason(rtr->last_recv_msg));
+       }
+
+       printf("\n");
+}
+
+static void
 show_result(u_int rescode)
 {
        if (rescode == 0)
@@ -1009,6 +1048,7 @@ const struct output show_output = {
        .rib_mem = show_rib_mem,
        .rib_hash = show_rib_hash,
        .set = show_rib_set,
+       .rtr = show_rtr,
        .result = show_result,
        .tail = show_tail
 };
Index: bgpctl/output_json.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/output_json.c,v
retrieving revision 1.6
diff -u -p -r1.6 output_json.c
--- bgpctl/output_json.c        25 Jan 2021 09:17:33 -0000      1.6
+++ bgpctl/output_json.c        26 Jan 2021 09:21:13 -0000
@@ -202,10 +202,10 @@ json_neighbor_full(struct peer *p)
        json_neighbor_stats(p);
 
        /* errors */
-       if (*(p->conf.reason))
+       if (p->conf.reason[0])
                json_do_printf("my_shutdown_reason", "%s",
                    log_reason(p->conf.reason));
-       if (*(p->stats.last_reason))
+       if (p->stats.last_reason[0])
                json_do_printf("last_shutdown_reason", "%s",
                    log_reason(p->stats.last_reason));
        errstr = fmt_errstr(p->stats.last_sent_errcode,
@@ -937,6 +937,43 @@ json_rib_set(struct ctl_show_set *set)
 }
 
 static void
+json_rtr(struct ctl_show_rtr *rtr)
+{
+       json_do_array("rtrs");
+
+       json_do_object("rtr");
+       if (rtr->descr[0])
+               json_do_printf("descr", "%s", rtr->descr);
+       json_do_printf("remote_addr", "%s", log_addr(&rtr->remote_addr));
+       json_do_uint("remote_port", rtr->remote_port);
+       if (rtr->local_addr.aid != AID_UNSPEC)
+               json_do_printf("local_addr", "%s", log_addr(&rtr->local_addr));
+
+       if (rtr->session_id != -1) {
+               json_do_uint("session_id", rtr->session_id);
+               json_do_uint("serial", rtr->serial);
+       }
+       json_do_uint("refresh", rtr->refresh);
+       json_do_uint("retry", rtr->retry);
+       json_do_uint("expire", rtr->expire);
+
+       if (rtr->last_sent_error != NO_ERROR) {
+               json_do_printf("last_sent_error", "%s",
+                   log_rtr_error(rtr->last_sent_error));
+               if (rtr->last_sent_msg[0])
+                       json_do_printf("last_sent_msg", "%s",
+                           log_reason(rtr->last_sent_msg));
+       }
+       if (rtr->last_recv_error != NO_ERROR) {
+               json_do_printf("last_recv_error", "%s",
+                   log_rtr_error(rtr->last_recv_error));
+               if (rtr->last_recv_msg[0])
+                       json_do_printf("last_recv_msg", "%s",
+                           log_reason(rtr->last_recv_msg));
+       }
+}
+
+static void
 json_result(u_int rescode)
 {
        if (rescode == 0)
@@ -971,6 +1008,7 @@ const struct output json_output = {
        .rib_mem = json_rib_mem,
        .rib_hash = json_rib_hash,
        .set = json_rib_set,
+       .rtr = json_rtr,
        .result = json_result,
        .tail = json_tail
 };
Index: bgpctl/parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/parser.c,v
retrieving revision 1.105
diff -u -p -r1.105 parser.c
--- bgpctl/parser.c     30 Dec 2020 07:31:19 -0000      1.105
+++ bgpctl/parser.c     25 Jan 2021 18:00:19 -0000
@@ -137,6 +137,7 @@ static const struct token t_show[] = {
        { KEYWORD,      "ip",           NONE,           t_show_ip},
        { KEYWORD,      "summary",      SHOW_SUMMARY,   t_show_summary},
        { KEYWORD,      "sets",         SHOW_SET,       NULL},
+       { KEYWORD,      "rtr",          SHOW_RTR,       NULL},
        { KEYWORD,      "mrt",          SHOW_MRT,       t_show_mrt},
        { ENDTOKEN,     "",             NONE,           NULL}
 };
Index: bgpctl/parser.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/parser.h,v
retrieving revision 1.39
diff -u -p -r1.39 parser.h
--- bgpctl/parser.h     30 Dec 2020 07:31:19 -0000      1.39
+++ bgpctl/parser.h     25 Jan 2021 18:00:28 -0000
@@ -33,6 +33,7 @@ enum actions {
        SHOW_RIB,
        SHOW_MRT,
        SHOW_SET,
+       SHOW_RTR,
        SHOW_RIB_MEM,
        SHOW_NEXTHOP,
        SHOW_INTERFACE,
Index: bgpd/Makefile
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/Makefile,v
retrieving revision 1.36
diff -u -p -r1.36 Makefile
--- bgpd/Makefile       1 Jan 2020 07:25:04 -0000       1.36
+++ bgpd/Makefile       26 Dec 2020 11:43:42 -0000
@@ -5,7 +5,7 @@ SRCS=   bgpd.c session.c log.c logmsg.c pa
        rde.c rde_rib.c rde_decide.c rde_prefix.c mrt.c kroute.c control.c \
        pfkey.c rde_update.c rde_attr.c rde_community.c printconf.c \
        rde_filter.c rde_sets.c rde_trie.c pftable.c name2id.c \
-       util.c carp.c timer.c rde_peer.c
+       util.c carp.c timer.c rde_peer.c rtr.c rtr_proto.c
 CFLAGS+= -Wall -I${.CURDIR}
 CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
 CFLAGS+= -Wmissing-declarations
Index: bgpd/bgpd.8
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.8,v
retrieving revision 1.63
diff -u -p -r1.63 bgpd.8
--- bgpd/bgpd.8 1 Feb 2021 07:39:59 -0000       1.63
+++ bgpd/bgpd.8 10 Feb 2021 16:02:25 -0000
@@ -413,6 +413,14 @@ has been started.
 .Re
 .Pp
 .Rs
+.%A R. Bush
+.%A R. Austein
+.%D September 2017
+.%R RFC 8210
+.%T The Resource Public Key Infrastructure (RPKI) to Router Protocol, Version 1
+.Re
+.Pp
+.Rs
 .%A J. Mauch
 .%A J. Snijders
 .%A G. Hankins
Index: bgpd/bgpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.c,v
retrieving revision 1.233
diff -u -p -r1.233 bgpd.c
--- bgpd/bgpd.c 4 Jan 2021 17:44:14 -0000       1.233
+++ bgpd/bgpd.c 25 Jan 2021 18:31:46 -0000
@@ -47,7 +47,9 @@ int           send_config(struct bgpd_config *);
 int            dispatch_imsg(struct imsgbuf *, int, struct bgpd_config *);
 int            control_setup(struct bgpd_config *);
 static void    getsockpair(int [2]);
-int            imsg_send_sockets(struct imsgbuf *, struct imsgbuf *);
+int            imsg_send_sockets(struct imsgbuf *, struct imsgbuf *,
+                   struct imsgbuf *);
+void           bgpd_rtr_connect(struct rtr_config *);
 
 int                     cflags;
 volatile sig_atomic_t   mrtdump;
@@ -57,6 +59,7 @@ pid_t                  reconfpid;
 int                     reconfpending;
 struct imsgbuf         *ibuf_se;
 struct imsgbuf         *ibuf_rde;
+struct imsgbuf         *ibuf_rtr;
 struct rib_names        ribnames = SIMPLEQ_HEAD_INITIALIZER(ribnames);
 char                   *cname;
 char                   *rcname;
@@ -91,9 +94,10 @@ usage(void)
 
 #define PFD_PIPE_SESSION       0
 #define PFD_PIPE_RDE           1
-#define PFD_SOCK_ROUTE         2
-#define PFD_SOCK_PFKEY         3
-#define POLL_MAX               4
+#define PFD_PIPE_RTR           2
+#define PFD_SOCK_ROUTE         3
+#define PFD_SOCK_PFKEY         4
+#define POLL_MAX               5
 #define MAX_TIMEOUT            3600
 
 int     cmd_opts;
@@ -107,7 +111,7 @@ main(int argc, char *argv[])
        struct peer             *p;
        struct pollfd            pfd[POLL_MAX];
        time_t                   timeout;
-       pid_t                    se_pid = 0, rde_pid = 0, pid;
+       pid_t                    se_pid = 0, rde_pid = 0, rtr_pid = 0, pid;
        char                    *conffile;
        char                    *saved_argv0;
        int                      debug = 0;
@@ -115,6 +119,7 @@ main(int argc, char *argv[])
        int                      ch, status;
        int                      pipe_m2s[2];
        int                      pipe_m2r[2];
+       int                      pipe_m2roa[2];
 
        conffile = CONFFILE;
 
@@ -126,7 +131,7 @@ main(int argc, char *argv[])
        if (saved_argv0 == NULL)
                saved_argv0 = "bgpd";
 
-       while ((ch = getopt(argc, argv, "cdD:f:nRSv")) != -1) {
+       while ((ch = getopt(argc, argv, "cdD:f:nRSTv")) != -1) {
                switch (ch) {
                case 'c':
                        cmd_opts |= BGPD_OPT_FORCE_DEMOTE;
@@ -156,6 +161,9 @@ main(int argc, char *argv[])
                case 'S':
                        proc = PROC_SE;
                        break;
+               case 'T':
+                       proc = PROC_RTR;
+                       break;
                default:
                        usage();
                        /* NOTREACHED */
@@ -168,7 +176,7 @@ main(int argc, char *argv[])
                usage();
 
        if (cmd_opts & BGPD_OPT_NOACTION) {
-               if ((conf = parse_config(conffile, NULL)) == NULL)
+               if ((conf = parse_config(conffile, NULL, NULL)) == NULL)
                        exit(1);
 
                if (cmd_opts & BGPD_OPT_VERBOSE)
@@ -193,6 +201,9 @@ main(int argc, char *argv[])
        case PROC_SE:
                session_main(debug, cmd_opts & BGPD_OPT_VERBOSE);
                /* NOTREACHED */
+       case PROC_RTR:
+               rtr_main(debug, cmd_opts & BGPD_OPT_VERBOSE);
+               /* NOTREACHED */
        }
 
        if (geteuid())
@@ -201,7 +212,7 @@ main(int argc, char *argv[])
        if (getpwnam(BGPD_USER) == NULL)
                errx(1, "unknown user %s", BGPD_USER);
 
-       if ((conf = parse_config(conffile, NULL)) == NULL) {
+       if ((conf = parse_config(conffile, NULL, NULL)) == NULL) {
                log_warnx("config file %s has errors", conffile);
                exit(1);
        }
@@ -219,12 +230,15 @@ main(int argc, char *argv[])
 
        getsockpair(pipe_m2s);
        getsockpair(pipe_m2r);
+       getsockpair(pipe_m2roa);
 
        /* fork children */
        rde_pid = start_child(PROC_RDE, saved_argv0, pipe_m2r[1], debug,
            cmd_opts & BGPD_OPT_VERBOSE);
        se_pid = start_child(PROC_SE, saved_argv0, pipe_m2s[1], debug,
            cmd_opts & BGPD_OPT_VERBOSE);
+       rtr_pid = start_child(PROC_RTR, saved_argv0, pipe_m2roa[1], debug,
+           cmd_opts & BGPD_OPT_VERBOSE);
 
        signal(SIGTERM, sighdlr);
        signal(SIGINT, sighdlr);
@@ -234,10 +248,12 @@ main(int argc, char *argv[])
        signal(SIGPIPE, SIG_IGN);
 
        if ((ibuf_se = malloc(sizeof(struct imsgbuf))) == NULL ||
-           (ibuf_rde = malloc(sizeof(struct imsgbuf))) == NULL)
+           (ibuf_rde = malloc(sizeof(struct imsgbuf))) == NULL ||
+           (ibuf_rtr = malloc(sizeof(struct imsgbuf))) == NULL)
                fatal(NULL);
        imsg_init(ibuf_se, pipe_m2s[0]);
        imsg_init(ibuf_rde, pipe_m2r[0]);
+       imsg_init(ibuf_rtr, pipe_m2roa[0]);
        mrt_init(ibuf_rde, ibuf_se);
        if (kr_init(&rfd) == -1)
                quit = 1;
@@ -262,7 +278,7 @@ BROKEN      if (pledge("stdio rpath wpath cpa
                fatal("pledge");
 #endif
 
-       if (imsg_send_sockets(ibuf_se, ibuf_rde))
+       if (imsg_send_sockets(ibuf_se, ibuf_rde, ibuf_rtr))
                fatal("could not establish imsg links");
        /* control setup needs to happen late since it sends imsgs */
        if (control_setup(conf) == -1)
@@ -285,6 +301,7 @@ BROKEN      if (pledge("stdio rpath wpath cpa
 
                set_pollfd(&pfd[PFD_PIPE_SESSION], ibuf_se);
                set_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde);
+               set_pollfd(&pfd[PFD_PIPE_RTR], ibuf_rtr);
 
                if (timeout < 0 || timeout > MAX_TIMEOUT)
                        timeout = MAX_TIMEOUT;
@@ -313,8 +330,18 @@ BROKEN     if (pledge("stdio rpath wpath cpa
                        ibuf_rde = NULL;
                        quit = 1;
                } else {
-                       if (dispatch_imsg(ibuf_rde, PFD_PIPE_RDE, conf) ==
-                           -1)
+                       if (dispatch_imsg(ibuf_rde, PFD_PIPE_RDE, conf) == -1)
+                               quit = 1;
+               }
+
+               if (handle_pollfd(&pfd[PFD_PIPE_RTR], ibuf_rtr) == -1) {
+                       log_warnx("main: Lost connection to RTR");
+                       msgbuf_clear(&ibuf_rtr->w);
+                       free(ibuf_rtr);
+                       ibuf_rtr = NULL;
+                       quit = 1;
+               } else {
+                       if (dispatch_imsg(ibuf_rtr, PFD_PIPE_RTR, conf) == -1)
                                quit = 1;
                }
 
@@ -377,6 +404,12 @@ BROKEN     if (pledge("stdio rpath wpath cpa
                free(ibuf_rde);
                ibuf_rde = NULL;
        }
+       if (ibuf_rtr) {
+               msgbuf_clear(&ibuf_rtr->w);
+               close(ibuf_rtr->fd);
+               free(ibuf_rtr);
+               ibuf_rtr = NULL;
+       }
 
        /* cleanup kernel data structures */
        carp_demote_shutdown();
@@ -404,6 +437,8 @@ BROKEN      if (pledge("stdio rpath wpath cpa
                                name = "route decision engine";
                        else if (pid == se_pid)
                                name = "session engine";
+                       else if (pid == rtr_pid)
+                               name = "rtr engine";
                        log_warnx("%s terminated; signal %d", name,
                                WTERMSIG(status));
                }
@@ -449,6 +484,9 @@ start_child(enum bgpd_process p, char *a
        case PROC_SE:
                argv[argc++] = "-S";
                break;
+       case PROC_RTR:
+               argv[argc++] = "-T";
+               break;
        }
        if (debug)
                argv[argc++] = "-d";
@@ -481,7 +519,8 @@ reconfigure(char *conffile, struct bgpd_
                return (2);
 
        log_info("rereading config");
-       if ((new_conf = parse_config(conffile, &conf->peers)) == NULL)
+       if ((new_conf = parse_config(conffile, &conf->peers,
+           &conf->rtrs)) == NULL)
                return (1);
 
        merge_config(conf, new_conf);
@@ -509,8 +548,9 @@ send_config(struct bgpd_config *conf)
        struct prefixset        *ps;
        struct prefixset_item   *psi, *npsi;
        struct roa              *roa, *nroa;
+       struct rtr_config       *rtr;
 
-       reconfpending = 2;      /* one per child */
+       reconfpending = 3;      /* one per child */
 
        expand_networks(conf);
 
@@ -523,6 +563,9 @@ send_config(struct bgpd_config *conf)
        if (imsg_compose(ibuf_rde, IMSG_RECONF_CONF, 0, 0, -1,
            conf, sizeof(*conf)) == -1)
                return (-1);
+       if (imsg_compose(ibuf_rtr, IMSG_RECONF_CONF, 0, 0, -1,
+           conf, sizeof(*conf)) == -1)
+               return (-1);
 
        TAILQ_FOREACH(la, conf->listen_addrs, entry) {
                if (imsg_compose(ibuf_se, IMSG_RECONF_LISTENER, 0, 0, la->fd,
@@ -595,17 +638,18 @@ send_config(struct bgpd_config *conf)
                free(ps);
        }
 
-       if (!RB_EMPTY(&conf->roa)) {
-               if (imsg_compose(ibuf_rde, IMSG_RECONF_ROA_SET, 0, 0, -1,
-                   NULL, 0) == -1)
+       /* roa table and rtr config are sent to the RTR engine */
+       RB_FOREACH_SAFE(roa, roa_tree, &conf->roa, nroa) {
+               RB_REMOVE(roa_tree, &conf->roa, roa);
+               if (imsg_compose(ibuf_rtr, IMSG_RECONF_ROA_ITEM, 0, 0,
+                   -1, roa, sizeof(*roa)) == -1)
+                       return (-1);
+               free(roa);
+       }
+       SIMPLEQ_FOREACH(rtr, &conf->rtrs, entry) {
+               if (imsg_compose(ibuf_rtr, IMSG_RECONF_RTR_CONFIG, rtr->id,
+                   0, -1, rtr->descr, sizeof(rtr->descr)) == -1)
                        return (-1);
-               RB_FOREACH_SAFE(roa, roa_tree, &conf->roa, nroa) {
-                       RB_REMOVE(roa_tree, &conf->roa, roa);
-                       if (imsg_compose(ibuf_rde, IMSG_RECONF_ROA_ITEM, 0, 0,
-                           -1, roa, sizeof(*roa)) == -1)
-                               return (-1);
-                       free(roa);
-               }
        }
 
        /* as-sets for filters in the RDE */
@@ -695,6 +739,8 @@ send_config(struct bgpd_config *conf)
                return (-1);
        if (imsg_compose(ibuf_rde, IMSG_RECONF_DRAIN, 0, 0, -1, NULL, 0) == -1)
                return (-1);
+       if (imsg_compose(ibuf_rtr, IMSG_RECONF_DRAIN, 0, 0, -1, NULL, 0) == -1)
+               return (-1);
 
        /* mrt changes can be sent out of bound */
        mrt_reconfigure(conf->mrt);
@@ -706,6 +752,7 @@ dispatch_imsg(struct imsgbuf *ibuf, int 
 {
        struct imsg              imsg;
        struct peer             *p;
+       struct rtr_config       *r;
        ssize_t                  n;
        int                      rv, verbose;
 
@@ -874,6 +921,9 @@ dispatch_imsg(struct imsgbuf *ibuf, int 
                                break;
                        }
                        if (idx == PFD_PIPE_SESSION) {
+                               imsg_compose(ibuf_rtr, IMSG_RECONF_DONE, 0,
+                                   0, -1, NULL, 0);
+                       } else if (idx == PFD_PIPE_RTR) {
                                imsg_compose(ibuf_rde, IMSG_RECONF_DONE, 0,
                                    0, -1, NULL, 0);
 
@@ -898,8 +948,65 @@ dispatch_imsg(struct imsgbuf *ibuf, int 
                                 */
                                imsg_compose(ibuf_se, IMSG_RECONF_DONE, 0,
                                    0, -1, NULL, 0);
-                               reconfpending = 2; /* expecting 2 DONE msg */
+                               reconfpending = 3; /* expecting 2 DONE msg */
+                       }
+                       break;
+               case IMSG_SOCKET_CONN:
+                       if (idx != PFD_PIPE_RTR) {
+                               log_warnx("connect request not from RTR");
+                       } else {
+                               SIMPLEQ_FOREACH(r, &conf->rtrs, entry) {
+                                       if (imsg.hdr.peerid == r->id)
+                                               break;
+                               }
+                               if (r == NULL)
+                                       log_warnx("unknown rtr id %d",
+                                           imsg.hdr.peerid);
+                               else
+                                       bgpd_rtr_connect(r);
+                       }
+                       break;
+               case IMSG_CTL_SHOW_RTR:
+                       if (idx == PFD_PIPE_SESSION) {
+                               SIMPLEQ_FOREACH(r, &conf->rtrs, entry) {
+                                       imsg_compose(ibuf_rtr, imsg.hdr.type,
+                                           r->id, imsg.hdr.pid, -1, NULL, 0);
+                               }
+                               imsg_compose(ibuf_rtr, IMSG_CTL_END,
+                                   0, imsg.hdr.pid, -1, NULL, 0);
+                       } else if (imsg.hdr.len != IMSG_HEADER_SIZE +
+                           sizeof(struct ctl_show_rtr)) {
+                               log_warnx("IMSG_CTL_SHOW_RTR with wrong len");
+                       } else if (idx == PFD_PIPE_RTR) {
+                               SIMPLEQ_FOREACH(r, &conf->rtrs, entry) {
+                                       if (imsg.hdr.peerid == r->id)
+                                               break;
+                               }
+                               if (r != NULL) {
+                                       struct ctl_show_rtr *msg;
+                                       msg = imsg.data;
+                                       strlcpy(msg->descr, r->descr,
+                                           sizeof(msg->descr));
+                                       msg->local_addr = r->local_addr;
+                                       msg->remote_addr = r->remote_addr;
+                                       msg->remote_port = r->remote_port;
+
+                                       imsg_compose(ibuf_se, imsg.hdr.type,
+                                           imsg.hdr.peerid, imsg.hdr.pid,
+                                           -1, imsg.data,
+                                           imsg.hdr.len - IMSG_HEADER_SIZE);
+                               }
+                       }
+                       break;
+               case IMSG_CTL_END:
+               case IMSG_CTL_SHOW_TIMER:
+                       if (idx != PFD_PIPE_RTR) {
+                               log_warnx("connect request not from RTR");
+                               break;
                        }
+                       imsg_compose(ibuf_se, imsg.hdr.type, imsg.hdr.peerid,
+                           imsg.hdr.pid, -1, imsg.data,
+                           imsg.hdr.len - IMSG_HEADER_SIZE);
                        break;
                default:
                        break;
@@ -1117,13 +1224,15 @@ getsockpair(int pipe[2])
 }
 
 int
-imsg_send_sockets(struct imsgbuf *se, struct imsgbuf *rde)
+imsg_send_sockets(struct imsgbuf *se, struct imsgbuf *rde, struct imsgbuf *roa)
 {
        int pipe_s2r[2];
        int pipe_s2r_ctl[2];
+       int pipe_r2r[2];
 
        getsockpair(pipe_s2r);
        getsockpair(pipe_s2r_ctl);
+       getsockpair(pipe_r2r);
 
        if (imsg_compose(se, IMSG_SOCKET_CONN, 0, 0, pipe_s2r[0],
            NULL, 0) == -1)
@@ -1139,5 +1248,44 @@ imsg_send_sockets(struct imsgbuf *se, st
            NULL, 0) == -1)
                return (-1);
 
+       if (imsg_compose(roa, IMSG_SOCKET_CONN_RTR, 0, 0, pipe_r2r[0],
+           NULL, 0) == -1)
+               return (-1);
+       if (imsg_compose(rde, IMSG_SOCKET_CONN_RTR, 0, 0, pipe_r2r[1],
+           NULL, 0) == -1)
+               return (-1);
+
        return (0);
+}
+
+void
+bgpd_rtr_connect(struct rtr_config *r)
+{
+       socklen_t len;
+       int fd;
+
+       /* XXX should be non-blocking */
+       fd = socket(aid2af(r->remote_addr.aid), SOCK_STREAM, 0);
+       if (fd == -1) {
+               log_warn("rtr %s", r->descr);
+               return;
+       }
+       if (r->local_addr.aid != AID_UNSPEC) {
+               if (bind(fd,  addr2sa(&r->local_addr, 0, &len), len) == -1) {
+                       log_warn("rtr %s: bind to %s", r->descr,
+                           log_addr(&r->local_addr));
+                       close(fd);
+                       return;
+               }
+       }
+
+       if (connect(fd, addr2sa(&r->remote_addr, r->remote_port, &len), len) ==
+           -1) {
+               log_warn("rtr %s: connect to %s:%u", r->descr,
+                   log_addr(&r->remote_addr), r->remote_port);
+               close(fd);
+               return;
+       }
+
+       imsg_compose(ibuf_rtr, IMSG_SOCKET_CONN, r->id, 0, fd, NULL, 0);
 }
Index: bgpd/bgpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.conf.5,v
retrieving revision 1.207
diff -u -p -r1.207 bgpd.conf.5
--- bgpd/bgpd.conf.5    27 Jan 2021 14:57:29 -0000      1.207
+++ bgpd/bgpd.conf.5    10 Feb 2021 16:02:25 -0000
@@ -494,6 +494,39 @@ and the Origin Validation State (OVS) is
 roa-set { 192.0.2.0/24 maxlen 24 source-as 64511
           203.0.113.0/24 source-as 64496 }
 .Ed
+.Pp
+.It Xo
+.Ic rtr Ar address
+.Ic { Ar ... Ic }
+.Xc
+The
+.Ic rtr
+block specifies a
+.Em RPKI to Router Protocol
+session.
+The rtr session properties are as follows:
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic descr Ar description
+Add a description.
+The description is used in logging and status reports, but has no further
+meaning for
+.Xr bgpd 8 .
+.Pp
+.It Ic local-address Ar address
+Bind to the specific IP address before opening the TCP connection to the
+.Em rtr
+server.
+.Pp
+.It Ic port Ar number
+Specify the TCP destination port for the
+.Em rtr
+session.
+If not specified the default
+.Ic port
+is
+.Em 323 .
+.El
 .El
 .Sh NETWORK ANNOUNCEMENTS
 .Ic network
Index: bgpd/bgpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.h,v
retrieving revision 1.411
diff -u -p -r1.411 bgpd.h
--- bgpd/bgpd.h 25 Jan 2021 09:15:23 -0000      1.411
+++ bgpd/bgpd.h 25 Jan 2021 16:52:41 -0000
@@ -120,7 +120,8 @@
 enum bgpd_process {
        PROC_MAIN,
        PROC_SE,
-       PROC_RDE
+       PROC_RDE,
+       PROC_RTR,
 };
 
 enum reconf_action {
@@ -266,6 +267,9 @@ SIMPLEQ_HEAD(as_set_head, as_set);
 struct filter_rule;
 TAILQ_HEAD(filter_head, filter_rule);
 
+struct rtr_config;
+SIMPLEQ_HEAD(rtr_config_head, rtr_config);
+
 struct bgpd_config {
        struct peer_head                         peers;
        struct l3vpn_head                        l3vpns;
@@ -278,8 +282,8 @@ struct bgpd_config {
        struct roa_tree                          roa;
        struct rde_prefixset_head                rde_prefixsets;
        struct rde_prefixset_head                rde_originsets;
-       struct rde_prefixset                     rde_roa;
        struct as_set_head                       as_sets;
+       struct rtr_config_head                   rtrs;
        char                                    *csock;
        char                                    *rcsock;
        int                                      flags;
@@ -433,6 +437,44 @@ struct network {
        TAILQ_ENTRY(network)            entry;
 };
 
+enum rtr_error {
+       NO_ERROR = -1,
+       CORRUPT_DATA = 0,
+       INTERNAL_ERROR,
+       NO_DATA_AVAILABLE,
+       INVALID_REQUEST,
+       UNSUPP_PROTOCOL_VERS,
+       UNSUPP_PDU_TYPE,
+       UNK_REC_WDRAWL,
+       DUP_REC_RECV,
+       UNEXP_PROTOCOL_VERS,
+};
+
+struct rtr_config {
+       SIMPLEQ_ENTRY(rtr_config)       entry;
+       char                            descr[PEER_DESCR_LEN];
+       struct bgpd_addr                remote_addr;
+       struct bgpd_addr                local_addr;
+       u_int32_t                       id;
+       in_addr_t                       remote_port;
+};
+
+struct ctl_show_rtr {
+       char                    descr[PEER_DESCR_LEN];
+       struct bgpd_addr        remote_addr;
+       struct bgpd_addr        local_addr;
+       uint32_t                serial;
+       uint32_t                refresh;
+       uint32_t                retry;
+       uint32_t                expire;
+       int                     session_id;
+       in_addr_t               remote_port;
+       enum rtr_error          last_sent_error;
+       enum rtr_error          last_recv_error;
+       char                    last_sent_msg[REASON_LEN];
+       char                    last_recv_msg[REASON_LEN];
+};
+
 enum imsg_type {
        IMSG_NONE,
        IMSG_CTL_END,
@@ -462,6 +504,7 @@ enum imsg_type {
        IMSG_CTL_LOG_VERBOSE,
        IMSG_CTL_SHOW_FIB_TABLES,
        IMSG_CTL_SHOW_SET,
+       IMSG_CTL_SHOW_RTR,
        IMSG_CTL_TERMINATE,
        IMSG_NETWORK_ADD,
        IMSG_NETWORK_ASPATH,
@@ -472,6 +515,7 @@ enum imsg_type {
        IMSG_FILTER_SET,
        IMSG_SOCKET_CONN,
        IMSG_SOCKET_CONN_CTL,
+       IMSG_SOCKET_CONN_RTR,
        IMSG_RECONF_CONF,
        IMSG_RECONF_RIB,
        IMSG_RECONF_PEER,
@@ -490,6 +534,7 @@ enum imsg_type {
        IMSG_RECONF_ORIGIN_SET,
        IMSG_RECONF_ROA_SET,
        IMSG_RECONF_ROA_ITEM,
+       IMSG_RECONF_RTR_CONFIG,
        IMSG_RECONF_DRAIN,
        IMSG_RECONF_DONE,
        IMSG_UPDATE,
@@ -1194,11 +1239,13 @@ void            free_prefixsets(struct prefixset_h
 void           free_rde_prefixsets(struct rde_prefixset_head *);
 void           free_prefixtree(struct prefixset_tree *);
 void           free_roatree(struct roa_tree *);
+void           free_rtrs(struct rtr_config_head *);
 void           filterlist_free(struct filter_head *);
 int            host(const char *, struct bgpd_addr *, u_int8_t *);
 u_int32_t      get_bgpid(void);
 void           expand_networks(struct bgpd_config *);
 RB_PROTOTYPE(prefixset_tree, prefixset_item, entry, prefixset_cmp);
+int            roa_cmp(struct roa *, struct roa *);
 RB_PROTOTYPE(roa_tree, roa, entry, roa_cmp);
 
 /* kroute.c */
@@ -1262,8 +1309,10 @@ void              pftable_unref(u_int16_t);
 u_int16_t       pftable_ref(u_int16_t);
 
 /* parse.y */
-int             cmdline_symset(char *);
-struct prefixset *find_prefixset(char *, struct prefixset_head *);
+int                    cmdline_symset(char *);
+struct prefixset       *find_prefixset(char *, struct prefixset_head *);
+struct bgpd_config     *parse_config(char *, struct peer_head *,
+                           struct rtr_config_head *);
 
 /* pftable.c */
 int    pftable_exists(const char *);
@@ -1320,6 +1369,7 @@ const char        *log_as(u_int32_t);
 const char     *log_rd(u_int64_t);
 const char     *log_ext_subtype(short, u_int8_t);
 const char     *log_reason(const char *);
+const char     *log_rtr_error(enum rtr_error);
 int             aspath_snprint(char *, size_t, void *, u_int16_t);
 int             aspath_asprint(char **, void *, u_int16_t);
 size_t          aspath_strlen(void *, u_int16_t);
@@ -1356,7 +1406,8 @@ const char *       get_baudrate(unsigned long
 static const char * const log_procnames[] = {
        "parent",
        "SE",
-       "RDE"
+       "RDE",
+       "RTR"
 };
 
 /* logmsg.c and needed by bgpctl */
@@ -1485,6 +1536,10 @@ static const char * const timernames[] =
        "IdleHoldTimer",
        "IdleHoldResetTimer",
        "CarpUndemoteTimer",
+       "RestartTimer",
+       "RTR RefreshTimer",
+       "RTR RetryTimer",
+       "RTR ExpireTimer",
        ""
 };
 
Index: bgpd/config.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/config.c,v
retrieving revision 1.98
diff -u -p -r1.98 config.c
--- bgpd/config.c       4 Jan 2021 13:42:11 -0000       1.98
+++ bgpd/config.c       4 Jan 2021 15:32:07 -0000
@@ -60,6 +60,7 @@ new_config(void)
        SIMPLEQ_INIT(&conf->rde_originsets);
        RB_INIT(&conf->roa);
        SIMPLEQ_INIT(&conf->as_sets);
+       SIMPLEQ_INIT(&conf->rtrs);
 
        TAILQ_INIT(conf->filters);
        TAILQ_INIT(conf->listen_addrs);
@@ -163,6 +164,18 @@ free_roatree(struct roa_tree *r)
 }
 
 void
+free_rtrs(struct rtr_config_head *rh)
+{
+       struct rtr_config       *r;
+
+       while (!SIMPLEQ_EMPTY(rh)) {
+               r = SIMPLEQ_FIRST(rh);
+               SIMPLEQ_REMOVE_HEAD(rh, entry);
+               free(r);
+       }
+}
+
+void
 free_config(struct bgpd_config *conf)
 {
        struct peer             *p, *next;
@@ -178,6 +191,7 @@ free_config(struct bgpd_config *conf)
        free_rde_prefixsets(&conf->rde_originsets);
        as_sets_free(&conf->as_sets);
        free_roatree(&conf->roa);
+       free_rtrs(&conf->rtrs);
 
        while ((la = TAILQ_FIRST(conf->listen_addrs)) != NULL) {
                TAILQ_REMOVE(conf->listen_addrs, la, entry);
@@ -246,6 +260,10 @@ merge_config(struct bgpd_config *xconf, 
        RB_ROOT(&xconf->roa) = RB_ROOT(&conf->roa);
        RB_ROOT(&conf->roa) = NULL;
 
+       /* switch the rtr_configs, first remove the old ones */
+       free_rtrs(&xconf->rtrs);
+       SIMPLEQ_CONCAT(&xconf->rtrs, &conf->rtrs);
+
        /* switch the prefixsets, first remove the old ones */
        free_prefixsets(&xconf->prefixsets);
        SIMPLEQ_CONCAT(&xconf->prefixsets, &conf->prefixsets);
@@ -582,7 +600,7 @@ prefixset_cmp(struct prefixset_item *a, 
 
 RB_GENERATE(prefixset_tree, prefixset_item, entry, prefixset_cmp);
 
-static inline int
+int
 roa_cmp(struct roa *a, struct roa *b)
 {
        int i;
Index: bgpd/control.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/control.c,v
retrieving revision 1.103
diff -u -p -r1.103 control.c
--- bgpd/control.c      30 Dec 2020 07:29:56 -0000      1.103
+++ bgpd/control.c      25 Jan 2021 17:56:27 -0000
@@ -448,6 +448,7 @@ control_dispatch_msg(struct pollfd *pfd,
                case IMSG_CTL_RELOAD:
                case IMSG_CTL_SHOW_INTERFACE:
                case IMSG_CTL_SHOW_FIB_TABLES:
+               case IMSG_CTL_SHOW_RTR:
                        c->ibuf.pid = imsg.hdr.pid;
                        imsg_ctl_parent(imsg.hdr.type, 0, imsg.hdr.pid,
                            imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE);
Index: bgpd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/parse.y,v
retrieving revision 1.412
diff -u -p -r1.412 parse.y
--- bgpd/parse.y        25 Jan 2021 09:15:23 -0000      1.412
+++ bgpd/parse.y        26 Jan 2021 09:05:15 -0000
@@ -89,12 +89,14 @@ char                *symget(const char *);
 static struct bgpd_config      *conf;
 static struct network_head     *netconf;
 static struct peer_head                *new_peers, *cur_peers;
+static struct rtr_config_head  *cur_rtrs;
 static struct peer             *curpeer;
 static struct peer             *curgroup;
 static struct rde_rib          *currib;
 static struct l3vpn            *curvpn;
 static struct prefixset                *curpset, *curoset;
 static struct roa_tree         *curroatree;
+static struct rtr_config       *currtr;
 static struct filter_head      *filter_l;
 static struct filter_head      *peerfilter_l;
 static struct filter_head      *groupfilter_l;
@@ -162,6 +164,7 @@ static void  add_as_set(u_int32_t);
 static void     done_as_set(void);
 static struct prefixset        *new_prefix_set(char *, int);
 static void     add_roa_set(struct prefixset_item *, u_int32_t, u_int8_t);
+static struct rtr_config *get_rtr(struct bgpd_addr *);
 
 typedef struct {
        union {
@@ -196,7 +199,7 @@ typedef struct {
 
 %token AS ROUTERID HOLDTIME YMIN LISTEN ON FIBUPDATE FIBPRIORITY RTABLE
 %token NONE UNICAST VPN RD EXPORT EXPORTTRGT IMPORTTRGT DEFAULTROUTE
-%token RDE RIB EVALUATE IGNORE COMPARE
+%token RDE RIB EVALUATE IGNORE COMPARE RTR PORT
 %token GROUP NEIGHBOR NETWORK
 %token EBGP IBGP
 %token LOCALAS REMOTEAS DESCR LOCALADDR MULTIHOP PASSIVE MAXPREFIX RESTART
@@ -252,6 +255,7 @@ grammar             : /* empty */
                | grammar prefixset '\n'
                | grammar roa_set '\n'
                | grammar origin_set '\n'
+               | grammar rtr '\n'
                | grammar rib '\n'
                | grammar conf_main '\n'
                | grammar l3vpn '\n'
@@ -551,6 +555,53 @@ roa_set_l  : prefixset_item SOURCEAS as4n
                }
                ;
 
+rtr            : RTR address   {
+                       currtr = get_rtr(&$2);
+                       currtr->remote_port = 323;
+                       SIMPLEQ_INSERT_TAIL(&conf->rtrs, currtr, entry);
+                       currtr = NULL;
+               }
+               | RTR address   {
+                       currtr = get_rtr(&$2);
+                       currtr->remote_port = 323;
+               } '{' optnl rtropt_l optnl '}' {
+                       SIMPLEQ_INSERT_TAIL(&conf->rtrs, currtr, entry);
+                       currtr = NULL;
+               }
+               ;
+
+rtropt_l       : rtropt
+               | rtropt_l optnl rtropt
+
+rtropt         : DESCR STRING          {
+                       if (strlcpy(currtr->descr, $2,
+                           sizeof(currtr->descr)) >=
+                           sizeof(currtr->descr)) {
+                               yyerror("descr \"%s\" too long: max %zu",
+                                   $2, sizeof(currtr->descr) - 1);
+                               free($2);
+                               YYERROR;
+                       }
+                       free($2);
+               }
+               | LOCALADDR address     {
+                       if ($2.aid != currtr->remote_addr.aid) {
+                               yyerror("Bad address family %s for "
+                                   "local-addr", aid2str($2.aid));
+                               YYERROR;
+                       }
+                       currtr->local_addr = $2;
+               }
+               | PORT NUMBER {
+                       if ($2 < 1 || $2 > USHRT_MAX) {
+                               yyerror("local-port must be between %u and %u",
+                                   1, USHRT_MAX);
+                               YYERROR;
+                       }
+                       currtr->remote_port = $2;
+               }
+               ;
+
 conf_main      : AS as4number          {
                        conf->as = $2;
                        if ($2 > USHRT_MAX)
@@ -2866,6 +2917,7 @@ lookup(char *s)
                { "password",           PASSWORD},
                { "peer-as",            PEERAS},
                { "pftable",            PFTABLE},
+               { "port",               PORT},
                { "prefix",             PREFIX},
                { "prefix-set",         PREFIXSET},
                { "prefixlen",          PREFIXLEN},
@@ -2887,6 +2939,7 @@ lookup(char *s)
                { "router-id",          ROUTERID},
                { "rtable",             RTABLE},
                { "rtlabel",            RTLABEL},
+               { "rtr",                RTR},
                { "self",               SELF},
                { "set",                SET},
                { "socket",             SOCKET },
@@ -3285,7 +3338,7 @@ init_config(struct bgpd_config *c)
 }
 
 struct bgpd_config *
-parse_config(char *filename, struct peer_head *ph)
+parse_config(char *filename, struct peer_head *ph, struct rtr_config_head *rh)
 {
        struct sym              *sym, *next;
        struct rde_rib          *rr;
@@ -3309,6 +3362,7 @@ parse_config(char *filename, struct peer
        curgroup = NULL;
 
        cur_peers = ph;
+       cur_rtrs = rh;
        new_peers = &conf->peers;
        netconf = &conf->networks;
 
@@ -4567,4 +4621,35 @@ add_roa_set(struct prefixset_item *npsi,
        if (r != NULL)
                /* just ignore duplicates */
                free(roa);
+}
+
+static struct rtr_config *
+get_rtr(struct bgpd_addr *addr)
+{
+       static uint32_t id; 
+       struct rtr_config *n, *p;
+
+       if (id == UINT32_MAX) {
+               yyerror("out of rtr session IDs");
+               return NULL;
+       }
+
+       n = calloc(1, sizeof(*n));
+       if (n == NULL) {
+               yyerror("out of memory");
+               return NULL;
+       }
+
+       n->remote_addr = *addr;
+       strlcpy(n->descr, log_addr(addr), sizeof(currtr->descr));
+
+       if (cur_rtrs)
+               SIMPLEQ_FOREACH(p, cur_rtrs, entry)
+                       if (memcmp(&p->remote_addr, addr, sizeof(*addr)) == 0)
+                               n->id = p->id;
+
+       if (n->id == 0)
+               n->id = ++id;   
+
+       return n;
 }
Index: bgpd/printconf.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/printconf.c,v
retrieving revision 1.145
diff -u -p -r1.145 printconf.c
--- bgpd/printconf.c    25 Jan 2021 09:15:23 -0000      1.145
+++ bgpd/printconf.c    26 Jan 2021 09:26:25 -0000
@@ -42,6 +42,7 @@ void           print_as_sets(struct as_set_head 
 void            print_prefixsets(struct prefixset_head *);
 void            print_originsets(struct prefixset_head *);
 void            print_roa(struct roa_tree *);
+void            print_rtrs(struct rtr_config_head *);
 void            print_peer(struct peer_config *, struct bgpd_config *,
                    const char *);
 const char     *print_auth_alg(u_int8_t);
@@ -579,6 +580,21 @@ print_roa(struct roa_tree *r)
 }
 
 void
+print_rtrs(struct rtr_config_head *rh)
+{
+       struct rtr_config *r;
+
+       SIMPLEQ_FOREACH(r, rh, entry) {
+               printf("rtr %s {\n", log_addr(&r->remote_addr));
+               printf("\tdescr \"%s\"\n", r->descr);
+               printf("\tport %u\n", r->remote_port);
+               if (r->local_addr.aid != AID_UNSPEC)
+                       printf("local-addr %s\n", log_addr(&r->local_addr));
+               printf("}\n\n");
+       }
+}
+
+void
 print_peer(struct peer_config *p, struct bgpd_config *conf, const char *c)
 {
        char            *method;
@@ -1010,6 +1026,7 @@ print_config(struct bgpd_config *conf, s
        struct l3vpn            *vpn;
 
        print_mainconf(conf);
+       print_rtrs(&conf->rtrs);
        print_roa(&conf->roa);
        print_as_sets(&conf->as_sets);
        print_prefixsets(&conf->prefixsets);
Index: bgpd/rde.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde.c,v
retrieving revision 1.514
diff -u -p -r1.514 rde.c
--- bgpd/rde.c  25 Jan 2021 09:15:24 -0000      1.514
+++ bgpd/rde.c  26 Jan 2021 07:33:18 -0000
@@ -32,7 +32,6 @@
 #include <string.h>
 #include <syslog.h>
 #include <unistd.h>
-#include <err.h>
 
 #include "bgpd.h"
 #include "rde.h"
@@ -42,11 +41,13 @@
 #define PFD_PIPE_MAIN          0
 #define PFD_PIPE_SESSION       1
 #define PFD_PIPE_SESSION_CTL   2
-#define PFD_PIPE_COUNT         3
+#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 imsg *);
 int             rde_update_update(struct rde_peer *, struct filterstate *,
@@ -79,6 +80,7 @@ static void    rde_softreconfig_in(struct 
 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 *, u_int8_t);
+static void     rde_roa_reload(void);
 static int      rde_no_as_set(struct rde_peer *);
 int             rde_update_queue_pending(void);
 void            rde_update_queue_runner(void);
@@ -102,8 +104,10 @@ int                 ovs_match(struct prefix *, u_int32
 
 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;
 
 volatile sig_atomic_t   rde_quit = 0;
 struct filter_head     *out_rules, *out_rules_tmp;
@@ -231,6 +235,7 @@ rde_main(int debug, int verbose)
                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;
                for (mctx = LIST_FIRST(&rde_mrts); mctx != 0; mctx = xmctx) {
@@ -282,6 +287,14 @@ rde_main(int debug, int verbose)
                } 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");
+                       msgbuf_clear(&ibuf_rtr->w);
+                       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 != 0; j++) {
                        if (pfd[j].fd == mctx->mrt.wbuf.fd &&
@@ -578,7 +591,7 @@ badnetdel:
                        break;
                case IMSG_CTL_SHOW_SET:
                        /* first roa set */
-                       pset = &conf->rde_roa;
+                       pset = &rde_roa;
                        memset(&cset, 0, sizeof(cset));
                        cset.type = ROA_SET;
                        strlcpy(cset.name, "RPKI ROA", sizeof(cset.name));
@@ -668,7 +681,6 @@ rde_dispatch_imsg_parent(struct imsgbuf 
        static struct l3vpn     *vpn;
        struct imsg              imsg;
        struct mrt               xmrt;
-       struct roa               roa;
        struct rde_rib           rr;
        struct filterstate       state;
        struct imsgbuf          *i;
@@ -693,15 +705,17 @@ rde_dispatch_imsg_parent(struct imsgbuf 
                switch (imsg.hdr.type) {
                case IMSG_SOCKET_CONN:
                case IMSG_SOCKET_CONN_CTL:
+               case IMSG_SOCKET_CONN_RTR:
                        if ((fd = imsg.fd) == -1) {
-                               log_warnx("expected to receive imsg fd to "
-                                   "SE but didn't receive any");
+                               log_warnx("expected to receive imsg fd "
+                                   "but didn't receive any");
                                break;
                        }
                        if ((i = malloc(sizeof(struct imsgbuf))) == NULL)
                                fatal(NULL);
                        imsg_init(i, fd);
-                       if (imsg.hdr.type == IMSG_SOCKET_CONN) {
+                       switch (imsg.hdr.type) {
+                       case IMSG_SOCKET_CONN:
                                if (ibuf_se) {
                                        log_warnx("Unexpected imsg connection "
                                            "to SE received");
@@ -709,7 +723,8 @@ rde_dispatch_imsg_parent(struct imsgbuf 
                                        free(ibuf_se);
                                }
                                ibuf_se = i;
-                       } else {
+                               break;
+                       case IMSG_SOCKET_CONN_CTL:
                                if (ibuf_se_ctl) {
                                        log_warnx("Unexpected imsg ctl "
                                            "connection to SE received");
@@ -717,6 +732,16 @@ rde_dispatch_imsg_parent(struct imsgbuf 
                                        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");
+                                       msgbuf_clear(&ibuf_rtr->w);
+                                       free(ibuf_rtr);
+                               }
+                               ibuf_rtr = i;
+                               break;
                        }
                        break;
                case IMSG_NETWORK_ADD:
@@ -865,17 +890,6 @@ rde_dispatch_imsg_parent(struct imsgbuf 
                        }
                        last_prefixset = ps;
                        break;
-               case IMSG_RECONF_ROA_SET:
-                       strlcpy(nconf->rde_roa.name, "RPKI ROA",
-                           sizeof(nconf->rde_roa.name));
-                       last_prefixset = &nconf->rde_roa;
-                       break;
-               case IMSG_RECONF_ROA_ITEM:
-                       if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(roa))
-                               fatalx("IMSG_RECONF_ROA_ITEM bad len");
-                       memcpy(&roa, imsg.data, sizeof(roa));
-                       rv = trie_roa_add(&last_prefixset->th, &roa);
-                       break;
                case IMSG_RECONF_PREFIX_SET_ITEM:
                        if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(psi))
                                fatalx("IMSG_RECONF_PREFIX_SET_ITEM bad len");
@@ -886,7 +900,7 @@ rde_dispatch_imsg_parent(struct imsgbuf 
                            &psi.p.addr, psi.p.len,
                            psi.p.len_min, psi.p.len_max);
                        if (rv == -1)
-                               log_warnx("trie_add(%s) %s/%u) failed",
+                               log_warnx("trie_add(%s) %s/%u failed",
                                    last_prefixset->name, log_addr(&psi.p.addr),
                                    psi.p.len);
                        break;
@@ -991,6 +1005,46 @@ rde_dispatch_imsg_parent(struct imsgbuf 
                        /* ignore end message because a dump is atomic */
                        break;
                default:
+                       fatalx("unhandled IMSG %u", imsg.hdr.type);
+               }
+               imsg_free(&imsg);
+       }
+}
+
+void
+rde_dispatch_imsg_rtr(struct imsgbuf *ibuf)
+{
+       struct imsg      imsg;
+       struct roa       roa;
+       int              n;
+
+       while (ibuf) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("rde_dispatch_imsg_parent: imsg_get error");
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_RECONF_ROA_SET:
+                       /* start of update */
+                       break;
+               case IMSG_RECONF_ROA_ITEM:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(roa))
+                               fatalx("IMSG_RECONF_ROA_ITEM bad len");
+                       memcpy(&roa, imsg.data, sizeof(roa));
+                       if (trie_roa_add(&roa_new.th, &roa) != 0) {
+                               struct bgpd_addr p = {
+                                       .aid = roa.aid,
+                                       .v6 = roa.prefix.inet6
+                               };
+                               log_warnx("trie_roa_add %s/%u failed",
+                                   log_addr(&p), roa.prefixlen);
+                       }
+                       break;
+               case IMSG_RECONF_DONE:
+                       /* end of update */
+                       rde_roa_reload();
                        break;
                }
                imsg_free(&imsg);
@@ -1439,7 +1493,7 @@ rde_update_update(struct rde_peer *peer,
        const char              *wmsg = "filtered, withdraw";
 
        peer->prefix_rcvd_update++;
-       vstate = rde_roa_validity(&conf->rde_roa, prefix, prefixlen,
+       vstate = rde_roa_validity(&rde_roa, prefix, prefixlen,
            aspath_origin(in->aspath.aspath));
 
        /* add original path to the Adj-RIB-In */
@@ -3153,7 +3207,6 @@ rde_reload_done(void)
        struct filter_head      *fh;
        struct rde_prefixset_head prefixsets_old;
        struct rde_prefixset_head originsets_old;
-       struct rde_prefixset     roa_old;
        struct as_set_head       as_sets_old;
        u_int16_t                rid;
        int                      reload = 0;
@@ -3166,7 +3219,6 @@ rde_reload_done(void)
        SIMPLEQ_CONCAT(&prefixsets_old, &conf->rde_prefixsets);
        SIMPLEQ_CONCAT(&originsets_old, &conf->rde_originsets);
        SIMPLEQ_CONCAT(&as_sets_old, &conf->as_sets);
-       roa_old = conf->rde_roa;
 
        /* merge the main config */
        copy_config(conf, nconf);
@@ -3176,10 +3228,6 @@ rde_reload_done(void)
        SIMPLEQ_CONCAT(&conf->rde_originsets, &nconf->rde_originsets);
        SIMPLEQ_CONCAT(&conf->as_sets, &nconf->as_sets);
 
-       conf->rde_roa = nconf->rde_roa;
-       conf->rde_roa.lastchange = roa_old.lastchange;
-       memset(&nconf->rde_roa, 0, sizeof(nconf->rde_roa));
-
        /* apply new set of l3vpn, sync will be done later */
        free_l3vpns(&conf->l3vpns);
        SIMPLEQ_CONCAT(&conf->l3vpns, &nconf->l3vpns);
@@ -3197,16 +3245,6 @@ rde_reload_done(void)
        peerself->conf.remote_masklen = 32;
        peerself->short_as = conf->short_as;
 
-       /* check if roa changed */
-       if (trie_equal(&conf->rde_roa.th, &roa_old.th) == 0) {
-               log_debug("roa change: reloading Adj-RIB-In");
-               conf->rde_roa.dirty = 1;
-               conf->rde_roa.lastchange = getmonotime();
-               reload++;       /* run softreconf in */
-       }
-
-       trie_free(&roa_old.th); /* old roa no longer needed */
-
        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);
@@ -3443,8 +3481,6 @@ rde_softreconfig_in(struct rib_entry *re
        struct rde_aspath       *asp;
        enum filter_actions      action;
        struct bgpd_addr         prefix;
-       int                      force_eval;
-       u_int8_t                 vstate;
        u_int16_t                i;
 
        pt = re->prefix;
@@ -3452,17 +3488,6 @@ rde_softreconfig_in(struct rib_entry *re
        LIST_FOREACH(p, &re->prefix_h, entry.list.rib) {
                asp = prefix_aspath(p);
                peer = prefix_peer(p);
-               force_eval = 0;
-
-               if (conf->rde_roa.dirty) {
-                       /* ROA validation state update */
-                       vstate = rde_roa_validity(&conf->rde_roa,
-                           &prefix, pt->prefixlen, aspath_origin(asp->aspath));
-                       if (vstate != p->validation_state) {
-                               force_eval = 1;
-                               p->validation_state = vstate;
-                       }
-               }
 
                /* skip announced networks, they are never filtered */
                if (asp->flags & F_PREFIX_ANNOUNCED)
@@ -3473,7 +3498,7 @@ rde_softreconfig_in(struct rib_entry *re
                        if (rib == NULL)
                                continue;
 
-                       if (rib->state != RECONF_RELOAD && !force_eval)
+                       if (rib->state != RECONF_RELOAD)
                                continue;
 
                        rde_filterstate_prep(&state, asp, prefix_communities(p),
@@ -3575,6 +3600,99 @@ rde_softreconfig_sync_done(void *arg, u_
 }
 
 /*
+ * ROA specific functions. The roa set is updated independent of the config
+ * so this runs outside of the softreconfig handlers.
+ */
+static void
+rde_roa_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;
+       u_int8_t                 vstate;
+       u_int16_t                i;
+
+       pt = re->prefix;
+       pt_getaddr(pt, &prefix);
+       LIST_FOREACH(p, &re->prefix_h, entry.list.rib) {
+               asp = prefix_aspath(p);
+               peer = prefix_peer(p);
+
+               /* ROA validation state update */
+               vstate = rde_roa_validity(&rde_roa,
+                   &prefix, pt->prefixlen, aspath_origin(asp->aspath));
+               if (vstate == p->validation_state)
+                       continue;
+               p->validation_state = vstate;
+
+               /* skip announced networks, they are never filtered */
+               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, asp, prefix_communities(p),
+                           prefix_nexthop(p), prefix_nhflags(p));
+                       action = rde_filter(rib->in_rules, peer, peer, &prefix,
+                           pt->prefixlen, p->validation_state, &state);
+
+                       if (action == ACTION_ALLOW) {
+                               /* update Local-RIB */
+                               prefix_update(rib, peer, &state, &prefix,
+                                   pt->prefixlen, p->validation_state);
+                       } else if (action == ACTION_DENY) {
+                               /* remove from Local-RIB */
+                               prefix_withdraw(rib, peer, &prefix,
+                                   pt->prefixlen);
+                       }
+
+                       rde_filterstate_clean(&state);
+               }
+       }
+}
+
+static void
+rde_roa_softreload_done(void *arg, u_int8_t aid)
+{
+       /* the roa update is done */
+       log_info("ROA softreload done");
+}
+
+static void
+rde_roa_reload(void)
+{
+       struct rde_prefixset roa_old;
+
+       roa_old = rde_roa;
+       rde_roa = roa_new;
+       memset(&roa_new, 0, sizeof(roa_new));
+
+       /* check if roa changed */
+       if (trie_equal(&rde_roa.th, &roa_old.th)) {
+               rde_roa.lastchange = roa_old.lastchange;
+               trie_free(&roa_old.th); /* old roa no longer needed */
+               return;
+       }
+
+       rde_roa.lastchange = getmonotime();
+       trie_free(&roa_old.th); /* old roa no longer needed */
+
+       log_debug("ROA change: reloading Adj-RIB-In");
+       if (rib_dump_new(RIB_ADJ_IN, AID_UNSPEC, RDE_RUNNER_ROUNDS,
+           rib_byid(RIB_ADJ_IN), rde_roa_softreload,
+           rde_roa_softreload_done, NULL) == -1)
+               fatal("%s: rib_dump_new", __func__);
+}
+
+/*
  * generic helper function
  */
 u_int32_t
@@ -3737,7 +3855,7 @@ network_add(struct network_config *nc, s
                rde_apply_set(vpnset, peerself, peerself, state,
                    nc->prefix.aid);
 
-       vstate = rde_roa_validity(&conf->rde_roa, &nc->prefix,
+       vstate = rde_roa_validity(&rde_roa, &nc->prefix,
            nc->prefixlen, aspath_origin(state->aspath.aspath));
        if (prefix_update(rib_byid(RIB_ADJ_IN), peerself, state, &nc->prefix,
            nc->prefixlen, vstate) == 1)
Index: bgpd/rtr.c
===================================================================
RCS file: bgpd/rtr.c
diff -N bgpd/rtr.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ bgpd/rtr.c  26 Jan 2021 09:26:00 -0000
@@ -0,0 +1,334 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Claudio Jeker <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/tree.h>
+#include <errno.h>
+#include <poll.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdint.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"
+
+static void    rtr_dispatch_imsg_parent(struct imsgbuf *);
+static void    rtr_dispatch_imsg_rde(struct imsgbuf *);
+
+volatile sig_atomic_t           rtr_quit;
+static struct imsgbuf          *ibuf_main;
+static struct imsgbuf          *ibuf_rde;
+static struct bgpd_config      *conf, *nconf;
+
+static void
+rtr_sighdlr(int sig)
+{
+       switch (sig) {
+       case SIGINT:
+       case SIGTERM:
+               rtr_quit = 1;
+               break;
+       }
+}
+
+#define PFD_PIPE_MAIN  0
+#define PFD_PIPE_RDE   1
+#define PFD_PIPE_COUNT 2
+
+void
+roa_insert(struct roa_tree *rt, struct roa *in)
+{
+       struct roa *roa;
+
+       if ((roa = malloc(sizeof(*roa))) == NULL)
+               fatal("roa alloc");
+       memcpy(roa, in, sizeof(*roa));
+       if (RB_INSERT(roa_tree, rt, roa) != NULL)
+               /* just ignore duplicates */
+               free(roa);
+}
+
+void
+rtr_main(int debug, int verbose)
+{
+       struct passwd           *pw;
+       struct pollfd           *pfd = NULL;
+       void                    *newp;
+       size_t                   pfd_elms = 0, i;
+       time_t                   now, timeout;
+
+       log_init(debug, LOG_DAEMON);
+       log_setverbose(verbose);
+
+       log_procinit(log_procnames[PROC_RTR]);
+
+       if ((pw = getpwnam(BGPD_USER)) == NULL)
+               fatal("getpwnam");
+
+       if (chroot(pw->pw_dir) == -1)
+               fatal("chroot");
+       if (chdir("/") == -1)
+               fatal("chdir(\"/\")");
+
+       setproctitle("rtr 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, rtr_sighdlr);
+       signal(SIGINT, rtr_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);
+       imsg_init(ibuf_main, 3);
+
+       conf = new_config();
+       log_info("rtr engine ready");
+
+       while (rtr_quit == 0) {
+               i = rtr_count();
+               if (pfd_elms < PFD_PIPE_COUNT + i) {
+                       if ((newp = reallocarray(pfd,
+                           PFD_PIPE_COUNT + i,
+                           sizeof(struct pollfd))) == NULL)
+                               fatal("realloc pollfd");
+                       pfd = newp;
+                       pfd_elms = PFD_PIPE_COUNT + i;
+               }
+               timeout = 240;  /* loop every 240s at least */
+               bzero(pfd, sizeof(struct pollfd) * pfd_elms);
+
+               set_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main);
+               set_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde);
+
+               now = getmonotime();
+               i = PFD_PIPE_COUNT;
+               i += rtr_poll_events(pfd + i, pfd_elms - i, &timeout);
+
+               if (poll(pfd, i, timeout * 1000) == -1) {
+                       if (errno != EINTR)
+                               fatal("poll error");
+                       continue;
+               }
+
+               if (handle_pollfd(&pfd[PFD_PIPE_MAIN], ibuf_main) == -1)
+                       fatalx("Lost connection to parent");
+               else
+                       rtr_dispatch_imsg_parent(ibuf_main);
+
+               if (handle_pollfd(&pfd[PFD_PIPE_RDE], ibuf_rde) == -1) {
+                       log_warnx("ROA: Lost connection to RDE");
+                       msgbuf_clear(&ibuf_rde->w);
+                       free(ibuf_rde);
+                       ibuf_rde = NULL;
+               } else
+                       rtr_dispatch_imsg_rde(ibuf_rde);
+
+               now = getmonotime();
+               i = PFD_PIPE_COUNT;
+               rtr_check_events(pfd + i, pfd_elms - i);
+       }
+
+       rtr_shutdown();
+
+       free_config(conf);
+       free(pfd);
+
+       /* close pipes */
+       if (ibuf_rde) {
+               msgbuf_clear(&ibuf_rde->w);
+               close(ibuf_rde->fd);
+               free(ibuf_rde);
+       }
+       msgbuf_clear(&ibuf_main->w);
+       close(ibuf_main->fd);
+       free(ibuf_main);
+
+       log_info("rtr engine exiting");
+       exit(0);
+}
+
+static void
+rtr_dispatch_imsg_parent(struct imsgbuf *ibuf)
+{
+       struct imsg     imsg;
+       struct roa      *roa;
+       struct rtr_session *rs;
+       int             n, fd;
+
+       while (ibuf) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("%s: imsg_get error", __func__);
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_SOCKET_CONN_RTR:
+                       if ((fd = imsg.fd) == -1) {
+                               log_warnx("expected to receive imsg fd "
+                                   "but didn't receive any");
+                               break;
+                       }
+                       if (ibuf_rde) {
+                               log_warnx("Unexpected imsg ctl "
+                                   "connection to RDE received");
+                               msgbuf_clear(&ibuf_rde->w);
+                               free(ibuf_rde);
+                       }
+                       if ((ibuf_rde = malloc(sizeof(struct imsgbuf))) == NULL)
+                               fatal(NULL);
+                       imsg_init(ibuf_rde, fd);
+                       break;
+               case IMSG_SOCKET_CONN:
+                       if ((fd = imsg.fd) == -1) {
+                               log_warnx("expected to receive imsg fd "
+                                   "but didn't receive any");
+                               break;
+                       }
+                       if ((rs = rtr_get(imsg.hdr.peerid)) == NULL) {
+                               log_warnx("IMSG_SOCKET_CONN: unknown rtr id %d",
+                                   imsg.hdr.peerid);
+                               break;
+                       }
+                       rtr_open(rs, fd);
+                       break;
+               case IMSG_RECONF_CONF:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(struct bgpd_config))
+                               fatalx("IMSG_RECONF_CONF bad len");
+                       nconf = new_config();
+                       copy_config(nconf, imsg.data);
+                       rtr_config_prep();
+                       break;
+               case IMSG_RECONF_ROA_ITEM:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(*roa))
+                               fatalx("IMSG_RECONF_ROA_ITEM bad len");
+                       roa_insert(&nconf->roa, imsg.data);
+                       break;
+               case IMSG_RECONF_RTR_CONFIG:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE != PEER_DESCR_LEN)
+                               fatalx("IMSG_RECONF_RTR_CONFIG bad len");
+                       rs = rtr_get(imsg.hdr.peerid);
+                       if (rs == NULL)
+                               rtr_new(imsg.hdr.peerid, imsg.data);
+                       else
+                               rtr_config_keep(rs);
+                       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");
+                       copy_config(conf, nconf);
+                       /* switch the roa, first remove the old one */
+                       free_roatree(&conf->roa);
+                       /* then move the RB tree root */
+                       RB_ROOT(&conf->roa) = RB_ROOT(&nconf->roa);
+                       RB_ROOT(&nconf->roa) = NULL;
+                       /* finally merge the rtr session */
+                       rtr_config_merge();
+                       rtr_recalc();
+                       log_info("ROA engine reconfigured");
+                       imsg_compose(ibuf_main, IMSG_RECONF_DONE, 0, 0,
+                           -1, NULL, 0);
+                       free_config(nconf);
+                       nconf = NULL;
+                       break;
+               case IMSG_CTL_SHOW_RTR:
+                       if ((rs = rtr_get(imsg.hdr.peerid)) == NULL) {
+                               log_warnx("IMSG_CTL_SHOW_RTR: "
+                                   "unknown rtr id %d", imsg.hdr.peerid);
+                               break;
+                       }
+                       rtr_show(rs, imsg.hdr.pid);
+                       break;
+               case IMSG_CTL_END:
+                       imsg_compose(ibuf_main, IMSG_CTL_END, 0, imsg.hdr.pid,
+                           -1, NULL, 0);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+}
+
+static void
+rtr_dispatch_imsg_rde(struct imsgbuf *ibuf)
+{
+       struct imsg     imsg;
+       int             n;
+
+       while (ibuf) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("%s: imsg_get error", __func__);
+               if (n == 0)
+                       break;
+
+               /* NOTHING */
+
+               imsg_free(&imsg);
+       }
+}
+
+void
+rtr_imsg_compose(int type, uint32_t id, pid_t pid, void *data, size_t datalen)
+{
+       imsg_compose(ibuf_main, type, id, pid, -1, data, datalen);
+}
+
+/*
+ * Merge all RPKI ROA trees into one as one big union.
+ * Simply try to add all roa entries into a new RB tree.
+ * This could be made a fair bit faster but for now this is good enough.
+ */
+void
+rtr_recalc(void)
+{
+       struct roa_tree rt;
+       struct roa *roa, *nr;
+
+       RB_INIT(&rt);
+
+       RB_FOREACH(roa, roa_tree, &conf->roa)
+               roa_insert(&rt, roa);
+       rtr_roa_merge(&rt);
+
+       imsg_compose(ibuf_rde, IMSG_RECONF_ROA_SET, 0, 0, -1, NULL, 0);
+       RB_FOREACH_SAFE(roa, roa_tree, &rt, nr) {
+               RB_REMOVE(roa_tree, &rt, roa);
+               imsg_compose(ibuf_rde, IMSG_RECONF_ROA_ITEM, 0, 0, -1,
+                   roa, sizeof(*roa));
+               free(roa);
+       }
+       imsg_compose(ibuf_rde, IMSG_RECONF_DONE, 0, 0, -1, NULL, 0);
+}
Index: bgpd/rtr_proto.c
===================================================================
RCS file: bgpd/rtr_proto.c
diff -N bgpd/rtr_proto.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ bgpd/rtr_proto.c    26 Jan 2021 08:20:22 -0000
@@ -0,0 +1,1157 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Claudio Jeker <[email protected]>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include <sys/tree.h>
+#include <errno.h>
+#include <stdint.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bgpd.h"
+#include "session.h"
+#include "log.h"
+
+struct rtr_header {
+       uint8_t         version;
+       uint8_t         type;
+       uint16_t        session_id; /* or error code */
+       uint32_t        length;
+};
+
+#define RTR_MAX_LEN            2048
+#define RTR_DEFAULT_REFRESH    3600
+#define RTR_DEFAULT_RETRY      600
+#define RTR_DEFAULT_EXPIRE     7200
+
+enum rtr_pdu_type {
+       SERIAL_NOTIFY = 0,
+       SERIAL_QUERY,
+       RESET_QUERY,
+       CACHE_RESPONSE,
+       IPV4_PREFIX,
+       IPV6_PREFIX = 6,
+       END_OF_DATA = 7,
+       CACHE_RESET = 8,
+       ROUTER_KEY = 9,
+       ERROR_REPORT = 10,
+};
+
+#define FLAG_ANNOUNCE  0x1
+#define FLAG_MASK      FLAG_ANNOUNCE
+struct rtr_ipv4 {
+       uint8_t         flags;
+       uint8_t         prefixlen;
+       uint8_t         maxlen;
+       uint8_t         zero;
+       uint32_t        prefix;
+       uint32_t        asnum;
+};
+
+struct rtr_ipv6 {
+       uint8_t         flags;
+       uint8_t         prefixlen;
+       uint8_t         maxlen;
+       uint8_t         zero;
+       uint32_t        prefix[4];
+       uint32_t        asnum;
+};
+
+struct rtr_endofdata {
+       uint32_t        serial;
+       uint32_t        refresh;
+       uint32_t        retry;
+       uint32_t        expire;
+};
+
+enum rtr_event {
+       RTR_EVNT_START,
+       RTR_EVNT_CON_OPEN,
+       RTR_EVNT_CON_CLOSED,
+       RTR_EVNT_TIMER_REFRESH,
+       RTR_EVNT_TIMER_RETRY,
+       RTR_EVNT_TIMER_EXPIRE,
+       RTR_EVNT_SEND_ERROR,
+       RTR_EVNT_SERIAL_NOTIFY,
+       RTR_EVNT_CACHE_RESPONSE,
+       RTR_EVNT_END_OF_DATA,
+       RTR_EVNT_CACHE_RESET,
+       RTR_EVNT_NO_DATA,
+};
+
+static const char *rtr_eventnames[] = {
+       "start",
+       "connection open",
+       "connection closed",
+       "refresh timer expired",
+       "retry timer expired",
+       "expire timer expired",
+       "sent error",
+       "serial notify received",
+       "cache response received",
+       "end of data received",
+       "cache reset received",
+       "no data"
+};
+
+enum rtr_state {
+       RTR_STATE_CLOSED,
+       RTR_STATE_ERROR,
+       RTR_STATE_IDLE,
+       RTR_STATE_ACTIVE,
+};
+
+static const char *rtr_statenames[] = {
+       "closed",
+       "error",
+       "idle",
+       "active"
+};
+
+struct rtr_session {
+       TAILQ_ENTRY(rtr_session)        entry;
+       char                            descr[PEER_DESCR_LEN];
+       struct roa_tree                 roa_set;
+       struct ibuf_read                r;
+       struct msgbuf                   w;
+       struct timer_head               timers;
+       uint32_t                        id;             /* rtr_config id */
+       uint32_t                        serial;
+       uint32_t                        refresh;
+       uint32_t                        retry;
+       uint32_t                        expire;
+       int                             session_id;
+       int                             fd;
+       enum rtr_state                  state;
+       enum reconf_action              reconf_action;
+       enum rtr_error                  last_sent_error;
+       enum rtr_error                  last_recv_error;
+       char                            last_sent_msg[REASON_LEN];
+       char                            last_recv_msg[REASON_LEN];
+};
+
+TAILQ_HEAD(, rtr_session) rtrs = TAILQ_HEAD_INITIALIZER(rtrs);
+
+static void    rtr_fsm(struct rtr_session *, enum rtr_event);
+
+static const char *
+log_rtr(struct rtr_session *rs)
+{
+       return rs->descr;
+}
+
+static const char *
+log_rtr_type(enum rtr_pdu_type type)
+{
+       static char buf[20];
+
+       switch (type) {
+       case SERIAL_NOTIFY:
+               return "serial notify";
+       case SERIAL_QUERY:
+               return "serial query";
+       case RESET_QUERY:
+               return "reset query";
+       case CACHE_RESPONSE:
+               return "cache response";
+       case IPV4_PREFIX:
+               return "IPv4 prefix";
+       case IPV6_PREFIX:
+               return "IPv6 prefix";
+       case END_OF_DATA:
+               return "end of data";
+       case CACHE_RESET:
+               return "cache reset";
+       case ROUTER_KEY:
+               return "router key";
+       case ERROR_REPORT:
+               return "error report";
+       default:
+               snprintf(buf, sizeof(buf), "unknown %u", type);
+               return buf;
+       }
+};
+
+static struct ibuf *
+rtr_newmsg(enum rtr_pdu_type type, uint32_t len, u_int16_t session_id)
+{
+       struct ibuf *buf;
+       struct rtr_header rh;
+
+       if (len > RTR_MAX_LEN) {
+               errno = ERANGE;
+               return NULL;
+       }
+       len += sizeof(rh);
+       if ((buf = ibuf_open(len)) == NULL)
+               return NULL;
+
+       memset(&rh, 0, sizeof(rh));
+       rh.version = 1;
+       rh.type = type;
+       rh.session_id = htons(session_id);
+       rh.length = htonl(len);
+
+       /* can not fail with fixed buffers */
+       ibuf_add(buf, &rh, sizeof(rh));
+       return buf;
+}
+
+/*
+ * Try to send an error PDU to cache, put connection into error
+ * state.
+ */
+static void
+rtr_send_error(struct rtr_session *rs, enum rtr_error err, char *msg,
+    void *pdu, size_t len)
+{
+       struct ibuf *buf;
+       size_t mlen = 0;
+       uint32_t hdrlen;
+
+       rs->last_sent_error = err;
+       if (msg) {
+               mlen = strlen(msg);
+               strlcpy(rs->last_sent_msg, msg, sizeof(rs->last_sent_msg));
+       } else
+               memset(rs->last_sent_msg, 0, sizeof(rs->last_sent_msg));
+
+       rtr_fsm(rs, RTR_EVNT_SEND_ERROR);
+
+       buf = rtr_newmsg(ERROR_REPORT, 2 * sizeof(hdrlen) + len + mlen, err);
+       if (buf == NULL) {
+               log_warn("rtr %s: send error report", log_rtr(rs));
+               return;
+       }
+
+       /* can not fail with fixed buffers */
+       hdrlen = ntohl(len);
+       ibuf_add(buf, &hdrlen, sizeof(hdrlen));
+       ibuf_add(buf, pdu, len);
+       hdrlen = ntohl(mlen);
+       ibuf_add(buf, &hdrlen, sizeof(hdrlen));
+       ibuf_add(buf, msg, mlen);
+       ibuf_close(&rs->w, buf);
+
+       log_warnx("%s: sending error report[%u] %s", log_rtr(rs), err,
+           msg ? msg : "");
+}
+
+static void
+rtr_reset_query(struct rtr_session *rs)
+{
+       struct ibuf *buf;
+
+       buf = rtr_newmsg(RESET_QUERY, 0, 0);
+       if (buf == NULL) {
+               log_warn("rtr %s: send reset query", log_rtr(rs));
+               rtr_send_error(rs, INTERNAL_ERROR, "out of memory", NULL, 0);
+               return;
+       }
+       ibuf_close(&rs->w, buf);
+}
+
+static void
+rtr_serial_query(struct rtr_session *rs)
+{
+       struct ibuf *buf;
+       uint32_t s;
+
+       buf = rtr_newmsg(SERIAL_QUERY, sizeof(s), rs->session_id);
+       if (buf == NULL) {
+               log_warn("rtr %s: send serial query", log_rtr(rs));
+               rtr_send_error(rs, INTERNAL_ERROR, "out of memory", NULL, 0);
+               return;
+       }
+
+       /* can not fail with fixed buffers */
+       s = htonl(rs->serial);
+       ibuf_add(buf, &s, sizeof(s));
+       ibuf_close(&rs->w, buf);
+}
+
+/*
+ * Validate the common rtr header (first 8 bytes) including the
+ * included length field.
+ * Returns -1 on failure. On success msgtype and msglen are set
+ * and the function return 0.
+ */
+static int
+rtr_parse_header(struct rtr_session *rs, void *buf,
+    size_t *msglen, enum rtr_pdu_type *msgtype)
+{
+       struct rtr_header rh;
+       uint32_t len = 16;      /* default for ERROR_REPORT */
+       int session_id;
+
+       memcpy(&rh, buf, sizeof(rh));
+
+       if (rh.version != 1) {
+               log_warnx("rtr %s: received message with unsupported version",
+                   log_rtr(rs));
+               rtr_send_error(rs, UNEXP_PROTOCOL_VERS, NULL, &rh, sizeof(rh));
+               return -1;
+       }
+
+       *msgtype = rh.type;
+       *msglen = ntohl(rh.length);
+
+       switch (*msgtype) {
+       case SERIAL_NOTIFY:
+               session_id = rs->session_id;
+               len = 12;
+               break;
+       case CACHE_RESPONSE:
+               /* set session_id if not yet happened */
+               if (rs->session_id == -1)
+                       rs->session_id = ntohs(rh.session_id);
+               session_id = rs->session_id;
+               len = 8;
+               break;
+       case IPV4_PREFIX:
+               session_id = 0;
+               len = 20;
+               break;
+       case IPV6_PREFIX:
+               session_id = 0;
+               len = 32;
+               break;
+       case END_OF_DATA:
+               session_id = rs->session_id;
+               len = 24;
+               break;
+       case CACHE_RESET:
+               session_id = 0;
+               len = 8;
+               break;
+       case ROUTER_KEY:
+               len = 36;       /* XXX probably too small, but we ignore it */
+               /* FALLTHROUGH */
+       case ERROR_REPORT:
+               if (*msglen > RTR_MAX_LEN) {
+                       log_warnx("rtr %s: received %s: msg too big: %zu byte",
+                           log_rtr(rs), log_rtr_type(*msgtype), *msglen);
+                       rtr_send_error(rs, CORRUPT_DATA, "too big",
+                           &rh, sizeof(rh));
+                       return -1;
+               }
+               if (*msglen < len) {
+                       log_warnx("rtr %s: received %s: msg too small: "
+                           "%zu byte", log_rtr(rs), log_rtr_type(*msgtype),
+                           *msglen);
+                       rtr_send_error(rs, CORRUPT_DATA, "too small",
+                           &rh, sizeof(rh));
+                       return -1;
+               }
+               /*
+                * session_id check ommitted since ROUTER_KEY and ERROR_REPORT
+                * use the field for different things.
+                */
+               return 0;
+       default:
+               log_warnx("rtr %s: received unknown message: type %u",
+                   log_rtr(rs), *msgtype);
+               rtr_send_error(rs, UNSUPP_PDU_TYPE, NULL, &rh, sizeof(rh));
+               return -1;
+       }
+
+       if (len != *msglen) {
+               log_warnx("rtr %s: received %s: illegal len: %zu byte not %u",
+                   log_rtr(rs), log_rtr_type(*msgtype), *msglen, len);
+               rtr_send_error(rs, CORRUPT_DATA, "bad length",
+                   &rh, sizeof(rh));
+               return -1;
+       }
+
+       if (session_id != ntohs(rh.session_id)) {
+               /* ignore SERIAL_NOTIFY during startup */
+               if (rs->session_id == -1 && *msgtype == SERIAL_NOTIFY)
+                       return 0;
+
+               log_warnx("rtr %s: received %s: bad session_id: %d != %d",
+                   log_rtr(rs), log_rtr_type(*msgtype), ntohs(rh.session_id),
+                   session_id);
+               rtr_send_error(rs, CORRUPT_DATA, "bad session_id",
+                   &rh, sizeof(rh));
+               return -1;
+       }
+
+       return 0;
+}
+
+static int
+rtr_parse_notify(struct rtr_session *rs, uint8_t *buf, size_t len)
+{
+       if (rs->state == RTR_STATE_ACTIVE) {
+               log_warnx("rtr %s: received %s: while active (ignored)",
+                   log_rtr(rs), log_rtr_type(SERIAL_NOTIFY));
+               return 0;
+       }
+
+       rtr_fsm(rs, RTR_EVNT_SERIAL_NOTIFY);
+       return 0;
+}
+
+static int
+rtr_parse_cache_response(struct rtr_session *rs, uint8_t *buf, size_t len)
+{
+       if (rs->state != RTR_STATE_IDLE) {
+               log_warnx("rtr %s: received %s: out of context",
+                   log_rtr(rs), log_rtr_type(CACHE_RESPONSE));
+               return -1;
+       }
+
+       rtr_fsm(rs, RTR_EVNT_CACHE_RESPONSE);
+       return 0;
+}
+
+static int
+rtr_parse_ipv4_prefix(struct rtr_session *rs, uint8_t *buf, size_t len)
+{
+       struct rtr_ipv4 ip4;
+       struct roa *roa;
+
+       if (len != sizeof(struct rtr_header) + sizeof(ip4)) {
+               log_warnx("rtr %s: received %s: bad pdu len",
+                   log_rtr(rs), log_rtr_type(IPV4_PREFIX));
+               rtr_send_error(rs, CORRUPT_DATA, "bad len", buf, len);
+               return -1;
+       }
+
+       if (rs->state != RTR_STATE_ACTIVE) {
+               log_warnx("rtr %s: received %s: out of context",
+                   log_rtr(rs), log_rtr_type(IPV4_PREFIX));
+               rtr_send_error(rs, CORRUPT_DATA, NULL, buf, len);
+               return -1;
+       }
+
+       memcpy(&ip4, buf + sizeof(struct rtr_header), sizeof(ip4));
+
+       if ((roa = calloc(1, sizeof(*roa))) == NULL) {
+               log_warn("rtr %s: received %s",
+                   log_rtr(rs), log_rtr_type(IPV4_PREFIX));
+               rtr_send_error(rs, INTERNAL_ERROR, "out of memory", NULL, 0);
+               return -1;
+       }
+       roa->aid = AID_INET;
+       roa->prefixlen = ip4.prefixlen;
+       roa->maxlen = ip4.maxlen;
+       roa->asnum = ntohl(ip4.asnum);
+       roa->prefix.inet.s_addr = ip4.prefix;
+
+       if (ip4.flags & FLAG_ANNOUNCE) {
+               if (RB_INSERT(roa_tree, &rs->roa_set, roa) != NULL) {
+                       log_warnx("rtr %s: received %s: duplicate announcement",
+                           log_rtr(rs), log_rtr_type(IPV4_PREFIX));
+                       rtr_send_error(rs, DUP_REC_RECV, NULL, buf, len);
+                       free(roa);
+                       return -1;
+               }
+       } else {
+               struct roa *r;
+
+               r = RB_FIND(roa_tree, &rs->roa_set, roa);
+               if (r == NULL) {
+                       log_warnx("rtr %s: received %s: unknown withdrawl",
+                           log_rtr(rs), log_rtr_type(IPV4_PREFIX));
+                       rtr_send_error(rs, UNK_REC_WDRAWL, NULL, buf, len);
+                       free(roa);
+                       return -1;
+               }
+               RB_REMOVE(roa_tree, &rs->roa_set, r);
+               free(r);
+               free(roa);
+       }
+
+       return 0;
+}
+
+static int
+rtr_parse_ipv6_prefix(struct rtr_session *rs, uint8_t *buf, size_t len)
+{
+       struct rtr_ipv6 ip6;
+       struct roa *roa;
+
+       if (len != sizeof(struct rtr_header) + sizeof(ip6)) {
+               log_warnx("rtr %s: received %s: bad pdu len",
+                   log_rtr(rs), log_rtr_type(IPV6_PREFIX));
+               rtr_send_error(rs, CORRUPT_DATA, "bad len", buf, len);
+               return -1;
+       }
+
+       if (rs->state != RTR_STATE_ACTIVE) {
+               log_warnx("rtr %s: received %s: out of context",
+                   log_rtr(rs), log_rtr_type(IPV6_PREFIX));
+               rtr_send_error(rs, CORRUPT_DATA, NULL, buf, len);
+               return -1;
+       }
+
+       memcpy(&ip6, buf + sizeof(struct rtr_header), sizeof(ip6));
+
+       if ((roa = calloc(1, sizeof(*roa))) == NULL) {
+               log_warn("rtr %s: received %s",
+                   log_rtr(rs), log_rtr_type(IPV6_PREFIX));
+               rtr_send_error(rs, INTERNAL_ERROR, "out of memory", NULL, 0);
+               return -1;
+       }
+       roa->aid = AID_INET6;
+       roa->prefixlen = ip6.prefixlen;
+       roa->maxlen = ip6.maxlen;
+       roa->asnum = ntohl(ip6.asnum);
+       memcpy(&roa->prefix.inet6, ip6.prefix, sizeof(roa->prefix.inet6));
+
+       if (ip6.flags & FLAG_ANNOUNCE) {
+               if (RB_INSERT(roa_tree, &rs->roa_set, roa) != NULL) {
+                       log_warnx("rtr %s: received %s: duplicate announcement",
+                           log_rtr(rs), log_rtr_type(IPV6_PREFIX));
+                       rtr_send_error(rs, DUP_REC_RECV, NULL, buf, len);
+                       free(roa);
+                       return -1;
+               }
+       } else {
+               struct roa *r;
+
+               r = RB_FIND(roa_tree, &rs->roa_set, roa);
+               if (r == NULL) {
+                       log_warnx("rtr %s: received %s: unknown withdrawl",
+                           log_rtr(rs), log_rtr_type(IPV6_PREFIX));
+                       rtr_send_error(rs, UNK_REC_WDRAWL, NULL, buf, len);
+                       free(roa);
+                       return -1;
+               }
+               RB_REMOVE(roa_tree, &rs->roa_set, r);
+               free(r);
+               free(roa);
+       }
+       return 0;
+}
+
+static int
+rtr_parse_end_of_data(struct rtr_session *rs, uint8_t *buf, size_t len)
+{
+       struct rtr_endofdata eod;
+       uint32_t t;
+
+       buf += sizeof(struct rtr_header);
+       len -= sizeof(struct rtr_header);
+
+       if (len != sizeof(eod)) {
+               log_warnx("rtr %s: received %s: bad pdu len",
+                   log_rtr(rs), log_rtr_type(END_OF_DATA));
+               return -1;
+       }
+
+       memcpy(&eod, buf, sizeof(eod));
+
+       if (rs->state != RTR_STATE_ACTIVE) {
+               log_warnx("rtr %s: received %s: out of context",
+                   log_rtr(rs), log_rtr_type(END_OF_DATA));
+               return -1;
+       }
+
+       rs->serial = ntohl(eod.serial);
+       /* validate timer values to be in the right range */
+       t = ntohl(eod.refresh);
+       if (t < 1 || t > 86400)
+               goto bad;
+       rs->refresh = t;
+       t = ntohl(eod.retry);
+       if (t < 1 || t > 7200)
+               goto bad;
+       rs->retry = t;
+       t = ntohl(eod.expire);
+       if (t < 600 || t > 172800)
+               goto bad;
+       if (t <= rs->retry || t <= rs->refresh)
+               goto bad;
+       rs->expire = t;
+
+       rtr_fsm(rs, RTR_EVNT_END_OF_DATA);
+       return 0;
+
+bad:
+       log_warnx("rtr %s: received %s: bad timeout values",
+           log_rtr(rs), log_rtr_type(END_OF_DATA));
+       return -1;
+}
+
+static int
+rtr_parse_cache_reset(struct rtr_session *rs, uint8_t *buf, size_t len)
+{
+       if (rs->state != RTR_STATE_IDLE) {
+               log_warnx("rtr %s: received %s: out of context",
+                   log_rtr(rs), log_rtr_type(CACHE_RESET));
+               return -1;
+       }
+
+       rtr_fsm(rs, RTR_EVNT_CACHE_RESET);
+       return 0;
+}
+
+/*
+ * Parse an Error Response message. This function behaves a bit different
+ * from other parse functions since on error the connection needs to be
+ * dropped without sending an error response back.
+ */
+static int
+rtr_parse_error(struct rtr_session *rs, uint8_t *buf, size_t len)
+{
+       struct rtr_header rh;
+       uint32_t pdu_len, msg_len;
+       uint8_t *msg;
+       char *str = NULL;
+       uint16_t errcode;
+
+       memcpy(&rh, buf, sizeof(rh));
+       buf += sizeof(struct rtr_header);
+       len -= sizeof(struct rtr_header);
+       errcode = ntohs(rh.session_id);
+
+       memcpy(&pdu_len, buf, sizeof(pdu_len));
+       pdu_len = ntohs(pdu_len);
+
+       if (len < pdu_len + sizeof(pdu_len)) {
+               log_warnx("rtr %s: received %s: bad pdu len: %u byte",
+                   log_rtr(rs), log_rtr_type(ERROR_REPORT), pdu_len);
+               rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+               return -1;
+       }
+
+       /* for now just ignore the embedded pdu */
+       buf += pdu_len + sizeof(pdu_len);
+       len -= pdu_len + sizeof(pdu_len);
+
+       memcpy(&msg_len, buf, sizeof(msg_len));
+       msg_len = ntohs(msg_len);
+
+       if (len < msg_len + sizeof(msg_len)) {
+               log_warnx("rtr %s: received %s: bad msg len: %u byte",
+                   log_rtr(rs), log_rtr_type(ERROR_REPORT), msg_len);
+               rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+               return -1;
+       }
+
+       msg = buf + sizeof(msg_len);
+       if (msg_len != 0)
+               /* optional error msg, no need to check for failure */
+               str = strndup(msg, msg_len);
+
+       log_warnx("rtr %s: received error: %s%s%s", log_rtr(rs),
+           log_rtr_error(errcode), str ? ": " : "", str ? str : "");
+
+       if (errcode == NO_DATA_AVAILABLE) {
+               rtr_fsm(rs, RTR_EVNT_NO_DATA);
+       } else {
+               rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+               rs->last_recv_error = errcode;
+               if (str)
+                       strlcpy(rs->last_recv_msg, str,
+                           sizeof(rs->last_recv_msg));
+               else
+                       memset(rs->last_recv_msg, 0,
+                           sizeof(rs->last_recv_msg));
+
+               free(str);
+               return -1;
+       }
+       free(str);
+
+       return 0;
+}
+
+/*
+ * Try to process received rtr message, it is possible that not a full
+ * message is in the buffer. In that case stop, once new data is available
+ * a retry will be done.
+ */
+static void
+rtr_process_msg(struct rtr_session *rs)
+{
+       size_t rpos, av, left;
+       void *rptr;
+       size_t msglen;
+       enum rtr_pdu_type msgtype;
+
+       rpos = 0;
+       av = rs->r.wpos;
+
+       for (;;) {
+               if (rpos + sizeof(struct rtr_header) > av)
+                       break;
+               rptr = rs->r.buf + rpos;
+               if (rtr_parse_header(rs, rptr, &msglen, &msgtype) == -1)
+                       return;
+
+               /* missing data */
+               if (rpos + msglen > av)
+                       break;
+
+               switch (msgtype) {
+               case SERIAL_NOTIFY:
+                       if (rtr_parse_notify(rs, rptr, msglen) == -1) {
+                               rtr_send_error(rs, CORRUPT_DATA, NULL,
+                                   rptr, msglen);
+                               return;
+                       }
+                       break;
+               case CACHE_RESPONSE:
+                       if (rtr_parse_cache_response(rs, rptr, msglen) == -1) {
+                               rtr_send_error(rs, CORRUPT_DATA, NULL,
+                                   rptr, msglen);
+                               return;
+                       }
+                       break;
+               case IPV4_PREFIX:
+                       if (rtr_parse_ipv4_prefix(rs, rptr, msglen) == -1) {
+                               return;
+                       }
+                       break;
+               case IPV6_PREFIX:
+                       if (rtr_parse_ipv6_prefix(rs, rptr, msglen) == -1) {
+                               return;
+                       }
+                       break;
+               case END_OF_DATA:
+                       if (rtr_parse_end_of_data(rs, rptr, msglen) == -1) {
+                               rtr_send_error(rs, CORRUPT_DATA, NULL,
+                                   rptr, msglen);
+                               return;
+                       }
+                       break;
+               case CACHE_RESET:
+                       if (rtr_parse_cache_reset(rs, rptr, msglen) == -1) {
+                               rtr_send_error(rs, CORRUPT_DATA, NULL,
+                                   rptr, msglen);
+                               return;
+                       }
+                       break;
+               case ROUTER_KEY:
+                       /* silently ignore router key */
+                       break;
+               case ERROR_REPORT:
+                       if (rtr_parse_error(rs, rptr, msglen) == -1)
+                               /* no need to send back an error */
+                               return;
+                       break;
+               default:
+                       log_warnx("rtr %s: received %s: unexpected pdu type",
+                           log_rtr(rs), log_rtr_type(msgtype));
+                       rtr_send_error(rs, INVALID_REQUEST, NULL, rptr, msglen);
+                       return;
+               }
+               rpos += msglen;
+       }
+
+       left = av - rpos;
+       memmove(&rs->r.buf, rs->r.buf + rpos, left);
+       rs->r.wpos = left;
+}
+
+/*
+ * Simple FSM for RTR sessions
+ */
+static void
+rtr_fsm(struct rtr_session *rs, enum rtr_event event)
+{
+       enum rtr_state prev_state = rs->state;
+
+       switch (event) {
+       case RTR_EVNT_CON_CLOSED:
+               if (rs->state != RTR_STATE_CLOSED) {
+                       /* flush buffers */
+                       msgbuf_clear(&rs->w);
+                       rs->r.wpos = 0;
+                       close(rs->fd);
+                       rs->fd = -1;
+               }
+               rs->state = RTR_STATE_CLOSED;
+               /* try to reopen session */
+               timer_set(&rs->timers, Timer_Rtr_Retry,
+                   arc4random_uniform(10));
+               break;
+       case RTR_EVNT_START:
+       case RTR_EVNT_TIMER_RETRY:
+               switch (rs->state) {
+               case RTR_STATE_ERROR:
+                       rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+                       return;
+               case RTR_STATE_CLOSED:
+                       timer_set(&rs->timers, Timer_Rtr_Retry, rs->retry);
+                       rtr_imsg_compose(IMSG_SOCKET_CONN, rs->id, 0, NULL, 0);
+                       return;
+               default:
+                       break;
+               }
+               /* FALLTHROUGH */
+       case RTR_EVNT_CON_OPEN:
+               timer_stop(&rs->timers, Timer_Rtr_Retry);
+               if (rs->session_id == -1)
+                       rtr_reset_query(rs);
+               else
+                       rtr_serial_query(rs);
+               break;
+       case RTR_EVNT_SERIAL_NOTIFY:
+               /* schedule a refresh after a quick wait */
+               timer_set(&rs->timers, Timer_Rtr_Refresh,
+                   arc4random_uniform(10));
+               break;
+       case RTR_EVNT_TIMER_REFRESH:
+               /* send serial query */
+               rtr_serial_query(rs);
+               break;
+       case RTR_EVNT_TIMER_EXPIRE:
+               free_roatree(&rs->roa_set);
+               rtr_recalc();
+               break;
+       case RTR_EVNT_CACHE_RESPONSE:
+               rs->state = RTR_STATE_ACTIVE;
+               timer_stop(&rs->timers, Timer_Rtr_Refresh);
+               timer_stop(&rs->timers, Timer_Rtr_Retry);
+               break;
+       case RTR_EVNT_END_OF_DATA:
+               /* start refresh and expire timers */
+               timer_set(&rs->timers, Timer_Rtr_Refresh, rs->refresh);
+               timer_set(&rs->timers, Timer_Rtr_Expire, rs->expire);
+               rs->state = RTR_STATE_IDLE;
+               rtr_recalc();
+               break;
+       case RTR_EVNT_CACHE_RESET:
+               /* reset session and retry after a quick wait */
+               rs->session_id = -1;
+               free_roatree(&rs->roa_set);
+               timer_set(&rs->timers, Timer_Rtr_Retry,
+                   arc4random_uniform(10));
+               break;
+       case RTR_EVNT_NO_DATA:
+               /* start retry timer */
+               timer_set(&rs->timers, Timer_Rtr_Retry, rs->retry);
+               /* stop refresh timer just to be sure */
+               timer_stop(&rs->timers, Timer_Rtr_Refresh);
+               break;
+       case RTR_EVNT_SEND_ERROR:
+               rs->state = RTR_STATE_ERROR;
+               /* flush receive buffer */
+               rs->r.wpos = 0;
+               break;
+       }
+
+       log_info("rtr %s: state change %s -> %s, reason: %s",
+           log_rtr(rs), rtr_statenames[prev_state], rtr_statenames[rs->state],
+           rtr_eventnames[event]);
+}
+
+/*
+ * IO handler for RTR sessions
+ */
+static void
+rtr_dispatch_msg(struct pollfd *pfd, struct rtr_session *rs)
+{
+       ssize_t n;
+       int error;
+
+       if (pfd->revents & POLLHUP) {
+               log_warnx("rtr %s: Connection closed, hangup", log_rtr(rs));
+               rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+               return;
+       }
+       if (pfd->revents & (POLLERR|POLLNVAL)) {
+               log_warnx("rtr %s: Connection closed, error", log_rtr(rs));
+               rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+               return;
+       }
+       if (pfd->revents & POLLOUT && rs->w.queued) {
+               if ((error = ibuf_write(&rs->w)) <= 0 && errno != EAGAIN) {
+                       if (error == 0)
+                               log_warnx("rtr %s: Connection closed",
+                                   log_rtr(rs));
+                       else if (error == -1)
+                               log_warn("rtr %s: write error", log_rtr(rs));
+                       rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+                       return;
+               }
+               if (rs->w.queued == 0 && rs->state == RTR_STATE_ERROR)
+                       rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+       }
+       if (pfd->revents & POLLIN) {
+               if ((n = read(rs->fd, rs->r.buf + rs->r.wpos,
+                   sizeof(rs->r.buf) - rs->r.wpos)) == -1) {
+                       if (errno != EINTR && errno != EAGAIN) {
+                               log_warn("rtr %s: read error", log_rtr(rs));
+                               rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+                       }
+                       return;
+               }
+               if (n == 0) {
+                       log_warnx("rtr %s: Connection closed", log_rtr(rs));
+                       rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+                       return;
+               }
+               rs->r.wpos += n;
+
+               /* new data arrived, try to process it */
+               rtr_process_msg(rs);
+       }
+
+}
+
+void
+rtr_check_events(struct pollfd *pfds, size_t npfds)
+{
+       struct rtr_session *rs;
+       struct timer *t;
+       time_t now;
+       size_t i = 0;
+
+       for (i = 0; i < npfds; i++) {
+               if (pfds[i].revents == 0)
+                       continue;
+               TAILQ_FOREACH(rs, &rtrs, entry)
+                       if (rs->fd == pfds[i].fd) {
+                               rtr_dispatch_msg(&pfds[i], rs);
+                               break;
+                       }
+               if (rs == NULL)
+                       log_warnx("%s: unknown fd in pollfds", __func__);
+       }
+
+       /* run all timers */
+       now = getmonotime();
+       TAILQ_FOREACH(rs, &rtrs, entry)
+               if ((t = timer_nextisdue(&rs->timers, now)) != NULL) {
+                       log_debug("rtr %s: %s triggered", log_rtr(rs),
+                           timernames[t->type]);
+                       /* stop timer so it does not trigger again */
+                       timer_stop(&rs->timers, t->type);
+                       switch (t->type) {
+                       case Timer_Rtr_Refresh:
+                               rtr_fsm(rs, RTR_EVNT_TIMER_REFRESH);
+                               break;
+                       case Timer_Rtr_Retry:
+                               rtr_fsm(rs, RTR_EVNT_TIMER_RETRY);
+                               break;
+                       case Timer_Rtr_Expire:
+                               rtr_fsm(rs, RTR_EVNT_TIMER_EXPIRE);
+                               break;
+                       default:
+                               fatalx("King Bula lost in time");
+                       }
+               }
+}
+
+size_t
+rtr_count(void)
+{
+       struct rtr_session *rs;
+       size_t count = 0;
+
+       TAILQ_FOREACH(rs, &rtrs, entry)
+               count++;
+       return count;
+}
+
+size_t
+rtr_poll_events(struct pollfd *pfds, size_t npfds, time_t *timeout)
+{
+       struct rtr_session *rs;
+       time_t now = getmonotime();
+       size_t i = 0;
+
+       TAILQ_FOREACH(rs, &rtrs, entry) {
+               time_t nextaction;
+               struct pollfd *pfd = pfds + i++;
+
+               if (i > npfds)
+                       fatalx("%s: too many sessions for pollfd", __func__);
+
+               if ((nextaction = timer_nextduein(&rs->timers, now)) != -1 &&
+                   nextaction < *timeout)
+                       *timeout = nextaction;
+
+               if (rs->state == RTR_STATE_CLOSED) {
+                       pfd->fd = -1;
+                       continue;
+               }
+
+               pfd->fd = rs->fd;
+               pfd->events = 0;
+
+               if (rs->w.queued)
+                       pfd->events |= POLLOUT;
+               if (rs->state >= RTR_STATE_IDLE)
+                       pfd->events |= POLLIN;
+       }
+
+       return i;
+}
+
+struct rtr_session *
+rtr_new(uint32_t id, char *descr)
+{
+       struct rtr_session *rs;
+       
+       if ((rs = calloc(1, sizeof(*rs))) == NULL)
+               fatal("RTR session %s", descr);
+
+       RB_INIT(&rs->roa_set);
+       TAILQ_INIT(&rs->timers);
+       msgbuf_init(&rs->w);
+
+       strlcpy(rs->descr, descr, sizeof(rs->descr));
+       rs->id = id;
+       rs->session_id = -1;
+       rs->refresh = RTR_DEFAULT_REFRESH;
+       rs->retry = RTR_DEFAULT_RETRY;
+       rs->expire = RTR_DEFAULT_EXPIRE;
+       rs->state = RTR_STATE_CLOSED;
+       rs->reconf_action = RECONF_REINIT;
+       rs->last_recv_error = NO_ERROR;
+       rs->last_sent_error = NO_ERROR;
+
+       /* make sure that some timer is running to abort bad sessions */
+       timer_set(&rs->timers, Timer_Rtr_Expire, rs->expire);
+
+       log_debug("rtr %s: new session, start", log_rtr(rs));
+       TAILQ_INSERT_TAIL(&rtrs, rs, entry);
+       rtr_fsm(rs, RTR_EVNT_START);
+
+       return rs;
+}
+
+struct rtr_session *
+rtr_get(uint32_t id)
+{
+       struct rtr_session *rs;
+
+       TAILQ_FOREACH(rs, &rtrs, entry)
+               if (rs->id == id)
+                       return rs;
+       return NULL;
+}
+
+void
+rtr_free(struct rtr_session *rs)
+{
+       if (rs == NULL)
+               return;
+
+       rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+       timer_remove_all(&rs->timers);
+       free_roatree(&rs->roa_set);
+       free(rs);
+}
+
+void
+rtr_open(struct rtr_session *rs, int fd)
+{
+       if (rs->state != RTR_STATE_CLOSED) {
+               log_warnx("rtr %s: bad session state", log_rtr(rs));
+               rtr_fsm(rs, RTR_EVNT_CON_CLOSED);
+       }
+
+       log_debug("rtr %s: connection opened", log_rtr(rs));
+
+       rs->fd = rs->w.fd = fd;
+       rs->state = RTR_STATE_IDLE;
+       rtr_fsm(rs, RTR_EVNT_CON_OPEN);
+}
+
+void
+rtr_config_prep(void)
+{
+       struct rtr_session *rs;
+
+       TAILQ_FOREACH(rs, &rtrs, entry)
+               rs->reconf_action = RECONF_DELETE;
+}
+
+void
+rtr_config_merge(void)
+{
+       struct rtr_session *rs, *nrs;
+
+       TAILQ_FOREACH_SAFE(rs, &rtrs, entry, nrs)
+               if (rs->reconf_action == RECONF_DELETE) {
+                       TAILQ_REMOVE(&rtrs, rs, entry);
+                       rtr_free(rs);
+               }
+}
+
+void
+rtr_config_keep(struct rtr_session *rs)
+{
+       rs->reconf_action = RECONF_KEEP;
+}
+
+void
+rtr_roa_merge(struct roa_tree *rt)
+{
+       struct rtr_session *rs;
+       struct roa *roa;
+
+       TAILQ_FOREACH(rs, &rtrs, entry) {
+               RB_FOREACH(roa, roa_tree, &rs->roa_set)
+                       roa_insert(rt, roa);
+       }
+}
+
+void
+rtr_shutdown(void)
+{
+       struct rtr_session *rs, *nrs;
+
+       TAILQ_FOREACH_SAFE(rs, &rtrs, entry, nrs)
+               rtr_free(rs);
+}
+
+void
+rtr_show(struct rtr_session *rs, pid_t pid)
+{
+       struct ctl_show_rtr msg;
+       struct ctl_timer ct;
+       u_int i;
+       time_t d;
+
+       memset(&msg, 0, sizeof(msg));
+
+       /* descr, remote_addr, local_addr and remote_port set by parent */
+       msg.serial = rs->serial;
+       msg.refresh = rs->refresh;
+       msg.retry = rs->retry;
+       msg.expire = rs->expire;
+       msg.session_id = rs->session_id;
+       msg.last_sent_error = rs->last_sent_error;
+       msg.last_recv_error = rs->last_recv_error;
+       strlcpy(msg.last_sent_msg, rs->last_sent_msg,
+           sizeof(msg.last_sent_msg));
+       strlcpy(msg.last_recv_msg, rs->last_recv_msg,
+           sizeof(msg.last_recv_msg));
+
+       /* send back imsg */
+       rtr_imsg_compose(IMSG_CTL_SHOW_RTR, rs->id, pid, &msg, sizeof(msg));
+
+       /* send back timer imsgs */
+       for (i = 1; i < Timer_Max; i++) {
+               if (!timer_running(&rs->timers, i, &d))
+                       continue;
+               ct.type = i;
+               ct.val = d;
+               rtr_imsg_compose(IMSG_CTL_SHOW_TIMER, rs->id, pid,
+                   &ct, sizeof(ct));
+       }
+}
Index: bgpd/session.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/session.c,v
retrieving revision 1.410
diff -u -p -r1.410 session.c
--- bgpd/session.c      5 Jan 2021 10:02:44 -0000       1.410
+++ bgpd/session.c      25 Jan 2021 18:28:02 -0000
@@ -2829,6 +2829,8 @@ session_dispatch_imsg(struct imsgbuf *ib
                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");
                        control_imsg_relay(&imsg);
Index: bgpd/session.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/session.h,v
retrieving revision 1.149
diff -u -p -r1.149 session.h
--- bgpd/session.h      23 Dec 2020 13:20:48 -0000      1.149
+++ bgpd/session.h      25 Jan 2021 18:13:49 -0000
@@ -186,6 +186,9 @@ enum Timer {
        Timer_IdleHoldReset,
        Timer_CarpUndemote,
        Timer_RestartTimeout,
+       Timer_Rtr_Refresh,
+       Timer_Rtr_Retry,
+       Timer_Rtr_Expire,
        Timer_Max
 };
 
@@ -281,9 +284,6 @@ void                 mrt_dump_state(struct mrt *, u_in
                     struct peer *);
 void            mrt_done(struct mrt *);
 
-/* parse.y */
-struct bgpd_config *parse_config(char *, struct peer_head *);
-
 /* pfkey.c */
 int    pfkey_read(int, struct sadb_msg *);
 int    pfkey_establish(struct peer *);
@@ -299,7 +299,30 @@ void       tcp_md5_del_listener(struct bgpd_co
 void   print_config(struct bgpd_config *, struct rib_names *);
 
 /* rde.c */
-void    rde_main(int, int);
+void   rde_main(int, int);
+
+/* rtr_proto.c */
+struct rtr_session;
+size_t                  rtr_count(void);
+void                    rtr_check_events(struct pollfd *, size_t);
+size_t                  rtr_poll_events(struct pollfd *, size_t, time_t *);
+struct rtr_session     *rtr_new(uint32_t, char *);
+struct rtr_session     *rtr_get(uint32_t);
+void                    rtr_free(struct rtr_session *);
+void                    rtr_open(struct rtr_session *, int);
+struct roa_tree                *rtr_get_roa(struct rtr_session *);
+void                    rtr_config_prep(void);
+void                    rtr_config_merge(void);
+void                    rtr_config_keep(struct rtr_session *);
+void                    rtr_roa_merge(struct roa_tree *);
+void                    rtr_shutdown(void);
+void                    rtr_show(struct rtr_session *, pid_t);
+
+/* rtr.c */
+void   roa_insert(struct roa_tree *, struct roa *);
+void   rtr_main(int, int);
+void   rtr_imsg_compose(int, uint32_t, pid_t, void *, size_t);
+void   rtr_recalc(void);
 
 /* session.c */
 RB_PROTOTYPE(peer_head, peer, entry, peer_compare);
Index: bgpd/util.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/util.c,v
retrieving revision 1.60
diff -u -p -r1.60 util.c
--- bgpd/util.c 25 Jan 2021 09:15:24 -0000      1.60
+++ bgpd/util.c 25 Jan 2021 16:33:19 -0000
@@ -162,6 +162,38 @@ log_reason(const char *communication) {
 }
 
 const char *
+log_rtr_error(enum rtr_error err)
+{
+       static char buf[20];
+
+       switch (err) {
+       case NO_ERROR:
+               return "No Error";
+       case CORRUPT_DATA:
+               return "Corrupt Data";
+       case INTERNAL_ERROR:
+               return "Internal Error";
+       case NO_DATA_AVAILABLE:
+               return "No Data Available";
+       case INVALID_REQUEST:
+               return "Invalid Request";
+       case UNSUPP_PROTOCOL_VERS:
+               return "Unsupported Protocol Version";
+       case UNSUPP_PDU_TYPE:
+               return "Unsupported PDU Type";
+       case UNK_REC_WDRAWL:
+               return "Withdrawl of Unknown Record";
+       case DUP_REC_RECV:
+               return "Duplicate Announcement Received";
+       case UNEXP_PROTOCOL_VERS:
+               return "Unexpected Protocol Version";
+       default:
+               snprintf(buf, sizeof(buf), "unknown %u", err);
+               return buf;
+       }
+}
+
+const char *
 aspath_delim(u_int8_t seg_type, int closing)
 {
        static char db[8];

Reply via email to