]> git.proxmox.com Git - mirror_frr.git/commitdiff
ldpd: copy original sources from OpenBSD (14/09/2016)
authorRenato Westphal <renato@opensourcerouting.org>
Tue, 1 Mar 2016 18:27:36 +0000 (15:27 -0300)
committerDonald Sharp <sharpd@cumulusnetworks.com>
Fri, 23 Sep 2016 13:31:05 +0000 (09:31 -0400)
Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
32 files changed:
ldpd/accept.c [new file with mode: 0644]
ldpd/address.c [new file with mode: 0644]
ldpd/adjacency.c [new file with mode: 0644]
ldpd/control.c [new file with mode: 0644]
ldpd/control.h [new file with mode: 0644]
ldpd/hello.c [new file with mode: 0644]
ldpd/init.c [new file with mode: 0644]
ldpd/interface.c [new file with mode: 0644]
ldpd/keepalive.c [new file with mode: 0644]
ldpd/l2vpn.c [new file with mode: 0644]
ldpd/labelmapping.c [new file with mode: 0644]
ldpd/lde.c [new file with mode: 0644]
ldpd/lde.h [new file with mode: 0644]
ldpd/lde_lib.c [new file with mode: 0644]
ldpd/ldp.h [new file with mode: 0644]
ldpd/ldpd.c [new file with mode: 0644]
ldpd/ldpd.h [new file with mode: 0644]
ldpd/ldpe.c [new file with mode: 0644]
ldpd/ldpe.h [new file with mode: 0644]
ldpd/log.c [new file with mode: 0644]
ldpd/log.h [new file with mode: 0644]
ldpd/neighbor.c [new file with mode: 0644]
ldpd/notification.c [new file with mode: 0644]
ldpd/packet.c [new file with mode: 0644]
ldpd/pfkey.c [new file with mode: 0644]
ldpd/socket.c [new file with mode: 0644]
ldpd/util.c [new file with mode: 0644]
lib/imsg-buffer.c [new file with mode: 0644]
lib/imsg.c [new file with mode: 0644]
lib/imsg.h [new file with mode: 0644]
lib/openbsd-queue.h [new file with mode: 0644]
lib/openbsd-tree.h [new file with mode: 0644]

diff --git a/ldpd/accept.c b/ldpd/accept.c
new file mode 100644 (file)
index 0000000..bc13ad4
--- /dev/null
@@ -0,0 +1,135 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2012 Claudio Jeker <claudio@openbsd.org>
+ *
+ * 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/types.h>
+#include <stdlib.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+struct accept_ev {
+       LIST_ENTRY(accept_ev)    entry;
+       struct event             ev;
+       void                    (*accept_cb)(int, short, void *);
+       void                    *arg;
+       int                      fd;
+};
+
+struct {
+       LIST_HEAD(, accept_ev)  queue;
+       struct event            evt;
+} accept_queue;
+
+static void    accept_arm(void);
+static void    accept_unarm(void);
+static void    accept_cb(int, short, void *);
+static void    accept_timeout(int, short, void *);
+
+void
+accept_init(void)
+{
+       LIST_INIT(&accept_queue.queue);
+       evtimer_set(&accept_queue.evt, accept_timeout, NULL);
+}
+
+int
+accept_add(int fd, void (*cb)(int, short, void *), void *arg)
+{
+       struct accept_ev        *av;
+
+       if ((av = calloc(1, sizeof(*av))) == NULL)
+               return (-1);
+       av->fd = fd;
+       av->accept_cb = cb;
+       av->arg = arg;
+       LIST_INSERT_HEAD(&accept_queue.queue, av, entry);
+
+       event_set(&av->ev, av->fd, EV_READ, accept_cb, av);
+       event_add(&av->ev, NULL);
+
+       log_debug("%s: accepting on fd %d", __func__, fd);
+
+       return (0);
+}
+
+void
+accept_del(int fd)
+{
+       struct accept_ev        *av;
+
+       LIST_FOREACH(av, &accept_queue.queue, entry)
+               if (av->fd == fd) {
+                       log_debug("%s: %d removed from queue", __func__, fd);
+                       event_del(&av->ev);
+                       LIST_REMOVE(av, entry);
+                       free(av);
+                       return;
+               }
+}
+
+void
+accept_pause(void)
+{
+       struct timeval evtpause = { 1, 0 };
+
+       log_debug(__func__);
+       accept_unarm();
+       evtimer_add(&accept_queue.evt, &evtpause);
+}
+
+void
+accept_unpause(void)
+{
+       if (evtimer_pending(&accept_queue.evt, NULL)) {
+               log_debug(__func__);
+               evtimer_del(&accept_queue.evt);
+               accept_arm();
+       }
+}
+
+static void
+accept_arm(void)
+{
+       struct accept_ev        *av;
+       LIST_FOREACH(av, &accept_queue.queue, entry)
+               event_add(&av->ev, NULL);
+}
+
+static void
+accept_unarm(void)
+{
+       struct accept_ev        *av;
+       LIST_FOREACH(av, &accept_queue.queue, entry)
+               event_del(&av->ev);
+}
+
+static void
+accept_cb(int fd, short event, void *arg)
+{
+       struct accept_ev        *av = arg;
+       event_add(&av->ev, NULL);
+       av->accept_cb(fd, event, av->arg);
+}
+
+static void
+accept_timeout(int fd, short event, void *bula)
+{
+       log_debug(__func__);
+       accept_arm();
+}
diff --git a/ldpd/address.c b/ldpd/address.c
new file mode 100644 (file)
index 0000000..5e95fcc
--- /dev/null
@@ -0,0 +1,297 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * 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/types.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "lde.h"
+#include "log.h"
+
+static void     send_address(struct nbr *, int, struct if_addr_head *,
+                   unsigned int, int);
+static int      gen_address_list_tlv(struct ibuf *, uint16_t, int,
+                   struct if_addr_head *, unsigned int);
+static void     address_list_add(struct if_addr_head *, struct if_addr *);
+static void     address_list_clr(struct if_addr_head *);
+
+static void
+send_address(struct nbr *nbr, int af, struct if_addr_head *addr_list,
+    unsigned int addr_count, int withdraw)
+{
+       struct ibuf     *buf;
+       uint16_t         msg_type;
+       uint8_t          addr_size;
+       struct if_addr  *if_addr;
+       uint16_t         size;
+       unsigned int     tlv_addr_count = 0;
+       int              err = 0;
+
+       /* nothing to send */
+       if (LIST_EMPTY(addr_list))
+               return;
+
+       if (!withdraw)
+               msg_type = MSG_TYPE_ADDR;
+       else
+               msg_type = MSG_TYPE_ADDRWITHDRAW;
+
+       switch (af) {
+       case AF_INET:
+               addr_size = sizeof(struct in_addr);
+               break;
+       case AF_INET6:
+               addr_size = sizeof(struct in6_addr);
+               break;
+       default:
+               fatalx("send_address: unknown af");
+       }
+
+       while ((if_addr = LIST_FIRST(addr_list)) != NULL) {
+               /*
+                * Send as many addresses as possible - respect the session's
+                * negotiated maximum pdu length.
+                */
+               size = LDP_HDR_SIZE + LDP_MSG_SIZE + ADDR_LIST_SIZE;
+               if (size + addr_count * addr_size <= nbr->max_pdu_len)
+                       tlv_addr_count = addr_count;
+               else
+                       tlv_addr_count = (nbr->max_pdu_len - size) / addr_size;
+               size += tlv_addr_count * addr_size;
+               addr_count -= tlv_addr_count;
+
+               if ((buf = ibuf_open(size)) == NULL)
+                       fatal(__func__);
+
+               err |= gen_ldp_hdr(buf, size);
+               size -= LDP_HDR_SIZE;
+               err |= gen_msg_hdr(buf, msg_type, size);
+               size -= LDP_MSG_SIZE;
+               err |= gen_address_list_tlv(buf, size, af, addr_list,
+                   tlv_addr_count);
+               if (err) {
+                       address_list_clr(addr_list);
+                       ibuf_free(buf);
+                       return;
+               }
+
+               while ((if_addr = LIST_FIRST(addr_list)) != NULL) {
+                       log_debug("msg-out: %s: lsr-id %s, address %s",
+                           msg_name(msg_type), inet_ntoa(nbr->id),
+                           log_addr(af, &if_addr->addr));
+
+                       LIST_REMOVE(if_addr, entry);
+                       free(if_addr);
+                       if (--tlv_addr_count == 0)
+                               break;
+               }
+
+               evbuf_enqueue(&nbr->tcp->wbuf, buf);
+       }
+
+       nbr_fsm(nbr, NBR_EVT_PDU_SENT);
+}
+
+void
+send_address_single(struct nbr *nbr, struct if_addr *if_addr, int withdraw)
+{
+       struct if_addr_head      addr_list;
+
+       LIST_INIT(&addr_list);
+       address_list_add(&addr_list, if_addr);
+       send_address(nbr, if_addr->af, &addr_list, 1, withdraw);
+}
+
+void
+send_address_all(struct nbr *nbr, int af)
+{
+       struct if_addr_head      addr_list;
+       struct if_addr          *if_addr;
+       unsigned int             addr_count = 0;
+
+       LIST_INIT(&addr_list);
+       LIST_FOREACH(if_addr, &global.addr_list, entry) {
+               if (if_addr->af != af)
+                       continue;
+
+               address_list_add(&addr_list, if_addr);
+               addr_count++;
+       }
+
+       send_address(nbr, af, &addr_list, addr_count, 0);
+}
+
+int
+recv_address(struct nbr *nbr, char *buf, uint16_t len)
+{
+       struct ldp_msg          msg;
+       uint16_t                msg_type;
+       struct address_list_tlv alt;
+       enum imsg_type          type;
+       struct lde_addr         lde_addr;
+
+       memcpy(&msg, buf, sizeof(msg));
+       buf += LDP_MSG_SIZE;
+       len -= LDP_MSG_SIZE;
+
+       /* Address List TLV */
+       if (len < ADDR_LIST_SIZE) {
+               session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type);
+               return (-1);
+       }
+
+       memcpy(&alt, buf, sizeof(alt));
+       if (ntohs(alt.length) != len - TLV_HDR_SIZE) {
+               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+               return (-1);
+       }
+       if (ntohs(alt.type) != TLV_TYPE_ADDRLIST) {
+               session_shutdown(nbr, S_UNKNOWN_TLV, msg.id, msg.type);
+               return (-1);
+       }
+       switch (ntohs(alt.family)) {
+       case AF_IPV4:
+               if (!nbr->v4_enabled)
+                       /* just ignore the message */
+                       return (0);
+               break;
+       case AF_IPV6:
+               if (!nbr->v6_enabled)
+                       /* just ignore the message */
+                       return (0);
+               break;
+       default:
+               send_notification_nbr(nbr, S_UNSUP_ADDR, msg.id, msg.type);
+               return (-1);
+       }
+       buf += sizeof(alt);
+       len -= sizeof(alt);
+
+       msg_type = ntohs(msg.type);
+       if (msg_type == MSG_TYPE_ADDR)
+               type = IMSG_ADDRESS_ADD;
+       else
+               type = IMSG_ADDRESS_DEL;
+
+       while (len > 0) {
+               switch (ntohs(alt.family)) {
+               case AF_IPV4:
+                       if (len < sizeof(struct in_addr)) {
+                               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id,
+                                   msg.type);
+                               return (-1);
+                       }
+
+                       memset(&lde_addr, 0, sizeof(lde_addr));
+                       lde_addr.af = AF_INET;
+                       memcpy(&lde_addr.addr, buf, sizeof(struct in_addr));
+
+                       buf += sizeof(struct in_addr);
+                       len -= sizeof(struct in_addr);
+                       break;
+               case AF_IPV6:
+                       if (len < sizeof(struct in6_addr)) {
+                               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id,
+                                   msg.type);
+                               return (-1);
+                       }
+
+                       memset(&lde_addr, 0, sizeof(lde_addr));
+                       lde_addr.af = AF_INET6;
+                       memcpy(&lde_addr.addr, buf, sizeof(struct in6_addr));
+
+                       buf += sizeof(struct in6_addr);
+                       len -= sizeof(struct in6_addr);
+                       break;
+               default:
+                       fatalx("recv_address: unknown af");
+               }
+
+               log_debug("msg-in: %s: lsr-id %s, address %s",
+                   msg_name(msg_type), inet_ntoa(nbr->id),
+                   log_addr(lde_addr.af, &lde_addr.addr));
+
+               ldpe_imsg_compose_lde(type, nbr->peerid, 0, &lde_addr,
+                   sizeof(lde_addr));
+       }
+
+       return (0);
+}
+
+static int
+gen_address_list_tlv(struct ibuf *buf, uint16_t size, int af,
+    struct if_addr_head *addr_list, unsigned int tlv_addr_count)
+{
+       struct address_list_tlv  alt;
+       uint16_t                 addr_size;
+       struct if_addr          *if_addr;
+       int                      err = 0;
+
+       memset(&alt, 0, sizeof(alt));
+       alt.type = TLV_TYPE_ADDRLIST;
+       alt.length = htons(size - TLV_HDR_SIZE);
+
+       switch (af) {
+       case AF_INET:
+               alt.family = htons(AF_IPV4);
+               addr_size = sizeof(struct in_addr);
+               break;
+       case AF_INET6:
+               alt.family = htons(AF_IPV6);
+               addr_size = sizeof(struct in6_addr);
+               break;
+       default:
+               fatalx("gen_address_list_tlv: unknown af");
+       }
+
+       err |= ibuf_add(buf, &alt, sizeof(alt));
+       LIST_FOREACH(if_addr, addr_list, entry) {
+               err |= ibuf_add(buf, &if_addr->addr, addr_size);
+               if (--tlv_addr_count == 0)
+                       break;
+       }
+
+       return (err);
+}
+
+static void
+address_list_add(struct if_addr_head *addr_list, struct if_addr *if_addr)
+{
+       struct if_addr          *new;
+
+       new = malloc(sizeof(*new));
+       if (new == NULL)
+               fatal(__func__);
+       *new = *if_addr;
+
+       LIST_INSERT_HEAD(addr_list, new, entry);
+}
+
+static void
+address_list_clr(struct if_addr_head *addr_list)
+{
+       struct if_addr          *if_addr;
+
+       while ((if_addr = LIST_FIRST(addr_list)) != NULL) {
+               LIST_REMOVE(if_addr, entry);
+               free(if_addr);
+       }
+}
diff --git a/ldpd/adjacency.c b/ldpd/adjacency.c
new file mode 100644 (file)
index 0000000..2667177
--- /dev/null
@@ -0,0 +1,346 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2015 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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/types.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+static void     adj_del_single(struct adj *);
+static void     adj_itimer(int, short, void *);
+static void     tnbr_del(struct tnbr *);
+static void     tnbr_hello_timer(int, short, void *);
+static void     tnbr_start_hello_timer(struct tnbr *);
+static void     tnbr_stop_hello_timer(struct tnbr *);
+
+struct adj *
+adj_new(struct in_addr lsr_id, struct hello_source *source,
+    union ldpd_addr *addr)
+{
+       struct adj      *adj;
+
+       log_debug("%s: lsr-id %s, %s", __func__, inet_ntoa(lsr_id),
+           log_hello_src(source));
+
+       if ((adj = calloc(1, sizeof(*adj))) == NULL)
+               fatal(__func__);
+
+       adj->lsr_id = lsr_id;
+       adj->nbr = NULL;
+       adj->source = *source;
+       adj->trans_addr = *addr;
+
+       evtimer_set(&adj->inactivity_timer, adj_itimer, adj);
+
+       LIST_INSERT_HEAD(&global.adj_list, adj, global_entry);
+
+       switch (source->type) {
+       case HELLO_LINK:
+               LIST_INSERT_HEAD(&source->link.ia->adj_list, adj, ia_entry);
+               break;
+       case HELLO_TARGETED:
+               source->target->adj = adj;
+               break;
+       }
+
+       return (adj);
+}
+
+static void
+adj_del_single(struct adj *adj)
+{
+       log_debug("%s: lsr-id %s, %s (%s)", __func__, inet_ntoa(adj->lsr_id),
+           log_hello_src(&adj->source), af_name(adj_get_af(adj)));
+
+       adj_stop_itimer(adj);
+
+       LIST_REMOVE(adj, global_entry);
+       if (adj->nbr)
+               LIST_REMOVE(adj, nbr_entry);
+       switch (adj->source.type) {
+       case HELLO_LINK:
+               LIST_REMOVE(adj, ia_entry);
+               break;
+       case HELLO_TARGETED:
+               adj->source.target->adj = NULL;
+               break;
+       }
+
+       free(adj);
+}
+
+void
+adj_del(struct adj *adj, uint32_t notif_status)
+{
+       struct nbr      *nbr = adj->nbr;
+       struct adj      *atmp;
+
+       adj_del_single(adj);
+
+       /*
+        * If the neighbor still exists but none of its remaining
+        * adjacencies (if any) are from the preferred address-family,
+        * then delete it.
+        */
+       if (nbr && nbr_adj_count(nbr, nbr->af) == 0) {
+               LIST_FOREACH_SAFE(adj, &nbr->adj_list, nbr_entry, atmp)
+                       adj_del_single(adj);
+               session_shutdown(nbr, notif_status, 0, 0);
+               nbr_del(nbr);
+       }
+}
+
+struct adj *
+adj_find(struct hello_source *source)
+{
+       struct adj *adj;
+
+       LIST_FOREACH(adj, &global.adj_list, global_entry) {
+               if (adj->source.type != source->type)
+                       continue;
+
+               switch (source->type) {
+               case HELLO_LINK:
+                       if (ldp_addrcmp(source->link.ia->af,
+                           &adj->source.link.src_addr,
+                           &source->link.src_addr) == 0)
+                               return (adj);
+                       break;
+               case HELLO_TARGETED:
+                       if (adj->source.target == source->target)
+                               return (adj);
+                       break;
+               }
+       }
+
+       return (NULL);
+}
+
+int
+adj_get_af(struct adj *adj)
+{
+       switch (adj->source.type) {
+       case HELLO_LINK:
+               return (adj->source.link.ia->af);
+       case HELLO_TARGETED:
+               return (adj->source.target->af);
+       default:
+               fatalx("adj_get_af: unknown hello type");
+       }
+}
+
+/* adjacency timers */
+
+/* ARGSUSED */
+static void
+adj_itimer(int fd, short event, void *arg)
+{
+       struct adj *adj = arg;
+
+       log_debug("%s: lsr-id %s", __func__, inet_ntoa(adj->lsr_id));
+
+       if (adj->source.type == HELLO_TARGETED) {
+               if (!(adj->source.target->flags & F_TNBR_CONFIGURED) &&
+                   adj->source.target->pw_count == 0) {
+                       /* remove dynamic targeted neighbor */
+                       tnbr_del(adj->source.target);
+                       return;
+               }
+               adj->source.target->adj = NULL;
+       }
+
+       adj_del(adj, S_HOLDTIME_EXP);
+}
+
+void
+adj_start_itimer(struct adj *adj)
+{
+       struct timeval  tv;
+
+       timerclear(&tv);
+       tv.tv_sec = adj->holdtime;
+       if (evtimer_add(&adj->inactivity_timer, &tv) == -1)
+               fatal(__func__);
+}
+
+void
+adj_stop_itimer(struct adj *adj)
+{
+       if (evtimer_pending(&adj->inactivity_timer, NULL) &&
+           evtimer_del(&adj->inactivity_timer) == -1)
+               fatal(__func__);
+}
+
+/* targeted neighbors */
+
+struct tnbr *
+tnbr_new(struct ldpd_conf *xconf, int af, union ldpd_addr *addr)
+{
+       struct tnbr             *tnbr;
+
+       if ((tnbr = calloc(1, sizeof(*tnbr))) == NULL)
+               fatal(__func__);
+
+       tnbr->af = af;
+       tnbr->addr = *addr;
+       tnbr->state = TNBR_STA_DOWN;
+       tnbr->hello_holdtime = (ldp_af_conf_get(xconf, af))->thello_holdtime;
+       tnbr->hello_interval = (ldp_af_conf_get(xconf, af))->thello_interval;
+
+       return (tnbr);
+}
+
+static void
+tnbr_del(struct tnbr *tnbr)
+{
+       tnbr_stop_hello_timer(tnbr);
+       if (tnbr->adj)
+               adj_del(tnbr->adj, S_SHUTDOWN);
+       LIST_REMOVE(tnbr, entry);
+       free(tnbr);
+}
+
+struct tnbr *
+tnbr_find(struct ldpd_conf *xconf, int af, union ldpd_addr *addr)
+{
+       struct tnbr *tnbr;
+
+       LIST_FOREACH(tnbr, &xconf->tnbr_list, entry)
+               if (af == tnbr->af &&
+                   ldp_addrcmp(af, addr, &tnbr->addr) == 0)
+                       return (tnbr);
+
+       return (NULL);
+}
+
+struct tnbr *
+tnbr_check(struct tnbr *tnbr)
+{
+       if (!(tnbr->flags & (F_TNBR_CONFIGURED|F_TNBR_DYNAMIC)) &&
+           tnbr->pw_count == 0) {
+               tnbr_del(tnbr);
+               return (NULL);
+       }
+
+       return (tnbr);
+}
+
+void
+tnbr_update(struct tnbr *tnbr)
+{
+       int                      socket_ok, rtr_id_ok;
+
+       if ((ldp_af_global_get(&global, tnbr->af))->ldp_edisc_socket != -1)
+               socket_ok = 1;
+       else
+               socket_ok = 0;
+
+       if (leconf->rtr_id.s_addr != INADDR_ANY)
+               rtr_id_ok = 1;
+       else
+               rtr_id_ok = 0;
+
+       if (tnbr->state == TNBR_STA_DOWN) {
+               if (!socket_ok || !rtr_id_ok)
+                       return;
+
+               tnbr->state = TNBR_STA_ACTIVE;
+               send_hello(HELLO_TARGETED, NULL, tnbr);
+
+               evtimer_set(&tnbr->hello_timer, tnbr_hello_timer, tnbr);
+               tnbr_start_hello_timer(tnbr);
+       } else if (tnbr->state == TNBR_STA_ACTIVE) {
+               if (socket_ok && rtr_id_ok)
+                       return;
+
+               tnbr->state = TNBR_STA_DOWN;
+               tnbr_stop_hello_timer(tnbr);
+       }
+}
+
+void
+tnbr_update_all(int af)
+{
+       struct tnbr             *tnbr;
+
+       /* update targeted neighbors */
+       LIST_FOREACH(tnbr, &leconf->tnbr_list, entry)
+               if (tnbr->af == af || af == AF_UNSPEC)
+                       tnbr_update(tnbr);
+}
+
+/* target neighbors timers */
+
+/* ARGSUSED */
+static void
+tnbr_hello_timer(int fd, short event, void *arg)
+{
+       struct tnbr     *tnbr = arg;
+
+       send_hello(HELLO_TARGETED, NULL, tnbr);
+       tnbr_start_hello_timer(tnbr);
+}
+
+static void
+tnbr_start_hello_timer(struct tnbr *tnbr)
+{
+       struct timeval   tv;
+
+       timerclear(&tv);
+       tv.tv_sec = tnbr->hello_interval;
+       if (evtimer_add(&tnbr->hello_timer, &tv) == -1)
+               fatal(__func__);
+}
+
+static void
+tnbr_stop_hello_timer(struct tnbr *tnbr)
+{
+       if (evtimer_pending(&tnbr->hello_timer, NULL) &&
+           evtimer_del(&tnbr->hello_timer) == -1)
+               fatal(__func__);
+}
+
+struct ctl_adj *
+adj_to_ctl(struct adj *adj)
+{
+       static struct ctl_adj    actl;
+
+       actl.af = adj_get_af(adj);
+       actl.id = adj->lsr_id;
+       actl.type = adj->source.type;
+       switch (adj->source.type) {
+       case HELLO_LINK:
+               memcpy(actl.ifname, adj->source.link.ia->iface->name,
+                   sizeof(actl.ifname));
+               break;
+       case HELLO_TARGETED:
+               actl.src_addr = adj->source.target->addr;
+               break;
+       }
+       actl.holdtime = adj->holdtime;
+       actl.trans_addr = adj->trans_addr;
+
+       return (&actl);
+}
diff --git a/ldpd/control.c b/ldpd/control.c
new file mode 100644 (file)
index 0000000..42322eb
--- /dev/null
@@ -0,0 +1,308 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+#include "control.h"
+
+#define        CONTROL_BACKLOG 5
+
+static void             control_accept(int, short, void *);
+static struct ctl_conn *control_connbyfd(int);
+static struct ctl_conn *control_connbypid(pid_t);
+static void             control_close(int);
+static void             control_dispatch_imsg(int, short, void *);
+
+struct ctl_conns        ctl_conns;
+
+static int              control_fd;
+
+int
+control_init(void)
+{
+       struct sockaddr_un       sun;
+       int                      fd;
+       mode_t                   old_umask;
+
+       if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+           0)) == -1) {
+               log_warn("%s: socket", __func__);
+               return (-1);
+       }
+
+       memset(&sun, 0, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+       strlcpy(sun.sun_path, LDPD_SOCKET, sizeof(sun.sun_path));
+
+       if (unlink(LDPD_SOCKET) == -1)
+               if (errno != ENOENT) {
+                       log_warn("%s: unlink %s", __func__, LDPD_SOCKET);
+                       close(fd);
+                       return (-1);
+               }
+
+       old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+       if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+               log_warn("%s: bind: %s", __func__, LDPD_SOCKET);
+               close(fd);
+               umask(old_umask);
+               return (-1);
+       }
+       umask(old_umask);
+
+       if (chmod(LDPD_SOCKET, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
+               log_warn("%s: chmod", __func__);
+               close(fd);
+               (void)unlink(LDPD_SOCKET);
+               return (-1);
+       }
+
+       control_fd = fd;
+
+       return (0);
+}
+
+int
+control_listen(void)
+{
+       if (listen(control_fd, CONTROL_BACKLOG) == -1) {
+               log_warn("%s: listen", __func__);
+               return (-1);
+       }
+
+       return (accept_add(control_fd, control_accept, NULL));
+}
+
+void
+control_cleanup(void)
+{
+       accept_del(control_fd);
+       close(control_fd);
+       unlink(LDPD_SOCKET);
+}
+
+/* ARGSUSED */
+static void
+control_accept(int listenfd, short event, void *bula)
+{
+       int                      connfd;
+       socklen_t                len;
+       struct sockaddr_un       sun;
+       struct ctl_conn         *c;
+
+       len = sizeof(sun);
+       if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len,
+           SOCK_NONBLOCK | SOCK_CLOEXEC)) == -1) {
+               /*
+                * Pause accept if we are out of file descriptors, or
+                * libevent will haunt us here too.
+                */
+               if (errno == ENFILE || errno == EMFILE)
+                       accept_pause();
+               else if (errno != EWOULDBLOCK && errno != EINTR &&
+                   errno != ECONNABORTED)
+                       log_warn("%s: accept4", __func__);
+               return;
+       }
+
+       if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
+               log_warn(__func__);
+               close(connfd);
+               return;
+       }
+
+       imsg_init(&c->iev.ibuf, connfd);
+       c->iev.handler = control_dispatch_imsg;
+       c->iev.events = EV_READ;
+       event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
+           c->iev.handler, &c->iev);
+       event_add(&c->iev.ev, NULL);
+
+       TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+static struct ctl_conn *
+control_connbyfd(int fd)
+{
+       struct ctl_conn *c;
+
+       for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd;
+           c = TAILQ_NEXT(c, entry))
+               ;       /* nothing */
+
+       return (c);
+}
+
+static struct ctl_conn *
+control_connbypid(pid_t pid)
+{
+       struct ctl_conn *c;
+
+       for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.pid != pid;
+           c = TAILQ_NEXT(c, entry))
+               ;       /* nothing */
+
+       return (c);
+}
+
+static void
+control_close(int fd)
+{
+       struct ctl_conn *c;
+
+       if ((c = control_connbyfd(fd)) == NULL) {
+               log_warnx("%s: fd %d: not found", __func__, fd);
+               return;
+       }
+
+       msgbuf_clear(&c->iev.ibuf.w);
+       TAILQ_REMOVE(&ctl_conns, c, entry);
+
+       event_del(&c->iev.ev);
+       close(c->iev.ibuf.fd);
+       accept_unpause();
+       free(c);
+}
+
+/* ARGSUSED */
+static void
+control_dispatch_imsg(int fd, short event, void *bula)
+{
+       struct ctl_conn *c;
+       struct imsg      imsg;
+       ssize_t          n;
+       unsigned int     ifidx;
+       int              verbose;
+
+       if ((c = control_connbyfd(fd)) == NULL) {
+               log_warnx("%s: fd %d: not found", __func__, fd);
+               return;
+       }
+
+       if (event & EV_READ) {
+               if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) ||
+                   n == 0) {
+                       control_close(fd);
+                       return;
+               }
+       }
+       if (event & EV_WRITE) {
+               if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
+                       control_close(fd);
+                       return;
+               }
+       }
+
+       for (;;) {
+               if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
+                       control_close(fd);
+                       return;
+               }
+
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_CTL_FIB_COUPLE:
+               case IMSG_CTL_FIB_DECOUPLE:
+               case IMSG_CTL_RELOAD:
+                       c->iev.ibuf.pid = imsg.hdr.pid;
+                       ldpe_imsg_compose_parent(imsg.hdr.type, 0, NULL, 0);
+                       break;
+               case IMSG_CTL_KROUTE:
+               case IMSG_CTL_KROUTE_ADDR:
+               case IMSG_CTL_IFINFO:
+                       c->iev.ibuf.pid = imsg.hdr.pid;
+                       ldpe_imsg_compose_parent(imsg.hdr.type,
+                           imsg.hdr.pid, imsg.data,
+                           imsg.hdr.len - IMSG_HEADER_SIZE);
+                       break;
+               case IMSG_CTL_SHOW_INTERFACE:
+                       if (imsg.hdr.len == IMSG_HEADER_SIZE +
+                           sizeof(ifidx)) {
+                               memcpy(&ifidx, imsg.data, sizeof(ifidx));
+                               ldpe_iface_ctl(c, ifidx);
+                               imsg_compose_event(&c->iev, IMSG_CTL_END, 0,
+                                   0, -1, NULL, 0);
+                       }
+                       break;
+               case IMSG_CTL_SHOW_DISCOVERY:
+                       ldpe_adj_ctl(c);
+                       break;
+               case IMSG_CTL_SHOW_LIB:
+               case IMSG_CTL_SHOW_L2VPN_PW:
+               case IMSG_CTL_SHOW_L2VPN_BINDING:
+                       c->iev.ibuf.pid = imsg.hdr.pid;
+                       ldpe_imsg_compose_lde(imsg.hdr.type, 0, imsg.hdr.pid,
+                           imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE);
+                       break;
+               case IMSG_CTL_SHOW_NBR:
+                       ldpe_nbr_ctl(c);
+                       break;
+               case IMSG_CTL_CLEAR_NBR:
+                       if (imsg.hdr.len != IMSG_HEADER_SIZE +
+                           sizeof(struct ctl_nbr))
+                               break;
+
+                       nbr_clear_ctl(imsg.data);
+                       break;
+               case IMSG_CTL_LOG_VERBOSE:
+                       if (imsg.hdr.len != IMSG_HEADER_SIZE +
+                           sizeof(verbose))
+                               break;
+
+                       /* forward to other processes */
+                       ldpe_imsg_compose_parent(imsg.hdr.type, imsg.hdr.pid,
+                           imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE);
+                       ldpe_imsg_compose_lde(imsg.hdr.type, 0, imsg.hdr.pid,
+                           imsg.data, imsg.hdr.len - IMSG_HEADER_SIZE);
+
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+                       log_verbose(verbose);
+                       break;
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+
+       imsg_event_add(&c->iev);
+}
+
+int
+control_imsg_relay(struct imsg *imsg)
+{
+       struct ctl_conn *c;
+
+       if ((c = control_connbypid(imsg->hdr.pid)) == NULL)
+               return (0);
+
+       return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid,
+           -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE));
+}
diff --git a/ldpd/control.h b/ldpd/control.h
new file mode 100644 (file)
index 0000000..fd6e470
--- /dev/null
@@ -0,0 +1,38 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _CONTROL_H_
+#define        _CONTROL_H_
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+struct ctl_conn {
+       TAILQ_ENTRY(ctl_conn)   entry;
+       struct imsgev           iev;
+};
+TAILQ_HEAD(ctl_conns, ctl_conn);
+
+extern struct ctl_conns ctl_conns;
+
+int    control_init(void);
+int    control_listen(void);
+void   control_cleanup(void);
+int    control_imsg_relay(struct imsg *);
+
+#endif /* _CONTROL_H_ */
diff --git a/ldpd/hello.c b/ldpd/hello.c
new file mode 100644 (file)
index 0000000..d0daed3
--- /dev/null
@@ -0,0 +1,574 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * 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/types.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+static int     gen_hello_prms_tlv(struct ibuf *buf, uint16_t, uint16_t);
+static int     gen_opt4_hello_prms_tlv(struct ibuf *, uint16_t, uint32_t);
+static int     gen_opt16_hello_prms_tlv(struct ibuf *, uint16_t, uint8_t *);
+static int     gen_ds_hello_prms_tlv(struct ibuf *, uint32_t);
+static int     tlv_decode_hello_prms(char *, uint16_t, uint16_t *, uint16_t *);
+static int     tlv_decode_opt_hello_prms(char *, uint16_t, int *, int,
+                   union ldpd_addr *, uint32_t *, uint16_t *);
+
+int
+send_hello(enum hello_type type, struct iface_af *ia, struct tnbr *tnbr)
+{
+       int                      af;
+       union ldpd_addr          dst;
+       uint16_t                 size, holdtime = 0, flags = 0;
+       int                      fd = 0;
+       struct ibuf             *buf;
+       int                      err = 0;
+
+       switch (type) {
+       case HELLO_LINK:
+               af = ia->af;
+               holdtime = ia->hello_holdtime;
+               flags = 0;
+               fd = (ldp_af_global_get(&global, af))->ldp_disc_socket;
+
+               /* multicast destination address */
+               switch (af) {
+               case AF_INET:
+                       if (!(leconf->ipv4.flags & F_LDPD_AF_NO_GTSM))
+                               flags |= F_HELLO_GTSM;
+                       dst.v4 = global.mcast_addr_v4;
+                       break;
+               case AF_INET6:
+                       dst.v6 = global.mcast_addr_v6;
+                       break;
+               default:
+                       fatalx("send_hello: unknown af");
+               }
+               break;
+       case HELLO_TARGETED:
+               af = tnbr->af;
+               holdtime = tnbr->hello_holdtime;
+               flags = F_HELLO_TARGETED;
+               if ((tnbr->flags & F_TNBR_CONFIGURED) || tnbr->pw_count)
+                       flags |= F_HELLO_REQ_TARG;
+               fd = (ldp_af_global_get(&global, af))->ldp_edisc_socket;
+
+               /* unicast destination address */
+               dst = tnbr->addr;
+               break;
+       default:
+               fatalx("send_hello: unknown hello type");
+       }
+
+       /* calculate message size */
+       size = LDP_HDR_SIZE + LDP_MSG_SIZE + sizeof(struct hello_prms_tlv);
+       switch (af) {
+       case AF_INET:
+               size += sizeof(struct hello_prms_opt4_tlv);
+               break;
+       case AF_INET6:
+               size += sizeof(struct hello_prms_opt16_tlv);
+               break;
+       default:
+               fatalx("send_hello: unknown af");
+       }
+       size += sizeof(struct hello_prms_opt4_tlv);
+       if (ldp_is_dual_stack(leconf))
+               size += sizeof(struct hello_prms_opt4_tlv);
+
+       /* generate message */
+       if ((buf = ibuf_open(size)) == NULL)
+               fatal(__func__);
+
+       err |= gen_ldp_hdr(buf, size);
+       size -= LDP_HDR_SIZE;
+       err |= gen_msg_hdr(buf, MSG_TYPE_HELLO, size);
+       err |= gen_hello_prms_tlv(buf, holdtime, flags);
+
+       /*
+        * RFC 7552 - Section 6.1:
+        * "An LSR MUST include only the transport address whose address
+        * family is the same as that of the IP packet carrying the Hello
+        * message".
+        */
+       switch (af) {
+       case AF_INET:
+               err |= gen_opt4_hello_prms_tlv(buf, TLV_TYPE_IPV4TRANSADDR,
+                   leconf->ipv4.trans_addr.v4.s_addr);
+               break;
+       case AF_INET6:
+               err |= gen_opt16_hello_prms_tlv(buf, TLV_TYPE_IPV6TRANSADDR,
+                   leconf->ipv6.trans_addr.v6.s6_addr);
+               break;
+       default:
+               fatalx("send_hello: unknown af");
+       }
+
+       err |= gen_opt4_hello_prms_tlv(buf, TLV_TYPE_CONFIG,
+           htonl(global.conf_seqnum));
+
+       /*
+        * RFC 7552 - Section 6.1.1:
+        * "A Dual-stack LSR (i.e., an LSR supporting Dual-stack LDP for a peer)
+        * MUST include the Dual-Stack capability TLV in all of its LDP Hellos".
+        */
+       if (ldp_is_dual_stack(leconf))
+               err |= gen_ds_hello_prms_tlv(buf, leconf->trans_pref);
+
+       if (err) {
+               ibuf_free(buf);
+               return (-1);
+       }
+
+       send_packet(fd, af, &dst, ia, buf->buf, buf->wpos);
+       ibuf_free(buf);
+
+       return (0);
+}
+
+void
+recv_hello(struct in_addr lsr_id, struct ldp_msg *msg, int af,
+    union ldpd_addr *src, struct iface *iface, int multicast, char *buf,
+    uint16_t len)
+{
+       struct adj              *adj = NULL;
+       struct nbr              *nbr, *nbrt;
+       uint16_t                 holdtime, flags;
+       int                      tlvs_rcvd;
+       int                      ds_tlv;
+       union ldpd_addr          trans_addr;
+       uint32_t                 scope_id = 0;
+       uint32_t                 conf_seqnum;
+       uint16_t                 trans_pref;
+       int                      r;
+       struct hello_source      source;
+       struct iface_af         *ia = NULL;
+       struct tnbr             *tnbr = NULL;
+
+       r = tlv_decode_hello_prms(buf, len, &holdtime, &flags);
+       if (r == -1) {
+               log_debug("%s: lsr-id %s: failed to decode params", __func__,
+                   inet_ntoa(lsr_id));
+               return;
+       }
+       /* safety checks */
+       if (holdtime != 0 && holdtime < MIN_HOLDTIME) {
+               log_debug("%s: lsr-id %s: invalid hello holdtime (%u)",
+                   __func__, inet_ntoa(lsr_id), holdtime);
+               return;
+       }
+       if (multicast && (flags & F_HELLO_TARGETED)) {
+               log_debug("%s: lsr-id %s: multicast targeted hello", __func__,
+                   inet_ntoa(lsr_id));
+               return;
+       }
+       if (!multicast && !((flags & F_HELLO_TARGETED))) {
+               log_debug("%s: lsr-id %s: unicast link hello", __func__,
+                   inet_ntoa(lsr_id));
+               return;
+       }
+       buf += r;
+       len -= r;
+
+       r = tlv_decode_opt_hello_prms(buf, len, &tlvs_rcvd, af, &trans_addr,
+           &conf_seqnum, &trans_pref);
+       if (r == -1) {
+               log_debug("%s: lsr-id %s: failed to decode optional params",
+                   __func__, inet_ntoa(lsr_id));
+               return;
+       }
+       if (r != len) {
+               log_debug("%s: lsr-id %s: unexpected data in message",
+                   __func__, inet_ntoa(lsr_id));
+               return;
+       }
+
+       /* implicit transport address */
+       if (!(tlvs_rcvd & F_HELLO_TLV_RCVD_ADDR))
+               trans_addr = *src;
+       if (bad_addr(af, &trans_addr)) {
+               log_debug("%s: lsr-id %s: invalid transport address %s",
+                   __func__, inet_ntoa(lsr_id), log_addr(af, &trans_addr));
+               return;
+       }
+       if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&trans_addr.v6)) {
+               /*
+                * RFC 7552 - Section 6.1:
+                * "An LSR MUST use a global unicast IPv6 address in an IPv6
+                * Transport Address optional object of outgoing targeted
+                * Hellos and check for the same in incoming targeted Hellos
+                * (i.e., MUST discard the targeted Hello if it failed the
+                * check)".
+                */
+               if (flags & F_HELLO_TARGETED) {
+                       log_debug("%s: lsr-id %s: invalid targeted hello "
+                           "transport address %s", __func__, inet_ntoa(lsr_id),
+                            log_addr(af, &trans_addr));
+                       return;
+               }
+               scope_id = iface->ifindex;
+       }
+
+       memset(&source, 0, sizeof(source));
+       if (flags & F_HELLO_TARGETED) {
+               /*
+                * RFC 7552 - Section 5.2:
+               * "The link-local IPv6 addresses MUST NOT be used as the
+               * targeted LDP Hello packet's source or destination addresses".
+               */
+               if (af == AF_INET6 && IN6_IS_SCOPE_EMBED(&src->v6)) {
+                       log_debug("%s: lsr-id %s: targeted hello with "
+                           "link-local source address", __func__,
+                           inet_ntoa(lsr_id));
+                       return;
+               }
+
+               tnbr = tnbr_find(leconf, af, src);
+
+               /* remove the dynamic tnbr if the 'R' bit was cleared */
+               if (tnbr && (tnbr->flags & F_TNBR_DYNAMIC) &&
+                   !((flags & F_HELLO_REQ_TARG))) {
+                       tnbr->flags &= ~F_TNBR_DYNAMIC;
+                       tnbr = tnbr_check(tnbr);
+               }
+
+               if (!tnbr) {
+                       if (!((flags & F_HELLO_REQ_TARG) &&
+                           ((ldp_af_conf_get(leconf, af))->flags &
+                           F_LDPD_AF_THELLO_ACCEPT)))
+                               return;
+
+                       tnbr = tnbr_new(leconf, af, src);
+                       tnbr->flags |= F_TNBR_DYNAMIC;
+                       tnbr_update(tnbr);
+                       LIST_INSERT_HEAD(&leconf->tnbr_list, tnbr, entry);
+               }
+
+               source.type = HELLO_TARGETED;
+               source.target = tnbr;
+       } else {
+               ia = iface_af_get(iface, af);
+               source.type = HELLO_LINK;
+               source.link.ia = ia;
+               source.link.src_addr = *src;
+       }
+
+       adj = adj_find(&source);
+       nbr = nbr_find_ldpid(lsr_id.s_addr);
+
+       /* check dual-stack tlv */
+       ds_tlv = (tlvs_rcvd & F_HELLO_TLV_RCVD_DS) ? 1 : 0;
+       if (ds_tlv && trans_pref != leconf->trans_pref) {
+               /*
+                * RFC 7552 - Section 6.1.1:
+                * "If the Dual-Stack capability TLV is present and the remote
+                * preference does not match the local preference (or does not
+                * get recognized), then the LSR MUST discard the Hello message
+                * and log an error.
+                * If an LDP session was already in place, then the LSR MUST
+                * send a fatal Notification message with status code of
+                * 'Transport Connection Mismatch' and reset the session".
+                */
+               log_debug("%s: lsr-id %s: remote transport preference does not "
+                   "match the local preference", __func__, inet_ntoa(lsr_id));
+               if (nbr)
+                       session_shutdown(nbr, S_TRANS_MISMTCH, msg->id,
+                           msg->type);
+               if (adj)
+                       adj_del(adj, S_SHUTDOWN);
+               return;
+       }
+
+       /*
+        * Check for noncompliant dual-stack neighbor according to
+        * RFC 7552 section 6.1.1.
+        */
+       if (nbr && !ds_tlv) {
+               switch (af) {
+               case AF_INET:
+                       if (nbr_adj_count(nbr, AF_INET6) > 0) {
+                               session_shutdown(nbr, S_DS_NONCMPLNCE,
+                                   msg->id, msg->type);
+                               return;
+                       }
+                       break;
+               case AF_INET6:
+                       if (nbr_adj_count(nbr, AF_INET) > 0) {
+                               session_shutdown(nbr, S_DS_NONCMPLNCE,
+                                   msg->id, msg->type);
+                               return;
+                       }
+                       break;
+               default:
+                       fatalx("recv_hello: unknown af");
+               }
+       }
+
+       /*
+        * Protections against misconfigured networks and buggy implementations.
+        */
+       if (nbr && nbr->af == af &&
+           (ldp_addrcmp(af, &nbr->raddr, &trans_addr) ||
+           nbr->raddr_scope != scope_id)) {
+               log_warnx("%s: lsr-id %s: hello packet advertising a different "
+                   "transport address", __func__, inet_ntoa(lsr_id));
+               if (adj)
+                       adj_del(adj, S_SHUTDOWN);
+               return;
+       }
+       if (nbr == NULL) {
+               nbrt = nbr_find_addr(af, &trans_addr);
+               if (nbrt) {
+                       log_debug("%s: transport address %s is already being "
+                           "used by lsr-id %s", __func__, log_addr(af,
+                           &trans_addr), inet_ntoa(nbrt->id));
+                       if (adj)
+                               adj_del(adj, S_SHUTDOWN);
+                       return;
+               }
+       }
+
+       if (adj == NULL) {
+               adj = adj_new(lsr_id, &source, &trans_addr);
+               if (nbr) {
+                       adj->nbr = nbr;
+                       LIST_INSERT_HEAD(&nbr->adj_list, adj, nbr_entry);
+               }
+       }
+
+       /*
+        * If the hello adjacency's address-family doesn't match the local
+        * preference, then an adjacency is still created but we don't attempt
+        * to start an LDP session.
+        */
+       if (nbr == NULL && (!ds_tlv ||
+           ((trans_pref == DUAL_STACK_LDPOV4 && af == AF_INET) ||
+           (trans_pref == DUAL_STACK_LDPOV6 && af == AF_INET6))))
+               nbr = nbr_new(lsr_id, af, ds_tlv, &trans_addr, scope_id);
+
+       /* dynamic LDPv4 GTSM negotiation as per RFC 6720 */
+       if (nbr) {
+               if (flags & F_HELLO_GTSM)
+                       nbr->flags |= F_NBR_GTSM_NEGOTIATED;
+               else
+                       nbr->flags &= ~F_NBR_GTSM_NEGOTIATED;
+       }
+
+       /* update neighbor's configuration sequence number */
+       if (nbr && (tlvs_rcvd & F_HELLO_TLV_RCVD_CONF)) {
+               if (conf_seqnum > nbr->conf_seqnum &&
+                   nbr_pending_idtimer(nbr))
+                       nbr_stop_idtimer(nbr);
+               nbr->conf_seqnum = conf_seqnum;
+       }
+
+       /* always update the holdtime to properly handle runtime changes */
+       switch (source.type) {
+       case HELLO_LINK:
+               if (holdtime == 0)
+                       holdtime = LINK_DFLT_HOLDTIME;
+
+               adj->holdtime = min(ia->hello_holdtime, holdtime);
+               break;
+       case HELLO_TARGETED:
+               if (holdtime == 0)
+                       holdtime = TARGETED_DFLT_HOLDTIME;
+
+               adj->holdtime = min(tnbr->hello_holdtime, holdtime);
+       }
+       if (adj->holdtime != INFINITE_HOLDTIME)
+               adj_start_itimer(adj);
+       else
+               adj_stop_itimer(adj);
+
+       if (nbr && nbr->state == NBR_STA_PRESENT && !nbr_pending_idtimer(nbr) &&
+           nbr_session_active_role(nbr) && !nbr_pending_connect(nbr))
+               nbr_establish_connection(nbr);
+}
+
+static int
+gen_hello_prms_tlv(struct ibuf *buf, uint16_t holdtime, uint16_t flags)
+{
+       struct hello_prms_tlv   parms;
+
+       memset(&parms, 0, sizeof(parms));
+       parms.type = htons(TLV_TYPE_COMMONHELLO);
+       parms.length = htons(sizeof(parms.holdtime) + sizeof(parms.flags));
+       parms.holdtime = htons(holdtime);
+       parms.flags = htons(flags);
+
+       return (ibuf_add(buf, &parms, sizeof(parms)));
+}
+
+static int
+gen_opt4_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint32_t value)
+{
+       struct hello_prms_opt4_tlv      parms;
+
+       memset(&parms, 0, sizeof(parms));
+       parms.type = htons(type);
+       parms.length = htons(sizeof(parms.value));
+       parms.value = value;
+
+       return (ibuf_add(buf, &parms, sizeof(parms)));
+}
+
+static int
+gen_opt16_hello_prms_tlv(struct ibuf *buf, uint16_t type, uint8_t *value)
+{
+       struct hello_prms_opt16_tlv     parms;
+
+       memset(&parms, 0, sizeof(parms));
+       parms.type = htons(type);
+       parms.length = htons(sizeof(parms.value));
+       memcpy(&parms.value, value, sizeof(parms.value));
+
+       return (ibuf_add(buf, &parms, sizeof(parms)));
+}
+
+static int
+gen_ds_hello_prms_tlv(struct ibuf *buf, uint32_t value)
+{
+       if (leconf->flags & F_LDPD_DS_CISCO_INTEROP)
+               value = htonl(value);
+       else
+               value = htonl(value << 28);
+
+       return (gen_opt4_hello_prms_tlv(buf, TLV_TYPE_DUALSTACK, value));
+}
+
+static int
+tlv_decode_hello_prms(char *buf, uint16_t len, uint16_t *holdtime,
+    uint16_t *flags)
+{
+       struct hello_prms_tlv   tlv;
+
+       if (len < sizeof(tlv))
+               return (-1);
+       memcpy(&tlv, buf, sizeof(tlv));
+
+       if (tlv.type != htons(TLV_TYPE_COMMONHELLO))
+               return (-1);
+       if (ntohs(tlv.length) != sizeof(tlv) - TLV_HDR_SIZE)
+               return (-1);
+
+       *holdtime = ntohs(tlv.holdtime);
+       *flags = ntohs(tlv.flags);
+
+       return (sizeof(tlv));
+}
+
+static int
+tlv_decode_opt_hello_prms(char *buf, uint16_t len, int *tlvs_rcvd, int af,
+    union ldpd_addr *addr, uint32_t *conf_number, uint16_t *trans_pref)
+{
+       struct tlv      tlv;
+       uint16_t        tlv_len;
+       int             total = 0;
+
+       *tlvs_rcvd = 0;
+       memset(addr, 0, sizeof(*addr));
+       *conf_number = 0;
+       *trans_pref = 0;
+
+       /*
+        * RFC 7552 - Section 6.1:
+        * "An LSR SHOULD accept the Hello message that contains both IPv4 and
+        * IPv6 Transport Address optional objects but MUST use only the
+        * transport address whose address family is the same as that of the
+        * IP packet carrying the Hello message.  An LSR SHOULD accept only
+        * the first Transport Address optional object for a given address
+        * family in the received Hello message and ignore the rest if the
+        * LSR receives more than one Transport Address optional object for a
+        * given address family".
+        */
+       while (len >= sizeof(tlv)) {
+               memcpy(&tlv, buf, TLV_HDR_SIZE);
+               tlv_len = ntohs(tlv.length);
+               if (tlv_len + TLV_HDR_SIZE > len)
+                       return (-1);
+               buf += TLV_HDR_SIZE;
+               len -= TLV_HDR_SIZE;
+               total += TLV_HDR_SIZE;
+
+               switch (ntohs(tlv.type)) {
+               case TLV_TYPE_IPV4TRANSADDR:
+                       if (tlv_len != sizeof(addr->v4))
+                               return (-1);
+                       if (af != AF_INET)
+                               return (-1);
+                       if (*tlvs_rcvd & F_HELLO_TLV_RCVD_ADDR)
+                               break;
+                       memcpy(&addr->v4, buf, sizeof(addr->v4));
+                       *tlvs_rcvd |= F_HELLO_TLV_RCVD_ADDR;
+                       break;
+               case TLV_TYPE_IPV6TRANSADDR:
+                       if (tlv_len != sizeof(addr->v6))
+                               return (-1);
+                       if (af != AF_INET6)
+                               return (-1);
+                       if (*tlvs_rcvd & F_HELLO_TLV_RCVD_ADDR)
+                               break;
+                       memcpy(&addr->v6, buf, sizeof(addr->v6));
+                       *tlvs_rcvd |= F_HELLO_TLV_RCVD_ADDR;
+                       break;
+               case TLV_TYPE_CONFIG:
+                       if (tlv_len != sizeof(uint32_t))
+                               return (-1);
+                       memcpy(conf_number, buf, sizeof(uint32_t));
+                       *tlvs_rcvd |= F_HELLO_TLV_RCVD_CONF;
+                       break;
+               case TLV_TYPE_DUALSTACK:
+                       if (tlv_len != sizeof(uint32_t))
+                               return (-1);
+                       /*
+                        * RFC 7552 - Section 6.1:
+                        * "A Single-stack LSR does not need to use the
+                        * Dual-Stack capability in Hello messages and SHOULD
+                        * ignore this capability if received".
+                        */
+                       if (!ldp_is_dual_stack(leconf))
+                               break;
+                       /* Shame on you, Cisco! */
+                       if (leconf->flags & F_LDPD_DS_CISCO_INTEROP) {
+                               memcpy(trans_pref, buf + sizeof(uint16_t),
+                                   sizeof(uint16_t));
+                               *trans_pref = ntohs(*trans_pref);
+                       } else {
+                               memcpy(trans_pref, buf , sizeof(uint16_t));
+                               *trans_pref = ntohs(*trans_pref) >> 12;
+                       }
+                       *tlvs_rcvd |= F_HELLO_TLV_RCVD_DS;
+                       break;
+               default:
+                       /* if unknown flag set, ignore TLV */
+                       if (!(ntohs(tlv.type) & UNKNOWN_FLAG))
+                               return (-1);
+                       break;
+               }
+               buf += tlv_len;
+               len -= tlv_len;
+               total += tlv_len;
+       }
+
+       return (total);
+}
diff --git a/ldpd/init.c b/ldpd/init.c
new file mode 100644 (file)
index 0000000..eb22bf5
--- /dev/null
@@ -0,0 +1,166 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * 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/types.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+static int     gen_init_prms_tlv(struct ibuf *, struct nbr *);
+
+void
+send_init(struct nbr *nbr)
+{
+       struct ibuf             *buf;
+       uint16_t                 size;
+       int                      err = 0;
+
+       log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
+
+       size = LDP_HDR_SIZE + LDP_MSG_SIZE + SESS_PRMS_SIZE;
+       if ((buf = ibuf_open(size)) == NULL)
+               fatal(__func__);
+
+       err |= gen_ldp_hdr(buf, size);
+       size -= LDP_HDR_SIZE;
+       err |= gen_msg_hdr(buf, MSG_TYPE_INIT, size);
+       size -= LDP_MSG_SIZE;
+       err |= gen_init_prms_tlv(buf, nbr);
+       if (err) {
+               ibuf_free(buf);
+               return;
+       }
+
+       evbuf_enqueue(&nbr->tcp->wbuf, buf);
+}
+
+int
+recv_init(struct nbr *nbr, char *buf, uint16_t len)
+{
+       struct ldp_msg          msg;
+       struct sess_prms_tlv    sess;
+       uint16_t                max_pdu_len;
+
+       log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
+
+       memcpy(&msg, buf, sizeof(msg));
+       buf += LDP_MSG_SIZE;
+       len -= LDP_MSG_SIZE;
+
+       if (len < SESS_PRMS_SIZE) {
+               session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type);
+               return (-1);
+       }
+       memcpy(&sess, buf, sizeof(sess));
+       if (ntohs(sess.length) != SESS_PRMS_LEN) {
+               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+               return (-1);
+       }
+       if (ntohs(sess.proto_version) != LDP_VERSION) {
+               session_shutdown(nbr, S_BAD_PROTO_VER, msg.id, msg.type);
+               return (-1);
+       }
+       if (ntohs(sess.keepalive_time) < MIN_KEEPALIVE) {
+               session_shutdown(nbr, S_KEEPALIVE_BAD, msg.id, msg.type);
+               return (-1);
+       }
+       if (sess.lsr_id != leconf->rtr_id.s_addr ||
+           ntohs(sess.lspace_id) != 0) {
+               session_shutdown(nbr, S_NO_HELLO, msg.id, msg.type);
+               return (-1);
+       }
+
+       buf += SESS_PRMS_SIZE;
+       len -= SESS_PRMS_SIZE;
+
+       /* Optional Parameters */
+       while (len > 0) {
+               struct tlv      tlv;
+               uint16_t        tlv_len;
+
+               if (len < sizeof(tlv)) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+                       return (-1);
+               }
+
+               memcpy(&tlv, buf, TLV_HDR_SIZE);
+               tlv_len = ntohs(tlv.length);
+               if (tlv_len + TLV_HDR_SIZE > len) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+                       return (-1);
+               }
+               buf += TLV_HDR_SIZE;
+               len -= TLV_HDR_SIZE;
+
+               switch (ntohs(tlv.type)) {
+               case TLV_TYPE_ATMSESSIONPAR:
+                       session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type);
+                       return (-1);
+               case TLV_TYPE_FRSESSION:
+                       session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type);
+                       return (-1);
+               default:
+                       if (!(ntohs(tlv.type) & UNKNOWN_FLAG))
+                               send_notification_nbr(nbr, S_UNKNOWN_TLV,
+                                   msg.id, msg.type);
+                       /* ignore unknown tlv */
+                       break;
+               }
+               buf += tlv_len;
+               len -= tlv_len;
+       }
+
+       nbr->keepalive = min(nbr_get_keepalive(nbr->af, nbr->id),
+           ntohs(sess.keepalive_time));
+
+       max_pdu_len = ntohs(sess.max_pdu_len);
+       /*
+        * RFC 5036 - Section 3.5.3:
+        * "A value of 255 or less specifies the default maximum length of
+        * 4096 octets".
+        */
+       if (max_pdu_len <= 255)
+               max_pdu_len = LDP_MAX_LEN;
+       nbr->max_pdu_len = min(max_pdu_len, LDP_MAX_LEN);
+
+       nbr_fsm(nbr, NBR_EVT_INIT_RCVD);
+
+       return (0);
+}
+
+static int
+gen_init_prms_tlv(struct ibuf *buf, struct nbr *nbr)
+{
+       struct sess_prms_tlv    parms;
+
+       memset(&parms, 0, sizeof(parms));
+       parms.type = htons(TLV_TYPE_COMMONSESSION);
+       parms.length = htons(SESS_PRMS_LEN);
+       parms.proto_version = htons(LDP_VERSION);
+       parms.keepalive_time = htons(nbr_get_keepalive(nbr->af, nbr->id));
+       parms.reserved = 0;
+       parms.pvlim = 0;
+       parms.max_pdu_len = 0;
+       parms.lsr_id = nbr->id.s_addr;
+       parms.lspace_id = 0;
+
+       return (ibuf_add(buf, &parms, SESS_PRMS_SIZE));
+}
diff --git a/ldpd/interface.c b/ldpd/interface.c
new file mode 100644 (file)
index 0000000..ff4c8f1
--- /dev/null
@@ -0,0 +1,531 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/time.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+static struct if_addr  *if_addr_new(struct kaddr *);
+static struct if_addr  *if_addr_lookup(struct if_addr_head *, struct kaddr *);
+static int              if_start(struct iface *, int);
+static int              if_reset(struct iface *, int);
+static void             if_update_af(struct iface_af *, int);
+static void             if_hello_timer(int, short, void *);
+static void             if_start_hello_timer(struct iface_af *);
+static void             if_stop_hello_timer(struct iface_af *);
+static int              if_join_ipv4_group(struct iface *, struct in_addr *);
+static int              if_leave_ipv4_group(struct iface *, struct in_addr *);
+static int              if_join_ipv6_group(struct iface *, struct in6_addr *);
+static int              if_leave_ipv6_group(struct iface *, struct in6_addr *);
+
+struct iface *
+if_new(struct kif *kif)
+{
+       struct iface            *iface;
+
+       if ((iface = calloc(1, sizeof(*iface))) == NULL)
+               fatal("if_new: calloc");
+
+       strlcpy(iface->name, kif->ifname, sizeof(iface->name));
+
+       /* get type */
+       if (kif->flags & IFF_POINTOPOINT)
+               iface->type = IF_TYPE_POINTOPOINT;
+       if (kif->flags & IFF_BROADCAST &&
+           kif->flags & IFF_MULTICAST)
+               iface->type = IF_TYPE_BROADCAST;
+
+       /* get index and flags */
+       LIST_INIT(&iface->addr_list);
+       iface->ifindex = kif->ifindex;
+       iface->flags = kif->flags;
+       iface->linkstate = kif->link_state;
+       iface->if_type = kif->if_type;
+
+       /* ipv4 */
+       iface->ipv4.af = AF_INET;
+       iface->ipv4.iface = iface;
+       iface->ipv4.enabled = 0;
+       iface->ipv4.state = IF_STA_DOWN;
+       LIST_INIT(&iface->ipv4.adj_list);
+
+       /* ipv6 */
+       iface->ipv6.af = AF_INET6;
+       iface->ipv6.iface = iface;
+       iface->ipv6.enabled = 0;
+       iface->ipv6.state = IF_STA_DOWN;
+       LIST_INIT(&iface->ipv6.adj_list);
+
+       return (iface);
+}
+
+struct iface *
+if_lookup(struct ldpd_conf *xconf, unsigned short ifindex)
+{
+       struct iface *iface;
+
+       LIST_FOREACH(iface, &xconf->iface_list, entry)
+               if (iface->ifindex == ifindex)
+                       return (iface);
+
+       return (NULL);
+}
+
+void
+if_exit(struct iface *iface)
+{
+       struct if_addr          *if_addr;
+
+       log_debug("%s: interface %s", __func__, iface->name);
+
+       if (iface->ipv4.state == IF_STA_ACTIVE)
+               if_reset(iface, AF_INET);
+       if (iface->ipv6.state == IF_STA_ACTIVE)
+               if_reset(iface, AF_INET6);
+
+       while ((if_addr = LIST_FIRST(&iface->addr_list)) != NULL) {
+               LIST_REMOVE(if_addr, entry);
+               free(if_addr);
+       }
+}
+
+struct iface_af *
+iface_af_get(struct iface *iface, int af)
+{
+       switch (af) {
+       case AF_INET:
+               return (&iface->ipv4);
+       case AF_INET6:
+               return (&iface->ipv6);
+       default:
+               fatalx("iface_af_get: unknown af");
+       }
+}
+
+static struct if_addr *
+if_addr_new(struct kaddr *ka)
+{
+       struct if_addr  *if_addr;
+
+       if ((if_addr = calloc(1, sizeof(*if_addr))) == NULL)
+               fatal(__func__);
+
+       if_addr->af = ka->af;
+       if_addr->addr = ka->addr;
+       if_addr->prefixlen = ka->prefixlen;
+       if_addr->dstbrd = ka->dstbrd;
+
+       return (if_addr);
+}
+
+static struct if_addr *
+if_addr_lookup(struct if_addr_head *addr_list, struct kaddr *ka)
+{
+       struct if_addr  *if_addr;
+       int              af = ka->af;
+
+       LIST_FOREACH(if_addr, addr_list, entry)
+               if (!ldp_addrcmp(af, &if_addr->addr, &ka->addr) &&
+                   if_addr->prefixlen == ka->prefixlen &&
+                   !ldp_addrcmp(af, &if_addr->dstbrd, &ka->dstbrd))
+                       return (if_addr);
+
+       return (NULL);
+}
+
+void
+if_addr_add(struct kaddr *ka)
+{
+       struct iface            *iface;
+       struct if_addr          *if_addr;
+       struct nbr              *nbr;
+
+       if (if_addr_lookup(&global.addr_list, ka) == NULL) {
+               if_addr = if_addr_new(ka);
+
+               LIST_INSERT_HEAD(&global.addr_list, if_addr, entry);
+               RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
+                       if (nbr->state != NBR_STA_OPER)
+                               continue;
+                       if (if_addr->af == AF_INET && !nbr->v4_enabled)
+                               continue;
+                       if (if_addr->af == AF_INET6 && !nbr->v6_enabled)
+                               continue;
+
+                       send_address_single(nbr, if_addr, 0);
+               }
+       }
+
+       iface = if_lookup(leconf, ka->ifindex);
+       if (iface) {
+               if (ka->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&ka->addr.v6))
+                       iface->linklocal = ka->addr.v6;
+
+               if (if_addr_lookup(&iface->addr_list, ka) == NULL) {
+                       if_addr = if_addr_new(ka);
+                       LIST_INSERT_HEAD(&iface->addr_list, if_addr, entry);
+                       if_update(iface, if_addr->af);
+               }
+       }
+}
+
+void
+if_addr_del(struct kaddr *ka)
+{
+       struct iface            *iface;
+       struct if_addr          *if_addr;
+       struct nbr              *nbr;
+
+       iface = if_lookup(leconf, ka->ifindex);
+       if (iface) {
+               if (ka->af == AF_INET6 &&
+                   IN6_ARE_ADDR_EQUAL(&iface->linklocal, &ka->addr.v6))
+                       memset(&iface->linklocal, 0, sizeof(iface->linklocal));
+
+               if_addr = if_addr_lookup(&iface->addr_list, ka);
+               if (if_addr) {
+                       LIST_REMOVE(if_addr, entry);
+                       if_update(iface, if_addr->af);
+                       free(if_addr);
+               }
+       }
+
+       if_addr = if_addr_lookup(&global.addr_list, ka);
+       if (if_addr) {
+               RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
+                       if (nbr->state != NBR_STA_OPER)
+                               continue;
+                       if (if_addr->af == AF_INET && !nbr->v4_enabled)
+                               continue;
+                       if (if_addr->af == AF_INET6 && !nbr->v6_enabled)
+                               continue;
+                       send_address_single(nbr, if_addr, 1);
+               }
+               LIST_REMOVE(if_addr, entry);
+               free(if_addr);
+       }
+}
+
+static int
+if_start(struct iface *iface, int af)
+{
+       struct iface_af         *ia;
+       struct timeval           now;
+
+       log_debug("%s: %s address-family %s", __func__, iface->name,
+           af_name(af));
+
+       ia = iface_af_get(iface, af);
+
+       gettimeofday(&now, NULL);
+       ia->uptime = now.tv_sec;
+
+       switch (af) {
+       case AF_INET:
+               if (if_join_ipv4_group(iface, &global.mcast_addr_v4))
+                       return (-1);
+               break;
+       case AF_INET6:
+               if (if_join_ipv6_group(iface, &global.mcast_addr_v6))
+                       return (-1);
+               break;
+       default:
+               fatalx("if_start: unknown af");
+       }
+
+       send_hello(HELLO_LINK, ia, NULL);
+
+       evtimer_set(&ia->hello_timer, if_hello_timer, ia);
+       if_start_hello_timer(ia);
+       return (0);
+}
+
+static int
+if_reset(struct iface *iface, int af)
+{
+       struct iface_af         *ia;
+       struct adj              *adj;
+
+       log_debug("%s: %s address-family %s", __func__, iface->name,
+           af_name(af));
+
+       ia = iface_af_get(iface, af);
+       if_stop_hello_timer(ia);
+
+       while ((adj = LIST_FIRST(&ia->adj_list)) != NULL)
+               adj_del(adj, S_SHUTDOWN);
+
+       /* try to cleanup */
+       switch (af) {
+       case AF_INET:
+               if (global.ipv4.ldp_disc_socket != -1)
+                       if_leave_ipv4_group(iface, &global.mcast_addr_v4);
+               break;
+       case AF_INET6:
+               if (global.ipv6.ldp_disc_socket != -1)
+                       if_leave_ipv6_group(iface, &global.mcast_addr_v6);
+               break;
+       default:
+               fatalx("if_start: unknown af");
+       }
+
+       return (0);
+}
+
+static void
+if_update_af(struct iface_af *ia, int link_ok)
+{
+       int                      addr_ok = 0, socket_ok, rtr_id_ok;
+       struct if_addr          *if_addr;
+
+       switch (ia->af) {
+       case AF_INET:
+               /*
+                * NOTE: for LDPv4, each interface should have at least one
+                * valid IP address otherwise they can not be enabled.
+                */
+               LIST_FOREACH(if_addr, &ia->iface->addr_list, entry) {
+                       if (if_addr->af == AF_INET) {
+                               addr_ok = 1;
+                               break;
+                       }
+               }
+               break;
+       case AF_INET6:
+               /* for IPv6 the link-local address is enough. */
+               if (IN6_IS_ADDR_LINKLOCAL(&ia->iface->linklocal))
+                       addr_ok = 1;
+               break;
+       default:
+               fatalx("if_update_af: unknown af");
+       }
+
+       if ((ldp_af_global_get(&global, ia->af))->ldp_disc_socket != -1)
+               socket_ok = 1;
+       else
+               socket_ok = 0;
+
+       if (leconf->rtr_id.s_addr != INADDR_ANY)
+               rtr_id_ok = 1;
+       else
+               rtr_id_ok = 0;
+
+       if (ia->state == IF_STA_DOWN) {
+               if (!ia->enabled || !link_ok || !addr_ok || !socket_ok ||
+                   !rtr_id_ok)
+                       return;
+
+               ia->state = IF_STA_ACTIVE;
+               if_start(ia->iface, ia->af);
+       } else if (ia->state == IF_STA_ACTIVE) {
+               if (ia->enabled && link_ok && addr_ok && socket_ok && rtr_id_ok)
+                       return;
+
+               ia->state = IF_STA_DOWN;
+               if_reset(ia->iface, ia->af);
+       }
+}
+
+void
+if_update(struct iface *iface, int af)
+{
+       int                      link_ok;
+
+       link_ok = (iface->flags & IFF_UP) &&
+           LINK_STATE_IS_UP(iface->linkstate);
+
+       if (af == AF_INET || af == AF_UNSPEC)
+               if_update_af(&iface->ipv4, link_ok);
+       if (af == AF_INET6 || af == AF_UNSPEC)
+               if_update_af(&iface->ipv6, link_ok);
+}
+
+void
+if_update_all(int af)
+{
+       struct iface            *iface;
+
+       LIST_FOREACH(iface, &leconf->iface_list, entry)
+               if_update(iface, af);
+}
+
+/* timers */
+/* ARGSUSED */
+static void
+if_hello_timer(int fd, short event, void *arg)
+{
+       struct iface_af         *ia = arg;
+
+       send_hello(HELLO_LINK, ia, NULL);
+       if_start_hello_timer(ia);
+}
+
+static void
+if_start_hello_timer(struct iface_af *ia)
+{
+       struct timeval           tv;
+
+       timerclear(&tv);
+       tv.tv_sec = ia->hello_interval;
+       if (evtimer_add(&ia->hello_timer, &tv) == -1)
+               fatal(__func__);
+}
+
+static void
+if_stop_hello_timer(struct iface_af *ia)
+{
+       if (evtimer_pending(&ia->hello_timer, NULL) &&
+           evtimer_del(&ia->hello_timer) == -1)
+               fatal(__func__);
+}
+
+struct ctl_iface *
+if_to_ctl(struct iface_af *ia)
+{
+       static struct ctl_iface  ictl;
+       struct timeval           now;
+       struct adj              *adj;
+
+       ictl.af = ia->af;
+       memcpy(ictl.name, ia->iface->name, sizeof(ictl.name));
+       ictl.ifindex = ia->iface->ifindex;
+       ictl.state = ia->state;
+       ictl.flags = ia->iface->flags;
+       ictl.linkstate = ia->iface->linkstate;
+       ictl.type = ia->iface->type;
+       ictl.if_type = ia->iface->if_type;
+       ictl.hello_holdtime = ia->hello_holdtime;
+       ictl.hello_interval = ia->hello_interval;
+
+       gettimeofday(&now, NULL);
+       if (ia->state != IF_STA_DOWN &&
+           ia->uptime != 0) {
+               ictl.uptime = now.tv_sec - ia->uptime;
+       } else
+               ictl.uptime = 0;
+
+       ictl.adj_cnt = 0;
+       LIST_FOREACH(adj, &ia->adj_list, ia_entry)
+               ictl.adj_cnt++;
+
+       return (&ictl);
+}
+
+/* multicast membership sockopts */
+in_addr_t
+if_get_ipv4_addr(struct iface *iface)
+{
+       struct if_addr          *if_addr;
+
+       LIST_FOREACH(if_addr, &iface->addr_list, entry)
+               if (if_addr->af == AF_INET)
+                       return (if_addr->addr.v4.s_addr);
+
+       return (INADDR_ANY);
+}
+
+static int
+if_join_ipv4_group(struct iface *iface, struct in_addr *addr)
+{
+       struct ip_mreq           mreq;
+
+       log_debug("%s: interface %s addr %s", __func__, iface->name,
+           inet_ntoa(*addr));
+
+       mreq.imr_multiaddr = *addr;
+       mreq.imr_interface.s_addr = if_get_ipv4_addr(iface);
+
+       if (setsockopt(global.ipv4.ldp_disc_socket, IPPROTO_IP,
+           IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) < 0) {
+               log_warn("%s: error IP_ADD_MEMBERSHIP, interface %s address %s",
+                    __func__, iface->name, inet_ntoa(*addr));
+               return (-1);
+       }
+       return (0);
+}
+
+static int
+if_leave_ipv4_group(struct iface *iface, struct in_addr *addr)
+{
+       struct ip_mreq           mreq;
+
+       log_debug("%s: interface %s addr %s", __func__, iface->name,
+           inet_ntoa(*addr));
+
+       mreq.imr_multiaddr = *addr;
+       mreq.imr_interface.s_addr = if_get_ipv4_addr(iface);
+
+       if (setsockopt(global.ipv4.ldp_disc_socket, IPPROTO_IP,
+           IP_DROP_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) < 0) {
+               log_warn("%s: error IP_DROP_MEMBERSHIP, interface %s "
+                   "address %s", __func__, iface->name, inet_ntoa(*addr));
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+if_join_ipv6_group(struct iface *iface, struct in6_addr *addr)
+{
+       struct ipv6_mreq         mreq;
+
+       log_debug("%s: interface %s addr %s", __func__, iface->name,
+           log_in6addr(addr));
+
+       mreq.ipv6mr_multiaddr = *addr;
+       mreq.ipv6mr_interface = iface->ifindex;
+
+       if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6,
+           IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) {
+               log_warn("%s: error IPV6_JOIN_GROUP, interface %s address %s",
+                   __func__, iface->name, log_in6addr(addr));
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+if_leave_ipv6_group(struct iface *iface, struct in6_addr *addr)
+{
+       struct ipv6_mreq         mreq;
+
+       log_debug("%s: interface %s addr %s", __func__, iface->name,
+           log_in6addr(addr));
+
+       mreq.ipv6mr_multiaddr = *addr;
+       mreq.ipv6mr_interface = iface->ifindex;
+
+       if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6,
+           IPV6_LEAVE_GROUP, (void *)&mreq, sizeof(mreq)) < 0) {
+               log_warn("%s: error IPV6_LEAVE_GROUP, interface %s address %s",
+                   __func__, iface->name, log_in6addr(addr));
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/ldpd/keepalive.c b/ldpd/keepalive.c
new file mode 100644 (file)
index 0000000..4cd49d4
--- /dev/null
@@ -0,0 +1,58 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * 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/types.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+void
+send_keepalive(struct nbr *nbr)
+{
+       struct ibuf     *buf;
+       uint16_t         size;
+
+       size = LDP_HDR_SIZE + LDP_MSG_SIZE;
+       if ((buf = ibuf_open(size)) == NULL)
+               fatal(__func__);
+
+       gen_ldp_hdr(buf, size);
+       size -= LDP_HDR_SIZE;
+       gen_msg_hdr(buf, MSG_TYPE_KEEPALIVE, size);
+
+       evbuf_enqueue(&nbr->tcp->wbuf, buf);
+}
+
+int
+recv_keepalive(struct nbr *nbr, char *buf, uint16_t len)
+{
+       struct ldp_msg msg;
+
+       memcpy(&msg, buf, sizeof(msg));
+       if (len != LDP_MSG_SIZE) {
+               session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type);
+               return (-1);
+       }
+
+       if (nbr->state != NBR_STA_OPER)
+               nbr_fsm(nbr, NBR_EVT_KEEPALIVE_RCVD);
+
+       return (0);
+}
diff --git a/ldpd/l2vpn.c b/ldpd/l2vpn.c
new file mode 100644 (file)
index 0000000..22c9874
--- /dev/null
@@ -0,0 +1,510 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "lde.h"
+#include "log.h"
+
+static void     l2vpn_pw_fec(struct l2vpn_pw *, struct fec *);
+
+struct l2vpn *
+l2vpn_new(const char *name)
+{
+       struct l2vpn    *l2vpn;
+
+       if ((l2vpn = calloc(1, sizeof(*l2vpn))) == NULL)
+               fatal("l2vpn_new: calloc");
+
+       strlcpy(l2vpn->name, name, sizeof(l2vpn->name));
+
+       /* set default values */
+       l2vpn->mtu = DEFAULT_L2VPN_MTU;
+       l2vpn->pw_type = DEFAULT_PW_TYPE;
+
+       LIST_INIT(&l2vpn->if_list);
+       LIST_INIT(&l2vpn->pw_list);
+
+       return (l2vpn);
+}
+
+struct l2vpn *
+l2vpn_find(struct ldpd_conf *xconf, const char *name)
+{
+       struct l2vpn    *l2vpn;
+
+       LIST_FOREACH(l2vpn, &xconf->l2vpn_list, entry)
+               if (strcmp(l2vpn->name, name) == 0)
+                       return (l2vpn);
+
+       return (NULL);
+}
+
+void
+l2vpn_del(struct l2vpn *l2vpn)
+{
+       struct l2vpn_if         *lif;
+       struct l2vpn_pw         *pw;
+
+       while ((lif = LIST_FIRST(&l2vpn->if_list)) != NULL) {
+               LIST_REMOVE(lif, entry);
+               free(lif);
+       }
+       while ((pw = LIST_FIRST(&l2vpn->pw_list)) != NULL) {
+               LIST_REMOVE(pw, entry);
+               free(pw);
+       }
+
+       free(l2vpn);
+}
+
+void
+l2vpn_init(struct l2vpn *l2vpn)
+{
+       struct l2vpn_pw *pw;
+
+       LIST_FOREACH(pw, &l2vpn->pw_list, entry)
+               l2vpn_pw_init(pw);
+}
+
+void
+l2vpn_exit(struct l2vpn *l2vpn)
+{
+       struct l2vpn_pw         *pw;
+
+       LIST_FOREACH(pw, &l2vpn->pw_list, entry)
+               l2vpn_pw_exit(pw);
+}
+
+struct l2vpn_if *
+l2vpn_if_new(struct l2vpn *l2vpn, struct kif *kif)
+{
+       struct l2vpn_if *lif;
+
+       if ((lif = calloc(1, sizeof(*lif))) == NULL)
+               fatal("l2vpn_if_new: calloc");
+
+       lif->l2vpn = l2vpn;
+       strlcpy(lif->ifname, kif->ifname, sizeof(lif->ifname));
+       lif->ifindex = kif->ifindex;
+       lif->flags = kif->flags;
+       lif->link_state = kif->link_state;
+
+       return (lif);
+}
+
+struct l2vpn_if *
+l2vpn_if_find(struct l2vpn *l2vpn, unsigned int ifindex)
+{
+       struct l2vpn_if *lif;
+
+       LIST_FOREACH(lif, &l2vpn->if_list, entry)
+               if (lif->ifindex == ifindex)
+                       return (lif);
+
+       return (NULL);
+}
+
+struct l2vpn_pw *
+l2vpn_pw_new(struct l2vpn *l2vpn, struct kif *kif)
+{
+       struct l2vpn_pw *pw;
+
+       if ((pw = calloc(1, sizeof(*pw))) == NULL)
+               fatal("l2vpn_pw_new: calloc");
+
+       pw->l2vpn = l2vpn;
+       strlcpy(pw->ifname, kif->ifname, sizeof(pw->ifname));
+       pw->ifindex = kif->ifindex;
+
+       return (pw);
+}
+
+struct l2vpn_pw *
+l2vpn_pw_find(struct l2vpn *l2vpn, unsigned int ifindex)
+{
+       struct l2vpn_pw *pw;
+
+       LIST_FOREACH(pw, &l2vpn->pw_list, entry)
+               if (pw->ifindex == ifindex)
+                       return (pw);
+
+       return (NULL);
+}
+
+void
+l2vpn_pw_init(struct l2vpn_pw *pw)
+{
+       struct fec       fec;
+
+       l2vpn_pw_reset(pw);
+
+       l2vpn_pw_fec(pw, &fec);
+       lde_kernel_insert(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0,
+           0, (void *)pw);
+}
+
+void
+l2vpn_pw_exit(struct l2vpn_pw *pw)
+{
+       struct fec       fec;
+
+       l2vpn_pw_fec(pw, &fec);
+       lde_kernel_remove(&fec, AF_INET, (union ldpd_addr*)&pw->lsr_id, 0);
+}
+
+static void
+l2vpn_pw_fec(struct l2vpn_pw *pw, struct fec *fec)
+{
+       memset(fec, 0, sizeof(*fec));
+       fec->type = FEC_TYPE_PWID;
+       fec->u.pwid.type = pw->l2vpn->pw_type;
+       fec->u.pwid.pwid = pw->pwid;
+       fec->u.pwid.lsr_id = pw->lsr_id;
+}
+
+void
+l2vpn_pw_reset(struct l2vpn_pw *pw)
+{
+       pw->remote_group = 0;
+       pw->remote_mtu = 0;
+       pw->remote_status = 0;
+
+       if (pw->flags & F_PW_CWORD_CONF)
+               pw->flags |= F_PW_CWORD;
+       else
+               pw->flags &= ~F_PW_CWORD;
+
+       if (pw->flags & F_PW_STATUSTLV_CONF)
+               pw->flags |= F_PW_STATUSTLV;
+       else
+               pw->flags &= ~F_PW_STATUSTLV;
+}
+
+int
+l2vpn_pw_ok(struct l2vpn_pw *pw, struct fec_nh *fnh)
+{
+       struct fec               fec;
+       struct fec_node         *fn;
+
+       /* check for a remote label */
+       if (fnh->remote_label == NO_LABEL)
+               return (0);
+
+       /* MTUs must match */
+       if (pw->l2vpn->mtu != pw->remote_mtu)
+               return (0);
+
+       /* check pw status if applicable */
+       if ((pw->flags & F_PW_STATUSTLV) &&
+           pw->remote_status != PW_FORWARDING)
+               return (0);
+
+       /* check for a working lsp to the nexthop */
+       memset(&fec, 0, sizeof(fec));
+       switch (pw->af) {
+       case AF_INET:
+               fec.type = FEC_TYPE_IPV4;
+               fec.u.ipv4.prefix = pw->addr.v4;
+               fec.u.ipv4.prefixlen = 32;
+               break;
+       case AF_INET6:
+               fec.type = FEC_TYPE_IPV6;
+               fec.u.ipv6.prefix = pw->addr.v6;
+               fec.u.ipv6.prefixlen = 128;
+               break;
+       default:
+               fatalx("l2vpn_pw_ok: unknown af");
+       }
+
+       fn = (struct fec_node *)fec_find(&ft, &fec);
+       if (fn == NULL || fn->local_label == NO_LABEL)
+               return (0);
+       /*
+        * Need to ensure that there's a label binding for all nexthops.
+        * Otherwise, ECMP for this route could render the pseudowire unusable.
+        */
+       LIST_FOREACH(fnh, &fn->nexthops, entry)
+               if (fnh->remote_label == NO_LABEL)
+                       return (0);
+
+       return (1);
+}
+
+int
+l2vpn_pw_negotiate(struct lde_nbr *ln, struct fec_node *fn, struct map *map)
+{
+       struct l2vpn_pw         *pw;
+       struct status_tlv        st;
+
+       /* NOTE: thanks martini & friends for all this mess */
+
+       pw = (struct l2vpn_pw *) fn->data;
+       if (pw == NULL)
+               /*
+                * pseudowire not configured, return and record
+                * the mapping later
+                */
+               return (0);
+
+       /* RFC4447 - Section 6.2: control word negotiation */
+       if (fec_find(&ln->sent_map, &fn->fec)) {
+               if ((map->flags & F_MAP_PW_CWORD) &&
+                   !(pw->flags & F_PW_CWORD_CONF)) {
+                       /* ignore the received label mapping */
+                       return (1);
+               } else if (!(map->flags & F_MAP_PW_CWORD) &&
+                   (pw->flags & F_PW_CWORD_CONF)) {
+                       /* append a "Wrong C-bit" status code */
+                       st.status_code = S_WRONG_CBIT;
+                       st.msg_id = map->msg_id;
+                       st.msg_type = htons(MSG_TYPE_LABELMAPPING);
+                       lde_send_labelwithdraw(ln, fn, NO_LABEL, &st);
+
+                       pw->flags &= ~F_PW_CWORD;
+                       lde_send_labelmapping(ln, fn, 1);
+               }
+       } else if (map->flags & F_MAP_PW_CWORD) {
+               if (pw->flags & F_PW_CWORD_CONF)
+                       pw->flags |= F_PW_CWORD;
+               else
+                       /* act as if no label mapping had been received */
+                       return (1);
+       } else
+               pw->flags &= ~F_PW_CWORD;
+
+       /* RFC4447 - Section 5.4.3: pseudowire status negotiation */
+       if (fec_find(&ln->recv_map, &fn->fec) == NULL &&
+           !(map->flags & F_MAP_PW_STATUS))
+               pw->flags &= ~F_PW_STATUSTLV;
+
+       return (0);
+}
+
+void
+l2vpn_send_pw_status(uint32_t peerid, uint32_t status, struct fec *fec)
+{
+       struct notify_msg        nm;
+
+       memset(&nm, 0, sizeof(nm));
+       nm.status_code = S_PW_STATUS;
+       nm.pw_status = status;
+       nm.flags |= F_NOTIF_PW_STATUS;
+       lde_fec2map(fec, &nm.fec);
+       nm.flags |= F_NOTIF_FEC;
+
+       lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, peerid, 0,
+           &nm, sizeof(nm));
+}
+
+void
+l2vpn_recv_pw_status(struct lde_nbr *ln, struct notify_msg *nm)
+{
+       struct fec               fec;
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+       struct l2vpn_pw         *pw;
+
+       /* TODO group wildcard */
+       if (!(nm->fec.flags & F_MAP_PW_ID))
+               return;
+
+       lde_map2fec(&nm->fec, ln->id, &fec);
+       fn = (struct fec_node *)fec_find(&ft, &fec);
+       if (fn == NULL)
+               /* unknown fec */
+               return;
+
+       pw = (struct l2vpn_pw *) fn->data;
+       if (pw == NULL)
+               return;
+
+       fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)&ln->id, 0);
+       if (fnh == NULL)
+               return;
+
+       /* remote status didn't change */
+       if (pw->remote_status == nm->pw_status)
+               return;
+
+       pw->remote_status = nm->pw_status;
+
+       if (l2vpn_pw_ok(pw, fnh))
+               lde_send_change_klabel(fn, fnh);
+       else
+               lde_send_delete_klabel(fn, fnh);
+}
+
+void
+l2vpn_sync_pws(int af, union ldpd_addr *addr)
+{
+       struct l2vpn            *l2vpn;
+       struct l2vpn_pw         *pw;
+       struct fec               fec;
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+
+       LIST_FOREACH(l2vpn, &ldeconf->l2vpn_list, entry) {
+               LIST_FOREACH(pw, &l2vpn->pw_list, entry) {
+                       if (af != pw->af || ldp_addrcmp(af, &pw->addr, addr))
+                               continue;
+
+                       l2vpn_pw_fec(pw, &fec);
+                       fn = (struct fec_node *)fec_find(&ft, &fec);
+                       if (fn == NULL)
+                               continue;
+                       fnh = fec_nh_find(fn, AF_INET, (union ldpd_addr *)
+                           &pw->lsr_id, 0);
+                       if (fnh == NULL)
+                               continue;
+
+                       if (l2vpn_pw_ok(pw, fnh))
+                               lde_send_change_klabel(fn, fnh);
+                       else
+                               lde_send_delete_klabel(fn, fnh);
+               }
+       }
+}
+
+void
+l2vpn_pw_ctl(pid_t pid)
+{
+       struct l2vpn            *l2vpn;
+       struct l2vpn_pw         *pw;
+       static struct ctl_pw     pwctl;
+
+       LIST_FOREACH(l2vpn, &ldeconf->l2vpn_list, entry)
+               LIST_FOREACH(pw, &l2vpn->pw_list, entry) {
+                       memset(&pwctl, 0, sizeof(pwctl));
+                       strlcpy(pwctl.ifname, pw->ifname,
+                           sizeof(pwctl.ifname));
+                       pwctl.pwid = pw->pwid;
+                       pwctl.lsr_id = pw->lsr_id;
+                       pwctl.status = pw->flags & F_PW_STATUS_UP;
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_PW, 0,
+                           pid, &pwctl, sizeof(pwctl));
+               }
+}
+
+void
+l2vpn_binding_ctl(pid_t pid)
+{
+       struct fec              *f;
+       struct fec_node         *fn;
+       struct lde_map          *me;
+       struct l2vpn_pw         *pw;
+       static struct ctl_pw     pwctl;
+
+       RB_FOREACH(f, fec_tree, &ft) {
+               if (f->type != FEC_TYPE_PWID)
+                       continue;
+
+               fn = (struct fec_node *)f;
+               if (fn->local_label == NO_LABEL &&
+                   LIST_EMPTY(&fn->downstream))
+                       continue;
+
+               memset(&pwctl, 0, sizeof(pwctl));
+               pwctl.type = f->u.pwid.type;
+               pwctl.pwid = f->u.pwid.pwid;
+               pwctl.lsr_id = f->u.pwid.lsr_id;
+
+               pw = (struct l2vpn_pw *) fn->data;
+               if (pw) {
+                       pwctl.local_label = fn->local_label;
+                       pwctl.local_gid = 0;
+                       pwctl.local_ifmtu = pw->l2vpn->mtu;
+               } else
+                       pwctl.local_label = NO_LABEL;
+
+               LIST_FOREACH(me, &fn->downstream, entry)
+                       if (f->u.pwid.lsr_id.s_addr == me->nexthop->id.s_addr)
+                               break;
+
+               if (me) {
+                       pwctl.remote_label = me->map.label;
+                       pwctl.remote_gid = me->map.fec.pwid.group_id;
+                       if (me->map.flags & F_MAP_PW_IFMTU)
+                               pwctl.remote_ifmtu = me->map.fec.pwid.ifmtu;
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING,
+                           0, pid, &pwctl, sizeof(pwctl));
+               } else if (pw) {
+                       pwctl.remote_label = NO_LABEL;
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_SHOW_L2VPN_BINDING,
+                           0, pid, &pwctl, sizeof(pwctl));
+               }
+       }
+}
+
+/* ldpe */
+
+void
+ldpe_l2vpn_init(struct l2vpn *l2vpn)
+{
+       struct l2vpn_pw         *pw;
+
+       LIST_FOREACH(pw, &l2vpn->pw_list, entry)
+               ldpe_l2vpn_pw_init(pw);
+}
+
+void
+ldpe_l2vpn_exit(struct l2vpn *l2vpn)
+{
+       struct l2vpn_pw         *pw;
+
+       LIST_FOREACH(pw, &l2vpn->pw_list, entry)
+               ldpe_l2vpn_pw_exit(pw);
+}
+
+void
+ldpe_l2vpn_pw_init(struct l2vpn_pw *pw)
+{
+       struct tnbr             *tnbr;
+
+       tnbr = tnbr_find(leconf, pw->af, &pw->addr);
+       if (tnbr == NULL) {
+               tnbr = tnbr_new(leconf, pw->af, &pw->addr);
+               tnbr_update(tnbr);
+               LIST_INSERT_HEAD(&leconf->tnbr_list, tnbr, entry);
+       }
+
+       tnbr->pw_count++;
+}
+
+void
+ldpe_l2vpn_pw_exit(struct l2vpn_pw *pw)
+{
+       struct tnbr             *tnbr;
+
+       tnbr = tnbr_find(leconf, pw->af, &pw->addr);
+       if (tnbr) {
+               tnbr->pw_count--;
+               tnbr_check(tnbr);
+       }
+}
diff --git a/ldpd/labelmapping.c b/ldpd/labelmapping.c
new file mode 100644 (file)
index 0000000..88e6407
--- /dev/null
@@ -0,0 +1,764 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2014, 2015 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netmpls/mpls.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+static void     enqueue_pdu(struct nbr *, struct ibuf *, uint16_t);
+static int      gen_label_tlv(struct ibuf *, uint32_t);
+static int      tlv_decode_label(struct nbr *, struct ldp_msg *, char *,
+                   uint16_t, uint32_t *);
+static int      gen_reqid_tlv(struct ibuf *, uint32_t);
+
+static void
+enqueue_pdu(struct nbr *nbr, struct ibuf *buf, uint16_t size)
+{
+       struct ldp_hdr          *ldp_hdr;
+
+       ldp_hdr = ibuf_seek(buf, 0, sizeof(struct ldp_hdr));
+       ldp_hdr->length = htons(size);
+       evbuf_enqueue(&nbr->tcp->wbuf, buf);
+}
+
+/* Generic function that handles all Label Message types */
+void
+send_labelmessage(struct nbr *nbr, uint16_t type, struct mapping_head *mh)
+{
+       struct ibuf             *buf = NULL;
+       struct mapping_entry    *me;
+       uint16_t                 msg_size, size = 0;
+       int                      first = 1;
+       int                      err = 0;
+
+       /* nothing to send */
+       if (TAILQ_EMPTY(mh))
+               return;
+
+       while ((me = TAILQ_FIRST(mh)) != NULL) {
+               /* generate pdu */
+               if (first) {
+                       if ((buf = ibuf_open(nbr->max_pdu_len +
+                           LDP_HDR_DEAD_LEN)) == NULL)
+                               fatal(__func__);
+
+                       /* real size will be set up later */
+                       err |= gen_ldp_hdr(buf, 0);
+
+                       size = LDP_HDR_PDU_LEN;
+                       first = 0;
+               }
+
+               /* calculate size */
+               msg_size = LDP_MSG_SIZE + TLV_HDR_SIZE;
+               switch (me->map.type) {
+               case MAP_TYPE_WILDCARD:
+                       msg_size += FEC_ELM_WCARD_LEN;
+                       break;
+               case MAP_TYPE_PREFIX:
+                       msg_size += FEC_ELM_PREFIX_MIN_LEN +
+                           PREFIX_SIZE(me->map.fec.prefix.prefixlen);
+                       break;
+               case MAP_TYPE_PWID:
+                       msg_size += FEC_PWID_ELM_MIN_LEN;
+                       if (me->map.flags & F_MAP_PW_ID)
+                               msg_size += PW_STATUS_TLV_LEN;
+                       if (me->map.flags & F_MAP_PW_IFMTU)
+                               msg_size += FEC_SUBTLV_IFMTU_SIZE;
+                       if (me->map.flags & F_MAP_PW_STATUS)
+                               msg_size += PW_STATUS_TLV_SIZE;
+                       break;
+               }
+               if (me->map.label != NO_LABEL)
+                       msg_size += LABEL_TLV_SIZE;
+               if (me->map.flags & F_MAP_REQ_ID)
+                       msg_size += REQID_TLV_SIZE;
+               if (me->map.flags & F_MAP_STATUS)
+                       msg_size += STATUS_SIZE;
+
+               /* maximum pdu length exceeded, we need a new ldp pdu */
+               if (size + msg_size > nbr->max_pdu_len) {
+                       enqueue_pdu(nbr, buf, size);
+                       first = 1;
+                       continue;
+               }
+
+               size += msg_size;
+
+               /* append message and tlvs */
+               err |= gen_msg_hdr(buf, type, msg_size);
+               err |= gen_fec_tlv(buf, &me->map);
+               if (me->map.label != NO_LABEL)
+                       err |= gen_label_tlv(buf, me->map.label);
+               if (me->map.flags & F_MAP_REQ_ID)
+                       err |= gen_reqid_tlv(buf, me->map.requestid);
+               if (me->map.flags & F_MAP_PW_STATUS)
+                       err |= gen_pw_status_tlv(buf, me->map.pw_status);
+               if (me->map.flags & F_MAP_STATUS)
+                       err |= gen_status_tlv(buf, me->map.st.status_code,
+                           me->map.st.msg_id, me->map.st.msg_type);
+               if (err) {
+                       ibuf_free(buf);
+                       mapping_list_clr(mh);
+                       return;
+               }
+
+               log_debug("msg-out: %s: lsr-id %s, fec %s, label %s",
+                   msg_name(type), inet_ntoa(nbr->id), log_map(&me->map),
+                   log_label(me->map.label));
+
+               TAILQ_REMOVE(mh, me, entry);
+               free(me);
+       }
+
+       enqueue_pdu(nbr, buf, size);
+
+       nbr_fsm(nbr, NBR_EVT_PDU_SENT);
+}
+
+/* Generic function that handles all Label Message types */
+int
+recv_labelmessage(struct nbr *nbr, char *buf, uint16_t len, uint16_t type)
+{
+       struct ldp_msg           msg;
+       struct tlv               ft;
+       uint32_t                 label = NO_LABEL, reqid = 0;
+       uint32_t                 pw_status = 0;
+       uint8_t                  flags = 0;
+       int                      feclen, lbllen, tlen;
+       struct mapping_entry    *me;
+       struct mapping_head      mh;
+       struct map               map;
+
+       memcpy(&msg, buf, sizeof(msg));
+       buf += LDP_MSG_SIZE;
+       len -= LDP_MSG_SIZE;
+
+       /* FEC TLV */
+       if (len < sizeof(ft)) {
+               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+               return (-1);
+       }
+
+       memcpy(&ft, buf, sizeof(ft));
+       if (ntohs(ft.type) != TLV_TYPE_FEC) {
+               send_notification_nbr(nbr, S_MISS_MSG, msg.id, msg.type);
+               return (-1);
+       }
+       feclen = ntohs(ft.length);
+       if (feclen > len - TLV_HDR_SIZE) {
+               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+               return (-1);
+       }
+
+       buf += TLV_HDR_SIZE;    /* just advance to the end of the fec header */
+       len -= TLV_HDR_SIZE;
+
+       TAILQ_INIT(&mh);
+       do {
+               memset(&map, 0, sizeof(map));
+               map.msg_id = msg.id;
+
+               if ((tlen = tlv_decode_fec_elm(nbr, &msg, buf, feclen,
+                   &map)) == -1)
+                       goto err;
+               if (map.type == MAP_TYPE_PWID &&
+                   !(map.flags & F_MAP_PW_ID) &&
+                   type != MSG_TYPE_LABELWITHDRAW &&
+                   type != MSG_TYPE_LABELRELEASE) {
+                       send_notification_nbr(nbr, S_MISS_MSG, msg.id,
+                           msg.type);
+                       return (-1);
+               }
+
+               /*
+                * The Wildcard FEC Element can be used only in the
+                * Label Withdraw and Label Release messages.
+                */
+               if (map.type == MAP_TYPE_WILDCARD) {
+                       switch (type) {
+                       case MSG_TYPE_LABELMAPPING:
+                       case MSG_TYPE_LABELREQUEST:
+                       case MSG_TYPE_LABELABORTREQ:
+                               session_shutdown(nbr, S_UNKNOWN_FEC, msg.id,
+                                   msg.type);
+                               goto err;
+                       default:
+                               break;
+                       }
+               }
+
+               /*
+                * LDP supports the use of multiple FEC Elements per
+                * FEC for the Label Mapping message only.
+                */
+               if (type != MSG_TYPE_LABELMAPPING &&
+                   tlen != feclen) {
+                       session_shutdown(nbr, S_BAD_TLV_VAL, msg.id, msg.type);
+                       goto err;
+               }
+
+               mapping_list_add(&mh, &map);
+
+               buf += tlen;
+               len -= tlen;
+               feclen -= tlen;
+       } while (feclen > 0);
+
+       /* Mandatory Label TLV */
+       if (type == MSG_TYPE_LABELMAPPING) {
+               lbllen = tlv_decode_label(nbr, &msg, buf, len, &label);
+               if (lbllen == -1)
+                       goto err;
+
+               buf += lbllen;
+               len -= lbllen;
+       }
+
+       /* Optional Parameters */
+       while (len > 0) {
+               struct tlv      tlv;
+               uint16_t        tlv_len;
+               uint32_t        reqbuf, labelbuf, statusbuf;
+
+               if (len < sizeof(tlv)) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+                       goto err;
+               }
+
+               memcpy(&tlv, buf, TLV_HDR_SIZE);
+               tlv_len = ntohs(tlv.length);
+               if (tlv_len + TLV_HDR_SIZE > len) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+                       goto err;
+               }
+               buf += TLV_HDR_SIZE;
+               len -= TLV_HDR_SIZE;
+
+               switch (ntohs(tlv.type)) {
+               case TLV_TYPE_LABELREQUEST:
+                       switch (type) {
+                       case MSG_TYPE_LABELMAPPING:
+                       case MSG_TYPE_LABELREQUEST:
+                               if (tlv_len != REQID_TLV_LEN) {
+                                       session_shutdown(nbr, S_BAD_TLV_LEN,
+                                           msg.id, msg.type);
+                                       goto err;
+                               }
+
+                               flags |= F_MAP_REQ_ID;
+                               memcpy(&reqbuf, buf, sizeof(reqbuf));
+                               reqid = ntohl(reqbuf);
+                               break;
+                       default:
+                               /* ignore */
+                               break;
+                       }
+                       break;
+               case TLV_TYPE_HOPCOUNT:
+               case TLV_TYPE_PATHVECTOR:
+                       /* ignore */
+                       break;
+               case TLV_TYPE_GENERICLABEL:
+                       switch (type) {
+                       case MSG_TYPE_LABELWITHDRAW:
+                       case MSG_TYPE_LABELRELEASE:
+                               if (tlv_len != LABEL_TLV_LEN) {
+                                       session_shutdown(nbr, S_BAD_TLV_LEN,
+                                           msg.id, msg.type);
+                                       goto err;
+                               }
+
+                               memcpy(&labelbuf, buf, sizeof(labelbuf));
+                               label = ntohl(labelbuf);
+                               break;
+                       default:
+                               /* ignore */
+                               break;
+                       }
+                       break;
+               case TLV_TYPE_ATMLABEL:
+               case TLV_TYPE_FRLABEL:
+                       switch (type) {
+                       case MSG_TYPE_LABELWITHDRAW:
+                       case MSG_TYPE_LABELRELEASE:
+                               /* unsupported */
+                               session_shutdown(nbr, S_BAD_TLV_VAL, msg.id,
+                                   msg.type);
+                               goto err;
+                               break;
+                       default:
+                               /* ignore */
+                               break;
+                       }
+                       break;
+               case TLV_TYPE_STATUS:
+                       if (tlv_len != STATUS_TLV_LEN) {
+                               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id,
+                                   msg.type);
+                               goto err;
+                       }
+                       /* ignore */
+                       break;
+               case TLV_TYPE_PW_STATUS:
+                       switch (type) {
+                       case MSG_TYPE_LABELMAPPING:
+                               if (tlv_len != PW_STATUS_TLV_LEN) {
+                                       session_shutdown(nbr, S_BAD_TLV_LEN,
+                                           msg.id, msg.type);
+                                       goto err;
+                               }
+
+                               flags |= F_MAP_PW_STATUS;
+                               memcpy(&statusbuf, buf, sizeof(statusbuf));
+                               pw_status = ntohl(statusbuf);
+                               break;
+                       default:
+                               /* ignore */
+                               break;
+                       }
+                       break;
+               default:
+                       if (!(ntohs(tlv.type) & UNKNOWN_FLAG))
+                               send_notification_nbr(nbr, S_UNKNOWN_TLV,
+                                   msg.id, msg.type);
+                       /* ignore unknown tlv */
+                       break;
+               }
+               buf += tlv_len;
+               len -= tlv_len;
+       }
+
+       /* notify lde about the received message. */
+       while ((me = TAILQ_FIRST(&mh)) != NULL) {
+               int imsg_type = IMSG_NONE;
+
+               me->map.flags |= flags;
+               switch (me->map.type) {
+               case MAP_TYPE_PREFIX:
+                       switch (me->map.fec.prefix.af) {
+                       case AF_INET:
+                               if (label == MPLS_LABEL_IPV6NULL) {
+                                       session_shutdown(nbr, S_BAD_TLV_VAL,
+                                           msg.id, msg.type);
+                                       goto err;
+                               }
+                               if (!nbr->v4_enabled)
+                                       goto next;
+                               break;
+                       case AF_INET6:
+                               if (label == MPLS_LABEL_IPV4NULL) {
+                                       session_shutdown(nbr, S_BAD_TLV_VAL,
+                                           msg.id, msg.type);
+                                       goto err;
+                               }
+                               if (!nbr->v6_enabled)
+                                       goto next;
+                               break;
+                       default:
+                               fatalx("recv_labelmessage: unknown af");
+                       }
+                       break;
+               case MAP_TYPE_PWID:
+                       if (label <= MPLS_LABEL_RESERVED_MAX) {
+                               session_shutdown(nbr, S_BAD_TLV_VAL, msg.id,
+                                   msg.type);
+                               goto err;
+                       }
+                       if (me->map.flags & F_MAP_PW_STATUS)
+                               me->map.pw_status = pw_status;
+                       break;
+               default:
+                       break;
+               }
+               me->map.label = label;
+               if (me->map.flags & F_MAP_REQ_ID)
+                       me->map.requestid = reqid;
+
+               log_debug("msg-in: label mapping: lsr-id %s, fec %s, label %s",
+                   inet_ntoa(nbr->id), log_map(&me->map),
+                   log_label(me->map.label));
+
+               switch (type) {
+               case MSG_TYPE_LABELMAPPING:
+                       imsg_type = IMSG_LABEL_MAPPING;
+                       break;
+               case MSG_TYPE_LABELREQUEST:
+                       imsg_type = IMSG_LABEL_REQUEST;
+                       break;
+               case MSG_TYPE_LABELWITHDRAW:
+                       imsg_type = IMSG_LABEL_WITHDRAW;
+                       break;
+               case MSG_TYPE_LABELRELEASE:
+                       imsg_type = IMSG_LABEL_RELEASE;
+                       break;
+               case MSG_TYPE_LABELABORTREQ:
+                       imsg_type = IMSG_LABEL_ABORT;
+                       break;
+               default:
+                       break;
+               }
+
+               ldpe_imsg_compose_lde(imsg_type, nbr->peerid, 0, &me->map,
+                   sizeof(struct map));
+
+next:
+               TAILQ_REMOVE(&mh, me, entry);
+               free(me);
+       }
+
+       return (0);
+
+err:
+       mapping_list_clr(&mh);
+
+       return (-1);
+}
+
+/* Other TLV related functions */
+static int
+gen_label_tlv(struct ibuf *buf, uint32_t label)
+{
+       struct label_tlv        lt;
+
+       lt.type = htons(TLV_TYPE_GENERICLABEL);
+       lt.length = htons(LABEL_TLV_LEN);
+       lt.label = htonl(label);
+
+       return (ibuf_add(buf, &lt, sizeof(lt)));
+}
+
+static int
+tlv_decode_label(struct nbr *nbr, struct ldp_msg *msg, char *buf,
+    uint16_t len, uint32_t *label)
+{
+       struct label_tlv lt;
+
+       if (len < sizeof(lt)) {
+               session_shutdown(nbr, S_BAD_TLV_LEN, msg->id, msg->type);
+               return (-1);
+       }
+       memcpy(&lt, buf, sizeof(lt));
+
+       if (!(ntohs(lt.type) & TLV_TYPE_GENERICLABEL)) {
+               send_notification_nbr(nbr, S_MISS_MSG, msg->id, msg->type);
+               return (-1);
+       }
+
+       switch (htons(lt.type)) {
+       case TLV_TYPE_GENERICLABEL:
+               if (ntohs(lt.length) != sizeof(lt) - TLV_HDR_SIZE) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+
+               *label = ntohl(lt.label);
+               if (*label > MPLS_LABEL_MAX ||
+                   (*label <= MPLS_LABEL_RESERVED_MAX &&
+                    *label != MPLS_LABEL_IPV4NULL &&
+                    *label != MPLS_LABEL_IPV6NULL &&
+                    *label != MPLS_LABEL_IMPLNULL)) {
+                       session_shutdown(nbr, S_BAD_TLV_VAL, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+               break;
+       case TLV_TYPE_ATMLABEL:
+       case TLV_TYPE_FRLABEL:
+       default:
+               /* unsupported */
+               session_shutdown(nbr, S_BAD_TLV_VAL, msg->id, msg->type);
+               return (-1);
+       }
+
+       return (sizeof(lt));
+}
+
+static int
+gen_reqid_tlv(struct ibuf *buf, uint32_t reqid)
+{
+       struct reqid_tlv        rt;
+
+       rt.type = htons(TLV_TYPE_LABELREQUEST);
+       rt.length = htons(REQID_TLV_LEN);
+       rt.reqid = htonl(reqid);
+
+       return (ibuf_add(buf, &rt, sizeof(rt)));
+}
+
+int
+gen_pw_status_tlv(struct ibuf *buf, uint32_t status)
+{
+       struct pw_status_tlv    st;
+
+       st.type = htons(TLV_TYPE_PW_STATUS);
+       st.length = htons(PW_STATUS_TLV_LEN);
+       st.value = htonl(status);
+
+       return (ibuf_add(buf, &st, sizeof(st)));
+}
+
+int
+gen_fec_tlv(struct ibuf *buf, struct map *map)
+{
+       struct tlv      ft;
+       uint16_t        family, len, pw_type, ifmtu;
+       uint8_t         pw_len = 0;
+       uint32_t        group_id, pwid;
+       int             err = 0;
+
+       ft.type = htons(TLV_TYPE_FEC);
+
+       switch (map->type) {
+       case MAP_TYPE_WILDCARD:
+               ft.length = htons(sizeof(uint8_t));
+               err |= ibuf_add(buf, &ft, sizeof(ft));
+               err |= ibuf_add(buf, &map->type, sizeof(map->type));
+               break;
+       case MAP_TYPE_PREFIX:
+               len = PREFIX_SIZE(map->fec.prefix.prefixlen);
+               ft.length = htons(sizeof(map->type) + sizeof(family) +
+                   sizeof(map->fec.prefix.prefixlen) + len);
+               err |= ibuf_add(buf, &ft, sizeof(ft));
+               err |= ibuf_add(buf, &map->type, sizeof(map->type));
+               switch (map->fec.prefix.af) {
+               case AF_INET:
+                       family = htons(AF_IPV4);
+                       break;
+               case AF_INET6:
+                       family = htons(AF_IPV6);
+                       break;
+               default:
+                       fatalx("gen_fec_tlv: unknown af");
+                       break;
+               }
+               err |= ibuf_add(buf, &family, sizeof(family));
+               err |= ibuf_add(buf, &map->fec.prefix.prefixlen,
+                   sizeof(map->fec.prefix.prefixlen));
+               if (len)
+                       err |= ibuf_add(buf, &map->fec.prefix.prefix, len);
+               break;
+       case MAP_TYPE_PWID:
+               if (map->flags & F_MAP_PW_ID)
+                       pw_len += PW_STATUS_TLV_LEN;
+               if (map->flags & F_MAP_PW_IFMTU)
+                       pw_len += FEC_SUBTLV_IFMTU_SIZE;
+
+               len = FEC_PWID_ELM_MIN_LEN + pw_len;
+
+               ft.length = htons(len);
+               err |= ibuf_add(buf, &ft, sizeof(ft));
+
+               err |= ibuf_add(buf, &map->type, sizeof(uint8_t));
+               pw_type = map->fec.pwid.type;
+               if (map->flags & F_MAP_PW_CWORD)
+                       pw_type |= CONTROL_WORD_FLAG;
+               pw_type = htons(pw_type);
+               err |= ibuf_add(buf, &pw_type, sizeof(uint16_t));
+               err |= ibuf_add(buf, &pw_len, sizeof(uint8_t));
+               group_id = htonl(map->fec.pwid.group_id);
+               err |= ibuf_add(buf, &group_id, sizeof(uint32_t));
+               if (map->flags & F_MAP_PW_ID) {
+                       pwid = htonl(map->fec.pwid.pwid);
+                       err |= ibuf_add(buf, &pwid, sizeof(uint32_t));
+               }
+               if (map->flags & F_MAP_PW_IFMTU) {
+                       struct subtlv   stlv;
+
+                       stlv.type = SUBTLV_IFMTU;
+                       stlv.length = FEC_SUBTLV_IFMTU_SIZE;
+                       err |= ibuf_add(buf, &stlv, sizeof(uint16_t));
+
+                       ifmtu = htons(map->fec.pwid.ifmtu);
+                       err |= ibuf_add(buf, &ifmtu, sizeof(uint16_t));
+               }
+               break;
+       default:
+               break;
+       }
+
+       return (err);
+}
+
+int
+tlv_decode_fec_elm(struct nbr *nbr, struct ldp_msg *msg, char *buf,
+    uint16_t len, struct map *map)
+{
+       uint16_t        off = 0;
+       uint8_t         pw_len;
+
+       map->type = *buf;
+       off += sizeof(uint8_t);
+
+       switch (map->type) {
+       case MAP_TYPE_WILDCARD:
+               if (len == FEC_ELM_WCARD_LEN)
+                       return (off);
+               else {
+                       session_shutdown(nbr, S_BAD_TLV_VAL, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+               break;
+       case MAP_TYPE_PREFIX:
+               if (len < FEC_ELM_PREFIX_MIN_LEN) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+
+               /* Address Family */
+               memcpy(&map->fec.prefix.af, buf + off,
+                   sizeof(map->fec.prefix.af));
+               off += sizeof(map->fec.prefix.af);
+               map->fec.prefix.af = ntohs(map->fec.prefix.af);
+               switch (map->fec.prefix.af) {
+               case AF_IPV4:
+                       map->fec.prefix.af = AF_INET;
+                       break;
+               case AF_IPV6:
+                       map->fec.prefix.af = AF_INET6;
+                       break;
+               default:
+                       send_notification_nbr(nbr, S_UNSUP_ADDR, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+
+               /* Prefix Length */
+               map->fec.prefix.prefixlen = buf[off];
+               off += sizeof(uint8_t);
+               if (len < off + PREFIX_SIZE(map->fec.prefix.prefixlen)) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+
+               /* Prefix */
+               memset(&map->fec.prefix.prefix, 0,
+                   sizeof(map->fec.prefix.prefix));
+               memcpy(&map->fec.prefix.prefix, buf + off,
+                   PREFIX_SIZE(map->fec.prefix.prefixlen));
+
+               /* Just in case... */
+               ldp_applymask(map->fec.prefix.af, &map->fec.prefix.prefix,
+                   &map->fec.prefix.prefix, map->fec.prefix.prefixlen);
+
+               return (off + PREFIX_SIZE(map->fec.prefix.prefixlen));
+       case MAP_TYPE_PWID:
+               if (len < FEC_PWID_ELM_MIN_LEN) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+
+               /* PW type */
+               memcpy(&map->fec.pwid.type, buf + off, sizeof(uint16_t));
+               map->fec.pwid.type = ntohs(map->fec.pwid.type);
+               if (map->fec.pwid.type & CONTROL_WORD_FLAG) {
+                       map->flags |= F_MAP_PW_CWORD;
+                       map->fec.pwid.type &= ~CONTROL_WORD_FLAG;
+               }
+               off += sizeof(uint16_t);
+
+               /* PW info Length */
+               pw_len = buf[off];
+               off += sizeof(uint8_t);
+
+               if (len != FEC_PWID_ELM_MIN_LEN + pw_len) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+
+               /* Group ID */
+               memcpy(&map->fec.pwid.group_id, buf + off, sizeof(uint32_t));
+               map->fec.pwid.group_id = ntohl(map->fec.pwid.group_id);
+               off += sizeof(uint32_t);
+
+               /* PW ID */
+               if (pw_len == 0)
+                       return (off);
+
+               if (pw_len < sizeof(uint32_t)) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                           msg->type);
+                       return (-1);
+               }
+
+               memcpy(&map->fec.pwid.pwid, buf + off, sizeof(uint32_t));
+               map->fec.pwid.pwid = ntohl(map->fec.pwid.pwid);
+               map->flags |= F_MAP_PW_ID;
+               off += sizeof(uint32_t);
+               pw_len -= sizeof(uint32_t);
+
+               /* Optional Interface Parameter Sub-TLVs */
+               while (pw_len > 0) {
+                       struct subtlv   stlv;
+
+                       if (pw_len < sizeof(stlv)) {
+                               session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                                   msg->type);
+                               return (-1);
+                       }
+
+                       memcpy(&stlv, buf + off, sizeof(stlv));
+                       if (stlv.length > pw_len) {
+                               session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                                   msg->type);
+                               return (-1);
+                       }
+
+                       switch (stlv.type) {
+                       case SUBTLV_IFMTU:
+                               if (stlv.length != FEC_SUBTLV_IFMTU_SIZE) {
+                                       session_shutdown(nbr, S_BAD_TLV_LEN,
+                                           msg->id, msg->type);
+                                       return (-1);
+                               }
+                               memcpy(&map->fec.pwid.ifmtu, buf + off +
+                                   SUBTLV_HDR_SIZE, sizeof(uint16_t));
+                               map->fec.pwid.ifmtu = ntohs(map->fec.pwid.ifmtu);
+                               map->flags |= F_MAP_PW_IFMTU;
+                               break;
+                       default:
+                               /* ignore */
+                               break;
+                       }
+                       off += stlv.length;
+                       pw_len -= stlv.length;
+               }
+
+               return (off);
+       default:
+               send_notification_nbr(nbr, S_UNKNOWN_FEC, msg->id, msg->type);
+               break;
+       }
+
+       return (-1);
+}
diff --git a/ldpd/lde.c b/ldpd/lde.c
new file mode 100644 (file)
index 0000000..8859ed2
--- /dev/null
@@ -0,0 +1,1335 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2004, 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netmpls/mpls.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "ldp.h"
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+#include "lde.h"
+
+static void             lde_sig_handler(int sig, short, void *);
+static __dead void      lde_shutdown(void);
+static int              lde_imsg_compose_parent(int, pid_t, void *, uint16_t);
+static void             lde_dispatch_imsg(int, short, void *);
+static void             lde_dispatch_parent(int, short, void *);
+static __inline                 int lde_nbr_compare(struct lde_nbr *,
+                           struct lde_nbr *);
+static struct lde_nbr  *lde_nbr_new(uint32_t, struct lde_nbr *);
+static void             lde_nbr_del(struct lde_nbr *);
+static struct lde_nbr  *lde_nbr_find(uint32_t);
+static void             lde_nbr_clear(void);
+static void             lde_nbr_addr_update(struct lde_nbr *,
+                           struct lde_addr *, int);
+static void             lde_map_free(void *);
+static int              lde_address_add(struct lde_nbr *, struct lde_addr *);
+static int              lde_address_del(struct lde_nbr *, struct lde_addr *);
+static void             lde_address_list_free(struct lde_nbr *);
+
+RB_GENERATE(nbr_tree, lde_nbr, entry, lde_nbr_compare)
+
+struct ldpd_conf       *ldeconf;
+struct nbr_tree                 lde_nbrs = RB_INITIALIZER(&lde_nbrs);
+
+static struct imsgev   *iev_ldpe;
+static struct imsgev   *iev_main;
+
+/* ARGSUSED */
+static void
+lde_sig_handler(int sig, short event, void *arg)
+{
+       /*
+        * signal handler rules don't apply, libevent decouples for us
+        */
+
+       switch (sig) {
+       case SIGINT:
+       case SIGTERM:
+               lde_shutdown();
+               /* NOTREACHED */
+       default:
+               fatalx("unexpected signal");
+       }
+}
+
+/* label decision engine */
+void
+lde(int debug, int verbose)
+{
+       struct event             ev_sigint, ev_sigterm;
+       struct timeval           now;
+       struct passwd           *pw;
+
+       ldeconf = config_new_empty();
+
+       log_init(debug);
+       log_verbose(verbose);
+
+       setproctitle("label decision engine");
+       ldpd_process = PROC_LDE_ENGINE;
+
+       if ((pw = getpwnam(LDPD_USER)) == NULL)
+               fatal("getpwnam");
+
+       if (chroot(pw->pw_dir) == -1)
+               fatal("chroot");
+       if (chdir("/") == -1)
+               fatal("chdir(\"/\")");
+
+       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");
+
+       event_init();
+
+       /* setup signal handler */
+       signal_set(&ev_sigint, SIGINT, lde_sig_handler, NULL);
+       signal_set(&ev_sigterm, SIGTERM, lde_sig_handler, NULL);
+       signal_add(&ev_sigint, NULL);
+       signal_add(&ev_sigterm, NULL);
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGHUP, SIG_IGN);
+
+       /* setup pipe and event handler to the parent process */
+       if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
+               fatal(NULL);
+       imsg_init(&iev_main->ibuf, 3);
+       iev_main->handler = lde_dispatch_parent;
+       iev_main->events = EV_READ;
+       event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+           iev_main->handler, iev_main);
+       event_add(&iev_main->ev, NULL);
+
+       /* setup and start the LIB garbage collector */
+       evtimer_set(&gc_timer, lde_gc_timer, NULL);
+       lde_gc_start_timer();
+
+       gettimeofday(&now, NULL);
+       global.uptime = now.tv_sec;
+
+       event_dispatch();
+
+       lde_shutdown();
+}
+
+static __dead void
+lde_shutdown(void)
+{
+       /* close pipes */
+       msgbuf_clear(&iev_ldpe->ibuf.w);
+       close(iev_ldpe->ibuf.fd);
+       msgbuf_clear(&iev_main->ibuf.w);
+       close(iev_main->ibuf.fd);
+
+       lde_gc_stop_timer();
+       lde_nbr_clear();
+       fec_tree_clear();
+
+       config_clear(ldeconf);
+
+       free(iev_ldpe);
+       free(iev_main);
+
+       log_info("label decision engine exiting");
+       exit(0);
+}
+
+/* imesg */
+static int
+lde_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen)
+{
+       return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen));
+}
+
+int
+lde_imsg_compose_ldpe(int type, uint32_t peerid, pid_t pid, void *data,
+    uint16_t datalen)
+{
+       return (imsg_compose_event(iev_ldpe, type, peerid, pid,
+            -1, data, datalen));
+}
+
+/* ARGSUSED */
+static void
+lde_dispatch_imsg(int fd, short event, void *bula)
+{
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       struct imsg              imsg;
+       struct lde_nbr          *ln;
+       struct map               map;
+       struct lde_addr          lde_addr;
+       struct notify_msg        nm;
+       ssize_t                  n;
+       int                      shut = 0, verbose;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("lde_dispatch_imsg: imsg_get error");
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_LABEL_MAPPING_FULL:
+                       ln = lde_nbr_find(imsg.hdr.peerid);
+                       if (ln == NULL) {
+                               log_debug("%s: cannot find lde neighbor",
+                                   __func__);
+                               break;
+                       }
+
+                       fec_snap(ln);
+                       break;
+               case IMSG_LABEL_MAPPING:
+               case IMSG_LABEL_REQUEST:
+               case IMSG_LABEL_RELEASE:
+               case IMSG_LABEL_WITHDRAW:
+               case IMSG_LABEL_ABORT:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(map))
+                               fatalx("lde_dispatch_imsg: wrong imsg len");
+                       memcpy(&map, imsg.data, sizeof(map));
+
+                       ln = lde_nbr_find(imsg.hdr.peerid);
+                       if (ln == NULL) {
+                               log_debug("%s: cannot find lde neighbor",
+                                   __func__);
+                               break;
+                       }
+
+                       switch (imsg.hdr.type) {
+                       case IMSG_LABEL_MAPPING:
+                               lde_check_mapping(&map, ln);
+                               break;
+                       case IMSG_LABEL_REQUEST:
+                               lde_check_request(&map, ln);
+                               break;
+                       case IMSG_LABEL_RELEASE:
+                               if (map.type == MAP_TYPE_WILDCARD)
+                                       lde_check_release_wcard(&map, ln);
+                               else
+                                       lde_check_release(&map, ln);
+                               break;
+                       case IMSG_LABEL_WITHDRAW:
+                               if (map.type == MAP_TYPE_WILDCARD)
+                                       lde_check_withdraw_wcard(&map, ln);
+                               else
+                                       lde_check_withdraw(&map, ln);
+                               break;
+                       case IMSG_LABEL_ABORT:
+                               /* not necessary */
+                               break;
+                       }
+                       break;
+               case IMSG_ADDRESS_ADD:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(lde_addr))
+                               fatalx("lde_dispatch_imsg: wrong imsg len");
+                       memcpy(&lde_addr, imsg.data, sizeof(lde_addr));
+
+                       ln = lde_nbr_find(imsg.hdr.peerid);
+                       if (ln == NULL) {
+                               log_debug("%s: cannot find lde neighbor",
+                                   __func__);
+                               break;
+                       }
+                       if (lde_address_add(ln, &lde_addr) < 0) {
+                               log_debug("%s: cannot add address %s, it "
+                                   "already exists", __func__,
+                                   log_addr(lde_addr.af, &lde_addr.addr));
+                       }
+                       break;
+               case IMSG_ADDRESS_DEL:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(lde_addr))
+                               fatalx("lde_dispatch_imsg: wrong imsg len");
+                       memcpy(&lde_addr, imsg.data, sizeof(lde_addr));
+
+                       ln = lde_nbr_find(imsg.hdr.peerid);
+                       if (ln == NULL) {
+                               log_debug("%s: cannot find lde neighbor",
+                                   __func__);
+                               break;
+                       }
+                       if (lde_address_del(ln, &lde_addr) < 0) {
+                               log_debug("%s: cannot delete address %s, it "
+                                   "does not exist", __func__,
+                                   log_addr(lde_addr.af, &lde_addr.addr));
+                       }
+                       break;
+               case IMSG_NOTIFICATION:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(nm))
+                               fatalx("lde_dispatch_imsg: wrong imsg len");
+                       memcpy(&nm, imsg.data, sizeof(nm));
+
+                       ln = lde_nbr_find(imsg.hdr.peerid);
+                       if (ln == NULL) {
+                               log_debug("%s: cannot find lde neighbor",
+                                   __func__);
+                               break;
+                       }
+
+                       switch (nm.status_code) {
+                       case S_PW_STATUS:
+                               l2vpn_recv_pw_status(ln, &nm);
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               case IMSG_NEIGHBOR_UP:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(struct lde_nbr))
+                               fatalx("lde_dispatch_imsg: wrong imsg len");
+
+                       if (lde_nbr_find(imsg.hdr.peerid))
+                               fatalx("lde_dispatch_imsg: "
+                                   "neighbor already exists");
+                       lde_nbr_new(imsg.hdr.peerid, imsg.data);
+                       break;
+               case IMSG_NEIGHBOR_DOWN:
+                       lde_nbr_del(lde_nbr_find(imsg.hdr.peerid));
+                       break;
+               case IMSG_CTL_SHOW_LIB:
+                       rt_dump(imsg.hdr.pid);
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_END, 0,
+                           imsg.hdr.pid, NULL, 0);
+                       break;
+               case IMSG_CTL_SHOW_L2VPN_PW:
+                       l2vpn_pw_ctl(imsg.hdr.pid);
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_END, 0,
+                           imsg.hdr.pid, NULL, 0);
+                       break;
+               case IMSG_CTL_SHOW_L2VPN_BINDING:
+                       l2vpn_binding_ctl(imsg.hdr.pid);
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_END, 0,
+                           imsg.hdr.pid, NULL, 0);
+                       break;
+               case IMSG_CTL_LOG_VERBOSE:
+                       /* already checked by ldpe */
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+                       log_verbose(verbose);
+                       break;
+               default:
+                       log_debug("%s: unexpected imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* this pipe is dead, so remove the event handler */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+/* ARGSUSED */
+static void
+lde_dispatch_parent(int fd, short event, void *bula)
+{
+       static struct ldpd_conf *nconf;
+       struct iface            *niface;
+       struct tnbr             *ntnbr;
+       struct nbr_params       *nnbrp;
+       static struct l2vpn     *nl2vpn;
+       struct l2vpn_if         *nlif;
+       struct l2vpn_pw         *npw;
+       struct imsg              imsg;
+       struct kroute            kr;
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       ssize_t                  n;
+       int                      shut = 0;
+       struct fec               fec;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("lde_dispatch_parent: imsg_get error");
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_NETWORK_ADD:
+               case IMSG_NETWORK_DEL:
+                       if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof(kr)) {
+                               log_warnx("%s: wrong imsg len", __func__);
+                               break;
+                       }
+                       memcpy(&kr, imsg.data, sizeof(kr));
+
+                       switch (kr.af) {
+                       case AF_INET:
+                               fec.type = FEC_TYPE_IPV4;
+                               fec.u.ipv4.prefix = kr.prefix.v4;
+                               fec.u.ipv4.prefixlen = kr.prefixlen;
+                               break;
+                       case AF_INET6:
+                               fec.type = FEC_TYPE_IPV6;
+                               fec.u.ipv6.prefix = kr.prefix.v6;
+                               fec.u.ipv6.prefixlen = kr.prefixlen;
+                               break;
+                       default:
+                               fatalx("lde_dispatch_parent: unknown af");
+                       }
+
+                       switch (imsg.hdr.type) {
+                       case IMSG_NETWORK_ADD:
+                               lde_kernel_insert(&fec, kr.af, &kr.nexthop,
+                                   kr.priority, kr.flags & F_CONNECTED, NULL);
+                               break;
+                       case IMSG_NETWORK_DEL:
+                               lde_kernel_remove(&fec, kr.af, &kr.nexthop,
+                                   kr.priority);
+                               break;
+                       }
+                       break;
+               case IMSG_SOCKET_IPC:
+                       if (iev_ldpe) {
+                               log_warnx("%s: received unexpected imsg fd "
+                                   "to ldpe", __func__);
+                               break;
+                       }
+                       if ((fd = imsg.fd) == -1) {
+                               log_warnx("%s: expected to receive imsg fd to "
+                                   "ldpe but didn't receive any", __func__);
+                               break;
+                       }
+
+                       if ((iev_ldpe = malloc(sizeof(struct imsgev))) == NULL)
+                               fatal(NULL);
+                       imsg_init(&iev_ldpe->ibuf, fd);
+                       iev_ldpe->handler = lde_dispatch_imsg;
+                       iev_ldpe->events = EV_READ;
+                       event_set(&iev_ldpe->ev, iev_ldpe->ibuf.fd,
+                           iev_ldpe->events, iev_ldpe->handler, iev_ldpe);
+                       event_add(&iev_ldpe->ev, NULL);
+                       break;
+               case IMSG_RECONF_CONF:
+                       if ((nconf = malloc(sizeof(struct ldpd_conf))) ==
+                           NULL)
+                               fatal(NULL);
+                       memcpy(nconf, imsg.data, sizeof(struct ldpd_conf));
+
+                       LIST_INIT(&nconf->iface_list);
+                       LIST_INIT(&nconf->tnbr_list);
+                       LIST_INIT(&nconf->nbrp_list);
+                       LIST_INIT(&nconf->l2vpn_list);
+                       break;
+               case IMSG_RECONF_IFACE:
+                       if ((niface = malloc(sizeof(struct iface))) == NULL)
+                               fatal(NULL);
+                       memcpy(niface, imsg.data, sizeof(struct iface));
+
+                       LIST_INIT(&niface->addr_list);
+                       LIST_INIT(&niface->ipv4.adj_list);
+                       LIST_INIT(&niface->ipv6.adj_list);
+                       niface->ipv4.iface = niface;
+                       niface->ipv6.iface = niface;
+
+                       LIST_INSERT_HEAD(&nconf->iface_list, niface, entry);
+                       break;
+               case IMSG_RECONF_TNBR:
+                       if ((ntnbr = malloc(sizeof(struct tnbr))) == NULL)
+                               fatal(NULL);
+                       memcpy(ntnbr, imsg.data, sizeof(struct tnbr));
+
+                       LIST_INSERT_HEAD(&nconf->tnbr_list, ntnbr, entry);
+                       break;
+               case IMSG_RECONF_NBRP:
+                       if ((nnbrp = malloc(sizeof(struct nbr_params))) == NULL)
+                               fatal(NULL);
+                       memcpy(nnbrp, imsg.data, sizeof(struct nbr_params));
+
+                       LIST_INSERT_HEAD(&nconf->nbrp_list, nnbrp, entry);
+                       break;
+               case IMSG_RECONF_L2VPN:
+                       if ((nl2vpn = malloc(sizeof(struct l2vpn))) == NULL)
+                               fatal(NULL);
+                       memcpy(nl2vpn, imsg.data, sizeof(struct l2vpn));
+
+                       LIST_INIT(&nl2vpn->if_list);
+                       LIST_INIT(&nl2vpn->pw_list);
+
+                       LIST_INSERT_HEAD(&nconf->l2vpn_list, nl2vpn, entry);
+                       break;
+               case IMSG_RECONF_L2VPN_IF:
+                       if ((nlif = malloc(sizeof(struct l2vpn_if))) == NULL)
+                               fatal(NULL);
+                       memcpy(nlif, imsg.data, sizeof(struct l2vpn_if));
+
+                       nlif->l2vpn = nl2vpn;
+                       LIST_INSERT_HEAD(&nl2vpn->if_list, nlif, entry);
+                       break;
+               case IMSG_RECONF_L2VPN_PW:
+                       if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL)
+                               fatal(NULL);
+                       memcpy(npw, imsg.data, sizeof(struct l2vpn_pw));
+
+                       npw->l2vpn = nl2vpn;
+                       LIST_INSERT_HEAD(&nl2vpn->pw_list, npw, entry);
+                       break;
+               case IMSG_RECONF_END:
+                       merge_config(ldeconf, nconf);
+                       nconf = NULL;
+                       break;
+               default:
+                       log_debug("%s: unexpected imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* this pipe is dead, so remove the event handler */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+uint32_t
+lde_assign_label(void)
+{
+       static uint32_t label = MPLS_LABEL_RESERVED_MAX;
+
+       /* XXX some checks needed */
+       label++;
+       return (label);
+}
+
+void
+lde_send_change_klabel(struct fec_node *fn, struct fec_nh *fnh)
+{
+       struct kroute   kr;
+       struct kpw      kpw;
+       struct l2vpn_pw *pw;
+
+       switch (fn->fec.type) {
+       case FEC_TYPE_IPV4:
+               memset(&kr, 0, sizeof(kr));
+               kr.af = AF_INET;
+               kr.prefix.v4 = fn->fec.u.ipv4.prefix;
+               kr.prefixlen = fn->fec.u.ipv4.prefixlen;
+               kr.nexthop.v4 = fnh->nexthop.v4;
+               kr.local_label = fn->local_label;
+               kr.remote_label = fnh->remote_label;
+               kr.priority = fnh->priority;
+
+               lde_imsg_compose_parent(IMSG_KLABEL_CHANGE, 0, &kr,
+                   sizeof(kr));
+
+               if (fn->fec.u.ipv4.prefixlen == 32)
+                       l2vpn_sync_pws(AF_INET, (union ldpd_addr *)
+                           &fn->fec.u.ipv4.prefix);
+               break;
+       case FEC_TYPE_IPV6:
+               memset(&kr, 0, sizeof(kr));
+               kr.af = AF_INET6;
+               kr.prefix.v6 = fn->fec.u.ipv6.prefix;
+               kr.prefixlen = fn->fec.u.ipv6.prefixlen;
+               kr.nexthop.v6 = fnh->nexthop.v6;
+               kr.local_label = fn->local_label;
+               kr.remote_label = fnh->remote_label;
+               kr.priority = fnh->priority;
+
+               lde_imsg_compose_parent(IMSG_KLABEL_CHANGE, 0, &kr,
+                   sizeof(kr));
+
+               if (fn->fec.u.ipv6.prefixlen == 128)
+                       l2vpn_sync_pws(AF_INET6, (union ldpd_addr *)
+                           &fn->fec.u.ipv6.prefix);
+               break;
+       case FEC_TYPE_PWID:
+               if (fn->local_label == NO_LABEL ||
+                   fnh->remote_label == NO_LABEL)
+                       return;
+
+               pw = (struct l2vpn_pw *) fn->data;
+               pw->flags |= F_PW_STATUS_UP;
+
+               memset(&kpw, 0, sizeof(kpw));
+               kpw.ifindex = pw->ifindex;
+               kpw.pw_type = fn->fec.u.pwid.type;
+               kpw.af = pw->af;
+               kpw.nexthop = pw->addr;
+               kpw.local_label = fn->local_label;
+               kpw.remote_label = fnh->remote_label;
+               kpw.flags = pw->flags;
+
+               lde_imsg_compose_parent(IMSG_KPWLABEL_CHANGE, 0, &kpw,
+                   sizeof(kpw));
+               break;
+       }
+}
+
+void
+lde_send_delete_klabel(struct fec_node *fn, struct fec_nh *fnh)
+{
+       struct kroute    kr;
+       struct kpw       kpw;
+       struct l2vpn_pw *pw;
+
+       switch (fn->fec.type) {
+       case FEC_TYPE_IPV4:
+               memset(&kr, 0, sizeof(kr));
+               kr.af = AF_INET;
+               kr.prefix.v4 = fn->fec.u.ipv4.prefix;
+               kr.prefixlen = fn->fec.u.ipv4.prefixlen;
+               kr.nexthop.v4 = fnh->nexthop.v4;
+               kr.local_label = fn->local_label;
+               kr.remote_label = fnh->remote_label;
+               kr.priority = fnh->priority;
+
+               lde_imsg_compose_parent(IMSG_KLABEL_DELETE, 0, &kr,
+                   sizeof(kr));
+
+               if (fn->fec.u.ipv4.prefixlen == 32)
+                       l2vpn_sync_pws(AF_INET, (union ldpd_addr *)
+                           &fn->fec.u.ipv4.prefix);
+               break;
+       case FEC_TYPE_IPV6:
+               memset(&kr, 0, sizeof(kr));
+               kr.af = AF_INET6;
+               kr.prefix.v6 = fn->fec.u.ipv6.prefix;
+               kr.prefixlen = fn->fec.u.ipv6.prefixlen;
+               kr.nexthop.v6 = fnh->nexthop.v6;
+               kr.local_label = fn->local_label;
+               kr.remote_label = fnh->remote_label;
+               kr.priority = fnh->priority;
+
+               lde_imsg_compose_parent(IMSG_KLABEL_DELETE, 0, &kr,
+                   sizeof(kr));
+
+               if (fn->fec.u.ipv6.prefixlen == 128)
+                       l2vpn_sync_pws(AF_INET6, (union ldpd_addr *)
+                           &fn->fec.u.ipv6.prefix);
+               break;
+       case FEC_TYPE_PWID:
+               pw = (struct l2vpn_pw *) fn->data;
+               if (!(pw->flags & F_PW_STATUS_UP))
+                       return;
+               pw->flags &= ~F_PW_STATUS_UP;
+
+               memset(&kpw, 0, sizeof(kpw));
+               kpw.ifindex = pw->ifindex;
+               kpw.pw_type = fn->fec.u.pwid.type;
+               kpw.af = pw->af;
+               kpw.nexthop = pw->addr;
+               kpw.local_label = fn->local_label;
+               kpw.remote_label = fnh->remote_label;
+               kpw.flags = pw->flags;
+
+               lde_imsg_compose_parent(IMSG_KPWLABEL_DELETE, 0, &kpw,
+                   sizeof(kpw));
+               break;
+       }
+}
+
+void
+lde_fec2map(struct fec *fec, struct map *map)
+{
+       memset(map, 0, sizeof(*map));
+
+       switch (fec->type) {
+       case FEC_TYPE_IPV4:
+               map->type = MAP_TYPE_PREFIX;
+               map->fec.prefix.af = AF_INET;
+               map->fec.prefix.prefix.v4 = fec->u.ipv4.prefix;
+               map->fec.prefix.prefixlen = fec->u.ipv4.prefixlen;
+               break;
+       case FEC_TYPE_IPV6:
+               map->type = MAP_TYPE_PREFIX;
+               map->fec.prefix.af = AF_INET6;
+               map->fec.prefix.prefix.v6 = fec->u.ipv6.prefix;
+               map->fec.prefix.prefixlen = fec->u.ipv6.prefixlen;
+               break;
+       case FEC_TYPE_PWID:
+               map->type = MAP_TYPE_PWID;
+               map->fec.pwid.type = fec->u.pwid.type;
+               map->fec.pwid.group_id = 0;
+               map->flags |= F_MAP_PW_ID;
+               map->fec.pwid.pwid = fec->u.pwid.pwid;
+               break;
+       }
+}
+
+void
+lde_map2fec(struct map *map, struct in_addr lsr_id, struct fec *fec)
+{
+       memset(fec, 0, sizeof(*fec));
+
+       switch (map->type) {
+       case MAP_TYPE_PREFIX:
+               switch (map->fec.prefix.af) {
+               case AF_INET:
+                       fec->type = FEC_TYPE_IPV4;
+                       fec->u.ipv4.prefix = map->fec.prefix.prefix.v4;
+                       fec->u.ipv4.prefixlen = map->fec.prefix.prefixlen;
+                       break;
+               case AF_INET6:
+                       fec->type = FEC_TYPE_IPV6;
+                       fec->u.ipv6.prefix = map->fec.prefix.prefix.v6;
+                       fec->u.ipv6.prefixlen = map->fec.prefix.prefixlen;
+                       break;
+               default:
+                       fatalx("lde_map2fec: unknown af");
+                       break;
+               }
+               break;
+       case MAP_TYPE_PWID:
+               fec->type = FEC_TYPE_PWID;
+               fec->u.pwid.type = map->fec.pwid.type;
+               fec->u.pwid.pwid = map->fec.pwid.pwid;
+               fec->u.pwid.lsr_id = lsr_id;
+               break;
+       }
+}
+
+void
+lde_send_labelmapping(struct lde_nbr *ln, struct fec_node *fn, int single)
+{
+       struct lde_req  *lre;
+       struct lde_map  *me;
+       struct map       map;
+       struct l2vpn_pw *pw;
+
+       /*
+        * This function skips SL.1 - 3 and SL.9 - 14 because the label
+        * allocation is done way earlier (because of the merging nature of
+        * ldpd).
+        */
+
+       lde_fec2map(&fn->fec, &map);
+       switch (fn->fec.type) {
+       case FEC_TYPE_IPV4:
+               if (!ln->v4_enabled)
+                       return;
+               break;
+       case FEC_TYPE_IPV6:
+               if (!ln->v6_enabled)
+                       return;
+               break;
+       case FEC_TYPE_PWID:
+               pw = (struct l2vpn_pw *) fn->data;
+               if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr)
+                       /* not the remote end of the pseudowire */
+                       return;
+
+               map.flags |= F_MAP_PW_IFMTU;
+               map.fec.pwid.ifmtu = pw->l2vpn->mtu;
+               if (pw->flags & F_PW_CWORD)
+                       map.flags |= F_MAP_PW_CWORD;
+               if (pw->flags & F_PW_STATUSTLV) {
+                       map.flags |= F_MAP_PW_STATUS;
+                       /* VPLS are always up */
+                       map.pw_status = PW_FORWARDING;
+               }
+               break;
+       }
+       map.label = fn->local_label;
+
+       /* SL.6: is there a pending request for this mapping? */
+       lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec);
+       if (lre) {
+               /* set label request msg id in the mapping response. */
+               map.requestid = lre->msg_id;
+               map.flags = F_MAP_REQ_ID;
+
+               /* SL.7: delete record of pending request */
+               lde_req_del(ln, lre, 0);
+       }
+
+       /* SL.4: send label mapping */
+       lde_imsg_compose_ldpe(IMSG_MAPPING_ADD, ln->peerid, 0,
+           &map, sizeof(map));
+       if (single)
+               lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0,
+                   NULL, 0);
+
+       /* SL.5: record sent label mapping */
+       me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec);
+       if (me == NULL)
+               me = lde_map_add(ln, fn, 1);
+       me->map = map;
+}
+
+void
+lde_send_labelwithdraw(struct lde_nbr *ln, struct fec_node *fn, uint32_t label,
+    struct status_tlv *st)
+{
+       struct lde_wdraw        *lw;
+       struct map               map;
+       struct fec              *f;
+       struct l2vpn_pw         *pw;
+
+       if (fn) {
+               lde_fec2map(&fn->fec, &map);
+               switch (fn->fec.type) {
+               case FEC_TYPE_IPV4:
+                       if (!ln->v4_enabled)
+                               return;
+                       break;
+               case FEC_TYPE_IPV6:
+                       if (!ln->v6_enabled)
+                               return;
+                       break;
+               case FEC_TYPE_PWID:
+                       pw = (struct l2vpn_pw *) fn->data;
+                       if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr)
+                               /* not the remote end of the pseudowire */
+                               return;
+
+                       if (pw->flags & F_PW_CWORD)
+                               map.flags |= F_MAP_PW_CWORD;
+                       break;
+               }
+               map.label = fn->local_label;
+       } else {
+               memset(&map, 0, sizeof(map));
+               map.type = MAP_TYPE_WILDCARD;
+               map.label = label;
+       }
+
+       if (st) {
+               map.st.status_code = st->status_code;
+               map.st.msg_id = st->msg_id;
+               map.st.msg_type = st->msg_type;
+               map.flags |= F_MAP_STATUS;
+       }
+
+       /* SWd.1: send label withdraw. */
+       lde_imsg_compose_ldpe(IMSG_WITHDRAW_ADD, ln->peerid, 0,
+           &map, sizeof(map));
+       lde_imsg_compose_ldpe(IMSG_WITHDRAW_ADD_END, ln->peerid, 0, NULL, 0);
+
+       /* SWd.2: record label withdraw. */
+       if (fn) {
+               lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec);
+               if (lw == NULL)
+                       lw = lde_wdraw_add(ln, fn);
+               lw->label = map.label;
+       } else {
+               RB_FOREACH(f, fec_tree, &ft) {
+                       fn = (struct fec_node *)f;
+
+                       lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw,
+                           &fn->fec);
+                       if (lw == NULL)
+                               lw = lde_wdraw_add(ln, fn);
+                       lw->label = map.label;
+               }
+       }
+}
+
+void
+lde_send_labelwithdraw_all(struct fec_node *fn, uint32_t label)
+{
+       struct lde_nbr          *ln;
+
+       RB_FOREACH(ln, nbr_tree, &lde_nbrs)
+               lde_send_labelwithdraw(ln, fn, label, NULL);
+}
+
+void
+lde_send_labelrelease(struct lde_nbr *ln, struct fec_node *fn, uint32_t label)
+{
+       struct map               map;
+       struct l2vpn_pw         *pw;
+
+       if (fn) {
+               lde_fec2map(&fn->fec, &map);
+               switch (fn->fec.type) {
+               case FEC_TYPE_IPV4:
+                       if (!ln->v4_enabled)
+                               return;
+                       break;
+               case FEC_TYPE_IPV6:
+                       if (!ln->v6_enabled)
+                               return;
+                       break;
+               case FEC_TYPE_PWID:
+                       pw = (struct l2vpn_pw *) fn->data;
+                       if (pw == NULL || pw->lsr_id.s_addr != ln->id.s_addr)
+                               /* not the remote end of the pseudowire */
+                               return;
+
+                       if (pw->flags & F_PW_CWORD)
+                               map.flags |= F_MAP_PW_CWORD;
+                       break;
+               }
+       } else {
+               memset(&map, 0, sizeof(map));
+               map.type = MAP_TYPE_WILDCARD;
+       }
+       map.label = label;
+
+       lde_imsg_compose_ldpe(IMSG_RELEASE_ADD, ln->peerid, 0,
+           &map, sizeof(map));
+       lde_imsg_compose_ldpe(IMSG_RELEASE_ADD_END, ln->peerid, 0, NULL, 0);
+}
+
+void
+lde_send_notification(uint32_t peerid, uint32_t status_code, uint32_t msg_id,
+    uint16_t msg_type)
+{
+       struct notify_msg nm;
+
+       memset(&nm, 0, sizeof(nm));
+       nm.status_code = status_code;
+       /* 'msg_id' and 'msg_type' should be in network byte order */
+       nm.msg_id = msg_id;
+       nm.msg_type = msg_type;
+
+       lde_imsg_compose_ldpe(IMSG_NOTIFICATION_SEND, peerid, 0,
+           &nm, sizeof(nm));
+}
+
+static __inline int
+lde_nbr_compare(struct lde_nbr *a, struct lde_nbr *b)
+{
+       return (a->peerid - b->peerid);
+}
+
+static struct lde_nbr *
+lde_nbr_new(uint32_t peerid, struct lde_nbr *new)
+{
+       struct lde_nbr  *ln;
+
+       if ((ln = calloc(1, sizeof(*ln))) == NULL)
+               fatal(__func__);
+
+       ln->id = new->id;
+       ln->v4_enabled = new->v4_enabled;
+       ln->v6_enabled = new->v6_enabled;
+       ln->peerid = peerid;
+       fec_init(&ln->recv_map);
+       fec_init(&ln->sent_map);
+       fec_init(&ln->recv_req);
+       fec_init(&ln->sent_req);
+       fec_init(&ln->sent_wdraw);
+
+       TAILQ_INIT(&ln->addr_list);
+
+       if (RB_INSERT(nbr_tree, &lde_nbrs, ln) != NULL)
+               fatalx("lde_nbr_new: RB_INSERT failed");
+
+       return (ln);
+}
+
+static void
+lde_nbr_del(struct lde_nbr *ln)
+{
+       struct fec              *f;
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+       struct l2vpn_pw         *pw;
+
+       if (ln == NULL)
+               return;
+
+       /* uninstall received mappings */
+       RB_FOREACH(f, fec_tree, &ft) {
+               fn = (struct fec_node *)f;
+
+               LIST_FOREACH(fnh, &fn->nexthops, entry) {
+                       switch (f->type) {
+                       case FEC_TYPE_IPV4:
+                       case FEC_TYPE_IPV6:
+                               if (!lde_address_find(ln, fnh->af,
+                                   &fnh->nexthop))
+                                       continue;
+                               break;
+                       case FEC_TYPE_PWID:
+                               if (f->u.pwid.lsr_id.s_addr != ln->id.s_addr)
+                                       continue;
+                               pw = (struct l2vpn_pw *) fn->data;
+                               if (pw)
+                                       l2vpn_pw_reset(pw);
+                               break;
+                       default:
+                               break;
+                       }
+
+                       lde_send_delete_klabel(fn, fnh);
+                       fnh->remote_label = NO_LABEL;
+               }
+       }
+
+       lde_address_list_free(ln);
+
+       fec_clear(&ln->recv_map, lde_map_free);
+       fec_clear(&ln->sent_map, lde_map_free);
+       fec_clear(&ln->recv_req, free);
+       fec_clear(&ln->sent_req, free);
+       fec_clear(&ln->sent_wdraw, free);
+
+       RB_REMOVE(nbr_tree, &lde_nbrs, ln);
+
+       free(ln);
+}
+
+static struct lde_nbr *
+lde_nbr_find(uint32_t peerid)
+{
+       struct lde_nbr           ln;
+
+       ln.peerid = peerid;
+
+       return (RB_FIND(nbr_tree, &lde_nbrs, &ln));
+}
+
+struct lde_nbr *
+lde_nbr_find_by_lsrid(struct in_addr addr)
+{
+       struct lde_nbr          *ln;
+
+       RB_FOREACH(ln, nbr_tree, &lde_nbrs)
+               if (ln->id.s_addr == addr.s_addr)
+                       return (ln);
+
+       return (NULL);
+}
+
+struct lde_nbr *
+lde_nbr_find_by_addr(int af, union ldpd_addr *addr)
+{
+       struct lde_nbr          *ln;
+
+       RB_FOREACH(ln, nbr_tree, &lde_nbrs)
+               if (lde_address_find(ln, af, addr) != NULL)
+                       return (ln);
+
+       return (NULL);
+}
+
+static void
+lde_nbr_clear(void)
+{
+       struct lde_nbr  *ln;
+
+        while ((ln = RB_ROOT(&lde_nbrs)) != NULL)
+               lde_nbr_del(ln);
+}
+
+static void
+lde_nbr_addr_update(struct lde_nbr *ln, struct lde_addr *lde_addr, int removed)
+{
+       struct fec              *fec;
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+       struct lde_map          *me;
+
+       RB_FOREACH(fec, fec_tree, &ln->recv_map) {
+               fn = (struct fec_node *)fec_find(&ft, fec);
+               switch (fec->type) {
+               case FEC_TYPE_IPV4:
+                       if (lde_addr->af != AF_INET)
+                               continue;
+                       break;
+               case FEC_TYPE_IPV6:
+                       if (lde_addr->af != AF_INET6)
+                               continue;
+                       break;
+               default:
+                       continue;
+               }
+
+               LIST_FOREACH(fnh, &fn->nexthops, entry) {
+                       if (ldp_addrcmp(fnh->af, &fnh->nexthop,
+                           &lde_addr->addr))
+                               continue;
+
+                       if (removed) {
+                               lde_send_delete_klabel(fn, fnh);
+                               fnh->remote_label = NO_LABEL;
+                       } else {
+                               me = (struct lde_map *)fec;
+                               fnh->remote_label = me->map.label;
+                               lde_send_change_klabel(fn, fnh);
+                       }
+                       break;
+               }
+       }
+}
+
+struct lde_map *
+lde_map_add(struct lde_nbr *ln, struct fec_node *fn, int sent)
+{
+       struct lde_map  *me;
+
+       me = calloc(1, sizeof(*me));
+       if (me == NULL)
+               fatal(__func__);
+
+       me->fec = fn->fec;
+       me->nexthop = ln;
+
+       if (sent) {
+               LIST_INSERT_HEAD(&fn->upstream, me, entry);
+               if (fec_insert(&ln->sent_map, &me->fec))
+                       log_warnx("failed to add %s to sent map",
+                           log_fec(&me->fec));
+                       /* XXX on failure more cleanup is needed */
+       } else {
+               LIST_INSERT_HEAD(&fn->downstream, me, entry);
+               if (fec_insert(&ln->recv_map, &me->fec))
+                       log_warnx("failed to add %s to recv map",
+                           log_fec(&me->fec));
+       }
+
+       return (me);
+}
+
+void
+lde_map_del(struct lde_nbr *ln, struct lde_map *me, int sent)
+{
+       if (sent)
+               fec_remove(&ln->sent_map, &me->fec);
+       else
+               fec_remove(&ln->recv_map, &me->fec);
+
+       lde_map_free(me);
+}
+
+static void
+lde_map_free(void *ptr)
+{
+       struct lde_map  *map = ptr;
+
+       LIST_REMOVE(map, entry);
+       free(map);
+}
+
+struct lde_req *
+lde_req_add(struct lde_nbr *ln, struct fec *fec, int sent)
+{
+       struct fec_tree *t;
+       struct lde_req  *lre;
+
+       t = sent ? &ln->sent_req : &ln->recv_req;
+
+       lre = calloc(1, sizeof(*lre));
+       if (lre != NULL) {
+               lre->fec = *fec;
+
+               if (fec_insert(t, &lre->fec)) {
+                       log_warnx("failed to add %s to %s req",
+                           log_fec(&lre->fec), sent ? "sent" : "recv");
+                       free(lre);
+                       return (NULL);
+               }
+       }
+
+       return (lre);
+}
+
+void
+lde_req_del(struct lde_nbr *ln, struct lde_req *lre, int sent)
+{
+       if (sent)
+               fec_remove(&ln->sent_req, &lre->fec);
+       else
+               fec_remove(&ln->recv_req, &lre->fec);
+
+       free(lre);
+}
+
+struct lde_wdraw *
+lde_wdraw_add(struct lde_nbr *ln, struct fec_node *fn)
+{
+       struct lde_wdraw  *lw;
+
+       lw = calloc(1, sizeof(*lw));
+       if (lw == NULL)
+               fatal(__func__);
+
+       lw->fec = fn->fec;
+
+       if (fec_insert(&ln->sent_wdraw, &lw->fec))
+               log_warnx("failed to add %s to sent wdraw",
+                   log_fec(&lw->fec));
+
+       return (lw);
+}
+
+void
+lde_wdraw_del(struct lde_nbr *ln, struct lde_wdraw *lw)
+{
+       fec_remove(&ln->sent_wdraw, &lw->fec);
+       free(lw);
+}
+
+void
+lde_change_egress_label(int af, int was_implicit)
+{
+       struct lde_nbr  *ln;
+       struct fec      *f;
+       struct fec_node *fn;
+
+       RB_FOREACH(ln, nbr_tree, &lde_nbrs) {
+               /* explicit withdraw */
+               if (was_implicit)
+                       lde_send_labelwithdraw(ln, NULL, MPLS_LABEL_IMPLNULL,
+                           NULL);
+               else {
+                       if (ln->v4_enabled)
+                               lde_send_labelwithdraw(ln, NULL,
+                                   MPLS_LABEL_IPV4NULL, NULL);
+                       if (ln->v6_enabled)
+                               lde_send_labelwithdraw(ln, NULL,
+                                   MPLS_LABEL_IPV6NULL, NULL);
+               }
+
+               /* advertise new label of connected prefixes */
+               RB_FOREACH(f, fec_tree, &ft) {
+                       fn = (struct fec_node *)f;
+                       if (fn->local_label > MPLS_LABEL_RESERVED_MAX)
+                               continue;
+
+                       switch (af) {
+                       case AF_INET:
+                               if (fn->fec.type != FEC_TYPE_IPV4)
+                                       continue;
+                               break;
+                       case AF_INET6:
+                               if (fn->fec.type != FEC_TYPE_IPV6)
+                                       continue;
+                               break;
+                       default:
+                               fatalx("lde_change_egress_label: unknown af");
+                       }
+
+                       fn->local_label = egress_label(fn->fec.type);
+                       lde_send_labelmapping(ln, fn, 0);
+               }
+
+               lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0,
+                   NULL, 0);
+       }
+}
+
+static int
+lde_address_add(struct lde_nbr *ln, struct lde_addr *lde_addr)
+{
+       struct lde_addr         *new;
+
+       if (lde_address_find(ln, lde_addr->af, &lde_addr->addr) != NULL)
+               return (-1);
+
+       if ((new = calloc(1, sizeof(*new))) == NULL)
+               fatal(__func__);
+
+       new->af = lde_addr->af;
+       new->addr = lde_addr->addr;
+       TAILQ_INSERT_TAIL(&ln->addr_list, new, entry);
+
+       /* reevaluate the previously received mappings from this neighbor */
+       lde_nbr_addr_update(ln, lde_addr, 0);
+
+       return (0);
+}
+
+static int
+lde_address_del(struct lde_nbr *ln, struct lde_addr *lde_addr)
+{
+       lde_addr = lde_address_find(ln, lde_addr->af, &lde_addr->addr);
+       if (lde_addr == NULL)
+               return (-1);
+
+       /* reevaluate the previously received mappings from this neighbor */
+       lde_nbr_addr_update(ln, lde_addr, 1);
+
+       TAILQ_REMOVE(&ln->addr_list, lde_addr, entry);
+       free(lde_addr);
+
+       return (0);
+}
+
+struct lde_addr *
+lde_address_find(struct lde_nbr *ln, int af, union ldpd_addr *addr)
+{
+       struct lde_addr         *lde_addr;
+
+       TAILQ_FOREACH(lde_addr, &ln->addr_list, entry)
+               if (lde_addr->af == af &&
+                   ldp_addrcmp(af, &lde_addr->addr, addr) == 0)
+                       return (lde_addr);
+
+       return (NULL);
+}
+
+static void
+lde_address_list_free(struct lde_nbr *ln)
+{
+       struct lde_addr         *lde_addr;
+
+       while ((lde_addr = TAILQ_FIRST(&ln->addr_list)) != NULL) {
+               TAILQ_REMOVE(&ln->addr_list, lde_addr, entry);
+               free(lde_addr);
+       }
+}
diff --git a/ldpd/lde.h b/ldpd/lde.h
new file mode 100644 (file)
index 0000000..b0f7b20
--- /dev/null
@@ -0,0 +1,202 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _LDE_H_
+#define _LDE_H_
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+enum fec_type {
+       FEC_TYPE_IPV4,
+       FEC_TYPE_IPV6,
+       FEC_TYPE_PWID
+};
+
+struct fec {
+       RB_ENTRY(fec)           entry;
+       enum fec_type           type;
+       union {
+               struct {
+                       struct in_addr  prefix;
+                       uint8_t         prefixlen;
+               } ipv4;
+               struct {
+                       struct in6_addr prefix;
+                       uint8_t         prefixlen;
+               } ipv6;
+               struct {
+                       uint16_t        type;
+                       uint32_t        pwid;
+                       struct in_addr  lsr_id;
+               } pwid;
+       } u;
+};
+RB_HEAD(fec_tree, fec);
+RB_PROTOTYPE(fec_tree, fec, entry, fec_compare)
+
+/* request entries */
+struct lde_req {
+       struct fec               fec;
+       uint32_t                 msg_id;
+};
+
+/* mapping entries */
+struct lde_map {
+       struct fec               fec;
+       LIST_ENTRY(lde_map)      entry;
+       struct lde_nbr          *nexthop;
+       struct map               map;
+};
+
+/* withdraw entries */
+struct lde_wdraw {
+       struct fec               fec;
+       uint32_t                 label;
+};
+
+/* Addresses belonging to neighbor */
+struct lde_addr {
+       TAILQ_ENTRY(lde_addr)    entry;
+       int                      af;
+       union ldpd_addr          addr;
+};
+
+/* just the info LDE needs */
+struct lde_nbr {
+       RB_ENTRY(lde_nbr)        entry;
+       uint32_t                 peerid;
+       struct in_addr           id;
+       int                      v4_enabled;    /* announce/process v4 msgs */
+       int                      v6_enabled;    /* announce/process v6 msgs */
+       struct fec_tree          recv_req;
+       struct fec_tree          sent_req;
+       struct fec_tree          recv_map;
+       struct fec_tree          sent_map;
+       struct fec_tree          sent_wdraw;
+       TAILQ_HEAD(, lde_addr)   addr_list;
+};
+RB_HEAD(nbr_tree, lde_nbr);
+RB_PROTOTYPE(nbr_tree, lde_nbr, entry, lde_nbr_compare)
+
+struct fec_nh {
+       LIST_ENTRY(fec_nh)       entry;
+       int                      af;
+       union ldpd_addr          nexthop;
+       uint32_t                 remote_label;
+       uint8_t                  priority;
+};
+
+struct fec_node {
+       struct fec               fec;
+
+       LIST_HEAD(, fec_nh)      nexthops;      /* fib nexthops */
+       LIST_HEAD(, lde_map)     downstream;    /* recv mappings */
+       LIST_HEAD(, lde_map)     upstream;      /* sent mappings */
+
+       uint32_t                 local_label;
+       void                    *data;          /* fec specific data */
+};
+
+#define LDE_GC_INTERVAL 300
+
+extern struct ldpd_conf        *ldeconf;
+extern struct fec_tree  ft;
+extern struct nbr_tree  lde_nbrs;
+extern struct event     gc_timer;
+
+/* lde.c */
+void            lde(int, int);
+int             lde_imsg_compose_ldpe(int, uint32_t, pid_t, void *, uint16_t);
+uint32_t        lde_assign_label(void);
+void            lde_send_change_klabel(struct fec_node *, struct fec_nh *);
+void            lde_send_delete_klabel(struct fec_node *, struct fec_nh *);
+void            lde_fec2map(struct fec *, struct map *);
+void            lde_map2fec(struct map *, struct in_addr, struct fec *);
+void            lde_send_labelmapping(struct lde_nbr *, struct fec_node *,
+                   int);
+void            lde_send_labelwithdraw(struct lde_nbr *, struct fec_node *,
+                   uint32_t, struct status_tlv *);
+void            lde_send_labelwithdraw_all(struct fec_node *, uint32_t);
+void            lde_send_labelrelease(struct lde_nbr *, struct fec_node *,
+                   uint32_t);
+void            lde_send_notification(uint32_t, uint32_t, uint32_t, uint16_t);
+struct lde_nbr *lde_nbr_find_by_lsrid(struct in_addr);
+struct lde_nbr *lde_nbr_find_by_addr(int, union ldpd_addr *);
+struct lde_map *lde_map_add(struct lde_nbr *, struct fec_node *, int);
+void            lde_map_del(struct lde_nbr *, struct lde_map *, int);
+struct lde_req *lde_req_add(struct lde_nbr *, struct fec *, int);
+void            lde_req_del(struct lde_nbr *, struct lde_req *, int);
+struct lde_wdraw *lde_wdraw_add(struct lde_nbr *, struct fec_node *);
+void            lde_wdraw_del(struct lde_nbr *, struct lde_wdraw *);
+void            lde_change_egress_label(int, int);
+struct lde_addr        *lde_address_find(struct lde_nbr *, int,
+                   union ldpd_addr *);
+
+/* lde_lib.c */
+void            fec_init(struct fec_tree *);
+struct fec     *fec_find(struct fec_tree *, struct fec *);
+int             fec_insert(struct fec_tree *, struct fec *);
+int             fec_remove(struct fec_tree *, struct fec *);
+void            fec_clear(struct fec_tree *, void (*)(void *));
+void            rt_dump(pid_t);
+void            fec_snap(struct lde_nbr *);
+void            fec_tree_clear(void);
+struct fec_nh  *fec_nh_find(struct fec_node *, int, union ldpd_addr *,
+                   uint8_t);
+uint32_t        egress_label(enum fec_type);
+void            lde_kernel_insert(struct fec *, int, union ldpd_addr *,
+                   uint8_t, int, void *);
+void            lde_kernel_remove(struct fec *, int, union ldpd_addr *,
+                   uint8_t);
+void            lde_check_mapping(struct map *, struct lde_nbr *);
+void            lde_check_request(struct map *, struct lde_nbr *);
+void            lde_check_release(struct map *, struct lde_nbr *);
+void            lde_check_release_wcard(struct map *, struct lde_nbr *);
+void            lde_check_withdraw(struct map *, struct lde_nbr *);
+void            lde_check_withdraw_wcard(struct map *, struct lde_nbr *);
+void            lde_gc_timer(int, short, void *);
+void            lde_gc_start_timer(void);
+void            lde_gc_stop_timer(void);
+
+/* l2vpn.c */
+struct l2vpn   *l2vpn_new(const char *);
+struct l2vpn   *l2vpn_find(struct ldpd_conf *, const char *);
+void            l2vpn_del(struct l2vpn *);
+void            l2vpn_init(struct l2vpn *);
+void            l2vpn_exit(struct l2vpn *);
+struct l2vpn_if        *l2vpn_if_new(struct l2vpn *, struct kif *);
+struct l2vpn_if        *l2vpn_if_find(struct l2vpn *, unsigned int);
+struct l2vpn_pw        *l2vpn_pw_new(struct l2vpn *, struct kif *);
+struct l2vpn_pw *l2vpn_pw_find(struct l2vpn *, unsigned int);
+void            l2vpn_pw_init(struct l2vpn_pw *);
+void            l2vpn_pw_exit(struct l2vpn_pw *);
+void            l2vpn_pw_reset(struct l2vpn_pw *);
+int             l2vpn_pw_ok(struct l2vpn_pw *, struct fec_nh *);
+int             l2vpn_pw_negotiate(struct lde_nbr *, struct fec_node *,
+                   struct map *);
+void            l2vpn_send_pw_status(uint32_t, uint32_t, struct fec *);
+void            l2vpn_recv_pw_status(struct lde_nbr *, struct notify_msg *);
+void            l2vpn_sync_pws(int, union ldpd_addr *);
+void            l2vpn_pw_ctl(pid_t);
+void            l2vpn_binding_ctl(pid_t);
+
+#endif /* _LDE_H_ */
diff --git a/ldpd/lde_lib.c b/ldpd/lde_lib.c
new file mode 100644 (file)
index 0000000..d9c1f54
--- /dev/null
@@ -0,0 +1,784 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/socket.h>
+#include <netmpls/mpls.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "ldpd.h"
+#include "lde.h"
+#include "log.h"
+
+static __inline int     fec_compare(struct fec *, struct fec *);
+static int              lde_nbr_is_nexthop(struct fec_node *,
+                           struct lde_nbr *);
+static void             fec_free(void *);
+static struct fec_node *fec_add(struct fec *fec);
+static struct fec_nh   *fec_nh_add(struct fec_node *, int, union ldpd_addr *,
+                           uint8_t priority);
+static void             fec_nh_del(struct fec_nh *);
+
+RB_GENERATE(fec_tree, fec, entry, fec_compare)
+
+struct fec_tree                 ft = RB_INITIALIZER(&ft);
+struct event            gc_timer;
+
+/* FEC tree functions */
+void
+fec_init(struct fec_tree *fh)
+{
+       RB_INIT(fh);
+}
+
+static __inline int
+fec_compare(struct fec *a, struct fec *b)
+{
+       if (a->type < b->type)
+               return (-1);
+       if (a->type > b->type)
+               return (1);
+
+       switch (a->type) {
+       case FEC_TYPE_IPV4:
+               if (ntohl(a->u.ipv4.prefix.s_addr) <
+                   ntohl(b->u.ipv4.prefix.s_addr))
+                       return (-1);
+               if (ntohl(a->u.ipv4.prefix.s_addr) >
+                   ntohl(b->u.ipv4.prefix.s_addr))
+                       return (1);
+               if (a->u.ipv4.prefixlen < b->u.ipv4.prefixlen)
+                       return (-1);
+               if (a->u.ipv4.prefixlen > b->u.ipv4.prefixlen)
+                       return (1);
+               return (0);
+       case FEC_TYPE_IPV6:
+               if (memcmp(&a->u.ipv6.prefix, &b->u.ipv6.prefix,
+                   sizeof(struct in6_addr)) < 0)
+                       return (-1);
+               if (memcmp(&a->u.ipv6.prefix, &b->u.ipv6.prefix,
+                   sizeof(struct in6_addr)) > 0)
+                       return (1);
+               if (a->u.ipv6.prefixlen < b->u.ipv6.prefixlen)
+                       return (-1);
+               if (a->u.ipv6.prefixlen > b->u.ipv6.prefixlen)
+                       return (1);
+               return (0);
+       case FEC_TYPE_PWID:
+               if (a->u.pwid.type < b->u.pwid.type)
+                       return (-1);
+               if (a->u.pwid.type > b->u.pwid.type)
+                       return (1);
+               if (a->u.pwid.pwid < b->u.pwid.pwid)
+                       return (-1);
+               if (a->u.pwid.pwid > b->u.pwid.pwid)
+                       return (1);
+               if (ntohl(a->u.pwid.lsr_id.s_addr) <
+                   ntohl(b->u.pwid.lsr_id.s_addr))
+                       return (-1);
+               if (ntohl(a->u.pwid.lsr_id.s_addr) >
+                   ntohl(b->u.pwid.lsr_id.s_addr))
+                       return (1);
+               return (0);
+       }
+
+       return (-1);
+}
+
+struct fec *
+fec_find(struct fec_tree *fh, struct fec *f)
+{
+       return (RB_FIND(fec_tree, fh, f));
+}
+
+int
+fec_insert(struct fec_tree *fh, struct fec *f)
+{
+       if (RB_INSERT(fec_tree, fh, f) != NULL)
+               return (-1);
+       return (0);
+}
+
+int
+fec_remove(struct fec_tree *fh, struct fec *f)
+{
+       if (RB_REMOVE(fec_tree, fh, f) == NULL) {
+               log_warnx("%s failed for %s", __func__, log_fec(f));
+               return (-1);
+       }
+       return (0);
+}
+
+void
+fec_clear(struct fec_tree *fh, void (*free_cb)(void *))
+{
+       struct fec      *f;
+
+       while ((f = RB_ROOT(fh)) != NULL) {
+               fec_remove(fh, f);
+               free_cb(f);
+       }
+}
+
+/* routing table functions */
+static int
+lde_nbr_is_nexthop(struct fec_node *fn, struct lde_nbr *ln)
+{
+       struct fec_nh           *fnh;
+
+       LIST_FOREACH(fnh, &fn->nexthops, entry)
+               if (lde_address_find(ln, fnh->af, &fnh->nexthop))
+                       return (1);
+
+       return (0);
+}
+
+void
+rt_dump(pid_t pid)
+{
+       struct fec              *f;
+       struct fec_node         *fn;
+       struct lde_map          *me;
+       static struct ctl_rt     rtctl;
+
+       RB_FOREACH(f, fec_tree, &ft) {
+               fn = (struct fec_node *)f;
+               if (fn->local_label == NO_LABEL &&
+                   LIST_EMPTY(&fn->downstream))
+                       continue;
+
+               switch (fn->fec.type) {
+               case FEC_TYPE_IPV4:
+                       rtctl.af = AF_INET;
+                       rtctl.prefix.v4 = fn->fec.u.ipv4.prefix;
+                       rtctl.prefixlen = fn->fec.u.ipv4.prefixlen;
+                       break;
+               case FEC_TYPE_IPV6:
+                       rtctl.af = AF_INET6;
+                       rtctl.prefix.v6 = fn->fec.u.ipv6.prefix;
+                       rtctl.prefixlen = fn->fec.u.ipv6.prefixlen;
+                       break;
+               default:
+                       continue;
+               }
+
+               rtctl.local_label = fn->local_label;
+               LIST_FOREACH(me, &fn->downstream, entry) {
+                       rtctl.in_use = lde_nbr_is_nexthop(fn, me->nexthop);
+                       rtctl.nexthop = me->nexthop->id;
+                       rtctl.remote_label = me->map.label;
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB, 0, pid,
+                           &rtctl, sizeof(rtctl));
+               }
+               if (LIST_EMPTY(&fn->downstream)) {
+                       rtctl.in_use = 0;
+                       rtctl.nexthop.s_addr = INADDR_ANY;
+                       rtctl.remote_label = NO_LABEL;
+
+                       lde_imsg_compose_ldpe(IMSG_CTL_SHOW_LIB, 0, pid,
+                           &rtctl, sizeof(rtctl));
+               }
+       }
+}
+
+void
+fec_snap(struct lde_nbr *ln)
+{
+       struct fec      *f;
+       struct fec_node *fn;
+
+       RB_FOREACH(f, fec_tree, &ft) {
+               fn = (struct fec_node *)f;
+               if (fn->local_label == NO_LABEL)
+                       continue;
+
+               lde_send_labelmapping(ln, fn, 0);
+       }
+
+       lde_imsg_compose_ldpe(IMSG_MAPPING_ADD_END, ln->peerid, 0, NULL, 0);
+}
+
+static void
+fec_free(void *arg)
+{
+       struct fec_node *fn = arg;
+       struct fec_nh   *fnh;
+
+       while ((fnh = LIST_FIRST(&fn->nexthops)))
+               fec_nh_del(fnh);
+       if (!LIST_EMPTY(&fn->downstream))
+               log_warnx("%s: fec %s downstream list not empty", __func__,
+                   log_fec(&fn->fec));
+       if (!LIST_EMPTY(&fn->upstream))
+               log_warnx("%s: fec %s upstream list not empty", __func__,
+                   log_fec(&fn->fec));
+
+       free(fn);
+}
+
+void
+fec_tree_clear(void)
+{
+       fec_clear(&ft, fec_free);
+}
+
+static struct fec_node *
+fec_add(struct fec *fec)
+{
+       struct fec_node *fn;
+
+       fn = calloc(1, sizeof(*fn));
+       if (fn == NULL)
+               fatal(__func__);
+
+       fn->fec = *fec;
+       fn->local_label = NO_LABEL;
+       LIST_INIT(&fn->upstream);
+       LIST_INIT(&fn->downstream);
+       LIST_INIT(&fn->nexthops);
+
+       if (fec_insert(&ft, &fn->fec))
+               log_warnx("failed to add %s to ft tree",
+                   log_fec(&fn->fec));
+
+       return (fn);
+}
+
+struct fec_nh *
+fec_nh_find(struct fec_node *fn, int af, union ldpd_addr *nexthop,
+    uint8_t priority)
+{
+       struct fec_nh   *fnh;
+
+       LIST_FOREACH(fnh, &fn->nexthops, entry)
+               if (fnh->af == af &&
+                   ldp_addrcmp(af, &fnh->nexthop, nexthop) == 0 &&
+                   fnh->priority == priority)
+                       return (fnh);
+
+       return (NULL);
+}
+
+static struct fec_nh *
+fec_nh_add(struct fec_node *fn, int af, union ldpd_addr *nexthop,
+    uint8_t priority)
+{
+       struct fec_nh   *fnh;
+
+       fnh = calloc(1, sizeof(*fnh));
+       if (fnh == NULL)
+               fatal(__func__);
+
+       fnh->af = af;
+       fnh->nexthop = *nexthop;
+       fnh->remote_label = NO_LABEL;
+       fnh->priority = priority;
+       LIST_INSERT_HEAD(&fn->nexthops, fnh, entry);
+
+       return (fnh);
+}
+
+static void
+fec_nh_del(struct fec_nh *fnh)
+{
+       LIST_REMOVE(fnh, entry);
+       free(fnh);
+}
+
+uint32_t
+egress_label(enum fec_type fec_type)
+{
+       switch (fec_type) {
+       case FEC_TYPE_IPV4:
+               if (ldeconf->ipv4.flags & F_LDPD_AF_EXPNULL)
+                       return (MPLS_LABEL_IPV4NULL);
+               break;
+       case FEC_TYPE_IPV6:
+               if (ldeconf->ipv6.flags & F_LDPD_AF_EXPNULL)
+                       return (MPLS_LABEL_IPV6NULL);
+               break;
+       default:
+               fatalx("egress_label: unexpected fec type");
+       }
+
+       return (MPLS_LABEL_IMPLNULL);
+}
+
+void
+lde_kernel_insert(struct fec *fec, int af, union ldpd_addr *nexthop,
+    uint8_t priority, int connected, void *data)
+{
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+       struct lde_map          *me;
+       struct lde_nbr          *ln;
+
+       fn = (struct fec_node *)fec_find(&ft, fec);
+       if (fn == NULL)
+               fn = fec_add(fec);
+       if (fec_nh_find(fn, af, nexthop, priority) != NULL)
+               return;
+
+       log_debug("lde add fec %s nexthop %s",
+           log_fec(&fn->fec), log_addr(af, nexthop));
+
+       if (fn->fec.type == FEC_TYPE_PWID)
+               fn->data = data;
+
+       if (fn->local_label == NO_LABEL) {
+               if (connected)
+                       fn->local_label = egress_label(fn->fec.type);
+               else
+                       fn->local_label = lde_assign_label();
+
+               /* FEC.1: perform lsr label distribution procedure */
+               RB_FOREACH(ln, nbr_tree, &lde_nbrs)
+                       lde_send_labelmapping(ln, fn, 1);
+       }
+
+       fnh = fec_nh_add(fn, af, nexthop, priority);
+       lde_send_change_klabel(fn, fnh);
+
+       switch (fn->fec.type) {
+       case FEC_TYPE_IPV4:
+       case FEC_TYPE_IPV6:
+               ln = lde_nbr_find_by_addr(af, &fnh->nexthop);
+               break;
+       case FEC_TYPE_PWID:
+               ln = lde_nbr_find_by_lsrid(fn->fec.u.pwid.lsr_id);
+               break;
+       default:
+               ln = NULL;
+               break;
+       }
+
+       if (ln) {
+               /* FEC.2  */
+               me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec);
+               if (me)
+                       /* FEC.5 */
+                       lde_check_mapping(&me->map, ln);
+       }
+}
+
+void
+lde_kernel_remove(struct fec *fec, int af, union ldpd_addr *nexthop,
+    uint8_t priority)
+{
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+
+       fn = (struct fec_node *)fec_find(&ft, fec);
+       if (fn == NULL)
+               /* route lost */
+               return;
+       fnh = fec_nh_find(fn, af, nexthop, priority);
+       if (fnh == NULL)
+               /* route lost */
+               return;
+
+       log_debug("lde remove fec %s nexthop %s",
+           log_fec(&fn->fec), log_addr(af, nexthop));
+
+       lde_send_delete_klabel(fn, fnh);
+       fec_nh_del(fnh);
+       if (LIST_EMPTY(&fn->nexthops)) {
+               lde_send_labelwithdraw_all(fn, NO_LABEL);
+               fn->local_label = NO_LABEL;
+               if (fn->fec.type == FEC_TYPE_PWID)
+                       fn->data = NULL;
+       }
+}
+
+void
+lde_check_mapping(struct map *map, struct lde_nbr *ln)
+{
+       struct fec               fec;
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+       struct lde_req          *lre;
+       struct lde_map          *me;
+       struct l2vpn_pw         *pw;
+       int                      msgsource = 0;
+
+       lde_map2fec(map, ln->id, &fec);
+       fn = (struct fec_node *)fec_find(&ft, &fec);
+       if (fn == NULL)
+               fn = fec_add(&fec);
+
+       /* LMp.1: first check if we have a pending request running */
+       lre = (struct lde_req *)fec_find(&ln->sent_req, &fn->fec);
+       if (lre)
+               /* LMp.2: delete record of outstanding label request */
+               lde_req_del(ln, lre, 1);
+
+       /* RFC 4447 control word and status tlv negotiation */
+       if (map->type == MAP_TYPE_PWID && l2vpn_pw_negotiate(ln, fn, map))
+               return;
+
+       /*
+        * LMp.3 - LMp.8: loop detection - unnecessary for frame-mode
+        * mpls networks.
+        */
+
+       /* LMp.9 */
+       me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec);
+       if (me) {
+               /* LMp.10 */
+               if (me->map.label != map->label && lre == NULL) {
+                       /* LMp.10a */
+                       lde_send_labelrelease(ln, fn, me->map.label);
+
+                       /*
+                        * Can not use lde_nbr_find_by_addr() because there's
+                        * the possibility of multipath.
+                        */
+                       LIST_FOREACH(fnh, &fn->nexthops, entry) {
+                               if (lde_address_find(ln, fnh->af,
+                                   &fnh->nexthop) == NULL)
+                                       continue;
+
+                               lde_send_delete_klabel(fn, fnh);
+                               fnh->remote_label = NO_LABEL;
+                       }
+               }
+       }
+
+       /*
+        * LMp.11 - 12: consider multiple nexthops in order to
+        * support multipath
+        */
+       LIST_FOREACH(fnh, &fn->nexthops, entry) {
+               /* LMp.15: install FEC in FIB */
+               switch (fec.type) {
+               case FEC_TYPE_IPV4:
+               case FEC_TYPE_IPV6:
+                       if (!lde_address_find(ln, fnh->af, &fnh->nexthop))
+                               continue;
+
+                       fnh->remote_label = map->label;
+                       lde_send_change_klabel(fn, fnh);
+                       break;
+               case FEC_TYPE_PWID:
+                       pw = (struct l2vpn_pw *) fn->data;
+                       if (pw == NULL)
+                               continue;
+
+                       pw->remote_group = map->fec.pwid.group_id;
+                       if (map->flags & F_MAP_PW_IFMTU)
+                               pw->remote_mtu = map->fec.pwid.ifmtu;
+                       if (map->flags & F_MAP_PW_STATUS)
+                               pw->remote_status = map->pw_status;
+                       fnh->remote_label = map->label;
+                       if (l2vpn_pw_ok(pw, fnh))
+                               lde_send_change_klabel(fn, fnh);
+                       break;
+               default:
+                       break;
+               }
+
+               msgsource = 1;
+       }
+       /* LMp.13 & LMp.16: Record the mapping from this peer */
+       if (me == NULL)
+               me = lde_map_add(ln, fn, 0);
+       me->map = *map;
+
+       if (msgsource == 0)
+               /* LMp.13: just return since we use liberal lbl retention */
+               return;
+
+       /*
+        * LMp.17 - LMp.27 are unnecessary since we don't need to implement
+        * loop detection. LMp.28 - LMp.30 are unnecessary because we are
+        * merging capable.
+        */
+}
+
+void
+lde_check_request(struct map *map, struct lde_nbr *ln)
+{
+       struct fec       fec;
+       struct lde_req  *lre;
+       struct fec_node *fn;
+       struct fec_nh   *fnh;
+
+       /* LRq.1: skip loop detection (not necessary) */
+
+       /* LRq.2: is there a next hop for fec? */
+       lde_map2fec(map, ln->id, &fec);
+       fn = (struct fec_node *)fec_find(&ft, &fec);
+       if (fn == NULL || LIST_EMPTY(&fn->nexthops)) {
+               /* LRq.5: send No Route notification */
+               lde_send_notification(ln->peerid, S_NO_ROUTE, map->msg_id,
+                   htons(MSG_TYPE_LABELREQUEST));
+               return;
+       }
+
+       /* LRq.3: is MsgSource the next hop? */
+       LIST_FOREACH(fnh, &fn->nexthops, entry) {
+               switch (fec.type) {
+               case FEC_TYPE_IPV4:
+               case FEC_TYPE_IPV6:
+                       if (!lde_address_find(ln, fnh->af, &fnh->nexthop))
+                               continue;
+
+                       /* LRq.4: send Loop Detected notification */
+                       lde_send_notification(ln->peerid, S_LOOP_DETECTED,
+                           map->msg_id, htons(MSG_TYPE_LABELREQUEST));
+                       return;
+               default:
+                       break;
+               }
+       }
+
+       /* LRq.6: first check if we have a pending request running */
+       lre = (struct lde_req *)fec_find(&ln->recv_req, &fn->fec);
+       if (lre != NULL)
+               /* LRq.7: duplicate request */
+               return;
+
+       /* LRq.8: record label request */
+       lre = lde_req_add(ln, &fn->fec, 0);
+       if (lre != NULL)
+               lre->msg_id = ntohl(map->msg_id);
+
+       /* LRq.9: perform LSR label distribution */
+       lde_send_labelmapping(ln, fn, 1);
+
+       /*
+        * LRq.10: do nothing (Request Never) since we use liberal
+        * label retention.
+        * LRq.11 - 12 are unnecessary since we are merging capable.
+        */
+}
+
+void
+lde_check_release(struct map *map, struct lde_nbr *ln)
+{
+       struct fec               fec;
+       struct fec_node         *fn;
+       struct lde_wdraw        *lw;
+       struct lde_map          *me;
+
+       /* TODO group wildcard */
+       if (map->type == MAP_TYPE_PWID && !(map->flags & F_MAP_PW_ID))
+               return;
+
+       lde_map2fec(map, ln->id, &fec);
+       fn = (struct fec_node *)fec_find(&ft, &fec);
+       /* LRl.1: does FEC match a known FEC? */
+       if (fn == NULL)
+               return;
+
+       /* LRl.3: first check if we have a pending withdraw running */
+       lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec);
+       if (lw && (map->label == NO_LABEL ||
+           (lw->label != NO_LABEL && map->label == lw->label))) {
+               /* LRl.4: delete record of outstanding label withdraw */
+               lde_wdraw_del(ln, lw);
+       }
+
+       /* LRl.6: check sent map list and remove it if available */
+       me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec);
+       if (me && (map->label == NO_LABEL || map->label == me->map.label))
+               lde_map_del(ln, me, 1);
+
+       /*
+        * LRl.11 - 13 are unnecessary since we remove the label from
+        * forwarding/switching as soon as the FEC is unreachable.
+        */
+}
+
+void
+lde_check_release_wcard(struct map *map, struct lde_nbr *ln)
+{
+       struct fec              *f;
+       struct fec_node         *fn;
+       struct lde_wdraw        *lw;
+       struct lde_map          *me;
+
+       RB_FOREACH(f, fec_tree, &ft) {
+               fn = (struct fec_node *)f;
+
+               /* LRl.3: first check if we have a pending withdraw running */
+               lw = (struct lde_wdraw *)fec_find(&ln->sent_wdraw, &fn->fec);
+               if (lw && (map->label == NO_LABEL ||
+                   (lw->label != NO_LABEL && map->label == lw->label))) {
+                       /* LRl.4: delete record of outstanding lbl withdraw */
+                       lde_wdraw_del(ln, lw);
+               }
+
+               /* LRl.6: check sent map list and remove it if available */
+               me = (struct lde_map *)fec_find(&ln->sent_map, &fn->fec);
+               if (me &&
+                   (map->label == NO_LABEL || map->label == me->map.label))
+                       lde_map_del(ln, me, 1);
+
+               /*
+                * LRl.11 - 13 are unnecessary since we remove the label from
+                * forwarding/switching as soon as the FEC is unreachable.
+                */
+       }
+}
+
+void
+lde_check_withdraw(struct map *map, struct lde_nbr *ln)
+{
+       struct fec               fec;
+       struct fec_node         *fn;
+       struct fec_nh           *fnh;
+       struct lde_map          *me;
+       struct l2vpn_pw         *pw;
+
+       /* TODO group wildcard */
+       if (map->type == MAP_TYPE_PWID && !(map->flags & F_MAP_PW_ID))
+               return;
+
+       lde_map2fec(map, ln->id, &fec);
+       fn = (struct fec_node *)fec_find(&ft, &fec);
+       if (fn == NULL)
+               fn = fec_add(&fec);
+
+       /* LWd.1: remove label from forwarding/switching use */
+       LIST_FOREACH(fnh, &fn->nexthops, entry) {
+               switch (fec.type) {
+               case FEC_TYPE_IPV4:
+               case FEC_TYPE_IPV6:
+                       if (!lde_address_find(ln, fnh->af, &fnh->nexthop))
+                               continue;
+                       break;
+               case FEC_TYPE_PWID:
+                       pw = (struct l2vpn_pw *) fn->data;
+                       if (pw == NULL)
+                               continue;
+                       break;
+               default:
+                       break;
+               }
+               lde_send_delete_klabel(fn, fnh);
+               fnh->remote_label = NO_LABEL;
+       }
+
+       /* LWd.2: send label release */
+       lde_send_labelrelease(ln, fn, map->label);
+
+       /* LWd.3: check previously received label mapping */
+       me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec);
+       if (me && (map->label == NO_LABEL || map->label == me->map.label))
+               /* LWd.4: remove record of previously received lbl mapping */
+               lde_map_del(ln, me, 0);
+}
+
+void
+lde_check_withdraw_wcard(struct map *map, struct lde_nbr *ln)
+{
+       struct fec      *f;
+       struct fec_node *fn;
+       struct fec_nh   *fnh;
+       struct lde_map  *me;
+
+       /* LWd.2: send label release */
+       lde_send_labelrelease(ln, NULL, map->label);
+
+       RB_FOREACH(f, fec_tree, &ft) {
+               fn = (struct fec_node *)f;
+
+               /* LWd.1: remove label from forwarding/switching use */
+               LIST_FOREACH(fnh, &fn->nexthops, entry) {
+                       switch (f->type) {
+                       case FEC_TYPE_IPV4:
+                       case FEC_TYPE_IPV6:
+                               if (!lde_address_find(ln, fnh->af,
+                                   &fnh->nexthop))
+                                       continue;
+                               break;
+                       case FEC_TYPE_PWID:
+                               if (f->u.pwid.lsr_id.s_addr != ln->id.s_addr)
+                                       continue;
+                               break;
+                       default:
+                               break;
+                       }
+                       lde_send_delete_klabel(fn, fnh);
+                       fnh->remote_label = NO_LABEL;
+               }
+
+               /* LWd.3: check previously received label mapping */
+               me = (struct lde_map *)fec_find(&ln->recv_map, &fn->fec);
+               if (me && (map->label == NO_LABEL ||
+                   map->label == me->map.label))
+                       /*
+                        * LWd.4: remove record of previously received
+                        * label mapping
+                        */
+                       lde_map_del(ln, me, 0);
+       }
+}
+
+/* gabage collector timer: timer to remove dead entries from the LIB */
+
+/* ARGSUSED */
+void
+lde_gc_timer(int fd, short event, void *arg)
+{
+       struct fec      *fec, *safe;
+       struct fec_node *fn;
+       int              count = 0;
+
+       RB_FOREACH_SAFE(fec, fec_tree, &ft, safe) {
+               fn = (struct fec_node *) fec;
+
+               if (!LIST_EMPTY(&fn->nexthops) ||
+                   !LIST_EMPTY(&fn->downstream) ||
+                   !LIST_EMPTY(&fn->upstream))
+                       continue;
+
+               fec_remove(&ft, &fn->fec);
+               free(fn);
+               count++;
+       }
+
+       if (count > 0)
+               log_debug("%s: %u entries removed", __func__, count);
+
+       lde_gc_start_timer();
+}
+
+void
+lde_gc_start_timer(void)
+{
+       struct timeval   tv;
+
+       timerclear(&tv);
+       tv.tv_sec = LDE_GC_INTERVAL;
+       if (evtimer_add(&gc_timer, &tv) == -1)
+               fatal(__func__);
+}
+
+void
+lde_gc_stop_timer(void)
+{
+       if (evtimer_pending(&gc_timer, NULL) &&
+           evtimer_del(&gc_timer) == -1)
+               fatal(__func__);
+}
diff --git a/ldpd/ldp.h b/ldpd/ldp.h
new file mode 100644 (file)
index 0000000..77034b3
--- /dev/null
@@ -0,0 +1,304 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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.
+ */
+
+/* LDP protocol definitions */
+
+#ifndef _LDP_H_
+#define _LDP_H_
+
+#include <sys/types.h>
+
+/* misc */
+#define LDP_VERSION            1
+#define LDP_PORT               646
+#define LDP_MAX_LEN            4096
+
+/* All Routers on this Subnet group multicast addresses */
+#define AllRouters_v4          "224.0.0.2"
+#define AllRouters_v6          "ff02::2"
+
+#define LINK_DFLT_HOLDTIME     15
+#define TARGETED_DFLT_HOLDTIME 45
+#define MIN_HOLDTIME           3
+#define MAX_HOLDTIME           0xffff
+#define        INFINITE_HOLDTIME       0xffff
+
+#define DEFAULT_KEEPALIVE      180
+#define MIN_KEEPALIVE          3
+#define MAX_KEEPALIVE          0xffff
+#define KEEPALIVE_PER_PERIOD   3
+#define INIT_FSM_TIMEOUT       15
+
+#define        DEFAULT_HELLO_INTERVAL  5
+#define        MIN_HELLO_INTERVAL      1
+#define        MAX_HELLO_INTERVAL      0xffff
+
+#define        INIT_DELAY_TMR          15
+#define        MAX_DELAY_TMR           120
+
+#define        MIN_PWID_ID             1
+#define        MAX_PWID_ID             0xffffffff
+
+#define        DEFAULT_L2VPN_MTU       1500
+#define        MIN_L2VPN_MTU           512
+#define        MAX_L2VPN_MTU           0xffff
+
+/* LDP message types */
+#define MSG_TYPE_NOTIFICATION  0x0001
+#define MSG_TYPE_HELLO         0x0100
+#define MSG_TYPE_INIT          0x0200
+#define MSG_TYPE_KEEPALIVE     0x0201
+#define MSG_TYPE_ADDR          0x0300
+#define MSG_TYPE_ADDRWITHDRAW  0x0301
+#define MSG_TYPE_LABELMAPPING  0x0400
+#define MSG_TYPE_LABELREQUEST  0x0401
+#define MSG_TYPE_LABELWITHDRAW 0x0402
+#define MSG_TYPE_LABELRELEASE  0x0403
+#define MSG_TYPE_LABELABORTREQ 0x0404
+
+/* LDP TLV types */
+#define TLV_TYPE_FEC           0x0100
+#define TLV_TYPE_ADDRLIST      0x0101
+#define TLV_TYPE_HOPCOUNT      0x0103
+#define TLV_TYPE_PATHVECTOR    0x0104
+#define TLV_TYPE_GENERICLABEL  0x0200
+#define TLV_TYPE_ATMLABEL      0x0201
+#define TLV_TYPE_FRLABEL       0x0202
+#define TLV_TYPE_STATUS                0x0300
+#define TLV_TYPE_EXTSTATUS     0x0301
+#define TLV_TYPE_RETURNEDPDU   0x0302
+#define TLV_TYPE_RETURNEDMSG   0x0303
+#define TLV_TYPE_COMMONHELLO   0x0400
+#define TLV_TYPE_IPV4TRANSADDR 0x0401
+#define TLV_TYPE_CONFIG                0x0402
+#define TLV_TYPE_IPV6TRANSADDR 0x0403
+#define TLV_TYPE_COMMONSESSION 0x0500
+#define TLV_TYPE_ATMSESSIONPAR 0x0501
+#define TLV_TYPE_FRSESSION     0x0502
+#define TLV_TYPE_LABELREQUEST  0x0600
+/* RFC 4447 */
+#define TLV_TYPE_PW_STATUS     0x096A
+#define TLV_TYPE_PW_IF_PARAM   0x096B
+#define TLV_TYPE_PW_GROUP_ID   0x096C
+/* RFC 7552 */
+#define TLV_TYPE_DUALSTACK     0x8701
+
+/* LDP header */
+struct ldp_hdr {
+       uint16_t        version;
+       uint16_t        length;
+       uint32_t        lsr_id;
+       uint16_t        lspace_id;
+} __packed;
+
+#define        LDP_HDR_SIZE            10      /* actual size of the LDP header */
+#define        LDP_HDR_PDU_LEN         6       /* minimum "PDU Length" */
+#define LDP_HDR_DEAD_LEN       4
+
+/* TLV record */
+struct tlv {
+       uint16_t        type;
+       uint16_t        length;
+};
+#define        TLV_HDR_SIZE            4
+
+struct ldp_msg {
+       uint16_t        type;
+       uint16_t        length;
+       uint32_t        id;
+       /* Mandatory Parameters */
+       /* Optional Parameters */
+} __packed;
+
+#define LDP_MSG_SIZE           8       /* minimum size of LDP message */
+#define LDP_MSG_LEN            4       /* minimum "Message Length" */
+#define LDP_MSG_DEAD_LEN       4
+
+#define        UNKNOWN_FLAG            0x8000
+#define        FORWARD_FLAG            0xc000
+
+struct hello_prms_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint16_t        holdtime;
+       uint16_t        flags;
+};
+#define F_HELLO_TARGETED       0x8000
+#define F_HELLO_REQ_TARG       0x4000
+#define F_HELLO_GTSM           0x2000
+
+struct hello_prms_opt4_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint32_t        value;
+};
+
+struct hello_prms_opt16_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint8_t         value[16];
+};
+
+#define DUAL_STACK_LDPOV4      4
+#define DUAL_STACK_LDPOV6      6
+
+#define F_HELLO_TLV_RCVD_ADDR  0x01
+#define F_HELLO_TLV_RCVD_CONF  0x02
+#define F_HELLO_TLV_RCVD_DS    0x04
+
+#define        S_SUCCESS       0x00000000
+#define        S_BAD_LDP_ID    0x80000001
+#define        S_BAD_PROTO_VER 0x80000002
+#define        S_BAD_PDU_LEN   0x80000003
+#define        S_UNKNOWN_MSG   0x00000004
+#define        S_BAD_MSG_LEN   0x80000005
+#define        S_UNKNOWN_TLV   0x00000006
+#define        S_BAD_TLV_LEN   0x80000007
+#define        S_BAD_TLV_VAL   0x80000008
+#define        S_HOLDTIME_EXP  0x80000009
+#define        S_SHUTDOWN      0x8000000A
+#define        S_LOOP_DETECTED 0x0000000B
+#define        S_UNKNOWN_FEC   0x0000000C
+#define        S_NO_ROUTE      0x0000000D
+#define        S_NO_LABEL_RES  0x0000000E
+#define        S_AVAILABLE     0x0000000F
+#define        S_NO_HELLO      0x80000010
+#define        S_PARM_ADV_MODE 0x80000011
+#define        S_MAX_PDU_LEN   0x80000012
+#define        S_PARM_L_RANGE  0x80000013
+#define        S_KEEPALIVE_TMR 0x80000014
+#define        S_LAB_REQ_ABRT  0x00000015
+#define        S_MISS_MSG      0x00000016
+#define        S_UNSUP_ADDR    0x00000017
+#define        S_KEEPALIVE_BAD 0x80000018
+#define        S_INTERN_ERR    0x80000019
+/* RFC 4447 */
+#define S_ILLEGAL_CBIT 0x00000024
+#define S_WRONG_CBIT   0x00000025
+#define S_INCPT_BITRATE        0x00000026
+#define S_CEP_MISCONF  0x00000027
+#define S_PW_STATUS    0x00000028
+#define S_UNASSIGN_TAI 0x00000029
+#define S_MISCONF_ERR  0x0000002A
+#define S_WITHDRAW_MTHD        0x0000002B
+/* RFC 7552 */
+#define        S_TRANS_MISMTCH 0x80000032
+#define        S_DS_NONCMPLNCE 0x80000033
+
+struct sess_prms_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint16_t        proto_version;
+       uint16_t        keepalive_time;
+       uint8_t         reserved;
+       uint8_t         pvlim;
+       uint16_t        max_pdu_len;
+       uint32_t        lsr_id;
+       uint16_t        lspace_id;
+} __packed;
+
+#define SESS_PRMS_SIZE         18
+#define SESS_PRMS_LEN          14
+
+struct status_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint32_t        status_code;
+       uint32_t        msg_id;
+       uint16_t        msg_type;
+} __packed;
+
+#define STATUS_SIZE            14
+#define STATUS_TLV_LEN         10
+#define        STATUS_FATAL            0x80000000
+
+#define        AF_IPV4                 0x1
+#define        AF_IPV6                 0x2
+
+struct address_list_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint16_t        family;
+       /* address entries */
+} __packed;
+
+#define ADDR_LIST_SIZE         6
+
+#define FEC_ELM_WCARD_LEN      1
+#define FEC_ELM_PREFIX_MIN_LEN 4
+#define FEC_PWID_ELM_MIN_LEN   8
+
+#define        MAP_TYPE_WILDCARD       0x01
+#define        MAP_TYPE_PREFIX         0x02
+#define        MAP_TYPE_PWID           0x80
+#define        MAP_TYPE_GENPWID        0x81
+
+#define CONTROL_WORD_FLAG      0x8000
+#define PW_TYPE_ETHERNET_TAGGED        0x0004
+#define PW_TYPE_ETHERNET       0x0005
+#define DEFAULT_PW_TYPE                PW_TYPE_ETHERNET
+
+/* RFC 4447 Sub-TLV record */
+struct subtlv {
+       uint8_t         type;
+       uint8_t         length;
+};
+#define        SUBTLV_HDR_SIZE         2
+
+#define SUBTLV_IFMTU           0x01
+#define SUBTLV_VLANID          0x06
+
+#define FEC_SUBTLV_IFMTU_SIZE  4
+#define FEC_SUBTLV_VLANID_SIZE 4
+
+struct label_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint32_t        label;
+};
+#define LABEL_TLV_SIZE         8
+#define LABEL_TLV_LEN          4
+
+struct reqid_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint32_t        reqid;
+};
+#define REQID_TLV_SIZE         8
+#define REQID_TLV_LEN          4
+
+struct pw_status_tlv {
+       uint16_t        type;
+       uint16_t        length;
+       uint32_t        value;
+};
+#define PW_STATUS_TLV_SIZE     8
+#define PW_STATUS_TLV_LEN      4
+
+#define PW_FORWARDING          0
+#define PW_NOT_FORWARDING      (1 << 0)
+#define PW_LOCAL_RX_FAULT      (1 << 1)
+#define PW_LOCAL_TX_FAULT      (1 << 2)
+#define PW_PSN_RX_FAULT                (1 << 3)
+#define PW_PSN_TX_FAULT                (1 << 4)
+
+#define        NO_LABEL                UINT32_MAX
+
+#endif /* !_LDP_H_ */
diff --git a/ldpd/ldpd.c b/ldpd/ldpd.c
new file mode 100644 (file)
index 0000000..4e14491
--- /dev/null
@@ -0,0 +1,1227 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2008 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/wait.h>
+#include <err.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "lde.h"
+#include "log.h"
+
+static void             main_sig_handler(int, short, void *);
+static __dead void      usage(void);
+static __dead void      ldpd_shutdown(void);
+static pid_t            start_child(enum ldpd_process, char *, int, int, int);
+static void             main_dispatch_ldpe(int, short, void *);
+static void             main_dispatch_lde(int, short, void *);
+static int              main_imsg_compose_both(enum imsg_type, void *,
+                           uint16_t);
+static int              main_imsg_send_ipc_sockets(struct imsgbuf *,
+                           struct imsgbuf *);
+static void             main_imsg_send_net_sockets(int);
+static void             main_imsg_send_net_socket(int, enum socket_type);
+static int              main_imsg_send_config(struct ldpd_conf *);
+static int              ldp_reload(void);
+static void             merge_global(struct ldpd_conf *, struct ldpd_conf *);
+static void             merge_af(int, struct ldpd_af_conf *,
+                           struct ldpd_af_conf *);
+static void             merge_ifaces(struct ldpd_conf *, struct ldpd_conf *);
+static void             merge_iface_af(struct iface_af *, struct iface_af *);
+static void             merge_tnbrs(struct ldpd_conf *, struct ldpd_conf *);
+static void             merge_nbrps(struct ldpd_conf *, struct ldpd_conf *);
+static void             merge_l2vpns(struct ldpd_conf *, struct ldpd_conf *);
+static void             merge_l2vpn(struct ldpd_conf *, struct l2vpn *,
+                           struct l2vpn *);
+
+struct ldpd_global      global;
+struct ldpd_conf       *ldpd_conf;
+
+static char            *conffile;
+static struct imsgev   *iev_ldpe;
+static struct imsgev   *iev_lde;
+static pid_t            ldpe_pid;
+static pid_t            lde_pid;
+
+/* ARGSUSED */
+static void
+main_sig_handler(int sig, short event, void *arg)
+{
+       /* signal handler rules don't apply, libevent decouples for us */
+       switch (sig) {
+       case SIGTERM:
+       case SIGINT:
+               ldpd_shutdown();
+               /* NOTREACHED */
+       case SIGHUP:
+               if (ldp_reload() == -1)
+                       log_warnx("configuration reload failed");
+               else
+                       log_debug("configuration reloaded");
+               break;
+       default:
+               fatalx("unexpected signal");
+               /* NOTREACHED */
+       }
+}
+
+static __dead void
+usage(void)
+{
+       extern char *__progname;
+
+       fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
+           __progname);
+       exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+       struct event             ev_sigint, ev_sigterm, ev_sighup;
+       char                    *saved_argv0;
+       int                      ch;
+       int                      debug = 0, lflag = 0, eflag = 0;
+       int                      pipe_parent2ldpe[2];
+       int                      pipe_parent2lde[2];
+
+       conffile = CONF_FILE;
+       ldpd_process = PROC_MAIN;
+
+       log_init(1);    /* log to stderr until daemonized */
+       log_verbose(1);
+
+       saved_argv0 = argv[0];
+       if (saved_argv0 == NULL)
+               saved_argv0 = "ldpd";
+
+       while ((ch = getopt(argc, argv, "dD:f:nvLE")) != -1) {
+               switch (ch) {
+               case 'd':
+                       debug = 1;
+                       break;
+               case 'D':
+                       if (cmdline_symset(optarg) < 0)
+                               log_warnx("could not parse macro definition %s",
+                                   optarg);
+                       break;
+               case 'f':
+                       conffile = optarg;
+                       break;
+               case 'n':
+                       global.cmd_opts |= LDPD_OPT_NOACTION;
+                       break;
+               case 'v':
+                       if (global.cmd_opts & LDPD_OPT_VERBOSE)
+                               global.cmd_opts |= LDPD_OPT_VERBOSE2;
+                       global.cmd_opts |= LDPD_OPT_VERBOSE;
+                       break;
+               case 'L':
+                       lflag = 1;
+                       break;
+               case 'E':
+                       eflag = 1;
+                       break;
+               default:
+                       usage();
+                       /* NOTREACHED */
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+       if (argc > 0 || (lflag && eflag))
+               usage();
+
+       if (lflag)
+               lde(debug, global.cmd_opts & LDPD_OPT_VERBOSE);
+       else if (eflag)
+               ldpe(debug, global.cmd_opts & LDPD_OPT_VERBOSE);
+
+       /* fetch interfaces early */
+       kif_init();
+
+       /* parse config file */
+       if ((ldpd_conf = parse_config(conffile)) == NULL ) {
+               kif_clear();
+               exit(1);
+       }
+
+       if (global.cmd_opts & LDPD_OPT_NOACTION) {
+               if (global.cmd_opts & LDPD_OPT_VERBOSE)
+                       print_config(ldpd_conf);
+               else
+                       fprintf(stderr, "configuration OK\n");
+               kif_clear();
+               exit(0);
+       }
+
+       /* check for root privileges  */
+       if (geteuid())
+               errx(1, "need root privileges");
+
+       /* check for ldpd user */
+       if (getpwnam(LDPD_USER) == NULL)
+               errx(1, "unknown user %s", LDPD_USER);
+
+       log_init(debug);
+       log_verbose(global.cmd_opts & (LDPD_OPT_VERBOSE | LDPD_OPT_VERBOSE2));
+
+       if (!debug)
+               daemon(1, 0);
+
+       log_info("startup");
+
+       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+           PF_UNSPEC, pipe_parent2ldpe) == -1)
+               fatal("socketpair");
+       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
+           PF_UNSPEC, pipe_parent2lde) == -1)
+               fatal("socketpair");
+
+       /* start children */
+       lde_pid = start_child(PROC_LDE_ENGINE, saved_argv0,
+           pipe_parent2lde[1], debug, global.cmd_opts & LDPD_OPT_VERBOSE);
+       ldpe_pid = start_child(PROC_LDP_ENGINE, saved_argv0,
+           pipe_parent2ldpe[1], debug, global.cmd_opts & LDPD_OPT_VERBOSE);
+
+       event_init();
+
+       /* setup signal handler */
+       signal_set(&ev_sigint, SIGINT, main_sig_handler, NULL);
+       signal_set(&ev_sigterm, SIGTERM, main_sig_handler, NULL);
+       signal_set(&ev_sighup, SIGHUP, main_sig_handler, NULL);
+       signal_add(&ev_sigint, NULL);
+       signal_add(&ev_sigterm, NULL);
+       signal_add(&ev_sighup, NULL);
+       signal(SIGPIPE, SIG_IGN);
+
+       /* setup pipes to children */
+       if ((iev_ldpe = malloc(sizeof(struct imsgev))) == NULL ||
+           (iev_lde = malloc(sizeof(struct imsgev))) == NULL)
+               fatal(NULL);
+       imsg_init(&iev_ldpe->ibuf, pipe_parent2ldpe[0]);
+       iev_ldpe->handler = main_dispatch_ldpe;
+       imsg_init(&iev_lde->ibuf, pipe_parent2lde[0]);
+       iev_lde->handler = main_dispatch_lde;
+
+       /* setup event handler */
+       iev_ldpe->events = EV_READ;
+       event_set(&iev_ldpe->ev, iev_ldpe->ibuf.fd, iev_ldpe->events,
+           iev_ldpe->handler, iev_ldpe);
+       event_add(&iev_ldpe->ev, NULL);
+
+       iev_lde->events = EV_READ;
+       event_set(&iev_lde->ev, iev_lde->ibuf.fd, iev_lde->events,
+           iev_lde->handler, iev_lde);
+       event_add(&iev_lde->ev, NULL);
+
+       if (main_imsg_send_ipc_sockets(&iev_ldpe->ibuf, &iev_lde->ibuf))
+               fatal("could not establish imsg links");
+       main_imsg_send_config(ldpd_conf);
+
+       /* notify ldpe about existing interfaces and addresses */
+       kif_redistribute(NULL);
+
+       if (kr_init(!(ldpd_conf->flags & F_LDPD_NO_FIB_UPDATE)) == -1)
+               fatalx("kr_init failed");
+
+       if (ldpd_conf->ipv4.flags & F_LDPD_AF_ENABLED)
+               main_imsg_send_net_sockets(AF_INET);
+       if (ldpd_conf->ipv6.flags & F_LDPD_AF_ENABLED)
+               main_imsg_send_net_sockets(AF_INET6);
+
+       /* remove unneded stuff from config */
+               /* ... */
+
+       event_dispatch();
+
+       ldpd_shutdown();
+       /* NOTREACHED */
+       return (0);
+}
+
+static __dead void
+ldpd_shutdown(void)
+{
+       pid_t            pid;
+       int              status;
+
+       /* close pipes */
+       msgbuf_clear(&iev_ldpe->ibuf.w);
+       close(iev_ldpe->ibuf.fd);
+       msgbuf_clear(&iev_lde->ibuf.w);
+       close(iev_lde->ibuf.fd);
+
+       kr_shutdown();
+       config_clear(ldpd_conf);
+
+       log_debug("waiting for children to terminate");
+       do {
+               pid = wait(&status);
+               if (pid == -1) {
+                       if (errno != EINTR && errno != ECHILD)
+                               fatal("wait");
+               } else if (WIFSIGNALED(status))
+                       log_warnx("%s terminated; signal %d",
+                           (pid == lde_pid) ? "label decision engine" :
+                           "ldp engine", WTERMSIG(status));
+       } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+       free(iev_ldpe);
+       free(iev_lde);
+
+       log_info("terminating");
+       exit(0);
+}
+
+static pid_t
+start_child(enum ldpd_process p, char *argv0, int fd, int debug, int verbose)
+{
+       char    *argv[5];
+       int      argc = 0;
+       pid_t    pid;
+
+       switch (pid = fork()) {
+       case -1:
+               fatal("cannot fork");
+       case 0:
+               break;
+       default:
+               close(fd);
+               return (pid);
+       }
+
+       if (dup2(fd, 3) == -1)
+               fatal("cannot setup imsg fd");
+
+       argv[argc++] = argv0;
+       switch (p) {
+       case PROC_MAIN:
+               fatalx("Can not start main process");
+       case PROC_LDE_ENGINE:
+               argv[argc++] = "-L";
+               break;
+       case PROC_LDP_ENGINE:
+               argv[argc++] = "-E";
+               break;
+       }
+       if (debug)
+               argv[argc++] = "-d";
+       if (verbose)
+               argv[argc++] = "-v";
+       argv[argc++] = NULL;
+
+       execvp(argv0, argv);
+       fatal("execvp");
+}
+
+/* imsg handling */
+/* ARGSUSED */
+static void
+main_dispatch_ldpe(int fd, short event, void *bula)
+{
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       struct imsg              imsg;
+       int                      af;
+       ssize_t                  n;
+       int                      shut = 0, verbose;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("imsg_get");
+
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_REQUEST_SOCKETS:
+                       af = imsg.hdr.pid;
+                       main_imsg_send_net_sockets(af);
+                       break;
+               case IMSG_CTL_RELOAD:
+                       if (ldp_reload() == -1)
+                               log_warnx("configuration reload failed");
+                       else
+                               log_debug("configuration reloaded");
+                       break;
+               case IMSG_CTL_FIB_COUPLE:
+                       kr_fib_couple();
+                       break;
+               case IMSG_CTL_FIB_DECOUPLE:
+                       kr_fib_decouple();
+                       break;
+               case IMSG_CTL_KROUTE:
+               case IMSG_CTL_KROUTE_ADDR:
+                       kr_show_route(&imsg);
+                       break;
+               case IMSG_CTL_IFINFO:
+                       if (imsg.hdr.len == IMSG_HEADER_SIZE)
+                               kr_ifinfo(NULL, imsg.hdr.pid);
+                       else if (imsg.hdr.len == IMSG_HEADER_SIZE + IFNAMSIZ)
+                               kr_ifinfo(imsg.data, imsg.hdr.pid);
+                       else
+                               log_warnx("IFINFO request with wrong len");
+                       break;
+               case IMSG_CTL_LOG_VERBOSE:
+                       /* already checked by ldpe */
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+                       log_verbose(verbose);
+                       break;
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* this pipe is dead, so remove the event handler */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+/* ARGSUSED */
+static void
+main_dispatch_lde(int fd, short event, void *bula)
+{
+       struct imsgev   *iev = bula;
+       struct imsgbuf  *ibuf = &iev->ibuf;
+       struct imsg      imsg;
+       ssize_t          n;
+       int              shut = 0;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("msgbuf_write");
+               if (n == 0)
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("imsg_get");
+
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_KLABEL_CHANGE:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(struct kroute))
+                               fatalx("invalid size of IMSG_KLABEL_CHANGE");
+                       if (kr_change(imsg.data))
+                               log_warnx("%s: error changing route", __func__);
+                       break;
+               case IMSG_KLABEL_DELETE:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(struct kroute))
+                               fatalx("invalid size of IMSG_KLABEL_DELETE");
+                       if (kr_delete(imsg.data))
+                               log_warnx("%s: error deleting route", __func__);
+                       break;
+               case IMSG_KPWLABEL_CHANGE:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(struct kpw))
+                               fatalx("invalid size of IMSG_KPWLABEL_CHANGE");
+                       if (kmpw_set(imsg.data))
+                               log_warnx("%s: error changing pseudowire",
+                                   __func__);
+                       break;
+               case IMSG_KPWLABEL_DELETE:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE !=
+                           sizeof(struct kpw))
+                               fatalx("invalid size of IMSG_KPWLABEL_DELETE");
+                       if (kmpw_unset(imsg.data))
+                               log_warnx("%s: error unsetting pseudowire",
+                                   __func__);
+                       break;
+               default:
+                       log_debug("%s: error handling imsg %d", __func__,
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* this pipe is dead, so remove the event handler */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+void
+main_imsg_compose_ldpe(int type, pid_t pid, void *data, uint16_t datalen)
+{
+       if (iev_ldpe == NULL)
+               return;
+       imsg_compose_event(iev_ldpe, type, 0, pid, -1, data, datalen);
+}
+
+void
+main_imsg_compose_lde(int type, pid_t pid, void *data, uint16_t datalen)
+{
+       imsg_compose_event(iev_lde, type, 0, pid, -1, data, datalen);
+}
+
+static int
+main_imsg_compose_both(enum imsg_type type, void *buf, uint16_t len)
+{
+       if (imsg_compose_event(iev_ldpe, type, 0, 0, -1, buf, len) == -1)
+               return (-1);
+       if (imsg_compose_event(iev_lde, type, 0, 0, -1, buf, len) == -1)
+               return (-1);
+       return (0);
+}
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+       iev->events = EV_READ;
+       if (iev->ibuf.w.queued)
+               iev->events |= EV_WRITE;
+
+       event_del(&iev->ev);
+       event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev);
+       event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid,
+    pid_t pid, int fd, void *data, uint16_t datalen)
+{
+       int     ret;
+
+       if ((ret = imsg_compose(&iev->ibuf, type, peerid,
+           pid, fd, data, datalen)) != -1)
+               imsg_event_add(iev);
+       return (ret);
+}
+
+void
+evbuf_enqueue(struct evbuf *eb, struct ibuf *buf)
+{
+       ibuf_close(&eb->wbuf, buf);
+       evbuf_event_add(eb);
+}
+
+void
+evbuf_event_add(struct evbuf *eb)
+{
+       if (eb->wbuf.queued)
+               event_add(&eb->ev, NULL);
+}
+
+void
+evbuf_init(struct evbuf *eb, int fd, void (*handler)(int, short, void *),
+    void *arg)
+{
+       msgbuf_init(&eb->wbuf);
+       eb->wbuf.fd = fd;
+       event_set(&eb->ev, eb->wbuf.fd, EV_WRITE, handler, arg);
+}
+
+void
+evbuf_clear(struct evbuf *eb)
+{
+       event_del(&eb->ev);
+       msgbuf_clear(&eb->wbuf);
+       eb->wbuf.fd = -1;
+}
+
+static int
+main_imsg_send_ipc_sockets(struct imsgbuf *ldpe_buf, struct imsgbuf *lde_buf)
+{
+       int pipe_ldpe2lde[2];
+
+       if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
+           PF_UNSPEC, pipe_ldpe2lde) == -1)
+               return (-1);
+
+       if (imsg_compose(ldpe_buf, IMSG_SOCKET_IPC, 0, 0, pipe_ldpe2lde[0],
+           NULL, 0) == -1)
+               return (-1);
+       if (imsg_compose(lde_buf, IMSG_SOCKET_IPC, 0, 0, pipe_ldpe2lde[1],
+           NULL, 0) == -1)
+               return (-1);
+
+       return (0);
+}
+
+static void
+main_imsg_send_net_sockets(int af)
+{
+       main_imsg_send_net_socket(af, LDP_SOCKET_DISC);
+       main_imsg_send_net_socket(af, LDP_SOCKET_EDISC);
+       main_imsg_send_net_socket(af, LDP_SOCKET_SESSION);
+       imsg_compose_event(iev_ldpe, IMSG_SETUP_SOCKETS, af, 0, -1, NULL, 0);
+}
+
+static void
+main_imsg_send_net_socket(int af, enum socket_type type)
+{
+       int                      fd;
+
+       fd = ldp_create_socket(af, type);
+       if (fd == -1) {
+               log_warnx("%s: failed to create %s socket for address-family "
+                   "%s", __func__, socket_name(type), af_name(af));
+               return;
+       }
+
+       imsg_compose_event(iev_ldpe, IMSG_SOCKET_NET, af, 0, fd, &type,
+           sizeof(type));
+}
+
+struct ldpd_af_conf *
+ldp_af_conf_get(struct ldpd_conf *xconf, int af)
+{
+       switch (af) {
+       case AF_INET:
+               return (&xconf->ipv4);
+       case AF_INET6:
+               return (&xconf->ipv6);
+       default:
+               fatalx("ldp_af_conf_get: unknown af");
+       }
+}
+
+struct ldpd_af_global *
+ldp_af_global_get(struct ldpd_global *xglobal, int af)
+{
+       switch (af) {
+       case AF_INET:
+               return (&xglobal->ipv4);
+       case AF_INET6:
+               return (&xglobal->ipv6);
+       default:
+               fatalx("ldp_af_global_get: unknown af");
+       }
+}
+
+int
+ldp_is_dual_stack(struct ldpd_conf *xconf)
+{
+       return ((xconf->ipv4.flags & F_LDPD_AF_ENABLED) &&
+           (xconf->ipv6.flags & F_LDPD_AF_ENABLED));
+}
+
+static int
+main_imsg_send_config(struct ldpd_conf *xconf)
+{
+       struct iface            *iface;
+       struct tnbr             *tnbr;
+       struct nbr_params       *nbrp;
+       struct l2vpn            *l2vpn;
+       struct l2vpn_if         *lif;
+       struct l2vpn_pw         *pw;
+
+       if (main_imsg_compose_both(IMSG_RECONF_CONF, xconf,
+           sizeof(*xconf)) == -1)
+               return (-1);
+
+       LIST_FOREACH(iface, &xconf->iface_list, entry) {
+               if (main_imsg_compose_both(IMSG_RECONF_IFACE, iface,
+                   sizeof(*iface)) == -1)
+                       return (-1);
+       }
+
+       LIST_FOREACH(tnbr, &xconf->tnbr_list, entry) {
+               if (main_imsg_compose_both(IMSG_RECONF_TNBR, tnbr,
+                   sizeof(*tnbr)) == -1)
+                       return (-1);
+       }
+
+       LIST_FOREACH(nbrp, &xconf->nbrp_list, entry) {
+               if (main_imsg_compose_both(IMSG_RECONF_NBRP, nbrp,
+                   sizeof(*nbrp)) == -1)
+                       return (-1);
+       }
+
+       LIST_FOREACH(l2vpn, &xconf->l2vpn_list, entry) {
+               if (main_imsg_compose_both(IMSG_RECONF_L2VPN, l2vpn,
+                   sizeof(*l2vpn)) == -1)
+                       return (-1);
+
+               LIST_FOREACH(lif, &l2vpn->if_list, entry) {
+                       if (main_imsg_compose_both(IMSG_RECONF_L2VPN_IF, lif,
+                           sizeof(*lif)) == -1)
+                               return (-1);
+               }
+               LIST_FOREACH(pw, &l2vpn->pw_list, entry) {
+                       if (main_imsg_compose_both(IMSG_RECONF_L2VPN_PW, pw,
+                           sizeof(*pw)) == -1)
+                               return (-1);
+               }
+       }
+
+       if (main_imsg_compose_both(IMSG_RECONF_END, NULL, 0) == -1)
+               return (-1);
+
+       return (0);
+}
+
+static int
+ldp_reload(void)
+{
+       struct ldpd_conf        *xconf;
+
+       if ((xconf = parse_config(conffile)) == NULL)
+               return (-1);
+
+       if (main_imsg_send_config(xconf) == -1)
+               return (-1);
+
+       merge_config(ldpd_conf, xconf);
+
+       return (0);
+}
+
+void
+merge_config(struct ldpd_conf *conf, struct ldpd_conf *xconf)
+{
+       merge_global(conf, xconf);
+       merge_af(AF_INET, &conf->ipv4, &xconf->ipv4);
+       merge_af(AF_INET6, &conf->ipv6, &xconf->ipv6);
+       merge_ifaces(conf, xconf);
+       merge_tnbrs(conf, xconf);
+       merge_nbrps(conf, xconf);
+       merge_l2vpns(conf, xconf);
+       free(xconf);
+}
+
+static void
+merge_global(struct ldpd_conf *conf, struct ldpd_conf *xconf)
+{
+       /* change of router-id requires resetting all neighborships */
+       if (conf->rtr_id.s_addr != xconf->rtr_id.s_addr) {
+               if (ldpd_process == PROC_LDP_ENGINE) {
+                       ldpe_reset_nbrs(AF_INET);
+                       ldpe_reset_nbrs(AF_INET6);
+                       if (conf->rtr_id.s_addr == INADDR_ANY ||
+                           xconf->rtr_id.s_addr == INADDR_ANY) {
+                               if_update_all(AF_UNSPEC);
+                               tnbr_update_all(AF_UNSPEC);
+                       }
+               }
+               conf->rtr_id = xconf->rtr_id;
+       }
+
+       if (conf->trans_pref != xconf->trans_pref) {
+               if (ldpd_process == PROC_LDP_ENGINE)
+                       ldpe_reset_ds_nbrs();
+               conf->trans_pref = xconf->trans_pref;
+       }
+
+       if ((conf->flags & F_LDPD_DS_CISCO_INTEROP) !=
+           (xconf->flags & F_LDPD_DS_CISCO_INTEROP)) {
+               if (ldpd_process == PROC_LDP_ENGINE)
+                       ldpe_reset_ds_nbrs();
+       }
+
+       conf->flags = xconf->flags;
+}
+
+static void
+merge_af(int af, struct ldpd_af_conf *af_conf, struct ldpd_af_conf *xa)
+{
+       int                      egress_label_changed = 0;
+       int                      update_sockets = 0;
+
+       if (af_conf->keepalive != xa->keepalive) {
+               af_conf->keepalive = xa->keepalive;
+               if (ldpd_process == PROC_LDP_ENGINE)
+                       ldpe_stop_init_backoff(af);
+       }
+       af_conf->thello_holdtime = xa->thello_holdtime;
+       af_conf->thello_interval = xa->thello_interval;
+
+       /* update flags */
+       if (ldpd_process == PROC_LDP_ENGINE &&
+           (af_conf->flags & F_LDPD_AF_THELLO_ACCEPT) &&
+           !(xa->flags & F_LDPD_AF_THELLO_ACCEPT))
+               ldpe_remove_dynamic_tnbrs(af);
+
+       if ((af_conf->flags & F_LDPD_AF_NO_GTSM) !=
+           (xa->flags & F_LDPD_AF_NO_GTSM)) {
+               if (af == AF_INET6)
+                       /* need to set/unset IPV6_MINHOPCOUNT */
+                       update_sockets = 1;
+               else if (ldpd_process == PROC_LDP_ENGINE)
+                       /* for LDPv4 just resetting the neighbors is enough */
+                       ldpe_reset_nbrs(af);
+       }
+
+       if ((af_conf->flags & F_LDPD_AF_EXPNULL) !=
+           (xa->flags & F_LDPD_AF_EXPNULL))
+               egress_label_changed = 1;
+
+       af_conf->flags = xa->flags;
+
+       if (egress_label_changed) {
+               switch (ldpd_process) {
+               case PROC_LDE_ENGINE:
+                       lde_change_egress_label(af, af_conf->flags &
+                           F_LDPD_AF_EXPNULL);
+                       break;
+               case PROC_MAIN:
+                       kr_change_egress_label(af, af_conf->flags &
+                           F_LDPD_AF_EXPNULL);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       if (ldp_addrcmp(af, &af_conf->trans_addr, &xa->trans_addr)) {
+               af_conf->trans_addr = xa->trans_addr;
+               update_sockets = 1;
+       }
+
+       if (ldpd_process == PROC_MAIN && update_sockets)
+               imsg_compose_event(iev_ldpe, IMSG_CLOSE_SOCKETS, af, 0, -1,
+                   NULL, 0);
+}
+
+static void
+merge_ifaces(struct ldpd_conf *conf, struct ldpd_conf *xconf)
+{
+       struct iface            *iface, *itmp, *xi;
+
+       LIST_FOREACH_SAFE(iface, &conf->iface_list, entry, itmp) {
+               /* find deleted interfaces */
+               if ((xi = if_lookup(xconf, iface->ifindex)) == NULL) {
+                       LIST_REMOVE(iface, entry);
+                       if (ldpd_process == PROC_LDP_ENGINE)
+                               if_exit(iface);
+                       free(iface);
+               }
+       }
+       LIST_FOREACH_SAFE(xi, &xconf->iface_list, entry, itmp) {
+               /* find new interfaces */
+               if ((iface = if_lookup(conf, xi->ifindex)) == NULL) {
+                       LIST_REMOVE(xi, entry);
+                       LIST_INSERT_HEAD(&conf->iface_list, xi, entry);
+
+                       /* resend addresses to activate new interfaces */
+                       if (ldpd_process == PROC_MAIN)
+                               kif_redistribute(xi->name);
+                       continue;
+               }
+
+               /* update existing interfaces */
+               merge_iface_af(&iface->ipv4, &xi->ipv4);
+               merge_iface_af(&iface->ipv6, &xi->ipv6);
+               LIST_REMOVE(xi, entry);
+               free(xi);
+       }
+}
+
+static void
+merge_iface_af(struct iface_af *ia, struct iface_af *xi)
+{
+       if (ia->enabled != xi->enabled) {
+               ia->enabled = xi->enabled;
+               if (ldpd_process == PROC_LDP_ENGINE)
+                       if_update(ia->iface, ia->af);
+       }
+       ia->hello_holdtime = xi->hello_holdtime;
+       ia->hello_interval = xi->hello_interval;
+}
+
+static void
+merge_tnbrs(struct ldpd_conf *conf, struct ldpd_conf *xconf)
+{
+       struct tnbr             *tnbr, *ttmp, *xt;
+
+       LIST_FOREACH_SAFE(tnbr, &conf->tnbr_list, entry, ttmp) {
+               if (!(tnbr->flags & F_TNBR_CONFIGURED))
+                       continue;
+
+               /* find deleted tnbrs */
+               if ((xt = tnbr_find(xconf, tnbr->af, &tnbr->addr)) == NULL) {
+                       if (ldpd_process == PROC_LDP_ENGINE) {
+                               tnbr->flags &= ~F_TNBR_CONFIGURED;
+                               tnbr_check(tnbr);
+                       } else {
+                               LIST_REMOVE(tnbr, entry);
+                               free(tnbr);
+                       }
+               }
+       }
+       LIST_FOREACH_SAFE(xt, &xconf->tnbr_list, entry, ttmp) {
+               /* find new tnbrs */
+               if ((tnbr = tnbr_find(conf, xt->af, &xt->addr)) == NULL) {
+                       LIST_REMOVE(xt, entry);
+                       LIST_INSERT_HEAD(&conf->tnbr_list, xt, entry);
+
+                       if (ldpd_process == PROC_LDP_ENGINE)
+                               tnbr_update(xt);
+                       continue;
+               }
+
+               /* update existing tnbrs */
+               if (!(tnbr->flags & F_TNBR_CONFIGURED))
+                       tnbr->flags |= F_TNBR_CONFIGURED;
+               tnbr->hello_holdtime = xt->hello_holdtime;
+               tnbr->hello_interval = xt->hello_interval;
+               LIST_REMOVE(xt, entry);
+               free(xt);
+       }
+}
+
+static void
+merge_nbrps(struct ldpd_conf *conf, struct ldpd_conf *xconf)
+{
+       struct nbr_params       *nbrp, *ntmp, *xn;
+       struct nbr              *nbr;
+       int                      nbrp_changed;
+
+       LIST_FOREACH_SAFE(nbrp, &conf->nbrp_list, entry, ntmp) {
+               /* find deleted nbrps */
+               if ((xn = nbr_params_find(xconf, nbrp->lsr_id)) == NULL) {
+                       if (ldpd_process == PROC_LDP_ENGINE) {
+                               nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr);
+                               if (nbr) {
+                                       session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+                                       pfkey_remove(nbr);
+                                       if (nbr_session_active_role(nbr))
+                                               nbr_establish_connection(nbr);
+                               }
+                       }
+                       LIST_REMOVE(nbrp, entry);
+                       free(nbrp);
+               }
+       }
+       LIST_FOREACH_SAFE(xn, &xconf->nbrp_list, entry, ntmp) {
+               /* find new nbrps */
+               if ((nbrp = nbr_params_find(conf, xn->lsr_id)) == NULL) {
+                       LIST_REMOVE(xn, entry);
+                       LIST_INSERT_HEAD(&conf->nbrp_list, xn, entry);
+
+                       if (ldpd_process == PROC_LDP_ENGINE) {
+                               nbr = nbr_find_ldpid(xn->lsr_id.s_addr);
+                               if (nbr) {
+                                       session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+                                       if (pfkey_establish(nbr, xn) == -1)
+                                               fatalx("pfkey setup failed");
+                                       if (nbr_session_active_role(nbr))
+                                               nbr_establish_connection(nbr);
+                               }
+                       }
+                       continue;
+               }
+
+               /* update existing nbrps */
+               if (nbrp->flags != xn->flags ||
+                   nbrp->keepalive != xn->keepalive ||
+                   nbrp->gtsm_enabled != xn->gtsm_enabled ||
+                   nbrp->gtsm_hops != xn->gtsm_hops ||
+                   nbrp->auth.method != xn->auth.method ||
+                   strcmp(nbrp->auth.md5key, xn->auth.md5key) != 0)
+                       nbrp_changed = 1;
+               else
+                       nbrp_changed = 0;
+
+               nbrp->keepalive = xn->keepalive;
+               nbrp->gtsm_enabled = xn->gtsm_enabled;
+               nbrp->gtsm_hops = xn->gtsm_hops;
+               nbrp->auth.method = xn->auth.method;
+               strlcpy(nbrp->auth.md5key, xn->auth.md5key,
+                   sizeof(nbrp->auth.md5key));
+               nbrp->auth.md5key_len = xn->auth.md5key_len;
+               nbrp->flags = xn->flags;
+
+               if (ldpd_process == PROC_LDP_ENGINE) {
+                       nbr = nbr_find_ldpid(nbrp->lsr_id.s_addr);
+                       if (nbr && nbrp_changed) {
+                               session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+                               pfkey_remove(nbr);
+                               if (pfkey_establish(nbr, nbrp) == -1)
+                                       fatalx("pfkey setup failed");
+                               if (nbr_session_active_role(nbr))
+                                       nbr_establish_connection(nbr);
+                       }
+               }
+               LIST_REMOVE(xn, entry);
+               free(xn);
+       }
+}
+
+static void
+merge_l2vpns(struct ldpd_conf *conf, struct ldpd_conf *xconf)
+{
+       struct l2vpn            *l2vpn, *ltmp, *xl;
+
+       LIST_FOREACH_SAFE(l2vpn, &conf->l2vpn_list, entry, ltmp) {
+               /* find deleted l2vpns */
+               if ((xl = l2vpn_find(xconf, l2vpn->name)) == NULL) {
+                       LIST_REMOVE(l2vpn, entry);
+
+                       switch (ldpd_process) {
+                       case PROC_LDE_ENGINE:
+                               l2vpn_exit(l2vpn);
+                               break;
+                       case PROC_LDP_ENGINE:
+                               ldpe_l2vpn_exit(l2vpn);
+                               break;
+                       case PROC_MAIN:
+                               break;
+                       }
+                       l2vpn_del(l2vpn);
+               }
+       }
+       LIST_FOREACH_SAFE(xl, &xconf->l2vpn_list, entry, ltmp) {
+               /* find new l2vpns */
+               if ((l2vpn = l2vpn_find(conf, xl->name)) == NULL) {
+                       LIST_REMOVE(xl, entry);
+                       LIST_INSERT_HEAD(&conf->l2vpn_list, xl, entry);
+
+                       switch (ldpd_process) {
+                       case PROC_LDE_ENGINE:
+                               l2vpn_init(xl);
+                               break;
+                       case PROC_LDP_ENGINE:
+                               ldpe_l2vpn_init(xl);
+                               break;
+                       case PROC_MAIN:
+                               break;
+                       }
+                       continue;
+               }
+
+               /* update existing l2vpns */
+               merge_l2vpn(conf, l2vpn, xl);
+               LIST_REMOVE(xl, entry);
+               free(xl);
+       }
+}
+
+static void
+merge_l2vpn(struct ldpd_conf *xconf, struct l2vpn *l2vpn, struct l2vpn *xl)
+{
+       struct l2vpn_if         *lif, *ftmp, *xf;
+       struct l2vpn_pw         *pw, *ptmp, *xp;
+       struct nbr              *nbr;
+       int                      reset_nbr, reinstall_pwfec, reinstall_tnbr;
+       int                      previous_pw_type, previous_mtu;
+
+       previous_pw_type = l2vpn->pw_type;
+       previous_mtu = l2vpn->mtu;
+
+       /* merge intefaces */
+       LIST_FOREACH_SAFE(lif, &l2vpn->if_list, entry, ftmp) {
+               /* find deleted interfaces */
+               if ((xf = l2vpn_if_find(xl, lif->ifindex)) == NULL) {
+                       LIST_REMOVE(lif, entry);
+                       free(lif);
+               }
+       }
+       LIST_FOREACH_SAFE(xf, &xl->if_list, entry, ftmp) {
+               /* find new interfaces */
+               if ((lif = l2vpn_if_find(l2vpn, xf->ifindex)) == NULL) {
+                       LIST_REMOVE(xf, entry);
+                       LIST_INSERT_HEAD(&l2vpn->if_list, xf, entry);
+                       xf->l2vpn = l2vpn;
+                       continue;
+               }
+
+               LIST_REMOVE(xf, entry);
+               free(xf);
+       }
+
+       /* merge pseudowires */
+       LIST_FOREACH_SAFE(pw, &l2vpn->pw_list, entry, ptmp) {
+               /* find deleted pseudowires */
+               if ((xp = l2vpn_pw_find(xl, pw->ifindex)) == NULL) {
+                       switch (ldpd_process) {
+                       case PROC_LDE_ENGINE:
+                               l2vpn_pw_exit(pw);
+                               break;
+                       case PROC_LDP_ENGINE:
+                               ldpe_l2vpn_pw_exit(pw);
+                               break;
+                       case PROC_MAIN:
+                               break;
+                       }
+
+                       LIST_REMOVE(pw, entry);
+                       free(pw);
+               }
+       }
+       LIST_FOREACH_SAFE(xp, &xl->pw_list, entry, ptmp) {
+               /* find new pseudowires */
+               if ((pw = l2vpn_pw_find(l2vpn, xp->ifindex)) == NULL) {
+                       LIST_REMOVE(xp, entry);
+                       LIST_INSERT_HEAD(&l2vpn->pw_list, xp, entry);
+                       xp->l2vpn = l2vpn;
+
+                       switch (ldpd_process) {
+                       case PROC_LDE_ENGINE:
+                               l2vpn_pw_init(xp);
+                               break;
+                       case PROC_LDP_ENGINE:
+                               ldpe_l2vpn_pw_init(xp);
+                               break;
+                       case PROC_MAIN:
+                               break;
+                       }
+                       continue;
+               }
+
+               /* update existing pseudowire */
+               if (pw->af != xp->af ||
+                   ldp_addrcmp(pw->af, &pw->addr, &xp->addr))
+                       reinstall_tnbr = 1;
+               else
+                       reinstall_tnbr = 0;
+
+               /* changes that require a session restart */
+               if ((pw->flags & (F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF)) !=
+                   (xp->flags & (F_PW_STATUSTLV_CONF|F_PW_CWORD_CONF)))
+                       reset_nbr = 1;
+               else
+                       reset_nbr = 0;
+
+               if (l2vpn->pw_type != xl->pw_type || l2vpn->mtu != xl->mtu ||
+                   pw->pwid != xp->pwid || reinstall_tnbr || reset_nbr ||
+                   pw->lsr_id.s_addr != xp->lsr_id.s_addr)
+                       reinstall_pwfec = 1;
+               else
+                       reinstall_pwfec = 0;
+
+               if (ldpd_process == PROC_LDP_ENGINE) {
+                       if (reinstall_tnbr)
+                               ldpe_l2vpn_pw_exit(pw);
+                       if (reset_nbr) {
+                               nbr = nbr_find_ldpid(pw->lsr_id.s_addr);
+                               if (nbr && nbr->state == NBR_STA_OPER)
+                                       session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+                       }
+               }
+               if (ldpd_process == PROC_LDE_ENGINE &&
+                   !reset_nbr && reinstall_pwfec)
+                       l2vpn_pw_exit(pw);
+               pw->lsr_id = xp->lsr_id;
+               pw->af = xp->af;
+               pw->addr = xp->addr;
+               pw->pwid = xp->pwid;
+               strlcpy(pw->ifname, xp->ifname, sizeof(pw->ifname));
+               pw->ifindex = xp->ifindex;
+               if (xp->flags & F_PW_CWORD_CONF)
+                       pw->flags |= F_PW_CWORD_CONF;
+               else
+                       pw->flags &= ~F_PW_CWORD_CONF;
+               if (xp->flags & F_PW_STATUSTLV_CONF)
+                       pw->flags |= F_PW_STATUSTLV_CONF;
+               else
+                       pw->flags &= ~F_PW_STATUSTLV_CONF;
+               if (ldpd_process == PROC_LDP_ENGINE && reinstall_tnbr)
+                       ldpe_l2vpn_pw_init(pw);
+               if (ldpd_process == PROC_LDE_ENGINE &&
+                   !reset_nbr && reinstall_pwfec) {
+                       l2vpn->pw_type = xl->pw_type;
+                       l2vpn->mtu = xl->mtu;
+                       l2vpn_pw_init(pw);
+                       l2vpn->pw_type = previous_pw_type;
+                       l2vpn->mtu = previous_mtu;
+               }
+
+               LIST_REMOVE(xp, entry);
+               free(xp);
+       }
+
+       l2vpn->pw_type = xl->pw_type;
+       l2vpn->mtu = xl->mtu;
+       strlcpy(l2vpn->br_ifname, xl->br_ifname, sizeof(l2vpn->br_ifname));
+       l2vpn->br_ifindex = xl->br_ifindex;
+}
+
+struct ldpd_conf *
+config_new_empty(void)
+{
+       struct ldpd_conf        *xconf;
+
+       xconf = calloc(1, sizeof(*xconf));
+       if (xconf == NULL)
+               fatal(NULL);
+
+       LIST_INIT(&xconf->iface_list);
+       LIST_INIT(&xconf->tnbr_list);
+       LIST_INIT(&xconf->nbrp_list);
+       LIST_INIT(&xconf->l2vpn_list);
+
+       return (xconf);
+}
+
+void
+config_clear(struct ldpd_conf *conf)
+{
+       struct ldpd_conf        *xconf;
+
+       /*
+        * Merge current config with an empty config, this will deactivate
+        * and deallocate all the interfaces, pseudowires and so on. Before
+        * merging, copy the router-id and other variables to avoid some
+        * unnecessary operations, like trying to reset the neighborships.
+        */
+       xconf = config_new_empty();
+       xconf->ipv4 = conf->ipv4;
+       xconf->ipv6 = conf->ipv6;
+       xconf->rtr_id = conf->rtr_id;
+       xconf->trans_pref = conf->trans_pref;
+       xconf->flags = conf->flags;
+       merge_config(conf, xconf);
+       free(conf);
+}
diff --git a/ldpd/ldpd.h b/ldpd/ldpd.h
new file mode 100644 (file)
index 0000000..d32d23c
--- /dev/null
@@ -0,0 +1,606 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _LDPD_H_
+#define _LDPD_H_
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <event.h>
+#include <imsg.h>
+
+#include "ldp.h"
+
+#define CONF_FILE              "/etc/ldpd.conf"
+#define        LDPD_SOCKET             "/var/run/ldpd.sock"
+#define LDPD_USER              "_ldpd"
+
+#define LDPD_OPT_VERBOSE       0x00000001
+#define LDPD_OPT_VERBOSE2      0x00000002
+#define LDPD_OPT_NOACTION      0x00000004
+
+#define TCP_MD5_KEY_LEN                80
+#define L2VPN_NAME_LEN         32
+
+#define        RT_BUF_SIZE             16384
+#define        MAX_RTSOCK_BUF          128 * 1024
+#define        LDP_BACKLOG             128
+
+#define        F_LDPD_INSERTED         0x0001
+#define        F_CONNECTED             0x0002
+#define        F_STATIC                0x0004
+#define        F_DYNAMIC               0x0008
+#define        F_REJECT                0x0010
+#define        F_BLACKHOLE             0x0020
+#define        F_REDISTRIBUTED         0x0040
+
+struct evbuf {
+       struct msgbuf           wbuf;
+       struct event            ev;
+};
+
+struct imsgev {
+       struct imsgbuf           ibuf;
+       void                    (*handler)(int, short, void *);
+       struct event             ev;
+       short                    events;
+};
+
+enum imsg_type {
+       IMSG_NONE,
+       IMSG_CTL_RELOAD,
+       IMSG_CTL_SHOW_INTERFACE,
+       IMSG_CTL_SHOW_DISCOVERY,
+       IMSG_CTL_SHOW_NBR,
+       IMSG_CTL_SHOW_LIB,
+       IMSG_CTL_SHOW_L2VPN_PW,
+       IMSG_CTL_SHOW_L2VPN_BINDING,
+       IMSG_CTL_CLEAR_NBR,
+       IMSG_CTL_FIB_COUPLE,
+       IMSG_CTL_FIB_DECOUPLE,
+       IMSG_CTL_KROUTE,
+       IMSG_CTL_KROUTE_ADDR,
+       IMSG_CTL_IFINFO,
+       IMSG_CTL_END,
+       IMSG_CTL_LOG_VERBOSE,
+       IMSG_KLABEL_CHANGE,
+       IMSG_KLABEL_DELETE,
+       IMSG_KPWLABEL_CHANGE,
+       IMSG_KPWLABEL_DELETE,
+       IMSG_IFSTATUS,
+       IMSG_NEWADDR,
+       IMSG_DELADDR,
+       IMSG_LABEL_MAPPING,
+       IMSG_LABEL_MAPPING_FULL,
+       IMSG_LABEL_REQUEST,
+       IMSG_LABEL_RELEASE,
+       IMSG_LABEL_WITHDRAW,
+       IMSG_LABEL_ABORT,
+       IMSG_REQUEST_ADD,
+       IMSG_REQUEST_ADD_END,
+       IMSG_MAPPING_ADD,
+       IMSG_MAPPING_ADD_END,
+       IMSG_RELEASE_ADD,
+       IMSG_RELEASE_ADD_END,
+       IMSG_WITHDRAW_ADD,
+       IMSG_WITHDRAW_ADD_END,
+       IMSG_ADDRESS_ADD,
+       IMSG_ADDRESS_DEL,
+       IMSG_NOTIFICATION,
+       IMSG_NOTIFICATION_SEND,
+       IMSG_NEIGHBOR_UP,
+       IMSG_NEIGHBOR_DOWN,
+       IMSG_NETWORK_ADD,
+       IMSG_NETWORK_DEL,
+       IMSG_SOCKET_IPC,
+       IMSG_SOCKET_NET,
+       IMSG_CLOSE_SOCKETS,
+       IMSG_REQUEST_SOCKETS,
+       IMSG_SETUP_SOCKETS,
+       IMSG_RECONF_CONF,
+       IMSG_RECONF_IFACE,
+       IMSG_RECONF_TNBR,
+       IMSG_RECONF_NBRP,
+       IMSG_RECONF_L2VPN,
+       IMSG_RECONF_L2VPN_IF,
+       IMSG_RECONF_L2VPN_PW,
+       IMSG_RECONF_END
+};
+
+union ldpd_addr {
+       struct in_addr  v4;
+       struct in6_addr v6;
+};
+
+#define IN6_IS_SCOPE_EMBED(a)   \
+       ((IN6_IS_ADDR_LINKLOCAL(a)) ||  \
+        (IN6_IS_ADDR_MC_LINKLOCAL(a)) || \
+        (IN6_IS_ADDR_MC_INTFACELOCAL(a)))
+
+/* interface states */
+#define        IF_STA_DOWN             0x01
+#define        IF_STA_ACTIVE           0x02
+
+/* targeted neighbor states */
+#define        TNBR_STA_DOWN           0x01
+#define        TNBR_STA_ACTIVE         0x02
+
+/* interface types */
+enum iface_type {
+       IF_TYPE_POINTOPOINT,
+       IF_TYPE_BROADCAST
+};
+
+/* neighbor states */
+#define        NBR_STA_PRESENT         0x0001
+#define        NBR_STA_INITIAL         0x0002
+#define        NBR_STA_OPENREC         0x0004
+#define        NBR_STA_OPENSENT        0x0008
+#define        NBR_STA_OPER            0x0010
+#define        NBR_STA_SESSION         (NBR_STA_INITIAL | NBR_STA_OPENREC | \
+                               NBR_STA_OPENSENT | NBR_STA_OPER)
+
+/* neighbor events */
+enum nbr_event {
+       NBR_EVT_NOTHING,
+       NBR_EVT_MATCH_ADJ,
+       NBR_EVT_CONNECT_UP,
+       NBR_EVT_CLOSE_SESSION,
+       NBR_EVT_INIT_RCVD,
+       NBR_EVT_KEEPALIVE_RCVD,
+       NBR_EVT_PDU_RCVD,
+       NBR_EVT_PDU_SENT,
+       NBR_EVT_INIT_SENT
+};
+
+/* neighbor actions */
+enum nbr_action {
+       NBR_ACT_NOTHING,
+       NBR_ACT_RST_KTIMEOUT,
+       NBR_ACT_SESSION_EST,
+       NBR_ACT_RST_KTIMER,
+       NBR_ACT_CONNECT_SETUP,
+       NBR_ACT_PASSIVE_INIT,
+       NBR_ACT_KEEPALIVE_SEND,
+       NBR_ACT_CLOSE_SESSION
+};
+
+TAILQ_HEAD(mapping_head, mapping_entry);
+
+struct map {
+       uint8_t         type;
+       uint32_t        msg_id;
+       union {
+               struct {
+                       uint16_t        af;
+                       union ldpd_addr prefix;
+                       uint8_t         prefixlen;
+               } prefix;
+               struct {
+                       uint16_t        type;
+                       uint32_t        pwid;
+                       uint32_t        group_id;
+                       uint16_t        ifmtu;
+               } pwid;
+       } fec;
+       struct {
+               uint32_t        status_code;
+               uint32_t        msg_id;
+               uint16_t        msg_type;
+       } st;
+       uint32_t        label;
+       uint32_t        requestid;
+       uint32_t        pw_status;
+       uint8_t         flags;
+};
+#define F_MAP_REQ_ID   0x01    /* optional request message id present */
+#define F_MAP_STATUS   0x02    /* status */
+#define F_MAP_PW_CWORD 0x04    /* pseudowire control word */
+#define F_MAP_PW_ID    0x08    /* pseudowire connection id */
+#define F_MAP_PW_IFMTU 0x10    /* pseudowire interface parameter */
+#define F_MAP_PW_STATUS        0x20    /* pseudowire status */
+
+struct notify_msg {
+       uint32_t        status_code;
+       uint32_t        msg_id;         /* network byte order */
+       uint16_t        msg_type;       /* network byte order */
+       uint32_t        pw_status;
+       struct map      fec;
+       uint8_t         flags;
+};
+#define F_NOTIF_PW_STATUS      0x01    /* pseudowire status tlv present */
+#define F_NOTIF_FEC            0x02    /* fec tlv present */
+
+struct if_addr {
+       LIST_ENTRY(if_addr)      entry;
+       int                      af;
+       union ldpd_addr          addr;
+       uint8_t                  prefixlen;
+       union ldpd_addr          dstbrd;
+};
+LIST_HEAD(if_addr_head, if_addr);
+
+struct iface_af {
+       struct iface            *iface;
+       int                      af;
+       int                      enabled;
+       int                      state;
+       LIST_HEAD(, adj)         adj_list;
+       time_t                   uptime;
+       struct event             hello_timer;
+       uint16_t                 hello_holdtime;
+       uint16_t                 hello_interval;
+};
+
+struct iface {
+       LIST_ENTRY(iface)        entry;
+       char                     name[IF_NAMESIZE];
+       unsigned int             ifindex;
+       struct if_addr_head      addr_list;
+       struct in6_addr          linklocal;
+       enum iface_type          type;
+       uint8_t                  if_type;
+       uint16_t                 flags;
+       uint8_t                  linkstate;
+       struct iface_af          ipv4;
+       struct iface_af          ipv6;
+};
+
+/* source of targeted hellos */
+struct tnbr {
+       LIST_ENTRY(tnbr)         entry;
+       struct event             hello_timer;
+       struct adj              *adj;
+       int                      af;
+       union ldpd_addr          addr;
+       int                      state;
+       uint16_t                 hello_holdtime;
+       uint16_t                 hello_interval;
+       uint16_t                 pw_count;
+       uint8_t                  flags;
+};
+#define F_TNBR_CONFIGURED       0x01
+#define F_TNBR_DYNAMIC          0x02
+
+enum auth_method {
+       AUTH_NONE,
+       AUTH_MD5SIG
+};
+
+/* neighbor specific parameters */
+struct nbr_params {
+       LIST_ENTRY(nbr_params)   entry;
+       struct in_addr           lsr_id;
+       uint16_t                 keepalive;
+       int                      gtsm_enabled;
+       uint8_t                  gtsm_hops;
+       struct {
+               enum auth_method         method;
+               char                     md5key[TCP_MD5_KEY_LEN];
+               uint8_t                  md5key_len;
+       } auth;
+       uint8_t                  flags;
+};
+#define F_NBRP_KEEPALIVE        0x01
+#define F_NBRP_GTSM             0x02
+#define F_NBRP_GTSM_HOPS        0x04
+
+struct l2vpn_if {
+       LIST_ENTRY(l2vpn_if)     entry;
+       struct l2vpn            *l2vpn;
+       char                     ifname[IF_NAMESIZE];
+       unsigned int             ifindex;
+       uint16_t                 flags;
+       uint8_t                  link_state;
+};
+
+struct l2vpn_pw {
+       LIST_ENTRY(l2vpn_pw)     entry;
+       struct l2vpn            *l2vpn;
+       struct in_addr           lsr_id;
+       int                      af;
+       union ldpd_addr          addr;
+       uint32_t                 pwid;
+       char                     ifname[IF_NAMESIZE];
+       unsigned int             ifindex;
+       uint32_t                 remote_group;
+       uint16_t                 remote_mtu;
+       uint32_t                 remote_status;
+       uint8_t                  flags;
+};
+#define F_PW_STATUSTLV_CONF    0x01    /* status tlv configured */
+#define F_PW_STATUSTLV         0x02    /* status tlv negotiated */
+#define F_PW_CWORD_CONF                0x04    /* control word configured */
+#define F_PW_CWORD             0x08    /* control word negotiated */
+#define F_PW_STATUS_UP         0x10    /* pseudowire is operational */
+
+struct l2vpn {
+       LIST_ENTRY(l2vpn)        entry;
+       char                     name[L2VPN_NAME_LEN];
+       int                      type;
+       int                      pw_type;
+       int                      mtu;
+       char                     br_ifname[IF_NAMESIZE];
+       unsigned int             br_ifindex;
+       LIST_HEAD(, l2vpn_if)    if_list;
+       LIST_HEAD(, l2vpn_pw)    pw_list;
+};
+#define L2VPN_TYPE_VPWS                1
+#define L2VPN_TYPE_VPLS                2
+
+/* ldp_conf */
+enum ldpd_process {
+       PROC_MAIN,
+       PROC_LDP_ENGINE,
+       PROC_LDE_ENGINE
+} ldpd_process;
+
+enum socket_type {
+       LDP_SOCKET_DISC,
+       LDP_SOCKET_EDISC,
+       LDP_SOCKET_SESSION
+};
+
+enum hello_type {
+       HELLO_LINK,
+       HELLO_TARGETED
+};
+
+struct ldpd_af_conf {
+       uint16_t                 keepalive;
+       uint16_t                 thello_holdtime;
+       uint16_t                 thello_interval;
+       union ldpd_addr          trans_addr;
+       int                      flags;
+};
+#define        F_LDPD_AF_ENABLED       0x0001
+#define        F_LDPD_AF_THELLO_ACCEPT 0x0002
+#define        F_LDPD_AF_EXPNULL       0x0004
+#define        F_LDPD_AF_NO_GTSM       0x0008
+
+struct ldpd_conf {
+       struct in_addr           rtr_id;
+       struct ldpd_af_conf      ipv4;
+       struct ldpd_af_conf      ipv6;
+       LIST_HEAD(, iface)       iface_list;
+       LIST_HEAD(, tnbr)        tnbr_list;
+       LIST_HEAD(, nbr_params)  nbrp_list;
+       LIST_HEAD(, l2vpn)       l2vpn_list;
+       uint16_t                 trans_pref;
+       int                      flags;
+};
+#define        F_LDPD_NO_FIB_UPDATE    0x0001
+#define        F_LDPD_DS_CISCO_INTEROP 0x0002
+
+struct ldpd_af_global {
+       struct event             disc_ev;
+       struct event             edisc_ev;
+       int                      ldp_disc_socket;
+       int                      ldp_edisc_socket;
+       int                      ldp_session_socket;
+};
+
+struct ldpd_global {
+       int                      cmd_opts;
+       time_t                   uptime;
+       struct ldpd_af_global    ipv4;
+       struct ldpd_af_global    ipv6;
+       uint32_t                 conf_seqnum;
+       int                      pfkeysock;
+       struct if_addr_head      addr_list;
+       LIST_HEAD(, adj)         adj_list;
+       struct in_addr           mcast_addr_v4;
+       struct in6_addr          mcast_addr_v6;
+       TAILQ_HEAD(, pending_conn) pending_conns;
+};
+
+/* kroute */
+struct kroute {
+       int                      af;
+       union ldpd_addr          prefix;
+       uint8_t                  prefixlen;
+       union ldpd_addr          nexthop;
+       uint32_t                 local_label;
+       uint32_t                 remote_label;
+       unsigned short           ifindex;
+       uint8_t                  priority;
+       uint16_t                 flags;
+};
+
+struct kpw {
+       unsigned short           ifindex;
+       int                      pw_type;
+       int                      af;
+       union ldpd_addr          nexthop;
+       uint32_t                 local_label;
+       uint32_t                 remote_label;
+       uint8_t                  flags;
+};
+
+struct kaddr {
+       unsigned short           ifindex;
+       int                      af;
+       union ldpd_addr          addr;
+       uint8_t                  prefixlen;
+       union ldpd_addr          dstbrd;
+};
+
+struct kif {
+       char                     ifname[IF_NAMESIZE];
+       unsigned short           ifindex;
+       int                      flags;
+       uint8_t                  link_state;
+       int                      mtu;
+       uint8_t                  if_type;
+       uint64_t                 baudrate;
+};
+
+/* control data structures */
+struct ctl_iface {
+       int                      af;
+       char                     name[IF_NAMESIZE];
+       unsigned int             ifindex;
+       int                      state;
+       uint16_t                 flags;
+       uint8_t                  linkstate;
+       enum iface_type          type;
+       uint8_t                  if_type;
+       uint16_t                 hello_holdtime;
+       uint16_t                 hello_interval;
+       time_t                   uptime;
+       uint16_t                 adj_cnt;
+};
+
+struct ctl_adj {
+       int                      af;
+       struct in_addr           id;
+       enum hello_type          type;
+       char                     ifname[IF_NAMESIZE];
+       union ldpd_addr          src_addr;
+       uint16_t                 holdtime;
+       union ldpd_addr          trans_addr;
+};
+
+struct ctl_nbr {
+       int                      af;
+       struct in_addr           id;
+       union ldpd_addr          laddr;
+       union ldpd_addr          raddr;
+       time_t                   uptime;
+       int                      nbr_state;
+};
+
+struct ctl_rt {
+       int                      af;
+       union ldpd_addr          prefix;
+       uint8_t                  prefixlen;
+       struct in_addr           nexthop;       /* lsr-id */
+       uint32_t                 local_label;
+       uint32_t                 remote_label;
+       uint8_t                  flags;
+       uint8_t                  in_use;
+};
+
+struct ctl_pw {
+       uint16_t                 type;
+       char                     ifname[IF_NAMESIZE];
+       uint32_t                 pwid;
+       struct in_addr           lsr_id;
+       uint32_t                 local_label;
+       uint32_t                 local_gid;
+       uint16_t                 local_ifmtu;
+       uint32_t                 remote_label;
+       uint32_t                 remote_gid;
+       uint16_t                 remote_ifmtu;
+       uint32_t                 status;
+};
+
+extern struct ldpd_conf                *ldpd_conf;
+extern struct ldpd_global       global;
+
+/* parse.y */
+struct ldpd_conf       *parse_config(char *);
+int                     cmdline_symset(char *);
+
+/* kroute.c */
+int             kif_init(void);
+int             kr_init(int);
+void            kif_redistribute(const char *);
+int             kr_change(struct kroute *);
+int             kr_delete(struct kroute *);
+void            kr_shutdown(void);
+void            kr_fib_couple(void);
+void            kr_fib_decouple(void);
+void            kr_change_egress_label(int, int);
+void            kr_show_route(struct imsg *);
+void            kr_ifinfo(char *, pid_t);
+struct kif     *kif_findname(char *);
+void            kif_clear(void);
+int             kmpw_set(struct kpw *);
+int             kmpw_unset(struct kpw *);
+
+/* util.c */
+uint8_t                 mask2prefixlen(in_addr_t);
+uint8_t                 mask2prefixlen6(struct sockaddr_in6 *);
+in_addr_t       prefixlen2mask(uint8_t);
+struct in6_addr        *prefixlen2mask6(uint8_t);
+void            ldp_applymask(int, union ldpd_addr *,
+                   const union ldpd_addr *, int);
+int             ldp_addrcmp(int, const union ldpd_addr *,
+                   const union ldpd_addr *);
+int             ldp_addrisset(int, const union ldpd_addr *);
+int             ldp_prefixcmp(int, const union ldpd_addr *,
+                   const union ldpd_addr *, uint8_t);
+int             bad_addr_v4(struct in_addr);
+int             bad_addr_v6(struct in6_addr *);
+int             bad_addr(int, union ldpd_addr *);
+void            embedscope(struct sockaddr_in6 *);
+void            recoverscope(struct sockaddr_in6 *);
+void            addscope(struct sockaddr_in6 *, uint32_t);
+void            clearscope(struct in6_addr *);
+struct sockaddr        *addr2sa(int af, union ldpd_addr *, uint16_t);
+void            sa2addr(struct sockaddr *, int *, union ldpd_addr *);
+
+/* ldpd.c */
+void                    main_imsg_compose_ldpe(int, pid_t, void *, uint16_t);
+void                    main_imsg_compose_lde(int, pid_t, void *, uint16_t);
+void                    imsg_event_add(struct imsgev *);
+int                     imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t,
+                           int, void *, uint16_t);
+void                    evbuf_enqueue(struct evbuf *, struct ibuf *);
+void                    evbuf_event_add(struct evbuf *);
+void                    evbuf_init(struct evbuf *, int, void (*)(int, short, void *), void *);
+void                    evbuf_clear(struct evbuf *);
+struct ldpd_af_conf    *ldp_af_conf_get(struct ldpd_conf *, int);
+struct ldpd_af_global  *ldp_af_global_get(struct ldpd_global *, int);
+int                     ldp_is_dual_stack(struct ldpd_conf *);
+void                    merge_config(struct ldpd_conf *, struct ldpd_conf *);
+struct ldpd_conf       *config_new_empty(void);
+void                    config_clear(struct ldpd_conf *);
+
+/* socket.c */
+int             ldp_create_socket(int, enum socket_type);
+void            sock_set_recvbuf(int);
+int             sock_set_reuse(int, int);
+int             sock_set_bindany(int, int);
+int             sock_set_ipv4_tos(int, int);
+int             sock_set_ipv4_recvif(int, int);
+int             sock_set_ipv4_minttl(int, int);
+int             sock_set_ipv4_ucast_ttl(int fd, int);
+int             sock_set_ipv4_mcast_ttl(int, uint8_t);
+int             sock_set_ipv4_mcast(struct iface *);
+int             sock_set_ipv4_mcast_loop(int);
+int             sock_set_ipv6_dscp(int, int);
+int             sock_set_ipv6_pktinfo(int, int);
+int             sock_set_ipv6_minhopcount(int, int);
+int             sock_set_ipv6_ucast_hops(int, int);
+int             sock_set_ipv6_mcast_hops(int, int);
+int             sock_set_ipv6_mcast(struct iface *);
+int             sock_set_ipv6_mcast_loop(int);
+
+/* printconf.c */
+void   print_config(struct ldpd_conf *);
+
+#endif /* _LDPD_H_ */
diff --git a/ldpd/ldpe.c b/ldpd/ldpe.c
new file mode 100644 (file)
index 0000000..1b4d2a6
--- /dev/null
@@ -0,0 +1,808 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2008 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "lde.h"
+#include "control.h"
+#include "log.h"
+
+static void     ldpe_sig_handler(int, short, void *);
+static __dead void ldpe_shutdown(void);
+static void     ldpe_dispatch_main(int, short, void *);
+static void     ldpe_dispatch_lde(int, short, void *);
+static void     ldpe_dispatch_pfkey(int, short, void *);
+static void     ldpe_setup_sockets(int, int, int, int);
+static void     ldpe_close_sockets(int);
+static void     ldpe_iface_af_ctl(struct ctl_conn *, int, unsigned int);
+
+struct ldpd_conf       *leconf;
+struct ldpd_sysdep      sysdep;
+
+static struct imsgev   *iev_main;
+static struct imsgev   *iev_lde;
+static struct event     pfkey_ev;
+
+/* ARGSUSED */
+static void
+ldpe_sig_handler(int sig, short event, void *bula)
+{
+       switch (sig) {
+       case SIGINT:
+       case SIGTERM:
+               ldpe_shutdown();
+               /* NOTREACHED */
+       default:
+               fatalx("unexpected signal");
+       }
+}
+
+/* label distribution protocol engine */
+void
+ldpe(int debug, int verbose)
+{
+       struct passwd           *pw;
+       struct event             ev_sigint, ev_sigterm;
+
+       leconf = config_new_empty();
+
+       log_init(debug);
+       log_verbose(verbose);
+
+       setproctitle("ldp engine");
+       ldpd_process = PROC_LDP_ENGINE;
+
+       /* create ldpd control socket outside chroot */
+       if (control_init() == -1)
+               fatalx("control socket setup failed");
+
+       LIST_INIT(&global.addr_list);
+       LIST_INIT(&global.adj_list);
+       TAILQ_INIT(&global.pending_conns);
+       if (inet_pton(AF_INET, AllRouters_v4, &global.mcast_addr_v4) != 1)
+               fatal("inet_pton");
+       if (inet_pton(AF_INET6, AllRouters_v6, &global.mcast_addr_v6) != 1)
+               fatal("inet_pton");
+       global.pfkeysock = pfkey_init();
+
+       if ((pw = getpwnam(LDPD_USER)) == NULL)
+               fatal("getpwnam");
+
+       if (chroot(pw->pw_dir) == -1)
+               fatal("chroot");
+       if (chdir("/") == -1)
+               fatal("chdir(\"/\")");
+
+       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 cpath inet mcast recvfd", NULL) == -1)
+               fatal("pledge");
+
+       event_init();
+       accept_init();
+
+       /* setup signal handler */
+       signal_set(&ev_sigint, SIGINT, ldpe_sig_handler, NULL);
+       signal_set(&ev_sigterm, SIGTERM, ldpe_sig_handler, NULL);
+       signal_add(&ev_sigint, NULL);
+       signal_add(&ev_sigterm, NULL);
+       signal(SIGPIPE, SIG_IGN);
+       signal(SIGHUP, SIG_IGN);
+
+       /* setup pipe and event handler to the parent process */
+       if ((iev_main = malloc(sizeof(struct imsgev))) == NULL)
+               fatal(NULL);
+       imsg_init(&iev_main->ibuf, 3);
+       iev_main->handler = ldpe_dispatch_main;
+       iev_main->events = EV_READ;
+       event_set(&iev_main->ev, iev_main->ibuf.fd, iev_main->events,
+           iev_main->handler, iev_main);
+       event_add(&iev_main->ev, NULL);
+
+       if (sysdep.no_pfkey == 0) {
+               event_set(&pfkey_ev, global.pfkeysock, EV_READ | EV_PERSIST,
+                   ldpe_dispatch_pfkey, NULL);
+               event_add(&pfkey_ev, NULL);
+       }
+
+       /* mark sockets as closed */
+       global.ipv4.ldp_disc_socket = -1;
+       global.ipv4.ldp_edisc_socket = -1;
+       global.ipv4.ldp_session_socket = -1;
+       global.ipv6.ldp_disc_socket = -1;
+       global.ipv6.ldp_edisc_socket = -1;
+       global.ipv6.ldp_session_socket = -1;
+
+       /* listen on ldpd control socket */
+       TAILQ_INIT(&ctl_conns);
+       control_listen();
+
+       if ((pkt_ptr = calloc(1, IBUF_READ_SIZE)) == NULL)
+               fatal(__func__);
+
+       event_dispatch();
+
+       ldpe_shutdown();
+}
+
+static __dead void
+ldpe_shutdown(void)
+{
+       struct if_addr          *if_addr;
+       struct adj              *adj;
+
+       /* close pipes */
+       msgbuf_write(&iev_lde->ibuf.w);
+       msgbuf_clear(&iev_lde->ibuf.w);
+       close(iev_lde->ibuf.fd);
+       msgbuf_write(&iev_main->ibuf.w);
+       msgbuf_clear(&iev_main->ibuf.w);
+       close(iev_main->ibuf.fd);
+
+       control_cleanup();
+       config_clear(leconf);
+
+       if (sysdep.no_pfkey == 0) {
+               event_del(&pfkey_ev);
+               close(global.pfkeysock);
+       }
+       ldpe_close_sockets(AF_INET);
+       ldpe_close_sockets(AF_INET6);
+
+       /* remove addresses from global list */
+       while ((if_addr = LIST_FIRST(&global.addr_list)) != NULL) {
+               LIST_REMOVE(if_addr, entry);
+               free(if_addr);
+       }
+       while ((adj = LIST_FIRST(&global.adj_list)) != NULL)
+               adj_del(adj, S_SHUTDOWN);
+
+       /* clean up */
+       free(iev_lde);
+       free(iev_main);
+       free(pkt_ptr);
+
+       log_info("ldp engine exiting");
+       exit(0);
+}
+
+/* imesg */
+int
+ldpe_imsg_compose_parent(int type, pid_t pid, void *data, uint16_t datalen)
+{
+       return (imsg_compose_event(iev_main, type, 0, pid, -1, data, datalen));
+}
+
+int
+ldpe_imsg_compose_lde(int type, uint32_t peerid, pid_t pid, void *data,
+    uint16_t datalen)
+{
+       return (imsg_compose_event(iev_lde, type, peerid, pid, -1,
+           data, datalen));
+}
+
+/* ARGSUSED */
+static void
+ldpe_dispatch_main(int fd, short event, void *bula)
+{
+       static struct ldpd_conf *nconf;
+       struct iface            *niface;
+       struct tnbr             *ntnbr;
+       struct nbr_params       *nnbrp;
+       static struct l2vpn     *nl2vpn;
+       struct l2vpn_if         *nlif;
+       struct l2vpn_pw         *npw;
+       struct imsg              imsg;
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       struct iface            *iface = NULL;
+       struct kif              *kif;
+       int                      af;
+       enum socket_type        *socket_type;
+       static int               disc_socket = -1;
+       static int               edisc_socket = -1;
+       static int               session_socket = -1;
+       struct nbr              *nbr;
+       struct nbr_params       *nbrp;
+       int                      n, shut = 0;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("ldpe_dispatch_main: msgbuf_write");
+               if (n == 0)
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("ldpe_dispatch_main: imsg_get error");
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_IFSTATUS:
+                       if (imsg.hdr.len != IMSG_HEADER_SIZE +
+                           sizeof(struct kif))
+                               fatalx("IFSTATUS imsg with wrong len");
+                       kif = imsg.data;
+
+                       iface = if_lookup(leconf, kif->ifindex);
+                       if (!iface)
+                               break;
+
+                       iface->flags = kif->flags;
+                       iface->linkstate = kif->link_state;
+                       if_update(iface, AF_UNSPEC);
+                       break;
+               case IMSG_NEWADDR:
+                       if (imsg.hdr.len != IMSG_HEADER_SIZE +
+                           sizeof(struct kaddr))
+                               fatalx("NEWADDR imsg with wrong len");
+
+                       if_addr_add(imsg.data);
+                       break;
+               case IMSG_DELADDR:
+                       if (imsg.hdr.len != IMSG_HEADER_SIZE +
+                           sizeof(struct kaddr))
+                               fatalx("DELADDR imsg with wrong len");
+
+                       if_addr_del(imsg.data);
+                       break;
+               case IMSG_SOCKET_IPC:
+                       if (iev_lde) {
+                               log_warnx("%s: received unexpected imsg fd "
+                                   "to lde", __func__);
+                               break;
+                       }
+                       if ((fd = imsg.fd) == -1) {
+                               log_warnx("%s: expected to receive imsg fd to "
+                                   "lde but didn't receive any", __func__);
+                               break;
+                       }
+
+                       if ((iev_lde = malloc(sizeof(struct imsgev))) == NULL)
+                               fatal(NULL);
+                       imsg_init(&iev_lde->ibuf, fd);
+                       iev_lde->handler = ldpe_dispatch_lde;
+                       iev_lde->events = EV_READ;
+                       event_set(&iev_lde->ev, iev_lde->ibuf.fd,
+                           iev_lde->events, iev_lde->handler, iev_lde);
+                       event_add(&iev_lde->ev, NULL);
+                       break;
+               case IMSG_CLOSE_SOCKETS:
+                       af = imsg.hdr.peerid;
+
+                       RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
+                               if (nbr->af != af)
+                                       continue;
+                               session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+                               pfkey_remove(nbr);
+                       }
+                       ldpe_close_sockets(af);
+                       if_update_all(af);
+                       tnbr_update_all(af);
+
+                       disc_socket = -1;
+                       edisc_socket = -1;
+                       session_socket = -1;
+                       if ((ldp_af_conf_get(leconf, af))->flags &
+                           F_LDPD_AF_ENABLED)
+                               ldpe_imsg_compose_parent(IMSG_REQUEST_SOCKETS,
+                                   af, NULL, 0);
+                       break;
+               case IMSG_SOCKET_NET:
+                       if (imsg.hdr.len != IMSG_HEADER_SIZE +
+                           sizeof(enum socket_type))
+                               fatalx("SOCKET_NET imsg with wrong len");
+                       socket_type = imsg.data;
+
+                       switch (*socket_type) {
+                       case LDP_SOCKET_DISC:
+                               disc_socket = imsg.fd;
+                               break;
+                       case LDP_SOCKET_EDISC:
+                               edisc_socket = imsg.fd;
+                               break;
+                       case LDP_SOCKET_SESSION:
+                               session_socket = imsg.fd;
+                               break;
+                       }
+                       break;
+               case IMSG_SETUP_SOCKETS:
+                       af = imsg.hdr.peerid;
+                       if (disc_socket == -1 || edisc_socket == -1 ||
+                           session_socket == -1) {
+                               if (disc_socket != -1)
+                                       close(disc_socket);
+                               if (edisc_socket != -1)
+                                       close(edisc_socket);
+                               if (session_socket != -1)
+                                       close(session_socket);
+                               break;
+                       }
+
+                       ldpe_setup_sockets(af, disc_socket, edisc_socket,
+                           session_socket);
+                       if_update_all(af);
+                       tnbr_update_all(af);
+                       RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
+                               if (nbr->af != af)
+                                       continue;
+                               nbr->laddr = (ldp_af_conf_get(leconf,
+                                   af))->trans_addr;
+                               nbrp = nbr_params_find(leconf, nbr->id);
+                               if (nbrp && pfkey_establish(nbr, nbrp) == -1)
+                                       fatalx("pfkey setup failed");
+                               if (nbr_session_active_role(nbr))
+                                       nbr_establish_connection(nbr);
+                       }
+                       break;
+               case IMSG_RECONF_CONF:
+                       if ((nconf = malloc(sizeof(struct ldpd_conf))) ==
+                           NULL)
+                               fatal(NULL);
+                       memcpy(nconf, imsg.data, sizeof(struct ldpd_conf));
+
+                       LIST_INIT(&nconf->iface_list);
+                       LIST_INIT(&nconf->tnbr_list);
+                       LIST_INIT(&nconf->nbrp_list);
+                       LIST_INIT(&nconf->l2vpn_list);
+                       break;
+               case IMSG_RECONF_IFACE:
+                       if ((niface = malloc(sizeof(struct iface))) == NULL)
+                               fatal(NULL);
+                       memcpy(niface, imsg.data, sizeof(struct iface));
+
+                       LIST_INIT(&niface->addr_list);
+                       LIST_INIT(&niface->ipv4.adj_list);
+                       LIST_INIT(&niface->ipv6.adj_list);
+                       niface->ipv4.iface = niface;
+                       niface->ipv6.iface = niface;
+
+                       LIST_INSERT_HEAD(&nconf->iface_list, niface, entry);
+                       break;
+               case IMSG_RECONF_TNBR:
+                       if ((ntnbr = malloc(sizeof(struct tnbr))) == NULL)
+                               fatal(NULL);
+                       memcpy(ntnbr, imsg.data, sizeof(struct tnbr));
+
+                       LIST_INSERT_HEAD(&nconf->tnbr_list, ntnbr, entry);
+                       break;
+               case IMSG_RECONF_NBRP:
+                       if ((nnbrp = malloc(sizeof(struct nbr_params))) == NULL)
+                               fatal(NULL);
+                       memcpy(nnbrp, imsg.data, sizeof(struct nbr_params));
+
+                       LIST_INSERT_HEAD(&nconf->nbrp_list, nnbrp, entry);
+                       break;
+               case IMSG_RECONF_L2VPN:
+                       if ((nl2vpn = malloc(sizeof(struct l2vpn))) == NULL)
+                               fatal(NULL);
+                       memcpy(nl2vpn, imsg.data, sizeof(struct l2vpn));
+
+                       LIST_INIT(&nl2vpn->if_list);
+                       LIST_INIT(&nl2vpn->pw_list);
+
+                       LIST_INSERT_HEAD(&nconf->l2vpn_list, nl2vpn, entry);
+                       break;
+               case IMSG_RECONF_L2VPN_IF:
+                       if ((nlif = malloc(sizeof(struct l2vpn_if))) == NULL)
+                               fatal(NULL);
+                       memcpy(nlif, imsg.data, sizeof(struct l2vpn_if));
+
+                       nlif->l2vpn = nl2vpn;
+                       LIST_INSERT_HEAD(&nl2vpn->if_list, nlif, entry);
+                       break;
+               case IMSG_RECONF_L2VPN_PW:
+                       if ((npw = malloc(sizeof(struct l2vpn_pw))) == NULL)
+                               fatal(NULL);
+                       memcpy(npw, imsg.data, sizeof(struct l2vpn_pw));
+
+                       npw->l2vpn = nl2vpn;
+                       LIST_INSERT_HEAD(&nl2vpn->pw_list, npw, entry);
+                       break;
+               case IMSG_RECONF_END:
+                       merge_config(leconf, nconf);
+                       nconf = NULL;
+                       global.conf_seqnum++;
+                       break;
+               case IMSG_CTL_KROUTE:
+               case IMSG_CTL_KROUTE_ADDR:
+               case IMSG_CTL_IFINFO:
+               case IMSG_CTL_END:
+                       control_imsg_relay(&imsg);
+                       break;
+               default:
+                       log_debug("ldpe_dispatch_main: error handling imsg %d",
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* this pipe is dead, so remove the event handler */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+/* ARGSUSED */
+static void
+ldpe_dispatch_lde(int fd, short event, void *bula)
+{
+       struct imsgev           *iev = bula;
+       struct imsgbuf          *ibuf = &iev->ibuf;
+       struct imsg              imsg;
+       struct map               map;
+       struct notify_msg        nm;
+       int                      n, shut = 0;
+       struct nbr              *nbr = NULL;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+                       fatal("imsg_read error");
+               if (n == 0)     /* connection closed */
+                       shut = 1;
+       }
+       if (event & EV_WRITE) {
+               if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
+                       fatal("ldpe_dispatch_lde: msgbuf_write");
+               if (n == 0)
+                       shut = 1;
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal("ldpe_dispatch_lde: imsg_get error");
+               if (n == 0)
+                       break;
+
+               switch (imsg.hdr.type) {
+               case IMSG_MAPPING_ADD:
+               case IMSG_RELEASE_ADD:
+               case IMSG_REQUEST_ADD:
+               case IMSG_WITHDRAW_ADD:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(map))
+                               fatalx("invalid size of map request");
+                       memcpy(&map, imsg.data, sizeof(map));
+
+                       nbr = nbr_find_peerid(imsg.hdr.peerid);
+                       if (nbr == NULL) {
+                               log_debug("ldpe_dispatch_lde: cannot find "
+                                   "neighbor");
+                               break;
+                       }
+                       if (nbr->state != NBR_STA_OPER)
+                               break;
+
+                       switch (imsg.hdr.type) {
+                       case IMSG_MAPPING_ADD:
+                               mapping_list_add(&nbr->mapping_list, &map);
+                               break;
+                       case IMSG_RELEASE_ADD:
+                               mapping_list_add(&nbr->release_list, &map);
+                               break;
+                       case IMSG_REQUEST_ADD:
+                               mapping_list_add(&nbr->request_list, &map);
+                               break;
+                       case IMSG_WITHDRAW_ADD:
+                               mapping_list_add(&nbr->withdraw_list, &map);
+                               break;
+                       }
+                       break;
+               case IMSG_MAPPING_ADD_END:
+               case IMSG_RELEASE_ADD_END:
+               case IMSG_REQUEST_ADD_END:
+               case IMSG_WITHDRAW_ADD_END:
+                       nbr = nbr_find_peerid(imsg.hdr.peerid);
+                       if (nbr == NULL) {
+                               log_debug("ldpe_dispatch_lde: cannot find "
+                                   "neighbor");
+                               break;
+                       }
+                       if (nbr->state != NBR_STA_OPER)
+                               break;
+
+                       switch (imsg.hdr.type) {
+                       case IMSG_MAPPING_ADD_END:
+                               send_labelmessage(nbr, MSG_TYPE_LABELMAPPING,
+                                   &nbr->mapping_list);
+                               break;
+                       case IMSG_RELEASE_ADD_END:
+                               send_labelmessage(nbr, MSG_TYPE_LABELRELEASE,
+                                   &nbr->release_list);
+                               break;
+                       case IMSG_REQUEST_ADD_END:
+                               send_labelmessage(nbr, MSG_TYPE_LABELREQUEST,
+                                   &nbr->request_list);
+                               break;
+                       case IMSG_WITHDRAW_ADD_END:
+                               send_labelmessage(nbr, MSG_TYPE_LABELWITHDRAW,
+                                   &nbr->withdraw_list);
+                               break;
+                       }
+                       break;
+               case IMSG_NOTIFICATION_SEND:
+                       if (imsg.hdr.len - IMSG_HEADER_SIZE != sizeof(nm))
+                               fatalx("invalid size of OE request");
+                       memcpy(&nm, imsg.data, sizeof(nm));
+
+                       nbr = nbr_find_peerid(imsg.hdr.peerid);
+                       if (nbr == NULL) {
+                               log_debug("ldpe_dispatch_lde: cannot find "
+                                   "neighbor");
+                               break;
+                       }
+                       if (nbr->state != NBR_STA_OPER)
+                               break;
+
+                       send_notification_full(nbr->tcp, &nm);
+                       break;
+               case IMSG_CTL_END:
+               case IMSG_CTL_SHOW_LIB:
+               case IMSG_CTL_SHOW_L2VPN_PW:
+               case IMSG_CTL_SHOW_L2VPN_BINDING:
+                       control_imsg_relay(&imsg);
+                       break;
+               default:
+                       log_debug("ldpe_dispatch_lde: error handling imsg %d",
+                           imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+       if (!shut)
+               imsg_event_add(iev);
+       else {
+               /* this pipe is dead, so remove the event handler */
+               event_del(&iev->ev);
+               event_loopexit(NULL);
+       }
+}
+
+/* ARGSUSED */
+static void
+ldpe_dispatch_pfkey(int fd, short event, void *bula)
+{
+       if (event & EV_READ) {
+               if (pfkey_read(fd, NULL) == -1) {
+                       fatal("pfkey_read failed, exiting...");
+               }
+       }
+}
+
+static void
+ldpe_setup_sockets(int af, int disc_socket, int edisc_socket,
+    int session_socket)
+{
+       struct ldpd_af_global   *af_global;
+
+       af_global = ldp_af_global_get(&global, af);
+
+       /* discovery socket */
+       af_global->ldp_disc_socket = disc_socket;
+       event_set(&af_global->disc_ev, af_global->ldp_disc_socket,
+           EV_READ|EV_PERSIST, disc_recv_packet, NULL);
+       event_add(&af_global->disc_ev, NULL);
+
+       /* extended discovery socket */
+       af_global->ldp_edisc_socket = edisc_socket;
+       event_set(&af_global->edisc_ev, af_global->ldp_edisc_socket,
+           EV_READ|EV_PERSIST, disc_recv_packet, NULL);
+       event_add(&af_global->edisc_ev, NULL);
+
+       /* session socket */
+       af_global->ldp_session_socket = session_socket;
+       accept_add(af_global->ldp_session_socket, session_accept, NULL);
+}
+
+static void
+ldpe_close_sockets(int af)
+{
+       struct ldpd_af_global   *af_global;
+
+       af_global = ldp_af_global_get(&global, af);
+
+       /* discovery socket */
+       if (event_initialized(&af_global->disc_ev))
+               event_del(&af_global->disc_ev);
+       if (af_global->ldp_disc_socket != -1) {
+               close(af_global->ldp_disc_socket);
+               af_global->ldp_disc_socket = -1;
+       }
+
+       /* extended discovery socket */
+       if (event_initialized(&af_global->edisc_ev))
+               event_del(&af_global->edisc_ev);
+       if (af_global->ldp_edisc_socket != -1) {
+               close(af_global->ldp_edisc_socket);
+               af_global->ldp_edisc_socket = -1;
+       }
+
+       /* session socket */
+       if (af_global->ldp_session_socket != -1) {
+               accept_del(af_global->ldp_session_socket);
+               close(af_global->ldp_session_socket);
+               af_global->ldp_session_socket = -1;
+       }
+}
+
+void
+ldpe_reset_nbrs(int af)
+{
+       struct nbr              *nbr;
+
+       RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
+               if (nbr->af == af)
+                       session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+       }
+}
+
+void
+ldpe_reset_ds_nbrs(void)
+{
+       struct nbr              *nbr;
+
+       RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
+               if (nbr->ds_tlv)
+                       session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+       }
+}
+
+void
+ldpe_remove_dynamic_tnbrs(int af)
+{
+       struct tnbr             *tnbr, *safe;
+
+       LIST_FOREACH_SAFE(tnbr, &leconf->tnbr_list, entry, safe) {
+               if (tnbr->af != af)
+                       continue;
+
+               tnbr->flags &= ~F_TNBR_DYNAMIC;
+               tnbr_check(tnbr);
+       }
+}
+
+void
+ldpe_stop_init_backoff(int af)
+{
+       struct nbr              *nbr;
+
+       RB_FOREACH(nbr, nbr_id_head, &nbrs_by_id) {
+               if (nbr->af == af && nbr_pending_idtimer(nbr)) {
+                       nbr_stop_idtimer(nbr);
+                       nbr_establish_connection(nbr);
+               }
+       }
+}
+
+static void
+ldpe_iface_af_ctl(struct ctl_conn *c, int af, unsigned int idx)
+{
+       struct iface            *iface;
+       struct iface_af         *ia;
+       struct ctl_iface        *ictl;
+
+       LIST_FOREACH(iface, &leconf->iface_list, entry) {
+               if (idx == 0 || idx == iface->ifindex) {
+                       ia = iface_af_get(iface, af);
+                       if (!ia->enabled)
+                               continue;
+
+                       ictl = if_to_ctl(ia);
+                       imsg_compose_event(&c->iev,
+                            IMSG_CTL_SHOW_INTERFACE,
+                           0, 0, -1, ictl, sizeof(struct ctl_iface));
+               }
+       }
+}
+
+void
+ldpe_iface_ctl(struct ctl_conn *c, unsigned int idx)
+{
+       ldpe_iface_af_ctl(c, AF_INET, idx);
+       ldpe_iface_af_ctl(c, AF_INET6, idx);
+}
+
+void
+ldpe_adj_ctl(struct ctl_conn *c)
+{
+       struct nbr      *nbr;
+       struct adj      *adj;
+       struct ctl_adj  *actl;
+
+       RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) {
+               LIST_FOREACH(adj, &nbr->adj_list, nbr_entry) {
+                       actl = adj_to_ctl(adj);
+                       imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISCOVERY,
+                           0, 0, -1, actl, sizeof(struct ctl_adj));
+               }
+       }
+       /* show adjacencies not associated with any neighbor */
+       LIST_FOREACH(adj, &global.adj_list, global_entry) {
+               if (adj->nbr != NULL)
+                       continue;
+
+               actl = adj_to_ctl(adj);
+               imsg_compose_event(&c->iev, IMSG_CTL_SHOW_DISCOVERY, 0, 0,
+                   -1, actl, sizeof(struct ctl_adj));
+       }
+
+       imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0);
+}
+
+void
+ldpe_nbr_ctl(struct ctl_conn *c)
+{
+       struct nbr      *nbr;
+       struct ctl_nbr  *nctl;
+
+       RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) {
+               nctl = nbr_to_ctl(nbr);
+               imsg_compose_event(&c->iev, IMSG_CTL_SHOW_NBR, 0, 0, -1, nctl,
+                   sizeof(struct ctl_nbr));
+       }
+       imsg_compose_event(&c->iev, IMSG_CTL_END, 0, 0, -1, NULL, 0);
+}
+
+void
+mapping_list_add(struct mapping_head *mh, struct map *map)
+{
+       struct mapping_entry    *me;
+
+       me = calloc(1, sizeof(*me));
+       if (me == NULL)
+               fatal(__func__);
+       me->map = *map;
+
+       TAILQ_INSERT_TAIL(mh, me, entry);
+}
+
+void
+mapping_list_clr(struct mapping_head *mh)
+{
+       struct mapping_entry    *me;
+
+       while ((me = TAILQ_FIRST(mh)) != NULL) {
+               TAILQ_REMOVE(mh, me, entry);
+               free(me);
+       }
+}
diff --git a/ldpd/ldpe.h b/ldpd/ldpe.h
new file mode 100644 (file)
index 0000000..e640dd6
--- /dev/null
@@ -0,0 +1,282 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _LDPE_H_
+#define _LDPE_H_
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <net/pfkeyv2.h>
+
+#include "ldpd.h"
+
+#define min(x,y) ((x) <= (y) ? (x) : (y))
+#define max(x,y) ((x) > (y) ? (x) : (y))
+
+struct hello_source {
+       enum hello_type          type;
+       struct {
+               struct iface_af *ia;
+               union ldpd_addr  src_addr;
+       } link;
+       struct tnbr             *target;
+};
+
+struct adj {
+       LIST_ENTRY(adj)          global_entry;
+       LIST_ENTRY(adj)          nbr_entry;
+       LIST_ENTRY(adj)          ia_entry;
+       struct in_addr           lsr_id;
+       struct nbr              *nbr;
+       int                      ds_tlv;
+       struct hello_source      source;
+       struct event             inactivity_timer;
+       uint16_t                 holdtime;
+       union ldpd_addr          trans_addr;
+};
+
+struct tcp_conn {
+       struct nbr              *nbr;
+       int                      fd;
+       struct ibuf_read        *rbuf;
+       struct evbuf             wbuf;
+       struct event             rev;
+};
+
+struct nbr {
+       RB_ENTRY(nbr)            id_tree, addr_tree, pid_tree;
+       struct tcp_conn         *tcp;
+       LIST_HEAD(, adj)         adj_list;      /* adjacencies */
+       struct event             ev_connect;
+       struct event             keepalive_timer;
+       struct event             keepalive_timeout;
+       struct event             init_timeout;
+       struct event             initdelay_timer;
+
+       struct mapping_head      mapping_list;
+       struct mapping_head      withdraw_list;
+       struct mapping_head      request_list;
+       struct mapping_head      release_list;
+       struct mapping_head      abortreq_list;
+
+       uint32_t                 peerid;        /* unique ID in DB */
+       int                      af;
+       int                      ds_tlv;
+       int                      v4_enabled;    /* announce/process v4 msgs */
+       int                      v6_enabled;    /* announce/process v6 msgs */
+       struct in_addr           id;            /* lsr id */
+       union ldpd_addr          laddr;         /* local address */
+       union ldpd_addr          raddr;         /* remote address */
+       uint32_t                 raddr_scope;   /* remote address scope (v6) */
+       time_t                   uptime;
+       int                      fd;
+       int                      state;
+       uint32_t                 conf_seqnum;
+       int                      idtimer_cnt;
+       uint16_t                 keepalive;
+       uint16_t                 max_pdu_len;
+
+       struct {
+               uint8_t                 established;
+               uint32_t                spi_in;
+               uint32_t                spi_out;
+               enum auth_method        method;
+               char                    md5key[TCP_MD5_KEY_LEN];
+       } auth;
+       int                      flags;
+};
+#define F_NBR_GTSM_NEGOTIATED   0x01
+
+RB_HEAD(nbr_id_head, nbr);
+RB_PROTOTYPE(nbr_id_head, nbr, id_tree, nbr_id_compare)
+RB_HEAD(nbr_addr_head, nbr);
+RB_PROTOTYPE(nbr_addr_head, nbr, addr_tree, nbr_addr_compare)
+RB_HEAD(nbr_pid_head, nbr);
+RB_PROTOTYPE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare)
+
+struct pending_conn {
+       TAILQ_ENTRY(pending_conn)        entry;
+       int                              fd;
+       int                              af;
+       union ldpd_addr                  addr;
+       struct event                     ev_timeout;
+};
+#define PENDING_CONN_TIMEOUT   5
+
+struct mapping_entry {
+       TAILQ_ENTRY(mapping_entry)      entry;
+       struct map                      map;
+};
+
+struct ldpd_sysdep {
+       uint8_t         no_pfkey;
+       uint8_t         no_md5sig;
+};
+
+extern struct ldpd_conf                *leconf;
+extern struct ldpd_sysdep       sysdep;
+extern struct nbr_id_head       nbrs_by_id;
+extern struct nbr_addr_head     nbrs_by_addr;
+extern struct nbr_pid_head      nbrs_by_pid;
+
+/* accept.c */
+void   accept_init(void);
+int    accept_add(int, void (*)(int, short, void *), void *);
+void   accept_del(int);
+void   accept_pause(void);
+void   accept_unpause(void);
+
+/* hello.c */
+int     send_hello(enum hello_type, struct iface_af *, struct tnbr *);
+void    recv_hello(struct in_addr, struct ldp_msg *, int, union ldpd_addr *,
+           struct iface *, int, char *, uint16_t);
+
+/* init.c */
+void    send_init(struct nbr *);
+int     recv_init(struct nbr *, char *, uint16_t);
+
+/* keepalive.c */
+void    send_keepalive(struct nbr *);
+int     recv_keepalive(struct nbr *, char *, uint16_t);
+
+/* notification.c */
+void    send_notification_full(struct tcp_conn *, struct notify_msg *);
+void    send_notification(uint32_t, struct tcp_conn *, uint32_t,
+           uint16_t);
+void    send_notification_nbr(struct nbr *, uint32_t, uint32_t, uint16_t);
+int     recv_notification(struct nbr *, char *, uint16_t);
+int     gen_status_tlv(struct ibuf *, uint32_t, uint32_t, uint16_t);
+
+/* address.c */
+void    send_address_single(struct nbr *, struct if_addr *, int);
+void    send_address_all(struct nbr *, int);
+int     recv_address(struct nbr *, char *, uint16_t);
+
+/* labelmapping.c */
+#define PREFIX_SIZE(x) (((x) + 7) / 8)
+void    send_labelmessage(struct nbr *, uint16_t, struct mapping_head *);
+int     recv_labelmessage(struct nbr *, char *, uint16_t, uint16_t);
+int     gen_pw_status_tlv(struct ibuf *, uint32_t);
+int     gen_fec_tlv(struct ibuf *, struct map *);
+int     tlv_decode_fec_elm(struct nbr *, struct ldp_msg *, char *,
+           uint16_t, struct map *);
+
+/* ldpe.c */
+void            ldpe(int, int);
+int             ldpe_imsg_compose_parent(int, pid_t, void *,
+                   uint16_t);
+int             ldpe_imsg_compose_lde(int, uint32_t, pid_t, void *,
+                   uint16_t);
+void            ldpe_reset_nbrs(int);
+void            ldpe_reset_ds_nbrs(void);
+void            ldpe_remove_dynamic_tnbrs(int);
+void            ldpe_stop_init_backoff(int);
+struct ctl_conn;
+void            ldpe_iface_ctl(struct ctl_conn *, unsigned int);
+void            ldpe_adj_ctl(struct ctl_conn *);
+void            ldpe_nbr_ctl(struct ctl_conn *);
+void            mapping_list_add(struct mapping_head *, struct map *);
+void            mapping_list_clr(struct mapping_head *);
+
+/* interface.c */
+struct iface   *if_new(struct kif *);
+void            if_exit(struct iface *);
+struct iface   *if_lookup(struct ldpd_conf *, unsigned short);
+struct iface_af *iface_af_get(struct iface *, int);
+void            if_addr_add(struct kaddr *);
+void            if_addr_del(struct kaddr *);
+void            if_update(struct iface *, int);
+void            if_update_all(int);
+struct ctl_iface *if_to_ctl(struct iface_af *);
+in_addr_t       if_get_ipv4_addr(struct iface *);
+
+/* adjacency.c */
+struct adj     *adj_new(struct in_addr, struct hello_source *,
+                   union ldpd_addr *);
+void            adj_del(struct adj *, uint32_t);
+struct adj     *adj_find(struct hello_source *);
+int             adj_get_af(struct adj *adj);
+void            adj_start_itimer(struct adj *);
+void            adj_stop_itimer(struct adj *);
+struct tnbr    *tnbr_new(struct ldpd_conf *, int, union ldpd_addr *);
+struct tnbr    *tnbr_find(struct ldpd_conf *, int, union ldpd_addr *);
+struct tnbr    *tnbr_check(struct tnbr *);
+void            tnbr_update(struct tnbr *);
+void            tnbr_update_all(int);
+struct ctl_adj *adj_to_ctl(struct adj *);
+
+/* neighbor.c */
+int                     nbr_fsm(struct nbr *, enum nbr_event);
+struct nbr             *nbr_new(struct in_addr, int, int, union ldpd_addr *,
+                           uint32_t);
+void                    nbr_del(struct nbr *);
+struct nbr             *nbr_find_ldpid(uint32_t);
+struct nbr             *nbr_find_addr(int, union ldpd_addr *);
+struct nbr             *nbr_find_peerid(uint32_t);
+int                     nbr_adj_count(struct nbr *, int);
+int                     nbr_session_active_role(struct nbr *);
+void                    nbr_stop_ktimer(struct nbr *);
+void                    nbr_stop_ktimeout(struct nbr *);
+void                    nbr_stop_itimeout(struct nbr *);
+void                    nbr_start_idtimer(struct nbr *);
+void                    nbr_stop_idtimer(struct nbr *);
+int                     nbr_pending_idtimer(struct nbr *);
+int                     nbr_pending_connect(struct nbr *);
+int                     nbr_establish_connection(struct nbr *);
+int                     nbr_gtsm_enabled(struct nbr *, struct nbr_params *);
+int                     nbr_gtsm_setup(int, int, struct nbr_params *);
+int                     nbr_gtsm_check(int, struct nbr *, struct nbr_params *);
+struct nbr_params      *nbr_params_new(struct in_addr);
+struct nbr_params      *nbr_params_find(struct ldpd_conf *, struct in_addr);
+uint16_t                nbr_get_keepalive(int, struct in_addr);
+struct ctl_nbr         *nbr_to_ctl(struct nbr *);
+void                    nbr_clear_ctl(struct ctl_nbr *);
+
+/* packet.c */
+int                     gen_ldp_hdr(struct ibuf *, uint16_t);
+int                     gen_msg_hdr(struct ibuf *, uint16_t, uint16_t);
+int                     send_packet(int, int, union ldpd_addr *,
+                           struct iface_af *, void *, size_t);
+void                    disc_recv_packet(int, short, void *);
+void                    session_accept(int, short, void *);
+void                    session_accept_nbr(struct nbr *, int);
+void                    session_shutdown(struct nbr *, uint32_t, uint32_t,
+                           uint32_t);
+void                    session_close(struct nbr *);
+struct tcp_conn                *tcp_new(int, struct nbr *);
+void                    pending_conn_del(struct pending_conn *);
+struct pending_conn    *pending_conn_find(int, union ldpd_addr *);
+
+char   *pkt_ptr;       /* packet buffer */
+
+/* pfkey.c */
+int    pfkey_read(int, struct sadb_msg *);
+int    pfkey_establish(struct nbr *, struct nbr_params *);
+int    pfkey_remove(struct nbr *);
+int    pfkey_init(void);
+
+/* l2vpn.c */
+void   ldpe_l2vpn_init(struct l2vpn *);
+void   ldpe_l2vpn_exit(struct l2vpn *);
+void   ldpe_l2vpn_pw_init(struct l2vpn_pw *);
+void   ldpe_l2vpn_pw_exit(struct l2vpn_pw *);
+
+#endif /* _LDPE_H_ */
diff --git a/ldpd/log.c b/ldpd/log.c
new file mode 100644 (file)
index 0000000..e14b6e5
--- /dev/null
@@ -0,0 +1,600 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netmpls/mpls.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <limits.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "lde.h"
+#include "log.h"
+
+static const char * const procnames[] = {
+       "parent",
+       "ldpe",
+       "lde"
+};
+
+static void     vlog(int, const char *, va_list);
+
+static int      debug;
+static int      verbose;
+
+void
+log_init(int n_debug)
+{
+       extern char     *__progname;
+
+       debug = n_debug;
+
+       if (!debug)
+               openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+       tzset();
+}
+
+void
+log_verbose(int v)
+{
+       verbose = v;
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vlog(pri, fmt, ap);
+       va_end(ap);
+}
+
+static void
+vlog(int pri, const char *fmt, va_list ap)
+{
+       char    *nfmt;
+
+       if (debug) {
+               /* best effort in out of mem situations */
+               if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+                       vfprintf(stderr, fmt, ap);
+                       fprintf(stderr, "\n");
+               } else {
+                       vfprintf(stderr, nfmt, ap);
+                       free(nfmt);
+               }
+               fflush(stderr);
+       } else
+               vsyslog(pri, fmt, ap);
+}
+
+void
+log_warn(const char *emsg, ...)
+{
+       char    *nfmt;
+       va_list  ap;
+
+       /* best effort to even work in out of memory situations */
+       if (emsg == NULL)
+               logit(LOG_CRIT, "%s", strerror(errno));
+       else {
+               va_start(ap, emsg);
+
+               if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+                       /* we tried it... */
+                       vlog(LOG_CRIT, emsg, ap);
+                       logit(LOG_CRIT, "%s", strerror(errno));
+               } else {
+                       vlog(LOG_CRIT, nfmt, ap);
+                       free(nfmt);
+               }
+               va_end(ap);
+       }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_CRIT, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_INFO, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+       va_list  ap;
+
+       if (verbose & LDPD_OPT_VERBOSE) {
+               va_start(ap, emsg);
+               vlog(LOG_DEBUG, emsg, ap);
+               va_end(ap);
+       }
+}
+
+void
+fatal(const char *emsg)
+{
+       if (emsg == NULL)
+               logit(LOG_CRIT, "fatal in %s: %s", procnames[ldpd_process],
+                   strerror(errno));
+       else
+               if (errno)
+                       logit(LOG_CRIT, "fatal in %s: %s: %s",
+                           procnames[ldpd_process], emsg, strerror(errno));
+               else
+                       logit(LOG_CRIT, "fatal in %s: %s",
+                           procnames[ldpd_process], emsg);
+
+       exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+       errno = 0;
+       fatal(emsg);
+}
+
+#define NUM_LOGS       4
+const char *
+log_sockaddr(void *vp)
+{
+       static char      buf[NUM_LOGS][NI_MAXHOST];
+       static int       round = 0;
+       struct sockaddr *sa = vp;
+
+       round = (round + 1) % NUM_LOGS;
+
+       if (getnameinfo(sa, sa->sa_len, buf[round], NI_MAXHOST, NULL, 0,
+           NI_NUMERICHOST))
+               return ("(unknown)");
+       else
+               return (buf[round]);
+}
+
+const char *
+log_in6addr(const struct in6_addr *addr)
+{
+       struct sockaddr_in6     sa_in6;
+
+       memset(&sa_in6, 0, sizeof(sa_in6));
+       sa_in6.sin6_len = sizeof(sa_in6);
+       sa_in6.sin6_family = AF_INET6;
+       sa_in6.sin6_addr = *addr;
+
+       recoverscope(&sa_in6);
+
+       return (log_sockaddr(&sa_in6));
+}
+
+const char *
+log_in6addr_scope(const struct in6_addr *addr, unsigned int ifindex)
+{
+       struct sockaddr_in6     sa_in6;
+
+       memset(&sa_in6, 0, sizeof(sa_in6));
+       sa_in6.sin6_len = sizeof(sa_in6);
+       sa_in6.sin6_family = AF_INET6;
+       sa_in6.sin6_addr = *addr;
+
+       addscope(&sa_in6, ifindex);
+
+       return (log_sockaddr(&sa_in6));
+}
+
+const char *
+log_addr(int af, const union ldpd_addr *addr)
+{
+       static char      buf[NUM_LOGS][INET6_ADDRSTRLEN];
+       static int       round = 0;
+
+       switch (af) {
+       case AF_INET:
+               round = (round + 1) % NUM_LOGS;
+               if (inet_ntop(AF_INET, &addr->v4, buf[round],
+                   sizeof(buf[round])) == NULL)
+                       return ("???");
+               return (buf[round]);
+       case AF_INET6:
+               return (log_in6addr(&addr->v6));
+       default:
+               break;
+       }
+
+       return ("???");
+}
+
+#define        TF_BUFS 4
+#define        TF_LEN  32
+
+char *
+log_label(uint32_t label)
+{
+       char            *buf;
+       static char      tfbuf[TF_BUFS][TF_LEN];        /* ring buffer */
+       static int       idx = 0;
+
+       buf = tfbuf[idx++];
+       if (idx == TF_BUFS)
+               idx = 0;
+
+       switch (label) {
+       case NO_LABEL:
+               snprintf(buf, TF_LEN, "-");
+               break;
+       case MPLS_LABEL_IMPLNULL:
+               snprintf(buf, TF_LEN, "imp-null");
+               break;
+       case MPLS_LABEL_IPV4NULL:
+       case MPLS_LABEL_IPV6NULL:
+               snprintf(buf, TF_LEN, "exp-null");
+               break;
+       default:
+               snprintf(buf, TF_LEN, "%u", label);
+               break;
+       }
+
+       return (buf);
+}
+
+char *
+log_hello_src(const struct hello_source *src)
+{
+       static char buf[64];
+
+       switch (src->type) {
+       case HELLO_LINK:
+               snprintf(buf, sizeof(buf), "iface %s",
+                   src->link.ia->iface->name);
+               break;
+       case HELLO_TARGETED:
+               snprintf(buf, sizeof(buf), "source %s",
+                   log_addr(src->target->af, &src->target->addr));
+               break;
+       }
+
+       return (buf);
+}
+
+const char *
+log_map(const struct map *map)
+{
+       static char     buf[64];
+
+       switch (map->type) {
+       case MAP_TYPE_WILDCARD:
+               if (snprintf(buf, sizeof(buf), "wildcard") < 0)
+                       return ("???");
+               break;
+       case MAP_TYPE_PREFIX:
+               if (snprintf(buf, sizeof(buf), "%s/%u",
+                   log_addr(map->fec.prefix.af, &map->fec.prefix.prefix),
+                   map->fec.prefix.prefixlen) == -1)
+                       return ("???");
+               break;
+       case MAP_TYPE_PWID:
+               if (snprintf(buf, sizeof(buf), "pwid %u (%s)",
+                   map->fec.pwid.pwid,
+                   pw_type_name(map->fec.pwid.type)) == -1)
+                       return ("???");
+               break;
+       default:
+               return ("???");
+       }
+
+       return (buf);
+}
+
+const char *
+log_fec(const struct fec *fec)
+{
+       static char     buf[64];
+       union ldpd_addr addr;
+
+       switch (fec->type) {
+       case FEC_TYPE_IPV4:
+               addr.v4 = fec->u.ipv4.prefix;
+               if (snprintf(buf, sizeof(buf), "ipv4 %s/%u",
+                   log_addr(AF_INET, &addr), fec->u.ipv4.prefixlen) == -1)
+                       return ("???");
+               break;
+       case FEC_TYPE_IPV6:
+               addr.v6 = fec->u.ipv6.prefix;
+               if (snprintf(buf, sizeof(buf), "ipv6 %s/%u",
+                   log_addr(AF_INET6, &addr), fec->u.ipv6.prefixlen) == -1)
+                       return ("???");
+               break;
+       case FEC_TYPE_PWID:
+               if (snprintf(buf, sizeof(buf),
+                   "pwid %u (%s) - %s",
+                   fec->u.pwid.pwid, pw_type_name(fec->u.pwid.type),
+                   inet_ntoa(fec->u.pwid.lsr_id)) == -1)
+                       return ("???");
+               break;
+       default:
+               return ("???");
+       }
+
+       return (buf);
+}
+
+/* names */
+const char *
+af_name(int af)
+{
+       switch (af) {
+       case AF_INET:
+               return ("ipv4");
+       case AF_INET6:
+               return ("ipv6");
+       case AF_MPLS:
+               return ("mpls");
+       default:
+               return ("UNKNOWN");
+       }
+}
+
+const char *
+socket_name(int type)
+{
+       switch (type) {
+       case LDP_SOCKET_DISC:
+               return ("discovery");
+       case LDP_SOCKET_EDISC:
+               return ("extended discovery");
+       case LDP_SOCKET_SESSION:
+               return ("session");
+       default:
+               return ("UNKNOWN");
+       }
+}
+
+const char *
+nbr_state_name(int state)
+{
+       switch (state) {
+       case NBR_STA_PRESENT:
+               return ("PRESENT");
+       case NBR_STA_INITIAL:
+               return ("INITIALIZED");
+       case NBR_STA_OPENREC:
+               return ("OPENREC");
+       case NBR_STA_OPENSENT:
+               return ("OPENSENT");
+       case NBR_STA_OPER:
+               return ("OPERATIONAL");
+       default:
+               return ("UNKNOWN");
+       }
+}
+
+const char *
+if_state_name(int state)
+{
+       switch (state) {
+       case IF_STA_DOWN:
+               return ("DOWN");
+       case IF_STA_ACTIVE:
+               return ("ACTIVE");
+       default:
+               return ("UNKNOWN");
+       }
+}
+
+const char *
+if_type_name(enum iface_type type)
+{
+       switch (type) {
+       case IF_TYPE_POINTOPOINT:
+               return ("POINTOPOINT");
+       case IF_TYPE_BROADCAST:
+               return ("BROADCAST");
+       }
+       /* NOTREACHED */
+       return ("UNKNOWN");
+}
+
+const char *
+msg_name(uint16_t msg)
+{
+       static char buf[16];
+
+       switch (msg) {
+       case MSG_TYPE_NOTIFICATION:
+               return ("notification");
+       case MSG_TYPE_HELLO:
+               return ("hello");
+       case MSG_TYPE_INIT:
+               return ("initialization");
+       case MSG_TYPE_KEEPALIVE:
+               return ("keepalive");
+       case MSG_TYPE_ADDR:
+               return ("address");
+       case MSG_TYPE_ADDRWITHDRAW:
+               return ("address withdraw");
+       case MSG_TYPE_LABELMAPPING:
+               return ("label mapping");
+       case MSG_TYPE_LABELREQUEST:
+               return ("label request");
+       case MSG_TYPE_LABELWITHDRAW:
+               return ("label withdraw");
+       case MSG_TYPE_LABELRELEASE:
+               return ("label release");
+       case MSG_TYPE_LABELABORTREQ:
+       default:
+               snprintf(buf, sizeof(buf), "[%08x]", msg);
+               return (buf);
+       }
+}
+
+const char *
+status_code_name(uint32_t status)
+{
+       static char buf[16];
+
+       switch (status) {
+       case S_SUCCESS:
+               return ("Success");
+       case S_BAD_LDP_ID:
+               return ("Bad LDP Identifier");
+       case S_BAD_PROTO_VER:
+               return ("Bad Protocol Version");
+       case S_BAD_PDU_LEN:
+               return ("Bad PDU Length");
+       case S_UNKNOWN_MSG:
+               return ("Unknown Message Type");
+       case S_BAD_MSG_LEN:
+               return ("Bad Message Length");
+       case S_UNKNOWN_TLV:
+               return ("Unknown TLV");
+       case S_BAD_TLV_LEN:
+               return ("Bad TLV Length");
+       case S_BAD_TLV_VAL:
+               return ("Malformed TLV Value");
+       case S_HOLDTIME_EXP:
+               return ("Hold Timer Expired");
+       case S_SHUTDOWN:
+               return ("Shutdown");
+       case S_LOOP_DETECTED:
+               return ("Loop Detected");
+       case S_UNKNOWN_FEC:
+               return ("Unknown FEC");
+       case S_NO_ROUTE:
+               return ("No Route");
+       case S_NO_LABEL_RES:
+               return ("No Label Resources");
+       case S_AVAILABLE:
+               return ("Label Resources Available");
+       case S_NO_HELLO:
+               return ("Session Rejected, No Hello");
+       case S_PARM_ADV_MODE:
+               return ("Rejected Advertisement Mode Parameter");
+       case S_MAX_PDU_LEN:
+               return ("Rejected Max PDU Length Parameter");
+       case S_PARM_L_RANGE:
+               return ("Rejected Label Range Parameter");
+       case S_KEEPALIVE_TMR:
+               return ("KeepAlive Timer Expired");
+       case S_LAB_REQ_ABRT:
+               return ("Label Request Aborted");
+       case S_MISS_MSG:
+               return ("Missing Message Parameters");
+       case S_UNSUP_ADDR:
+               return ("Unsupported Address Family");
+       case S_KEEPALIVE_BAD:
+               return ("Bad KeepAlive Time");
+       case S_INTERN_ERR:
+               return ("Internal Error");
+       case S_ILLEGAL_CBIT:
+               return ("Illegal C-Bit");
+       case S_WRONG_CBIT:
+               return ("Wrong C-Bit");
+       case S_INCPT_BITRATE:
+               return ("Incompatible bit-rate");
+       case S_CEP_MISCONF:
+               return ("CEP-TDM mis-configuration");
+       case S_PW_STATUS:
+               return ("PW Status");
+       case S_UNASSIGN_TAI:
+               return ("Unassigned/Unrecognized TAI");
+       case S_MISCONF_ERR:
+               return ("Generic Misconfiguration Error");
+       case S_WITHDRAW_MTHD:
+               return ("Label Withdraw PW Status Method");
+       case S_TRANS_MISMTCH:
+               return ("Transport Connection Mismatch");
+       case S_DS_NONCMPLNCE:
+               return ("Dual-Stack Noncompliance");
+       default:
+               snprintf(buf, sizeof(buf), "[%08x]", status);
+               return (buf);
+       }
+}
+
+const char *
+pw_type_name(uint16_t pw_type)
+{
+       static char buf[64];
+
+       switch (pw_type) {
+       case PW_TYPE_ETHERNET_TAGGED:
+               return ("Eth Tagged");
+       case PW_TYPE_ETHERNET:
+               return ("Ethernet");
+       default:
+               snprintf(buf, sizeof(buf), "[%0x]", pw_type);
+               return (buf);
+       }
+}
+
+static char *msgtypes[] = {
+       "",
+       "RTM_ADD: Add Route",
+       "RTM_DELETE: Delete Route",
+       "RTM_CHANGE: Change Metrics or flags",
+       "RTM_GET: Report Metrics",
+       "RTM_LOSING: Kernel Suspects Partitioning",
+       "RTM_REDIRECT: Told to use different route",
+       "RTM_MISS: Lookup failed on this address",
+       "RTM_LOCK: fix specified metrics",
+       "RTM_OLDADD: caused by SIOCADDRT",
+       "RTM_OLDDEL: caused by SIOCDELRT",
+       "RTM_RESOLVE: Route created by cloning",
+       "RTM_NEWADDR: address being added to iface",
+       "RTM_DELADDR: address being removed from iface",
+       "RTM_IFINFO: iface status change",
+       "RTM_IFANNOUNCE: iface arrival/departure",
+       "RTM_DESYNC: route socket overflow",
+};
+
+void
+log_rtmsg(unsigned char rtm_type)
+{
+       if (!(verbose & LDPD_OPT_VERBOSE2))
+               return;
+
+       if (rtm_type > 0 &&
+           rtm_type < sizeof(msgtypes)/sizeof(msgtypes[0]))
+               log_debug("kernel message: %s", msgtypes[rtm_type]);
+       else
+               log_debug("kernel message: rtm_type %d out of range",
+                   rtm_type);
+}
diff --git a/ldpd/log.h b/ldpd/log.h
new file mode 100644 (file)
index 0000000..94c4630
--- /dev/null
@@ -0,0 +1,63 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _LOG_H_
+#define        _LOG_H_
+
+#include <stdarg.h>
+
+struct in6_addr;
+union ldpd_addr;
+struct hello_source;
+struct fec;
+
+void            log_init(int);
+void            log_verbose(int);
+void            logit(int, const char *, ...)
+                       __attribute__((__format__ (printf, 2, 3)));
+void            log_warn(const char *, ...)
+                       __attribute__((__format__ (printf, 1, 2)));
+void            log_warnx(const char *, ...)
+                       __attribute__((__format__ (printf, 1, 2)));
+void            log_info(const char *, ...)
+                       __attribute__((__format__ (printf, 1, 2)));
+void            log_debug(const char *, ...)
+                       __attribute__((__format__ (printf, 1, 2)));
+void            fatal(const char *) __dead
+                       __attribute__((__format__ (printf, 1, 0)));
+void            fatalx(const char *) __dead
+                       __attribute__((__format__ (printf, 1, 0)));
+const char     *log_sockaddr(void *);
+const char     *log_in6addr(const struct in6_addr *);
+const char     *log_in6addr_scope(const struct in6_addr *, unsigned int);
+const char     *log_addr(int, const union ldpd_addr *);
+char           *log_label(uint32_t);
+char           *log_hello_src(const struct hello_source *);
+const char     *log_map(const struct map *);
+const char     *log_fec(const struct fec *);
+const char     *af_name(int);
+const char     *socket_name(int);
+const char     *nbr_state_name(int);
+const char     *if_state_name(int);
+const char     *if_type_name(enum iface_type);
+const char     *msg_name(uint16_t);
+const char     *status_code_name(uint32_t);
+const char     *pw_type_name(uint16_t);
+void            log_rtmsg(unsigned char);
+
+#endif /* _LOG_H_ */
diff --git a/ldpd/neighbor.c b/ldpd/neighbor.c
new file mode 100644 (file)
index 0000000..d3f8373
--- /dev/null
@@ -0,0 +1,827 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/time.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "lde.h"
+#include "log.h"
+
+static __inline int     nbr_id_compare(struct nbr *, struct nbr *);
+static __inline int     nbr_addr_compare(struct nbr *, struct nbr *);
+static __inline int     nbr_pid_compare(struct nbr *, struct nbr *);
+static void             nbr_update_peerid(struct nbr *);
+static void             nbr_ktimer(int, short, void *);
+static void             nbr_start_ktimer(struct nbr *);
+static void             nbr_ktimeout(int, short, void *);
+static void             nbr_start_ktimeout(struct nbr *);
+static void             nbr_itimeout(int, short, void *);
+static void             nbr_start_itimeout(struct nbr *);
+static void             nbr_idtimer(int, short, void *);
+static int              nbr_act_session_operational(struct nbr *);
+static void             nbr_send_labelmappings(struct nbr *);
+
+RB_GENERATE(nbr_id_head, nbr, id_tree, nbr_id_compare)
+RB_GENERATE(nbr_addr_head, nbr, addr_tree, nbr_addr_compare)
+RB_GENERATE(nbr_pid_head, nbr, pid_tree, nbr_pid_compare)
+
+struct {
+       int             state;
+       enum nbr_event  event;
+       enum nbr_action action;
+       int             new_state;
+} nbr_fsm_tbl[] = {
+    /* current state   event that happened     action to take          resulting state */
+/* Passive Role */
+    {NBR_STA_PRESENT,  NBR_EVT_MATCH_ADJ,      NBR_ACT_NOTHING,        NBR_STA_INITIAL},
+    {NBR_STA_INITIAL,  NBR_EVT_INIT_RCVD,      NBR_ACT_PASSIVE_INIT,   NBR_STA_OPENREC},
+    {NBR_STA_OPENREC,  NBR_EVT_KEEPALIVE_RCVD, NBR_ACT_SESSION_EST,    NBR_STA_OPER},
+/* Active Role */
+    {NBR_STA_PRESENT,  NBR_EVT_CONNECT_UP,     NBR_ACT_CONNECT_SETUP,  NBR_STA_INITIAL},
+    {NBR_STA_INITIAL,  NBR_EVT_INIT_SENT,      NBR_ACT_NOTHING,        NBR_STA_OPENSENT},
+    {NBR_STA_OPENSENT, NBR_EVT_INIT_RCVD,      NBR_ACT_KEEPALIVE_SEND, NBR_STA_OPENREC},
+/* Session Maintenance */
+    {NBR_STA_OPER,     NBR_EVT_PDU_RCVD,       NBR_ACT_RST_KTIMEOUT,   0},
+    {NBR_STA_SESSION,  NBR_EVT_PDU_RCVD,       NBR_ACT_NOTHING,        0},
+    {NBR_STA_OPER,     NBR_EVT_PDU_SENT,       NBR_ACT_RST_KTIMER,     0},
+    {NBR_STA_SESSION,  NBR_EVT_PDU_SENT,       NBR_ACT_NOTHING,        0},
+/* Session Close */
+    {NBR_STA_PRESENT,  NBR_EVT_CLOSE_SESSION,  NBR_ACT_NOTHING,        0},
+    {NBR_STA_SESSION,  NBR_EVT_CLOSE_SESSION,  NBR_ACT_CLOSE_SESSION,  NBR_STA_PRESENT},
+    {-1,               NBR_EVT_NOTHING,        NBR_ACT_NOTHING,        0},
+};
+
+const char * const nbr_event_names[] = {
+       "NOTHING",
+       "ADJACENCY MATCHED",
+       "CONNECTION UP",
+       "SESSION CLOSE",
+       "INIT RECEIVED",
+       "KEEPALIVE RECEIVED",
+       "PDU RECEIVED",
+       "PDU SENT",
+       "INIT SENT"
+};
+
+const char * const nbr_action_names[] = {
+       "NOTHING",
+       "RESET KEEPALIVE TIMEOUT",
+       "START NEIGHBOR SESSION",
+       "RESET KEEPALIVE TIMER",
+       "SETUP NEIGHBOR CONNECTION",
+       "SEND INIT AND KEEPALIVE",
+       "SEND KEEPALIVE",
+       "CLOSE SESSION"
+};
+
+struct nbr_id_head nbrs_by_id = RB_INITIALIZER(&nbrs_by_id);
+struct nbr_addr_head nbrs_by_addr = RB_INITIALIZER(&nbrs_by_addr);
+struct nbr_pid_head nbrs_by_pid = RB_INITIALIZER(&nbrs_by_pid);
+
+static __inline int
+nbr_id_compare(struct nbr *a, struct nbr *b)
+{
+       return (ntohl(a->id.s_addr) - ntohl(b->id.s_addr));
+}
+
+static __inline int
+nbr_addr_compare(struct nbr *a, struct nbr *b)
+{
+       if (a->af < b->af)
+               return (-1);
+       if (a->af > b->af)
+               return (1);
+
+       return (ldp_addrcmp(a->af, &a->raddr, &b->raddr));
+}
+
+static __inline int
+nbr_pid_compare(struct nbr *a, struct nbr *b)
+{
+       return (a->peerid - b->peerid);
+}
+
+int
+nbr_fsm(struct nbr *nbr, enum nbr_event event)
+{
+       struct timeval  now;
+       int             old_state;
+       int             new_state = 0;
+       int             i;
+
+       old_state = nbr->state;
+       for (i = 0; nbr_fsm_tbl[i].state != -1; i++)
+               if ((nbr_fsm_tbl[i].state & old_state) &&
+                   (nbr_fsm_tbl[i].event == event)) {
+                       new_state = nbr_fsm_tbl[i].new_state;
+                       break;
+               }
+
+       if (nbr_fsm_tbl[i].state == -1) {
+               /* event outside of the defined fsm, ignore it. */
+               log_warnx("%s: lsr-id %s, event %s not expected in "
+                   "state %s", __func__, inet_ntoa(nbr->id),
+                   nbr_event_names[event], nbr_state_name(old_state));
+               return (0);
+       }
+
+       if (new_state != 0)
+               nbr->state = new_state;
+
+       if (old_state != nbr->state) {
+               log_debug("%s: event %s resulted in action %s and "
+                   "changing state for lsr-id %s from %s to %s",
+                   __func__, nbr_event_names[event],
+                   nbr_action_names[nbr_fsm_tbl[i].action],
+                   inet_ntoa(nbr->id), nbr_state_name(old_state),
+                   nbr_state_name(nbr->state));
+
+               if (nbr->state == NBR_STA_OPER) {
+                       gettimeofday(&now, NULL);
+                       nbr->uptime = now.tv_sec;
+               }
+       }
+
+       if (nbr->state == NBR_STA_OPER || nbr->state == NBR_STA_PRESENT)
+               nbr_stop_itimeout(nbr);
+       else
+               nbr_start_itimeout(nbr);
+
+       switch (nbr_fsm_tbl[i].action) {
+       case NBR_ACT_RST_KTIMEOUT:
+               nbr_start_ktimeout(nbr);
+               break;
+       case NBR_ACT_RST_KTIMER:
+               nbr_start_ktimer(nbr);
+               break;
+       case NBR_ACT_SESSION_EST:
+               nbr_act_session_operational(nbr);
+               nbr_start_ktimer(nbr);
+               nbr_start_ktimeout(nbr);
+               if (nbr->v4_enabled)
+                       send_address_all(nbr, AF_INET);
+               if (nbr->v6_enabled)
+                       send_address_all(nbr, AF_INET6);
+               nbr_send_labelmappings(nbr);
+               break;
+       case NBR_ACT_CONNECT_SETUP:
+               nbr->tcp = tcp_new(nbr->fd, nbr);
+
+               /* trigger next state */
+               send_init(nbr);
+               nbr_fsm(nbr, NBR_EVT_INIT_SENT);
+               break;
+       case NBR_ACT_PASSIVE_INIT:
+               send_init(nbr);
+               send_keepalive(nbr);
+               break;
+       case NBR_ACT_KEEPALIVE_SEND:
+               nbr_start_ktimeout(nbr);
+               send_keepalive(nbr);
+               break;
+       case NBR_ACT_CLOSE_SESSION:
+               ldpe_imsg_compose_lde(IMSG_NEIGHBOR_DOWN, nbr->peerid, 0,
+                   NULL, 0);
+               session_close(nbr);
+               break;
+       case NBR_ACT_NOTHING:
+               /* do nothing */
+               break;
+       }
+
+       return (0);
+}
+
+struct nbr *
+nbr_new(struct in_addr id, int af, int ds_tlv, union ldpd_addr *addr,
+    uint32_t scope_id)
+{
+       struct nbr              *nbr;
+       struct nbr_params       *nbrp;
+       struct adj              *adj;
+       struct pending_conn     *pconn;
+
+       log_debug("%s: lsr-id %s transport-address %s", __func__,
+           inet_ntoa(id), log_addr(af, addr));
+
+       if ((nbr = calloc(1, sizeof(*nbr))) == NULL)
+               fatal(__func__);
+
+       LIST_INIT(&nbr->adj_list);
+       nbr->state = NBR_STA_PRESENT;
+       nbr->peerid = 0;
+       nbr->af = af;
+       nbr->ds_tlv = ds_tlv;
+       if (af == AF_INET || ds_tlv)
+               nbr->v4_enabled = 1;
+       if (af == AF_INET6 || ds_tlv)
+               nbr->v6_enabled = 1;
+       nbr->id = id;
+       nbr->laddr = (ldp_af_conf_get(leconf, af))->trans_addr;
+       nbr->raddr = *addr;
+       nbr->raddr_scope = scope_id;
+       nbr->conf_seqnum = 0;
+
+       LIST_FOREACH(adj, &global.adj_list, global_entry) {
+               if (adj->lsr_id.s_addr == nbr->id.s_addr) {
+                       adj->nbr = nbr;
+                       LIST_INSERT_HEAD(&nbr->adj_list, adj, nbr_entry);
+               }
+       }
+
+       if (RB_INSERT(nbr_id_head, &nbrs_by_id, nbr) != NULL)
+               fatalx("nbr_new: RB_INSERT(nbrs_by_id) failed");
+       if (RB_INSERT(nbr_addr_head, &nbrs_by_addr, nbr) != NULL)
+               fatalx("nbr_new: RB_INSERT(nbrs_by_addr) failed");
+
+       TAILQ_INIT(&nbr->mapping_list);
+       TAILQ_INIT(&nbr->withdraw_list);
+       TAILQ_INIT(&nbr->request_list);
+       TAILQ_INIT(&nbr->release_list);
+       TAILQ_INIT(&nbr->abortreq_list);
+
+       /* set event structures */
+       evtimer_set(&nbr->keepalive_timeout, nbr_ktimeout, nbr);
+       evtimer_set(&nbr->keepalive_timer, nbr_ktimer, nbr);
+       evtimer_set(&nbr->init_timeout, nbr_itimeout, nbr);
+       evtimer_set(&nbr->initdelay_timer, nbr_idtimer, nbr);
+
+       nbrp = nbr_params_find(leconf, nbr->id);
+       if (nbrp && pfkey_establish(nbr, nbrp) == -1)
+               fatalx("pfkey setup failed");
+
+       pconn = pending_conn_find(nbr->af, &nbr->raddr);
+       if (pconn) {
+               session_accept_nbr(nbr, pconn->fd);
+               pending_conn_del(pconn);
+       }
+
+       return (nbr);
+}
+
+void
+nbr_del(struct nbr *nbr)
+{
+       log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
+
+       nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+       pfkey_remove(nbr);
+
+       if (nbr_pending_connect(nbr))
+               event_del(&nbr->ev_connect);
+       nbr_stop_ktimer(nbr);
+       nbr_stop_ktimeout(nbr);
+       nbr_stop_itimeout(nbr);
+       nbr_stop_idtimer(nbr);
+
+       mapping_list_clr(&nbr->mapping_list);
+       mapping_list_clr(&nbr->withdraw_list);
+       mapping_list_clr(&nbr->request_list);
+       mapping_list_clr(&nbr->release_list);
+       mapping_list_clr(&nbr->abortreq_list);
+
+       if (nbr->peerid)
+               RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr);
+       RB_REMOVE(nbr_id_head, &nbrs_by_id, nbr);
+       RB_REMOVE(nbr_addr_head, &nbrs_by_addr, nbr);
+
+       free(nbr);
+}
+
+static void
+nbr_update_peerid(struct nbr *nbr)
+{
+       static uint32_t  peercnt = 1;
+
+       if (nbr->peerid)
+               RB_REMOVE(nbr_pid_head, &nbrs_by_pid, nbr);
+
+       /* get next unused peerid */
+       while (nbr_find_peerid(++peercnt))
+               ;
+       nbr->peerid = peercnt;
+
+       if (RB_INSERT(nbr_pid_head, &nbrs_by_pid, nbr) != NULL)
+               fatalx("nbr_update_peerid: RB_INSERT(nbrs_by_pid) failed");
+}
+
+struct nbr *
+nbr_find_ldpid(uint32_t lsr_id)
+{
+       struct nbr      n;
+       n.id.s_addr = lsr_id;
+       return (RB_FIND(nbr_id_head, &nbrs_by_id, &n));
+}
+
+struct nbr *
+nbr_find_addr(int af, union ldpd_addr *addr)
+{
+       struct nbr      n;
+       n.af = af;
+       n.raddr = *addr;
+       return (RB_FIND(nbr_addr_head, &nbrs_by_addr, &n));
+}
+
+struct nbr *
+nbr_find_peerid(uint32_t peerid)
+{
+       struct nbr      n;
+       n.peerid = peerid;
+       return (RB_FIND(nbr_pid_head, &nbrs_by_pid, &n));
+}
+
+int
+nbr_adj_count(struct nbr *nbr, int af)
+{
+       struct adj      *adj;
+       int              total = 0;
+
+       LIST_FOREACH(adj, &nbr->adj_list, nbr_entry)
+               if (adj_get_af(adj) == af)
+                       total++;
+
+       return (total);
+}
+
+int
+nbr_session_active_role(struct nbr *nbr)
+{
+       if (ldp_addrcmp(nbr->af, &nbr->laddr, &nbr->raddr) > 0)
+               return (1);
+
+       return (0);
+}
+
+/* timers */
+
+/* Keepalive timer: timer to send keepalive message to neighbors */
+
+static void
+nbr_ktimer(int fd, short event, void *arg)
+{
+       struct nbr      *nbr = arg;
+
+       send_keepalive(nbr);
+       nbr_start_ktimer(nbr);
+}
+
+static void
+nbr_start_ktimer(struct nbr *nbr)
+{
+       struct timeval   tv;
+
+       /* send three keepalives per period */
+       timerclear(&tv);
+       tv.tv_sec = (time_t)(nbr->keepalive / KEEPALIVE_PER_PERIOD);
+       if (evtimer_add(&nbr->keepalive_timer, &tv) == -1)
+               fatal(__func__);
+}
+
+void
+nbr_stop_ktimer(struct nbr *nbr)
+{
+       if (evtimer_pending(&nbr->keepalive_timer, NULL) &&
+           evtimer_del(&nbr->keepalive_timer) == -1)
+               fatal(__func__);
+}
+
+/* Keepalive timeout: if the nbr hasn't sent keepalive */
+
+static void
+nbr_ktimeout(int fd, short event, void *arg)
+{
+       struct nbr *nbr = arg;
+
+       log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
+
+       session_shutdown(nbr, S_KEEPALIVE_TMR, 0, 0);
+}
+
+static void
+nbr_start_ktimeout(struct nbr *nbr)
+{
+       struct timeval  tv;
+
+       timerclear(&tv);
+       tv.tv_sec = nbr->keepalive;
+
+       if (evtimer_add(&nbr->keepalive_timeout, &tv) == -1)
+               fatal(__func__);
+}
+
+void
+nbr_stop_ktimeout(struct nbr *nbr)
+{
+       if (evtimer_pending(&nbr->keepalive_timeout, NULL) &&
+           evtimer_del(&nbr->keepalive_timeout) == -1)
+               fatal(__func__);
+}
+
+/* Session initialization timeout: if nbr got stuck in the initialization FSM */
+
+static void
+nbr_itimeout(int fd, short event, void *arg)
+{
+       struct nbr *nbr = arg;
+
+       log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
+
+       nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+}
+
+static void
+nbr_start_itimeout(struct nbr *nbr)
+{
+       struct timeval   tv;
+
+       timerclear(&tv);
+       tv.tv_sec = INIT_FSM_TIMEOUT;
+       if (evtimer_add(&nbr->init_timeout, &tv) == -1)
+               fatal(__func__);
+}
+
+void
+nbr_stop_itimeout(struct nbr *nbr)
+{
+       if (evtimer_pending(&nbr->init_timeout, NULL) &&
+           evtimer_del(&nbr->init_timeout) == -1)
+               fatal(__func__);
+}
+
+/* Init delay timer: timer to retry to iniziatize session */
+
+static void
+nbr_idtimer(int fd, short event, void *arg)
+{
+       struct nbr *nbr = arg;
+
+       log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
+
+       nbr_establish_connection(nbr);
+}
+
+void
+nbr_start_idtimer(struct nbr *nbr)
+{
+       struct timeval  tv;
+
+       timerclear(&tv);
+
+       tv.tv_sec = INIT_DELAY_TMR;
+       switch(nbr->idtimer_cnt) {
+       default:
+               /* do not further increase the counter */
+               tv.tv_sec = MAX_DELAY_TMR;
+               break;
+       case 2:
+               tv.tv_sec *= 2;
+               /* FALLTHROUGH */
+       case 1:
+               tv.tv_sec *= 2;
+               /* FALLTHROUGH */
+       case 0:
+               nbr->idtimer_cnt++;
+               break;
+       }
+
+       if (evtimer_add(&nbr->initdelay_timer, &tv) == -1)
+               fatal(__func__);
+}
+
+void
+nbr_stop_idtimer(struct nbr *nbr)
+{
+       if (evtimer_pending(&nbr->initdelay_timer, NULL) &&
+           evtimer_del(&nbr->initdelay_timer) == -1)
+               fatal(__func__);
+}
+
+int
+nbr_pending_idtimer(struct nbr *nbr)
+{
+       if (evtimer_pending(&nbr->initdelay_timer, NULL))
+               return (1);
+
+       return (0);
+}
+
+int
+nbr_pending_connect(struct nbr *nbr)
+{
+       if (event_initialized(&nbr->ev_connect) &&
+           event_pending(&nbr->ev_connect, EV_WRITE, NULL))
+               return (1);
+
+       return (0);
+}
+
+static void
+nbr_connect_cb(int fd, short event, void *arg)
+{
+       struct nbr      *nbr = arg;
+       int              error;
+       socklen_t        len;
+
+       len = sizeof(error);
+       if (getsockopt(nbr->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
+               log_warn("%s: getsockopt SOL_SOCKET SO_ERROR", __func__);
+               return;
+       }
+
+       if (error) {
+               close(nbr->fd);
+               errno = error;
+               log_debug("%s: error while connecting to %s: %s", __func__,
+                   log_addr(nbr->af, &nbr->raddr), strerror(errno));
+               return;
+       }
+
+       nbr_fsm(nbr, NBR_EVT_CONNECT_UP);
+}
+
+int
+nbr_establish_connection(struct nbr *nbr)
+{
+       struct sockaddr_storage  local_sa;
+       struct sockaddr_storage  remote_sa;
+       struct adj              *adj;
+       struct nbr_params       *nbrp;
+       int                      opt = 1;
+
+       nbr->fd = socket(nbr->af,
+           SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
+       if (nbr->fd == -1) {
+               log_warn("%s: error while creating socket", __func__);
+               return (-1);
+       }
+
+       nbrp = nbr_params_find(leconf, nbr->id);
+       if (nbrp && nbrp->auth.method == AUTH_MD5SIG) {
+               if (sysdep.no_pfkey || sysdep.no_md5sig) {
+                       log_warnx("md5sig configured but not available");
+                       close(nbr->fd);
+                       return (-1);
+               }
+               if (setsockopt(nbr->fd, IPPROTO_TCP, TCP_MD5SIG,
+                   &opt, sizeof(opt)) == -1) {
+                       log_warn("setsockopt md5sig");
+                       close(nbr->fd);
+                       return (-1);
+               }
+       }
+
+       memcpy(&local_sa, addr2sa(nbr->af, &nbr->laddr, 0), sizeof(local_sa));
+       memcpy(&remote_sa, addr2sa(nbr->af, &nbr->raddr, LDP_PORT),
+           sizeof(local_sa));
+       if (nbr->af == AF_INET6 && nbr->raddr_scope)
+               addscope((struct sockaddr_in6 *)&remote_sa, nbr->raddr_scope);
+
+       if (bind(nbr->fd, (struct sockaddr *)&local_sa,
+           local_sa.ss_len) == -1) {
+               log_warn("%s: error while binding socket to %s", __func__,
+                   log_sockaddr((struct sockaddr *)&local_sa));
+               close(nbr->fd);
+               return (-1);
+       }
+
+       if (nbr_gtsm_check(nbr->fd, nbr, nbrp)) {
+               close(nbr->fd);
+               return (-1);
+       }
+
+       /*
+        * Send an extra hello to guarantee that the remote peer has formed
+        * an adjacency as well.
+        */
+       LIST_FOREACH(adj, &nbr->adj_list, nbr_entry)
+               send_hello(adj->source.type, adj->source.link.ia,
+                   adj->source.target);
+
+       if (connect(nbr->fd, (struct sockaddr *)&remote_sa,
+           remote_sa.ss_len) == -1) {
+               if (errno == EINPROGRESS) {
+                       event_set(&nbr->ev_connect, nbr->fd, EV_WRITE,
+                           nbr_connect_cb, nbr);
+                       event_add(&nbr->ev_connect, NULL);
+                       return (0);
+               }
+               log_warn("%s: error while connecting to %s", __func__,
+                   log_sockaddr((struct sockaddr *)&remote_sa));
+               close(nbr->fd);
+               return (-1);
+       }
+
+       /* connection completed immediately */
+       nbr_fsm(nbr, NBR_EVT_CONNECT_UP);
+
+       return (0);
+}
+
+int
+nbr_gtsm_enabled(struct nbr *nbr, struct nbr_params *nbrp)
+{
+       /*
+        * RFC 6720 - Section 3:
+        * "This document allows for the implementation to provide an option to
+        * statically (e.g., via configuration) and/or dynamically override the
+        * default behavior and enable/disable GTSM on a per-peer basis".
+        */
+       if (nbrp && (nbrp->flags & F_NBRP_GTSM))
+               return (nbrp->gtsm_enabled);
+
+       if ((ldp_af_conf_get(leconf, nbr->af))->flags & F_LDPD_AF_NO_GTSM)
+               return (0);
+
+       /* By default, GTSM support has to be negotiated for LDPv4 */
+       if (nbr->af == AF_INET && !(nbr->flags & F_NBR_GTSM_NEGOTIATED))
+               return (0);
+
+       return (1);
+}
+
+int
+nbr_gtsm_setup(int fd, int af, struct nbr_params *nbrp)
+{
+       int      ttl = 255;
+
+       if (nbrp && (nbrp->flags & F_NBRP_GTSM_HOPS))
+               ttl = 256 - nbrp->gtsm_hops;
+
+       switch (af) {
+       case AF_INET:
+               if (sock_set_ipv4_minttl(fd, ttl) == -1)
+                       return (-1);
+               ttl = 255;
+               if (sock_set_ipv4_ucast_ttl(fd, ttl) == -1)
+                       return (-1);
+               break;
+       case AF_INET6:
+               if (sock_set_ipv6_minhopcount(fd, ttl) == -1)
+                       return (-1);
+               ttl = 255;
+               if (sock_set_ipv6_ucast_hops(fd, ttl) == -1)
+                       return (-1);
+               break;
+       default:
+               fatalx("nbr_gtsm_setup: unknown af");
+       }
+
+       return (0);
+}
+
+int
+nbr_gtsm_check(int fd, struct nbr *nbr, struct nbr_params *nbrp)
+{
+       if (!nbr_gtsm_enabled(nbr, nbrp)) {
+               switch (nbr->af) {
+               case AF_INET:
+                       sock_set_ipv4_ucast_ttl(fd, -1);
+                       break;
+               case AF_INET6:
+                       /*
+                        * Send packets with a Hop Limit of 255 even when GSTM
+                        * is disabled to guarantee interoperability.
+                        */
+                       sock_set_ipv6_ucast_hops(fd, 255);
+                       break;
+               default:
+                       fatalx("nbr_gtsm_check: unknown af");
+                       break;
+               }
+               return (0);
+       }
+
+       if (nbr_gtsm_setup(fd, nbr->af, nbrp) == -1) {
+               log_warnx("%s: error enabling GTSM for lsr-id %s", __func__,
+                   inet_ntoa(nbr->id));
+               return (-1);
+       }
+
+       return (0);
+}
+
+static int
+nbr_act_session_operational(struct nbr *nbr)
+{
+       struct lde_nbr   lde_nbr;
+
+       nbr->idtimer_cnt = 0;
+
+       /* this is necessary to avoid ipc synchronization issues */
+       nbr_update_peerid(nbr);
+
+       memset(&lde_nbr, 0, sizeof(lde_nbr));
+       lde_nbr.id = nbr->id;
+       lde_nbr.v4_enabled = nbr->v4_enabled;
+       lde_nbr.v6_enabled = nbr->v6_enabled;
+       return (ldpe_imsg_compose_lde(IMSG_NEIGHBOR_UP, nbr->peerid, 0,
+           &lde_nbr, sizeof(lde_nbr)));
+}
+
+static void
+nbr_send_labelmappings(struct nbr *nbr)
+{
+       ldpe_imsg_compose_lde(IMSG_LABEL_MAPPING_FULL, nbr->peerid, 0,
+           NULL, 0);
+}
+
+struct nbr_params *
+nbr_params_new(struct in_addr lsr_id)
+{
+       struct nbr_params       *nbrp;
+
+       if ((nbrp = calloc(1, sizeof(*nbrp))) == NULL)
+               fatal(__func__);
+
+       nbrp->lsr_id = lsr_id;
+       nbrp->auth.method = AUTH_NONE;
+
+       return (nbrp);
+}
+
+struct nbr_params *
+nbr_params_find(struct ldpd_conf *xconf, struct in_addr lsr_id)
+{
+       struct nbr_params *nbrp;
+
+       LIST_FOREACH(nbrp, &xconf->nbrp_list, entry)
+               if (nbrp->lsr_id.s_addr == lsr_id.s_addr)
+                       return (nbrp);
+
+       return (NULL);
+}
+
+uint16_t
+nbr_get_keepalive(int af, struct in_addr lsr_id)
+{
+       struct nbr_params       *nbrp;
+
+       nbrp = nbr_params_find(leconf, lsr_id);
+       if (nbrp && (nbrp->flags & F_NBRP_KEEPALIVE))
+               return (nbrp->keepalive);
+
+       return ((ldp_af_conf_get(leconf, af))->keepalive);
+}
+
+struct ctl_nbr *
+nbr_to_ctl(struct nbr *nbr)
+{
+       static struct ctl_nbr    nctl;
+       struct timeval           now;
+
+       nctl.af = nbr->af;
+       nctl.id = nbr->id;
+       nctl.laddr = nbr->laddr;
+       nctl.raddr = nbr->raddr;
+       nctl.nbr_state = nbr->state;
+
+       gettimeofday(&now, NULL);
+       if (nbr->state == NBR_STA_OPER) {
+               nctl.uptime = now.tv_sec - nbr->uptime;
+       } else
+               nctl.uptime = 0;
+
+       return (&nctl);
+}
+
+void
+nbr_clear_ctl(struct ctl_nbr *nctl)
+{
+       struct nbr              *nbr;
+
+       RB_FOREACH(nbr, nbr_addr_head, &nbrs_by_addr) {
+               if (ldp_addrisset(nctl->af, &nctl->raddr) &&
+                   ldp_addrcmp(nctl->af, &nctl->raddr, &nbr->raddr))
+                       continue;
+
+               log_debug("%s: neighbor %s manually cleared", __func__,
+                   log_addr(nbr->af, &nbr->raddr));
+               session_shutdown(nbr, S_SHUTDOWN, 0, 0);
+       }
+}
diff --git a/ldpd/notification.c b/ldpd/notification.c
new file mode 100644 (file)
index 0000000..f30646b
--- /dev/null
@@ -0,0 +1,239 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ *
+ * 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/types.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldp.h"
+#include "log.h"
+#include "ldpe.h"
+
+void
+send_notification_full(struct tcp_conn *tcp, struct notify_msg *nm)
+{
+       struct ibuf     *buf;
+       uint16_t         size;
+       int              err = 0;
+
+       /* calculate size */
+       size = LDP_HDR_SIZE + LDP_MSG_SIZE + STATUS_SIZE;
+       if (nm->flags & F_NOTIF_PW_STATUS)
+               size += PW_STATUS_TLV_SIZE;
+       if (nm->flags & F_NOTIF_FEC) {
+               size += TLV_HDR_SIZE;
+               switch (nm->fec.type) {
+               case MAP_TYPE_PWID:
+                       size += FEC_PWID_ELM_MIN_LEN;
+                       if (nm->fec.flags & F_MAP_PW_ID)
+                               size += sizeof(uint32_t);
+                       break;
+               }
+       }
+
+       if ((buf = ibuf_open(size)) == NULL)
+               fatal(__func__);
+
+       err |= gen_ldp_hdr(buf, size);
+       size -= LDP_HDR_SIZE;
+       err |= gen_msg_hdr(buf, MSG_TYPE_NOTIFICATION, size);
+       err |= gen_status_tlv(buf, nm->status_code, nm->msg_id, nm->msg_type);
+       /* optional tlvs */
+       if (nm->flags & F_NOTIF_PW_STATUS)
+               err |= gen_pw_status_tlv(buf, nm->pw_status);
+       if (nm->flags & F_NOTIF_FEC)
+               err |= gen_fec_tlv(buf, &nm->fec);
+       if (err) {
+               ibuf_free(buf);
+               return;
+       }
+
+       if (tcp->nbr)
+               log_debug("msg-out: notification: lsr-id %s, status %s%s",
+                   inet_ntoa(tcp->nbr->id), status_code_name(nm->status_code),
+                   (nm->status_code & STATUS_FATAL) ? " (fatal)" : "");
+
+       evbuf_enqueue(&tcp->wbuf, buf);
+}
+
+/* send a notification without optional tlvs */
+void
+send_notification(uint32_t status_code, struct tcp_conn *tcp, uint32_t msg_id,
+    uint16_t msg_type)
+{
+       struct notify_msg        nm;
+
+       memset(&nm, 0, sizeof(nm));
+       nm.status_code = status_code;
+       nm.msg_id = msg_id;
+       nm.msg_type = msg_type;
+
+       send_notification_full(tcp, &nm);
+}
+
+void
+send_notification_nbr(struct nbr *nbr, uint32_t status_code, uint32_t msg_id,
+    uint16_t msg_type)
+{
+       send_notification(status_code, nbr->tcp, msg_id, msg_type);
+       nbr_fsm(nbr, NBR_EVT_PDU_SENT);
+}
+
+int
+recv_notification(struct nbr *nbr, char *buf, uint16_t len)
+{
+       struct ldp_msg          msg;
+       struct status_tlv       st;
+       struct notify_msg       nm;
+       int                     tlen;
+
+       memcpy(&msg, buf, sizeof(msg));
+       buf += LDP_MSG_SIZE;
+       len -= LDP_MSG_SIZE;
+
+       if (len < STATUS_SIZE) {
+               session_shutdown(nbr, S_BAD_MSG_LEN, msg.id, msg.type);
+               return (-1);
+       }
+       memcpy(&st, buf, sizeof(st));
+
+       if (ntohs(st.length) > STATUS_SIZE - TLV_HDR_SIZE ||
+           ntohs(st.length) > len - TLV_HDR_SIZE) {
+               session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+               return (-1);
+       }
+       buf += STATUS_SIZE;
+       len -= STATUS_SIZE;
+
+       memset(&nm, 0, sizeof(nm));
+       nm.status_code = ntohl(st.status_code);
+
+       /* Optional Parameters */
+       while (len > 0) {
+               struct tlv      tlv;
+               uint16_t        tlv_len;
+
+               if (len < sizeof(tlv)) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+                       return (-1);
+               }
+
+               memcpy(&tlv, buf, TLV_HDR_SIZE);
+               tlv_len = ntohs(tlv.length);
+               if (tlv_len + TLV_HDR_SIZE > len) {
+                       session_shutdown(nbr, S_BAD_TLV_LEN, msg.id, msg.type);
+                       return (-1);
+               }
+               buf += TLV_HDR_SIZE;
+               len -= TLV_HDR_SIZE;
+
+               switch (ntohs(tlv.type)) {
+               case TLV_TYPE_EXTSTATUS:
+               case TLV_TYPE_RETURNEDPDU:
+               case TLV_TYPE_RETURNEDMSG:
+                       /* TODO is there any use for this? */
+                       break;
+               case TLV_TYPE_PW_STATUS:
+                       if (tlv_len != 4) {
+                               session_shutdown(nbr, S_BAD_TLV_LEN,
+                                   msg.id, msg.type);
+                               return (-1);
+                       }
+
+                       nm.pw_status = ntohl(*(uint32_t *)buf);
+                       nm.flags |= F_NOTIF_PW_STATUS;
+                       break;
+               case TLV_TYPE_FEC:
+                       if ((tlen = tlv_decode_fec_elm(nbr, &msg, buf,
+                           tlv_len, &nm.fec)) == -1)
+                               return (-1);
+                       /* allow only one fec element */
+                       if (tlen != tlv_len) {
+                               session_shutdown(nbr, S_BAD_TLV_VAL,
+                                   msg.id, msg.type);
+                               return (-1);
+                       }
+                       nm.flags |= F_NOTIF_FEC;
+                       break;
+               default:
+                       if (!(ntohs(tlv.type) & UNKNOWN_FLAG))
+                               send_notification_nbr(nbr, S_UNKNOWN_TLV,
+                                   msg.id, msg.type);
+                       /* ignore unknown tlv */
+                       break;
+               }
+               buf += tlv_len;
+               len -= tlv_len;
+       }
+
+       if (nm.status_code == S_PW_STATUS) {
+               if (!(nm.flags & (F_NOTIF_PW_STATUS|F_NOTIF_FEC))) {
+                       send_notification_nbr(nbr, S_MISS_MSG,
+                           msg.id, msg.type);
+                       return (-1);
+               }
+
+               switch (nm.fec.type) {
+               case MAP_TYPE_PWID:
+                       break;
+               default:
+                       send_notification_nbr(nbr, S_BAD_TLV_VAL,
+                           msg.id, msg.type);
+                       return (-1);
+               }
+       }
+
+       log_warnx("msg-in: notification: lsr-id %s, status %s%s",
+           inet_ntoa(nbr->id), status_code_name(ntohl(st.status_code)),
+           (st.status_code & htonl(STATUS_FATAL)) ? " (fatal)" : "");
+
+       if (st.status_code & htonl(STATUS_FATAL)) {
+               if (nbr->state == NBR_STA_OPENSENT)
+                       nbr_start_idtimer(nbr);
+
+               nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+               return (-1);
+       }
+
+       if (nm.status_code == S_PW_STATUS)
+               ldpe_imsg_compose_lde(IMSG_NOTIFICATION, nbr->peerid, 0,
+                   &nm, sizeof(nm));
+
+       return (0);
+}
+
+int
+gen_status_tlv(struct ibuf *buf, uint32_t status_code, uint32_t msg_id,
+    uint16_t msg_type)
+{
+       struct status_tlv       st;
+
+       memset(&st, 0, sizeof(st));
+       st.type = htons(TLV_TYPE_STATUS);
+       st.length = htons(STATUS_TLV_LEN);
+       st.status_code = htonl(status_code);
+       /*
+        * For convenience, msg_id and msg_type are already in network
+        * byte order.
+        */
+       st.msg_id = msg_id;
+       st.msg_type = msg_type;
+
+       return (ibuf_add(buf, &st, STATUS_SIZE));
+}
diff --git a/ldpd/packet.c b/ldpd/packet.c
new file mode 100644 (file)
index 0000000..7cc375c
--- /dev/null
@@ -0,0 +1,788 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2013, 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2004, 2005, 2008 Esben Norby <norby@openbsd.org>
+ *
+ * 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/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <net/if_dl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+static struct iface            *disc_find_iface(unsigned int, int,
+                                   union ldpd_addr *, int);
+static void                     session_read(int, short, void *);
+static void                     session_write(int, short, void *);
+static ssize_t                  session_get_pdu(struct ibuf_read *, char **);
+static void                     tcp_close(struct tcp_conn *);
+static struct pending_conn     *pending_conn_new(int, int, union ldpd_addr *);
+static void                     pending_conn_timeout(int, short, void *);
+
+int
+gen_ldp_hdr(struct ibuf *buf, uint16_t size)
+{
+       struct ldp_hdr  ldp_hdr;
+
+       memset(&ldp_hdr, 0, sizeof(ldp_hdr));
+       ldp_hdr.version = htons(LDP_VERSION);
+       /* exclude the 'Version' and 'PDU Length' fields from the total */
+       ldp_hdr.length = htons(size - LDP_HDR_DEAD_LEN);
+       ldp_hdr.lsr_id = leconf->rtr_id.s_addr;
+       ldp_hdr.lspace_id = 0;
+
+       return (ibuf_add(buf, &ldp_hdr, LDP_HDR_SIZE));
+}
+
+int
+gen_msg_hdr(struct ibuf *buf, uint16_t type, uint16_t size)
+{
+       static int      msgcnt = 0;
+       struct ldp_msg  msg;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.type = htons(type);
+       /* exclude the 'Type' and 'Length' fields from the total */
+       msg.length = htons(size - LDP_MSG_DEAD_LEN);
+       msg.id = htonl(++msgcnt);
+
+       return (ibuf_add(buf, &msg, sizeof(msg)));
+}
+
+/* send packets */
+int
+send_packet(int fd, int af, union ldpd_addr *dst, struct iface_af *ia,
+    void *pkt, size_t len)
+{
+       struct sockaddr         *sa;
+
+       switch (af) {
+       case AF_INET:
+               if (ia && IN_MULTICAST(ntohl(dst->v4.s_addr))) {
+                       /* set outgoing interface for multicast traffic */
+                       if (sock_set_ipv4_mcast(ia->iface) == -1) {
+                               log_debug("%s: error setting multicast "
+                                   "interface, %s", __func__, ia->iface->name);
+                               return (-1);
+                       }
+               }
+               break;
+       case AF_INET6:
+               if (ia && IN6_IS_ADDR_MULTICAST(&dst->v6)) {
+                       /* set outgoing interface for multicast traffic */
+                       if (sock_set_ipv6_mcast(ia->iface) == -1) {
+                               log_debug("%s: error setting multicast "
+                                   "interface, %s", __func__, ia->iface->name);
+                               return (-1);
+                       }
+               }
+               break;
+       default:
+               fatalx("send_packet: unknown af");
+       }
+
+       sa = addr2sa(af, dst, LDP_PORT);
+       if (sendto(fd, pkt, len, 0, sa, sa->sa_len) == -1) {
+               log_warn("%s: error sending packet to %s", __func__,
+                   log_sockaddr(sa));
+               return (-1);
+       }
+
+       return (0);
+}
+
+/* Discovery functions */
+#define CMSG_MAXLEN max(sizeof(struct sockaddr_dl), sizeof(struct in6_pktinfo))
+void
+disc_recv_packet(int fd, short event, void *bula)
+{
+       union {
+               struct  cmsghdr hdr;
+               char    buf[CMSG_SPACE(CMSG_MAXLEN)];
+       } cmsgbuf;
+       struct msghdr            m;
+       struct sockaddr_storage  from;
+       struct iovec             iov;
+       char                    *buf;
+       struct cmsghdr          *cmsg;
+       ssize_t                  r;
+       int                      multicast;
+       int                      af;
+       union ldpd_addr          src;
+       unsigned int             ifindex = 0;
+       struct iface            *iface;
+       uint16_t                 len;
+       struct ldp_hdr           ldp_hdr;
+       uint16_t                 pdu_len;
+       struct ldp_msg           msg;
+       uint16_t                 msg_len;
+       struct in_addr           lsr_id;
+
+       if (event != EV_READ)
+               return;
+
+       /* setup buffer */
+       memset(&m, 0, sizeof(m));
+       iov.iov_base = buf = pkt_ptr;
+       iov.iov_len = IBUF_READ_SIZE;
+       m.msg_name = &from;
+       m.msg_namelen = sizeof(from);
+       m.msg_iov = &iov;
+       m.msg_iovlen = 1;
+       m.msg_control = &cmsgbuf.buf;
+       m.msg_controllen = sizeof(cmsgbuf.buf);
+
+       if ((r = recvmsg(fd, &m, 0)) == -1) {
+               if (errno != EAGAIN && errno != EINTR)
+                       log_debug("%s: read error: %s", __func__,
+                           strerror(errno));
+               return;
+       }
+
+       multicast = (m.msg_flags & MSG_MCAST) ? 1 : 0;
+       sa2addr((struct sockaddr *)&from, &af, &src);
+       if (bad_addr(af, &src)) {
+               log_debug("%s: invalid source address: %s", __func__,
+                   log_addr(af, &src));
+               return;
+       }
+
+       for (cmsg = CMSG_FIRSTHDR(&m); cmsg != NULL;
+           cmsg = CMSG_NXTHDR(&m, cmsg)) {
+               if (af == AF_INET && cmsg->cmsg_level == IPPROTO_IP &&
+                   cmsg->cmsg_type == IP_RECVIF) {
+                       ifindex = ((struct sockaddr_dl *)
+                           CMSG_DATA(cmsg))->sdl_index;
+                       break;
+               }
+               if (af == AF_INET6 && cmsg->cmsg_level == IPPROTO_IPV6 &&
+                   cmsg->cmsg_type == IPV6_PKTINFO) {
+                       ifindex = ((struct in6_pktinfo *)
+                           CMSG_DATA(cmsg))->ipi6_ifindex;
+                       break;
+               }
+       }
+
+       /* find a matching interface */
+       iface = disc_find_iface(ifindex, af, &src, multicast);
+       if (iface == NULL)
+               return;
+
+       /* check packet size */
+       len = (uint16_t)r;
+       if (len < (LDP_HDR_SIZE + LDP_MSG_SIZE) || len > LDP_MAX_LEN) {
+               log_debug("%s: bad packet size, source %s", __func__,
+                   log_addr(af, &src));
+               return;
+       }
+
+       /* LDP header sanity checks */
+       memcpy(&ldp_hdr, buf, sizeof(ldp_hdr));
+       if (ntohs(ldp_hdr.version) != LDP_VERSION) {
+               log_debug("%s: invalid LDP version %d, source %s", __func__,
+                   ntohs(ldp_hdr.version), log_addr(af, &src));
+               return;
+       }
+       if (ntohs(ldp_hdr.lspace_id) != 0) {
+               log_debug("%s: invalid label space %u, source %s", __func__,
+                   ntohs(ldp_hdr.lspace_id), log_addr(af, &src));
+               return;
+       }
+       /* check "PDU Length" field */
+       pdu_len = ntohs(ldp_hdr.length);
+       if ((pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE)) ||
+           (pdu_len > (len - LDP_HDR_DEAD_LEN))) {
+               log_debug("%s: invalid LDP packet length %u, source %s",
+                   __func__, ntohs(ldp_hdr.length), log_addr(af, &src));
+               return;
+       }
+       buf += LDP_HDR_SIZE;
+       len -= LDP_HDR_SIZE;
+
+       lsr_id.s_addr = ldp_hdr.lsr_id;
+
+       /*
+        * For UDP, we process only the first message of each packet. This does
+        * not impose any restrictions since LDP uses UDP only for sending Hello
+        * packets.
+        */
+       memcpy(&msg, buf, sizeof(msg));
+
+       /* check "Message Length" field */
+       msg_len = ntohs(msg.length);
+       if (msg_len < LDP_MSG_LEN || ((msg_len + LDP_MSG_DEAD_LEN) > pdu_len)) {
+               log_debug("%s: invalid LDP message length %u, source %s",
+                   __func__, ntohs(msg.length), log_addr(af, &src));
+               return;
+       }
+       buf += LDP_MSG_SIZE;
+       len -= LDP_MSG_SIZE;
+
+       /* switch LDP packet type */
+       switch (ntohs(msg.type)) {
+       case MSG_TYPE_HELLO:
+               recv_hello(lsr_id, &msg, af, &src, iface, multicast, buf, len);
+               break;
+       default:
+               log_debug("%s: unknown LDP packet type, source %s", __func__,
+                   log_addr(af, &src));
+       }
+}
+
+static struct iface *
+disc_find_iface(unsigned int ifindex, int af, union ldpd_addr *src,
+    int multicast)
+{
+       struct iface    *iface;
+       struct iface_af *ia;
+       struct if_addr  *if_addr;
+       in_addr_t        mask;
+
+       iface = if_lookup(leconf, ifindex);
+       if (iface == NULL)
+               return (NULL);
+
+       /*
+        * For unicast packets, we just need to make sure that the interface
+        * is enabled for the given address-family.
+        */
+       if (!multicast) {
+               ia = iface_af_get(iface, af);
+               if (ia->enabled)
+                       return (iface);
+               return (NULL);
+       }
+
+       switch (af) {
+       case AF_INET:
+               LIST_FOREACH(if_addr, &iface->addr_list, entry) {
+                       if (if_addr->af != AF_INET)
+                               continue;
+
+                       switch (iface->type) {
+                       case IF_TYPE_POINTOPOINT:
+                               if (if_addr->dstbrd.v4.s_addr == src->v4.s_addr)
+                                       return (iface);
+                               break;
+                       default:
+                               mask = prefixlen2mask(if_addr->prefixlen);
+                               if ((if_addr->addr.v4.s_addr & mask) ==
+                                   (src->v4.s_addr & mask))
+                                       return (iface);
+                               break;
+                       }
+               }
+               break;
+       case AF_INET6:
+               if (IN6_IS_ADDR_LINKLOCAL(&src->v6))
+                       return (iface);
+               break;
+       default:
+               fatalx("disc_find_iface: unknown af");
+       }
+
+       return (NULL);
+}
+
+void
+session_accept(int fd, short event, void *bula)
+{
+       struct sockaddr_storage  src;
+       socklen_t                len = sizeof(src);
+       int                      newfd;
+       int                      af;
+       union ldpd_addr          addr;
+       struct nbr              *nbr;
+       struct pending_conn     *pconn;
+
+       if (!(event & EV_READ))
+               return;
+
+       newfd = accept4(fd, (struct sockaddr *)&src, &len,
+           SOCK_NONBLOCK | SOCK_CLOEXEC);
+       if (newfd == -1) {
+               /*
+                * Pause accept if we are out of file descriptors, or
+                * libevent will haunt us here too.
+                */
+               if (errno == ENFILE || errno == EMFILE) {
+                       accept_pause();
+               } else if (errno != EWOULDBLOCK && errno != EINTR &&
+                   errno != ECONNABORTED)
+                       log_debug("%s: accept error: %s", __func__,
+                           strerror(errno));
+               return;
+       }
+
+       sa2addr((struct sockaddr *)&src, &af, &addr);
+
+       /*
+        * Since we don't support label spaces, we can identify this neighbor
+        * just by its source address. This way we don't need to wait for its
+        * Initialization message to know who we are talking to.
+        */
+       nbr = nbr_find_addr(af, &addr);
+       if (nbr == NULL) {
+               /*
+                * According to RFC 5036, we would need to send a No Hello
+                * Error Notification message and close this TCP connection
+                * right now. But doing so would trigger the backoff exponential
+                * timer in the remote peer, which would considerably slow down
+                * the session establishment process. The trick here is to wait
+                * five seconds before sending the Notification Message. There's
+                * a good chance that the remote peer will send us a Hello
+                * message within this interval, so it's worth waiting before
+                * taking a more drastic measure.
+                */
+               pconn = pending_conn_find(af, &addr);
+               if (pconn)
+                       close(newfd);
+               else
+                       pending_conn_new(newfd, af, &addr);
+               return;
+       }
+       /* protection against buggy implementations */
+       if (nbr_session_active_role(nbr)) {
+               close(newfd);
+               return;
+       }
+       if (nbr->state != NBR_STA_PRESENT) {
+               log_debug("%s: lsr-id %s: rejecting additional transport "
+                   "connection", __func__, inet_ntoa(nbr->id));
+               close(newfd);
+               return;
+       }
+
+       session_accept_nbr(nbr, newfd);
+}
+
+void
+session_accept_nbr(struct nbr *nbr, int fd)
+{
+       struct nbr_params       *nbrp;
+       int                      opt;
+       socklen_t                len;
+
+       nbrp = nbr_params_find(leconf, nbr->id);
+       if (nbr_gtsm_check(fd, nbr, nbrp)) {
+               close(fd);
+               return;
+       }
+
+       if (nbrp && nbrp->auth.method == AUTH_MD5SIG) {
+               if (sysdep.no_pfkey || sysdep.no_md5sig) {
+                       log_warnx("md5sig configured but not available");
+                       close(fd);
+                       return;
+               }
+
+               len = sizeof(opt);
+               if (getsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt, &len) == -1)
+                       fatal("getsockopt TCP_MD5SIG");
+               if (!opt) {     /* non-md5'd connection! */
+                       log_warnx("connection attempt without md5 signature");
+                       close(fd);
+                       return;
+               }
+       }
+
+       nbr->tcp = tcp_new(fd, nbr);
+       nbr_fsm(nbr, NBR_EVT_MATCH_ADJ);
+}
+
+static void
+session_read(int fd, short event, void *arg)
+{
+       struct nbr      *nbr = arg;
+       struct tcp_conn *tcp = nbr->tcp;
+       struct ldp_hdr  *ldp_hdr;
+       struct ldp_msg  *msg;
+       char            *buf, *pdu;
+       ssize_t          n, len;
+       uint16_t         pdu_len, msg_len, msg_size, max_pdu_len;
+       int              ret;
+
+       if (event != EV_READ)
+               return;
+
+       if ((n = read(fd, tcp->rbuf->buf + tcp->rbuf->wpos,
+           sizeof(tcp->rbuf->buf) - tcp->rbuf->wpos)) == -1) {
+               if (errno != EINTR && errno != EAGAIN) {
+                       log_warn("%s: read error", __func__);
+                       nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+                       return;
+               }
+               /* retry read */
+               return;
+       }
+       if (n == 0) {
+               /* connection closed */
+               log_debug("%s: connection closed by remote end", __func__);
+               nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+               return;
+       }
+       tcp->rbuf->wpos += n;
+
+       while ((len = session_get_pdu(tcp->rbuf, &buf)) > 0) {
+               pdu = buf;
+               ldp_hdr = (struct ldp_hdr *)pdu;
+               if (ntohs(ldp_hdr->version) != LDP_VERSION) {
+                       session_shutdown(nbr, S_BAD_PROTO_VER, 0, 0);
+                       free(buf);
+                       return;
+               }
+
+               pdu_len = ntohs(ldp_hdr->length);
+               /*
+                * RFC 5036 - Section 3.5.3:
+                * "Prior to completion of the negotiation, the maximum
+                * allowable length is 4096 bytes".
+                */
+               if (nbr->state == NBR_STA_OPER)
+                       max_pdu_len = nbr->max_pdu_len;
+               else
+                       max_pdu_len = LDP_MAX_LEN;
+               if (pdu_len < (LDP_HDR_PDU_LEN + LDP_MSG_SIZE) ||
+                   pdu_len > max_pdu_len) {
+                       session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0);
+                       free(buf);
+                       return;
+               }
+               pdu_len -= LDP_HDR_PDU_LEN;
+               if (ldp_hdr->lsr_id != nbr->id.s_addr ||
+                   ldp_hdr->lspace_id != 0) {
+                       session_shutdown(nbr, S_BAD_LDP_ID, 0, 0);
+                       free(buf);
+                       return;
+               }
+               pdu += LDP_HDR_SIZE;
+               len -= LDP_HDR_SIZE;
+
+               nbr_fsm(nbr, NBR_EVT_PDU_RCVD);
+
+               while (len >= LDP_MSG_SIZE) {
+                       uint16_t type;
+
+                       msg = (struct ldp_msg *)pdu;
+                       type = ntohs(msg->type);
+                       msg_len = ntohs(msg->length);
+                       if (msg_len < LDP_MSG_LEN ||
+                           (msg_len + LDP_MSG_DEAD_LEN) > pdu_len) {
+                               session_shutdown(nbr, S_BAD_TLV_LEN, msg->id,
+                                   msg->type);
+                               free(buf);
+                               return;
+                       }
+                       msg_size = msg_len + LDP_MSG_DEAD_LEN;
+                       pdu_len -= msg_size;
+
+                       /* check for error conditions earlier */
+                       switch (type) {
+                       case MSG_TYPE_INIT:
+                               if ((nbr->state != NBR_STA_INITIAL) &&
+                                   (nbr->state != NBR_STA_OPENSENT)) {
+                                       session_shutdown(nbr, S_SHUTDOWN,
+                                           msg->id, msg->type);
+                                       free(buf);
+                                       return;
+                               }
+                               break;
+                       case MSG_TYPE_KEEPALIVE:
+                               if ((nbr->state == NBR_STA_INITIAL) ||
+                                   (nbr->state == NBR_STA_OPENSENT)) {
+                                       session_shutdown(nbr, S_SHUTDOWN,
+                                           msg->id, msg->type);
+                                       free(buf);
+                                       return;
+                               }
+                               break;
+                       case MSG_TYPE_ADDR:
+                       case MSG_TYPE_ADDRWITHDRAW:
+                       case MSG_TYPE_LABELMAPPING:
+                       case MSG_TYPE_LABELREQUEST:
+                       case MSG_TYPE_LABELWITHDRAW:
+                       case MSG_TYPE_LABELRELEASE:
+                       case MSG_TYPE_LABELABORTREQ:
+                               if (nbr->state != NBR_STA_OPER) {
+                                       session_shutdown(nbr, S_SHUTDOWN,
+                                           msg->id, msg->type);
+                                       free(buf);
+                                       return;
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+
+                       /* switch LDP packet type */
+                       switch (type) {
+                       case MSG_TYPE_NOTIFICATION:
+                               ret = recv_notification(nbr, pdu, msg_size);
+                               break;
+                       case MSG_TYPE_INIT:
+                               ret = recv_init(nbr, pdu, msg_size);
+                               break;
+                       case MSG_TYPE_KEEPALIVE:
+                               ret = recv_keepalive(nbr, pdu, msg_size);
+                               break;
+                       case MSG_TYPE_ADDR:
+                       case MSG_TYPE_ADDRWITHDRAW:
+                               ret = recv_address(nbr, pdu, msg_size);
+                               break;
+                       case MSG_TYPE_LABELMAPPING:
+                       case MSG_TYPE_LABELREQUEST:
+                       case MSG_TYPE_LABELWITHDRAW:
+                       case MSG_TYPE_LABELRELEASE:
+                       case MSG_TYPE_LABELABORTREQ:
+                               ret = recv_labelmessage(nbr, pdu, msg_size,
+                                   type);
+                               break;
+                       default:
+                               log_debug("%s: unknown LDP message from nbr %s",
+                                   __func__, inet_ntoa(nbr->id));
+                               if (!(ntohs(msg->type) & UNKNOWN_FLAG))
+                                       send_notification_nbr(nbr,
+                                           S_UNKNOWN_MSG, msg->id, msg->type);
+                               /* ignore the message */
+                               ret = 0;
+                               break;
+                       }
+
+                       if (ret == -1) {
+                               /* parser failed, giving up */
+                               free(buf);
+                               return;
+                       }
+
+                       /* Analyse the next message */
+                       pdu += msg_size;
+                       len -= msg_size;
+               }
+               free(buf);
+               if (len != 0) {
+                       session_shutdown(nbr, S_BAD_PDU_LEN, 0, 0);
+                       return;
+               }
+       }
+}
+
+static void
+session_write(int fd, short event, void *arg)
+{
+       struct tcp_conn *tcp = arg;
+       struct nbr      *nbr = tcp->nbr;
+
+       if (!(event & EV_WRITE))
+               return;
+
+       if (msgbuf_write(&tcp->wbuf.wbuf) <= 0)
+               if (errno != EAGAIN && nbr)
+                       nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+
+       if (nbr == NULL && !tcp->wbuf.wbuf.queued) {
+               /*
+                * We are done sending the notification message, now we can
+                * close the socket.
+                */
+               tcp_close(tcp);
+               return;
+       }
+
+       evbuf_event_add(&tcp->wbuf);
+}
+
+void
+session_shutdown(struct nbr *nbr, uint32_t status, uint32_t msg_id,
+    uint32_t msg_type)
+{
+       switch (nbr->state) {
+       case NBR_STA_PRESENT:
+               if (nbr_pending_connect(nbr))
+                       event_del(&nbr->ev_connect);
+               break;
+       case NBR_STA_INITIAL:
+       case NBR_STA_OPENREC:
+       case NBR_STA_OPENSENT:
+       case NBR_STA_OPER:
+               log_debug("%s: lsr-id %s", __func__, inet_ntoa(nbr->id));
+
+               send_notification_nbr(nbr, status, msg_id, msg_type);
+
+               nbr_fsm(nbr, NBR_EVT_CLOSE_SESSION);
+               break;
+       default:
+               fatalx("session_shutdown: unknown neighbor state");
+       }
+}
+
+void
+session_close(struct nbr *nbr)
+{
+       log_debug("%s: closing session with lsr-id %s", __func__,
+           inet_ntoa(nbr->id));
+
+       tcp_close(nbr->tcp);
+       nbr_stop_ktimer(nbr);
+       nbr_stop_ktimeout(nbr);
+       nbr_stop_itimeout(nbr);
+}
+
+static ssize_t
+session_get_pdu(struct ibuf_read *r, char **b)
+{
+       struct ldp_hdr  l;
+       size_t          av, dlen, left;
+
+       av = r->wpos;
+       if (av < sizeof(l))
+               return (0);
+
+       memcpy(&l, r->buf, sizeof(l));
+       dlen = ntohs(l.length) + LDP_HDR_DEAD_LEN;
+       if (dlen > av)
+               return (0);
+
+       if ((*b = malloc(dlen)) == NULL)
+               return (-1);
+
+       memcpy(*b, r->buf, dlen);
+       if (dlen < av) {
+               left = av - dlen;
+               memmove(r->buf, r->buf + dlen, left);
+               r->wpos = left;
+       } else
+               r->wpos = 0;
+
+       return (dlen);
+}
+
+struct tcp_conn *
+tcp_new(int fd, struct nbr *nbr)
+{
+       struct tcp_conn *tcp;
+
+       if ((tcp = calloc(1, sizeof(*tcp))) == NULL)
+               fatal(__func__);
+
+       tcp->fd = fd;
+       evbuf_init(&tcp->wbuf, tcp->fd, session_write, tcp);
+
+       if (nbr) {
+               if ((tcp->rbuf = calloc(1, sizeof(struct ibuf_read))) == NULL)
+                       fatal(__func__);
+
+               event_set(&tcp->rev, tcp->fd, EV_READ | EV_PERSIST,
+                   session_read, nbr);
+               event_add(&tcp->rev, NULL);
+               tcp->nbr = nbr;
+       }
+
+       return (tcp);
+}
+
+static void
+tcp_close(struct tcp_conn *tcp)
+{
+       /* try to flush write buffer */
+       msgbuf_write(&tcp->wbuf.wbuf);
+       evbuf_clear(&tcp->wbuf);
+
+       if (tcp->nbr) {
+               event_del(&tcp->rev);
+               free(tcp->rbuf);
+               tcp->nbr->tcp = NULL;
+       }
+
+       close(tcp->fd);
+       accept_unpause();
+       free(tcp);
+}
+
+static struct pending_conn *
+pending_conn_new(int fd, int af, union ldpd_addr *addr)
+{
+       struct pending_conn     *pconn;
+       struct timeval           tv;
+
+       if ((pconn = calloc(1, sizeof(*pconn))) == NULL)
+               fatal(__func__);
+
+       pconn->fd = fd;
+       pconn->af = af;
+       pconn->addr = *addr;
+       evtimer_set(&pconn->ev_timeout, pending_conn_timeout, pconn);
+       TAILQ_INSERT_TAIL(&global.pending_conns, pconn, entry);
+
+       timerclear(&tv);
+       tv.tv_sec = PENDING_CONN_TIMEOUT;
+       if (evtimer_add(&pconn->ev_timeout, &tv) == -1)
+               fatal(__func__);
+
+       return (pconn);
+}
+
+void
+pending_conn_del(struct pending_conn *pconn)
+{
+       if (evtimer_pending(&pconn->ev_timeout, NULL) &&
+           evtimer_del(&pconn->ev_timeout) == -1)
+               fatal(__func__);
+
+       TAILQ_REMOVE(&global.pending_conns, pconn, entry);
+       free(pconn);
+}
+
+struct pending_conn *
+pending_conn_find(int af, union ldpd_addr *addr)
+{
+       struct pending_conn     *pconn;
+
+       TAILQ_FOREACH(pconn, &global.pending_conns, entry)
+               if (af == pconn->af &&
+                   ldp_addrcmp(af, addr, &pconn->addr) == 0)
+                       return (pconn);
+
+       return (NULL);
+}
+
+static void
+pending_conn_timeout(int fd, short event, void *arg)
+{
+       struct pending_conn     *pconn = arg;
+       struct tcp_conn         *tcp;
+
+       log_debug("%s: no adjacency with remote end: %s", __func__,
+           log_addr(pconn->af, &pconn->addr));
+
+       /*
+        * Create a write buffer detached from any neighbor to send a
+        * notification message reliably.
+        */
+       tcp = tcp_new(pconn->fd, NULL);
+       send_notification(S_NO_HELLO, tcp, 0, 0);
+       msgbuf_write(&tcp->wbuf.wbuf);
+
+       pending_conn_del(pconn);
+}
diff --git a/ldpd/pfkey.c b/ldpd/pfkey.c
new file mode 100644 (file)
index 0000000..f0f16c8
--- /dev/null
@@ -0,0 +1,466 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2003, 2004 Markus Friedl <markus@openbsd.org>
+ *
+ * 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/types.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+static int      pfkey_send(int, uint8_t, uint8_t, uint8_t,
+                   int, union ldpd_addr *, union ldpd_addr *,
+                   uint32_t, uint8_t, int, char *, uint8_t, int, char *,
+                   uint16_t, uint16_t);
+static int      pfkey_reply(int, uint32_t *);
+static int      pfkey_sa_add(int, union ldpd_addr *, union ldpd_addr *,
+                   uint8_t, char *, uint32_t *);
+static int      pfkey_sa_remove(int, union ldpd_addr *, union ldpd_addr *,
+                   uint32_t *);
+static int      pfkey_md5sig_establish(struct nbr *, struct nbr_params *nbrp);
+static int      pfkey_md5sig_remove(struct nbr *);
+
+#define        PFKEY2_CHUNK sizeof(uint64_t)
+#define        ROUNDUP(x) (((x) + (PFKEY2_CHUNK - 1)) & ~(PFKEY2_CHUNK - 1))
+#define        IOV_CNT 20
+
+static uint32_t         sadb_msg_seq;
+static uint32_t         pid; /* should pid_t but pfkey needs uint32_t */
+static int      fd;
+
+static int
+pfkey_send(int sd, uint8_t satype, uint8_t mtype, uint8_t dir,
+    int af, union ldpd_addr *src, union ldpd_addr *dst, uint32_t spi,
+    uint8_t aalg, int alen, char *akey, uint8_t ealg, int elen, char *ekey,
+    uint16_t sport, uint16_t dport)
+{
+       struct sadb_msg         smsg;
+       struct sadb_sa          sa;
+       struct sadb_address     sa_src, sa_dst;
+       struct sadb_key         sa_akey, sa_ekey;
+       struct sadb_spirange    sa_spirange;
+       struct iovec            iov[IOV_CNT];
+       ssize_t                 n;
+       int                     len = 0;
+       int                     iov_cnt;
+       struct sockaddr_storage ssrc, sdst, smask, dmask;
+       struct sockaddr         *saptr;
+
+       if (!pid)
+               pid = getpid();
+
+       /* we need clean sockaddr... no ports set */
+       memset(&ssrc, 0, sizeof(ssrc));
+       memset(&smask, 0, sizeof(smask));
+       if ((saptr = addr2sa(af, src, 0)))
+               memcpy(&ssrc, saptr, sizeof(ssrc));
+       switch (af) {
+       case AF_INET:
+               memset(&((struct sockaddr_in *)&smask)->sin_addr, 0xff, 32/8);
+               break;
+       case AF_INET6:
+               memset(&((struct sockaddr_in6 *)&smask)->sin6_addr, 0xff,
+                   128/8);
+               break;
+       default:
+               return (-1);
+       }
+       smask.ss_family = ssrc.ss_family;
+       smask.ss_len = ssrc.ss_len;
+
+       memset(&sdst, 0, sizeof(sdst));
+       memset(&dmask, 0, sizeof(dmask));
+       if ((saptr = addr2sa(af, dst, 0)))
+               memcpy(&sdst, saptr, sizeof(sdst));
+       switch (af) {
+       case AF_INET:
+               memset(&((struct sockaddr_in *)&dmask)->sin_addr, 0xff, 32/8);
+               break;
+       case AF_INET6:
+               memset(&((struct sockaddr_in6 *)&dmask)->sin6_addr, 0xff,
+                   128/8);
+               break;
+       default:
+               return (-1);
+       }
+       dmask.ss_family = sdst.ss_family;
+       dmask.ss_len = sdst.ss_len;
+
+       memset(&smsg, 0, sizeof(smsg));
+       smsg.sadb_msg_version = PF_KEY_V2;
+       smsg.sadb_msg_seq = ++sadb_msg_seq;
+       smsg.sadb_msg_pid = pid;
+       smsg.sadb_msg_len = sizeof(smsg) / 8;
+       smsg.sadb_msg_type = mtype;
+       smsg.sadb_msg_satype = satype;
+
+       switch (mtype) {
+       case SADB_GETSPI:
+               memset(&sa_spirange, 0, sizeof(sa_spirange));
+               sa_spirange.sadb_spirange_exttype = SADB_EXT_SPIRANGE;
+               sa_spirange.sadb_spirange_len = sizeof(sa_spirange) / 8;
+               sa_spirange.sadb_spirange_min = 0x100;
+               sa_spirange.sadb_spirange_max = 0xffffffff;
+               sa_spirange.sadb_spirange_reserved = 0;
+               break;
+       case SADB_ADD:
+       case SADB_UPDATE:
+       case SADB_DELETE:
+               memset(&sa, 0, sizeof(sa));
+               sa.sadb_sa_exttype = SADB_EXT_SA;
+               sa.sadb_sa_len = sizeof(sa) / 8;
+               sa.sadb_sa_replay = 0;
+               sa.sadb_sa_spi = spi;
+               sa.sadb_sa_state = SADB_SASTATE_MATURE;
+               break;
+       }
+
+       memset(&sa_src, 0, sizeof(sa_src));
+       sa_src.sadb_address_exttype = SADB_EXT_ADDRESS_SRC;
+       sa_src.sadb_address_len = (sizeof(sa_src) + ROUNDUP(ssrc.ss_len)) / 8;
+
+       memset(&sa_dst, 0, sizeof(sa_dst));
+       sa_dst.sadb_address_exttype = SADB_EXT_ADDRESS_DST;
+       sa_dst.sadb_address_len = (sizeof(sa_dst) + ROUNDUP(sdst.ss_len)) / 8;
+
+       sa.sadb_sa_auth = aalg;
+       sa.sadb_sa_encrypt = SADB_X_EALG_AES; /* XXX */
+
+       switch (mtype) {
+       case SADB_ADD:
+       case SADB_UPDATE:
+               memset(&sa_akey, 0, sizeof(sa_akey));
+               sa_akey.sadb_key_exttype = SADB_EXT_KEY_AUTH;
+               sa_akey.sadb_key_len = (sizeof(sa_akey) +
+                   ((alen + 7) / 8) * 8) / 8;
+               sa_akey.sadb_key_bits = 8 * alen;
+
+               memset(&sa_ekey, 0, sizeof(sa_ekey));
+               sa_ekey.sadb_key_exttype = SADB_EXT_KEY_ENCRYPT;
+               sa_ekey.sadb_key_len = (sizeof(sa_ekey) +
+                   ((elen + 7) / 8) * 8) / 8;
+               sa_ekey.sadb_key_bits = 8 * elen;
+
+               break;
+       }
+
+       iov_cnt = 0;
+
+       /* msghdr */
+       iov[iov_cnt].iov_base = &smsg;
+       iov[iov_cnt].iov_len = sizeof(smsg);
+       iov_cnt++;
+
+       switch (mtype) {
+       case SADB_ADD:
+       case SADB_UPDATE:
+       case SADB_DELETE:
+               /* SA hdr */
+               iov[iov_cnt].iov_base = &sa;
+               iov[iov_cnt].iov_len = sizeof(sa);
+               smsg.sadb_msg_len += sa.sadb_sa_len;
+               iov_cnt++;
+               break;
+       case SADB_GETSPI:
+               /* SPI range */
+               iov[iov_cnt].iov_base = &sa_spirange;
+               iov[iov_cnt].iov_len = sizeof(sa_spirange);
+               smsg.sadb_msg_len += sa_spirange.sadb_spirange_len;
+               iov_cnt++;
+               break;
+       }
+
+       /* dest addr */
+       iov[iov_cnt].iov_base = &sa_dst;
+       iov[iov_cnt].iov_len = sizeof(sa_dst);
+       iov_cnt++;
+       iov[iov_cnt].iov_base = &sdst;
+       iov[iov_cnt].iov_len = ROUNDUP(sdst.ss_len);
+       smsg.sadb_msg_len += sa_dst.sadb_address_len;
+       iov_cnt++;
+
+       /* src addr */
+       iov[iov_cnt].iov_base = &sa_src;
+       iov[iov_cnt].iov_len = sizeof(sa_src);
+       iov_cnt++;
+       iov[iov_cnt].iov_base = &ssrc;
+       iov[iov_cnt].iov_len = ROUNDUP(ssrc.ss_len);
+       smsg.sadb_msg_len += sa_src.sadb_address_len;
+       iov_cnt++;
+
+       switch (mtype) {
+       case SADB_ADD:
+       case SADB_UPDATE:
+               if (alen) {
+                       /* auth key */
+                       iov[iov_cnt].iov_base = &sa_akey;
+                       iov[iov_cnt].iov_len = sizeof(sa_akey);
+                       iov_cnt++;
+                       iov[iov_cnt].iov_base = akey;
+                       iov[iov_cnt].iov_len = ((alen + 7) / 8) * 8;
+                       smsg.sadb_msg_len += sa_akey.sadb_key_len;
+                       iov_cnt++;
+               }
+               if (elen) {
+                       /* encryption key */
+                       iov[iov_cnt].iov_base = &sa_ekey;
+                       iov[iov_cnt].iov_len = sizeof(sa_ekey);
+                       iov_cnt++;
+                       iov[iov_cnt].iov_base = ekey;
+                       iov[iov_cnt].iov_len = ((elen + 7) / 8) * 8;
+                       smsg.sadb_msg_len += sa_ekey.sadb_key_len;
+                       iov_cnt++;
+               }
+               break;
+       }
+
+       len = smsg.sadb_msg_len * 8;
+       do {
+               n = writev(sd, iov, iov_cnt);
+       } while (n == -1 && (errno == EAGAIN || errno == EINTR));
+
+       if (n == -1) {
+               log_warn("writev (%d/%d)", iov_cnt, len);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+pfkey_read(int sd, struct sadb_msg *h)
+{
+       struct sadb_msg hdr;
+
+       if (recv(sd, &hdr, sizeof(hdr), MSG_PEEK) != sizeof(hdr)) {
+               if (errno == EAGAIN || errno == EINTR)
+                       return (0);
+               log_warn("pfkey peek");
+               return (-1);
+       }
+
+       /* XXX: Only one message can be outstanding. */
+       if (hdr.sadb_msg_seq == sadb_msg_seq &&
+           hdr.sadb_msg_pid == pid) {
+               if (h)
+                       *h = hdr;
+               return (0);
+       }
+
+       /* not ours, discard */
+       if (read(sd, &hdr, sizeof(hdr)) == -1) {
+               if (errno == EAGAIN || errno == EINTR)
+                       return (0);
+               log_warn("pfkey read");
+               return (-1);
+       }
+
+       return (1);
+}
+
+static int
+pfkey_reply(int sd, uint32_t *spip)
+{
+       struct sadb_msg hdr, *msg;
+       struct sadb_ext *ext;
+       struct sadb_sa *sa;
+       uint8_t *data;
+       ssize_t len;
+       int rv;
+
+       do {
+               rv = pfkey_read(sd, &hdr);
+               if (rv == -1)
+                       return (-1);
+       } while (rv);
+
+       if (hdr.sadb_msg_errno != 0) {
+               errno = hdr.sadb_msg_errno;
+               if (errno == ESRCH)
+                       return (0);
+               else {
+                       log_warn("pfkey");
+                       return (-1);
+               }
+       }
+       if ((data = reallocarray(NULL, hdr.sadb_msg_len, PFKEY2_CHUNK)) == NULL) {
+               log_warn("pfkey malloc");
+               return (-1);
+       }
+       len = hdr.sadb_msg_len * PFKEY2_CHUNK;
+       if (read(sd, data, len) != len) {
+               log_warn("pfkey read");
+               explicit_bzero(data, len);
+               free(data);
+               return (-1);
+       }
+
+       if (hdr.sadb_msg_type == SADB_GETSPI) {
+               if (spip == NULL) {
+                       explicit_bzero(data, len);
+                       free(data);
+                       return (0);
+               }
+
+               msg = (struct sadb_msg *)data;
+               for (ext = (struct sadb_ext *)(msg + 1);
+                   (size_t)((uint8_t *)ext - (uint8_t *)msg) <
+                   msg->sadb_msg_len * PFKEY2_CHUNK;
+                   ext = (struct sadb_ext *)((uint8_t *)ext +
+                   ext->sadb_ext_len * PFKEY2_CHUNK)) {
+                       if (ext->sadb_ext_type == SADB_EXT_SA) {
+                               sa = (struct sadb_sa *) ext;
+                               *spip = sa->sadb_sa_spi;
+                               break;
+                       }
+               }
+       }
+       explicit_bzero(data, len);
+       free(data);
+       return (0);
+}
+
+static int
+pfkey_sa_add(int af, union ldpd_addr *src, union ldpd_addr *dst, uint8_t keylen,
+    char *key, uint32_t *spi)
+{
+       if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_GETSPI, 0,
+           af, src, dst, 0, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0)
+               return (-1);
+       if (pfkey_reply(fd, spi) < 0)
+               return (-1);
+       if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_UPDATE, 0,
+           af, src, dst, *spi, 0, keylen, key, 0, 0, NULL, 0, 0) < 0)
+               return (-1);
+       if (pfkey_reply(fd, NULL) < 0)
+               return (-1);
+       return (0);
+}
+
+static int
+pfkey_sa_remove(int af, union ldpd_addr *src, union ldpd_addr *dst,
+    uint32_t *spi)
+{
+       if (pfkey_send(fd, SADB_X_SATYPE_TCPSIGNATURE, SADB_DELETE, 0,
+           af, src, dst, *spi, 0, 0, NULL, 0, 0, NULL, 0, 0) < 0)
+               return (-1);
+       if (pfkey_reply(fd, NULL) < 0)
+               return (-1);
+       *spi = 0;
+       return (0);
+}
+
+static int
+pfkey_md5sig_establish(struct nbr *nbr, struct nbr_params *nbrp)
+{
+       sleep(1);
+
+       if (!nbr->auth.spi_out)
+               if (pfkey_sa_add(nbr->af, &nbr->laddr, &nbr->raddr,
+                   nbrp->auth.md5key_len, nbrp->auth.md5key,
+                   &nbr->auth.spi_out) == -1)
+                       return (-1);
+       if (!nbr->auth.spi_in)
+               if (pfkey_sa_add(nbr->af, &nbr->raddr, &nbr->laddr,
+                   nbrp->auth.md5key_len, nbrp->auth.md5key,
+                   &nbr->auth.spi_in) == -1)
+                       return (-1);
+
+       nbr->auth.established = 1;
+       return (0);
+}
+
+static int
+pfkey_md5sig_remove(struct nbr *nbr)
+{
+       if (nbr->auth.spi_out)
+               if (pfkey_sa_remove(nbr->af, &nbr->laddr, &nbr->raddr,
+                   &nbr->auth.spi_out) == -1)
+                       return (-1);
+       if (nbr->auth.spi_in)
+               if (pfkey_sa_remove(nbr->af, &nbr->raddr, &nbr->laddr,
+                   &nbr->auth.spi_in) == -1)
+                       return (-1);
+
+       nbr->auth.established = 0;
+       nbr->auth.spi_in = 0;
+       nbr->auth.spi_out = 0;
+       nbr->auth.method = AUTH_NONE;
+       memset(nbr->auth.md5key, 0, sizeof(nbr->auth.md5key));
+
+       return (0);
+}
+
+int
+pfkey_establish(struct nbr *nbr, struct nbr_params *nbrp)
+{
+       if (nbrp->auth.method == AUTH_NONE)
+               return (0);
+
+       /*
+        * make sure we keep copies of everything we need to
+        * remove SAs and flows later again.
+        */
+       nbr->auth.method = nbrp->auth.method;
+
+       switch (nbr->auth.method) {
+       case AUTH_MD5SIG:
+               strlcpy(nbr->auth.md5key, nbrp->auth.md5key,
+                   sizeof(nbr->auth.md5key));
+               return (pfkey_md5sig_establish(nbr, nbrp));
+       default:
+               break;
+       }
+
+       return (0);
+}
+
+int
+pfkey_remove(struct nbr *nbr)
+{
+       if (nbr->auth.method == AUTH_NONE || !nbr->auth.established)
+               return (0);
+
+       switch (nbr->auth.method) {
+       case AUTH_MD5SIG:
+               return (pfkey_md5sig_remove(nbr));
+       default:
+               break;
+       }
+
+       return (0);
+}
+
+int
+pfkey_init(void)
+{
+       if ((fd = socket(PF_KEY, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
+           PF_KEY_V2)) == -1) {
+               if (errno == EPROTONOSUPPORT) {
+                       log_warnx("PF_KEY not available");
+                       sysdep.no_pfkey = 1;
+                       return (-1);
+               } else
+                       fatal("pfkey setup failed");
+       }
+       return (fd);
+}
diff --git a/ldpd/socket.c b/ldpd/socket.c
new file mode 100644 (file)
index 0000000..8f26771
--- /dev/null
@@ -0,0 +1,391 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2016 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2009 Michele Marchetto <michele@openbsd.org>
+ * Copyright (c) 2005 Claudio Jeker <claudio@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "ldpd.h"
+#include "ldpe.h"
+#include "log.h"
+
+int
+ldp_create_socket(int af, enum socket_type type)
+{
+       int                      fd, domain, proto;
+       union ldpd_addr          addr;
+       struct sockaddr_storage  local_sa;
+       int                      opt;
+
+       /* create socket */
+       switch (type) {
+       case LDP_SOCKET_DISC:
+       case LDP_SOCKET_EDISC:
+               domain = SOCK_DGRAM;
+               proto = IPPROTO_UDP;
+               break;
+       case LDP_SOCKET_SESSION:
+               domain = SOCK_STREAM;
+               proto = IPPROTO_TCP;
+               break;
+       default:
+               fatalx("ldp_create_socket: unknown socket type");
+       }
+       fd = socket(af, domain | SOCK_NONBLOCK | SOCK_CLOEXEC, proto);
+       if (fd == -1) {
+               log_warn("%s: error creating socket", __func__);
+               return (-1);
+       }
+
+       /* bind to a local address/port */
+       switch (type) {
+       case LDP_SOCKET_DISC:
+               /* listen on all addresses */
+               memset(&addr, 0, sizeof(addr));
+               memcpy(&local_sa, addr2sa(af, &addr, LDP_PORT),
+                   sizeof(local_sa));
+               break;
+       case LDP_SOCKET_EDISC:
+       case LDP_SOCKET_SESSION:
+               addr = (ldp_af_conf_get(ldpd_conf, af))->trans_addr;
+               memcpy(&local_sa, addr2sa(af, &addr, LDP_PORT),
+                   sizeof(local_sa));
+               if (sock_set_bindany(fd, 1) == -1) {
+                       close(fd);
+                       return (-1);
+               }
+               break;
+       }
+       if (sock_set_reuse(fd, 1) == -1) {
+               close(fd);
+               return (-1);
+       }
+       if (bind(fd, (struct sockaddr *)&local_sa, local_sa.ss_len) == -1) {
+               log_warn("%s: error binding socket", __func__);
+               close(fd);
+               return (-1);
+       }
+
+       /* set options */
+       switch (af) {
+       case AF_INET:
+               if (sock_set_ipv4_tos(fd, IPTOS_PREC_INTERNETCONTROL) == -1) {
+                       close(fd);
+                       return (-1);
+               }
+               if (type == LDP_SOCKET_DISC) {
+                       if (sock_set_ipv4_mcast_ttl(fd,
+                           IP_DEFAULT_MULTICAST_TTL) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+                       if (sock_set_ipv4_mcast_loop(fd) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+               }
+               if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) {
+                       if (sock_set_ipv4_recvif(fd, 1) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+               }
+               if (type == LDP_SOCKET_SESSION) {
+                       if (sock_set_ipv4_ucast_ttl(fd, 255) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+               }
+               break;
+       case AF_INET6:
+               if (sock_set_ipv6_dscp(fd, IPTOS_PREC_INTERNETCONTROL) == -1) {
+                       close(fd);
+                       return (-1);
+               }
+               if (type == LDP_SOCKET_DISC) {
+                       if (sock_set_ipv6_mcast_loop(fd) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+                       if (sock_set_ipv6_mcast_hops(fd, 255) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+                       if (!(ldpd_conf->ipv6.flags & F_LDPD_AF_NO_GTSM)) {
+                               if (sock_set_ipv6_minhopcount(fd, 255) == -1) {
+                                       close(fd);
+                                       return (-1);
+                               }
+                       }
+               }
+               if (type == LDP_SOCKET_DISC || type == LDP_SOCKET_EDISC) {
+                       if (sock_set_ipv6_pktinfo(fd, 1) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+               }
+               if (type == LDP_SOCKET_SESSION) {
+                       if (sock_set_ipv6_ucast_hops(fd, 255) == -1) {
+                               close(fd);
+                               return (-1);
+                       }
+               }
+               break;
+       }
+       switch (type) {
+       case LDP_SOCKET_DISC:
+       case LDP_SOCKET_EDISC:
+               sock_set_recvbuf(fd);
+               break;
+       case LDP_SOCKET_SESSION:
+               if (listen(fd, LDP_BACKLOG) == -1)
+                       log_warn("%s: error listening on socket", __func__);
+
+               opt = 1;
+               if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &opt,
+                   sizeof(opt)) == -1) {
+                       if (errno == ENOPROTOOPT) {     /* system w/o md5sig */
+                               log_warnx("md5sig not available, disabling");
+                               sysdep.no_md5sig = 1;
+                       } else {
+                               close(fd);
+                               return (-1);
+                       }
+               }
+               break;
+       }
+
+       return (fd);
+}
+
+void
+sock_set_recvbuf(int fd)
+{
+       int     bsize;
+
+       bsize = 65535;
+       while (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bsize,
+           sizeof(bsize)) == -1)
+               bsize /= 2;
+}
+
+int
+sock_set_reuse(int fd, int enable)
+{
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable,
+           sizeof(int)) < 0) {
+               log_warn("%s: error setting SO_REUSEADDR", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_bindany(int fd, int enable)
+{
+       if (setsockopt(fd, SOL_SOCKET, SO_BINDANY, &enable,
+           sizeof(int)) < 0) {
+               log_warn("%s: error setting SO_BINDANY", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv4_tos(int fd, int tos)
+{
+       if (setsockopt(fd, IPPROTO_IP, IP_TOS, (int *)&tos, sizeof(tos)) < 0) {
+               log_warn("%s: error setting IP_TOS to 0x%x", __func__, tos);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv4_recvif(int fd, int enable)
+{
+       if (setsockopt(fd, IPPROTO_IP, IP_RECVIF, &enable,
+           sizeof(enable)) < 0) {
+               log_warn("%s: error setting IP_RECVIF", __func__);
+               return (-1);
+       }
+       return (0);
+}
+
+int
+sock_set_ipv4_minttl(int fd, int ttl)
+{
+       if (setsockopt(fd, IPPROTO_IP, IP_MINTTL, &ttl, sizeof(ttl)) < 0) {
+               log_warn("%s: error setting IP_MINTTL", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv4_ucast_ttl(int fd, int ttl)
+{
+       if (setsockopt(fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) {
+               log_warn("%s: error setting IP_TTL", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv4_mcast_ttl(int fd, uint8_t ttl)
+{
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
+           (char *)&ttl, sizeof(ttl)) < 0) {
+               log_warn("%s: error setting IP_MULTICAST_TTL to %d",
+                   __func__, ttl);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv4_mcast(struct iface *iface)
+{
+       in_addr_t                addr;
+
+       addr = if_get_ipv4_addr(iface);
+
+       if (setsockopt(global.ipv4.ldp_disc_socket, IPPROTO_IP, IP_MULTICAST_IF,
+           &addr, sizeof(addr)) < 0) {
+               log_warn("%s: error setting IP_MULTICAST_IF, interface %s",
+                   __func__, iface->name);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv4_mcast_loop(int fd)
+{
+       uint8_t loop = 0;
+
+       if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+           (char *)&loop, sizeof(loop)) < 0) {
+               log_warn("%s: error setting IP_MULTICAST_LOOP", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv6_dscp(int fd, int dscp)
+{
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &dscp,
+           sizeof(dscp)) < 0) {
+               log_warn("%s: error setting IPV6_TCLASS", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv6_pktinfo(int fd, int enable)
+{
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enable,
+           sizeof(enable)) < 0) {
+               log_warn("%s: error setting IPV6_RECVPKTINFO", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv6_minhopcount(int fd, int hoplimit)
+{
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MINHOPCOUNT,
+           &hoplimit, sizeof(hoplimit)) < 0) {
+               log_warn("%s: error setting IPV6_MINHOPCOUNT", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv6_ucast_hops(int fd, int hoplimit)
+{
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
+           &hoplimit, sizeof(hoplimit)) < 0) {
+               log_warn("%s: error setting IPV6_UNICAST_HOPS", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv6_mcast_hops(int fd, int hoplimit)
+{
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+           &hoplimit, sizeof(hoplimit)) < 0) {
+               log_warn("%s: error setting IPV6_MULTICAST_HOPS", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv6_mcast(struct iface *iface)
+{
+       if (setsockopt(global.ipv6.ldp_disc_socket, IPPROTO_IPV6,
+           IPV6_MULTICAST_IF, &iface->ifindex, sizeof(iface->ifindex)) < 0) {
+               log_warn("%s: error setting IPV6_MULTICAST_IF, interface %s",
+                   __func__, iface->name);
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+sock_set_ipv6_mcast_loop(int fd)
+{
+       unsigned int    loop = 0;
+
+       if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+           &loop, sizeof(loop)) < 0) {
+               log_warn("%s: error setting IPV6_MULTICAST_LOOP", __func__);
+               return (-1);
+       }
+
+       return (0);
+}
diff --git a/ldpd/util.c b/ldpd/util.c
new file mode 100644 (file)
index 0000000..981a5c8
--- /dev/null
@@ -0,0 +1,356 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2015 Renato Westphal <renato@openbsd.org>
+ * Copyright (c) 2012 Alexander Bluhm <bluhm@openbsd.org>
+ * Copyright (c) 2004 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <string.h>
+
+#include "ldpd.h"
+#include "log.h"
+
+uint8_t
+mask2prefixlen(in_addr_t ina)
+{
+       if (ina == 0)
+               return (0);
+       else
+               return (33 - ffs(ntohl(ina)));
+}
+
+uint8_t
+mask2prefixlen6(struct sockaddr_in6 *sa_in6)
+{
+       uint8_t l = 0, *ap, *ep;
+
+       /*
+        * sin6_len is the size of the sockaddr so substract the offset of
+        * the possibly truncated sin6_addr struct.
+        */
+       ap = (uint8_t *)&sa_in6->sin6_addr;
+       ep = (uint8_t *)sa_in6 + sa_in6->sin6_len;
+       for (; ap < ep; ap++) {
+               /* this "beauty" is adopted from sbin/route/show.c ... */
+               switch (*ap) {
+               case 0xff:
+                       l += 8;
+                       break;
+               case 0xfe:
+                       l += 7;
+                       return (l);
+               case 0xfc:
+                       l += 6;
+                       return (l);
+               case 0xf8:
+                       l += 5;
+                       return (l);
+               case 0xf0:
+                       l += 4;
+                       return (l);
+               case 0xe0:
+                       l += 3;
+                       return (l);
+               case 0xc0:
+                       l += 2;
+                       return (l);
+               case 0x80:
+                       l += 1;
+                       return (l);
+               case 0x00:
+                       return (l);
+               default:
+                       fatalx("non contiguous inet6 netmask");
+               }
+       }
+
+       return (l);
+}
+
+in_addr_t
+prefixlen2mask(uint8_t prefixlen)
+{
+       if (prefixlen == 0)
+               return (0);
+
+       return (htonl(0xffffffff << (32 - prefixlen)));
+}
+
+struct in6_addr *
+prefixlen2mask6(uint8_t prefixlen)
+{
+       static struct in6_addr  mask;
+       int                     i;
+
+       memset(&mask, 0, sizeof(mask));
+       for (i = 0; i < prefixlen / 8; i++)
+               mask.s6_addr[i] = 0xff;
+       i = prefixlen % 8;
+       if (i)
+               mask.s6_addr[prefixlen / 8] = 0xff00 >> i;
+
+       return (&mask);
+}
+
+void
+ldp_applymask(int af, union ldpd_addr *dest, const union ldpd_addr *src,
+    int prefixlen)
+{
+       struct in6_addr mask;
+       int             i;
+
+       switch (af) {
+       case AF_INET:
+               dest->v4.s_addr = src->v4.s_addr & prefixlen2mask(prefixlen);
+               break;
+       case AF_INET6:
+               memset(&mask, 0, sizeof(mask));
+               for (i = 0; i < prefixlen / 8; i++)
+                       mask.s6_addr[i] = 0xff;
+               i = prefixlen % 8;
+               if (i)
+                       mask.s6_addr[prefixlen / 8] = 0xff00 >> i;
+
+               for (i = 0; i < 16; i++)
+                       dest->v6.s6_addr[i] = src->v6.s6_addr[i] &
+                           mask.s6_addr[i];
+               break;
+       default:
+               fatalx("ldp_applymask: unknown af");
+       }
+}
+
+int
+ldp_addrcmp(int af, const union ldpd_addr *a, const union ldpd_addr *b)
+{
+       switch (af) {
+       case AF_INET:
+               if (a->v4.s_addr == b->v4.s_addr)
+                       return (0);
+               return ((ntohl(a->v4.s_addr) > ntohl(b->v4.s_addr)) ? 1 : -1);
+       case AF_INET6:
+               return (memcmp(&a->v6, &b->v6, sizeof(struct in6_addr)));
+       default:
+               fatalx("ldp_addrcmp: unknown af");
+       }
+}
+
+int
+ldp_addrisset(int af, const union ldpd_addr *addr)
+{
+       switch (af) {
+       case AF_UNSPEC:
+               return (0);
+       case AF_INET:
+               if (addr->v4.s_addr != INADDR_ANY)
+                       return (1);
+               break;
+       case AF_INET6:
+               if (!IN6_IS_ADDR_UNSPECIFIED(&addr->v6))
+                       return (1);
+               break;
+       default:
+               fatalx("ldp_addrisset: unknown af");
+       }
+
+       return (0);
+}
+
+int
+ldp_prefixcmp(int af, const union ldpd_addr *a, const union ldpd_addr *b,
+    uint8_t prefixlen)
+{
+       in_addr_t       mask, aa, ba;
+       int             i;
+       uint8_t         m;
+
+       switch (af) {
+       case AF_INET:
+               if (prefixlen == 0)
+                       return (0);
+               if (prefixlen > 32)
+                       fatalx("ldp_prefixcmp: bad IPv4 prefixlen");
+               mask = htonl(prefixlen2mask(prefixlen));
+               aa = htonl(a->v4.s_addr) & mask;
+               ba = htonl(b->v4.s_addr) & mask;
+               return (aa - ba);
+       case AF_INET6:
+               if (prefixlen == 0)
+                       return (0);
+               if (prefixlen > 128)
+                       fatalx("ldp_prefixcmp: bad IPv6 prefixlen");
+               for (i = 0; i < prefixlen / 8; i++)
+                       if (a->v6.s6_addr[i] != b->v6.s6_addr[i])
+                               return (a->v6.s6_addr[i] - b->v6.s6_addr[i]);
+               i = prefixlen % 8;
+               if (i) {
+                       m = 0xff00 >> i;
+                       if ((a->v6.s6_addr[prefixlen / 8] & m) !=
+                           (b->v6.s6_addr[prefixlen / 8] & m))
+                               return ((a->v6.s6_addr[prefixlen / 8] & m) -
+                                   (b->v6.s6_addr[prefixlen / 8] & m));
+               }
+               return (0);
+       default:
+               fatalx("ldp_prefixcmp: unknown af");
+       }
+       return (-1);
+}
+
+int
+bad_addr_v4(struct in_addr addr)
+{
+       uint32_t         a = ntohl(addr.s_addr);
+
+       if (((a >> IN_CLASSA_NSHIFT) == 0) ||
+           ((a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET) ||
+           IN_MULTICAST(a) || IN_BADCLASS(a))
+               return (1);
+
+       return (0);
+}
+
+int
+bad_addr_v6(struct in6_addr *addr)
+{
+       if (IN6_IS_ADDR_UNSPECIFIED(addr) ||
+           IN6_IS_ADDR_LOOPBACK(addr) ||
+           IN6_IS_ADDR_MULTICAST(addr) ||
+           IN6_IS_ADDR_SITELOCAL(addr) ||
+           IN6_IS_ADDR_V4MAPPED(addr) ||
+           IN6_IS_ADDR_V4COMPAT(addr))
+               return (1);
+
+       return (0);
+}
+
+int
+bad_addr(int af, union ldpd_addr *addr)
+{
+       switch (af) {
+       case AF_INET:
+               return (bad_addr_v4(addr->v4));
+       case AF_INET6:
+               return (bad_addr_v6(&addr->v6));
+       default:
+               fatalx("bad_addr: unknown af");
+       }
+}
+
+void
+embedscope(struct sockaddr_in6 *sin6)
+{
+       uint16_t         tmp16;
+
+       if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) {
+               memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16));
+               if (tmp16 != 0) {
+                       log_warnx("%s: address %s already has embeded scope %u",
+                           __func__, log_sockaddr(sin6), ntohs(tmp16));
+               }
+               tmp16 = htons(sin6->sin6_scope_id);
+               memcpy(&sin6->sin6_addr.s6_addr[2], &tmp16, sizeof(tmp16));
+               sin6->sin6_scope_id = 0;
+       }
+}
+
+void
+recoverscope(struct sockaddr_in6 *sin6)
+{
+       uint16_t         tmp16;
+
+       if (sin6->sin6_scope_id != 0)
+               log_warnx("%s: address %s already has scope id %u",
+                   __func__, log_sockaddr(sin6), sin6->sin6_scope_id);
+
+       if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr)) {
+               memcpy(&tmp16, &sin6->sin6_addr.s6_addr[2], sizeof(tmp16));
+               sin6->sin6_scope_id = ntohs(tmp16);
+               sin6->sin6_addr.s6_addr[2] = 0;
+               sin6->sin6_addr.s6_addr[3] = 0;
+       }
+}
+
+void
+addscope(struct sockaddr_in6 *sin6, uint32_t id)
+{
+       if (sin6->sin6_scope_id != 0)
+               log_warnx("%s: address %s already has scope id %u", __func__,
+                   log_sockaddr(sin6), sin6->sin6_scope_id);
+
+       if (IN6_IS_SCOPE_EMBED(&sin6->sin6_addr))
+               sin6->sin6_scope_id = id;
+}
+
+void
+clearscope(struct in6_addr *in6)
+{
+       if (IN6_IS_SCOPE_EMBED(in6)) {
+               in6->s6_addr[2] = 0;
+               in6->s6_addr[3] = 0;
+       }
+}
+
+struct sockaddr *
+addr2sa(int af, union ldpd_addr *addr, uint16_t port)
+{
+       static struct sockaddr_storage   ss;
+       struct sockaddr_in              *sa_in = (struct sockaddr_in *)&ss;
+       struct sockaddr_in6             *sa_in6 = (struct sockaddr_in6 *)&ss;
+
+       memset(&ss, 0, sizeof(ss));
+       switch (af) {
+       case AF_INET:
+               sa_in->sin_family = AF_INET;
+               sa_in->sin_len = sizeof(struct sockaddr_in);
+               sa_in->sin_addr = addr->v4;
+               sa_in->sin_port = htons(port);
+               break;
+       case AF_INET6:
+               sa_in6->sin6_family = AF_INET6;
+               sa_in6->sin6_len = sizeof(struct sockaddr_in6);
+               sa_in6->sin6_addr = addr->v6;
+               sa_in6->sin6_port = htons(port);
+               break;
+       default:
+               fatalx("addr2sa: unknown af");
+       }
+
+       return ((struct sockaddr *)&ss);
+}
+
+void
+sa2addr(struct sockaddr *sa, int *af, union ldpd_addr *addr)
+{
+       struct sockaddr_in              *sa_in = (struct sockaddr_in *)sa;
+       struct sockaddr_in6             *sa_in6 = (struct sockaddr_in6 *)sa;
+
+       memset(addr, 0, sizeof(*addr));
+       switch (sa->sa_family) {
+       case AF_INET:
+               *af = AF_INET;
+               addr->v4 = sa_in->sin_addr;
+               break;
+       case AF_INET6:
+               *af = AF_INET6;
+               addr->v6 = sa_in6->sin6_addr;
+               break;
+       default:
+               fatalx("sa2addr: unknown af");
+       }
+}
diff --git a/lib/imsg-buffer.c b/lib/imsg-buffer.c
new file mode 100644 (file)
index 0000000..61b2c09
--- /dev/null
@@ -0,0 +1,309 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "imsg.h"
+
+int    ibuf_realloc(struct ibuf *, size_t);
+void   ibuf_enqueue(struct msgbuf *, struct ibuf *);
+void   ibuf_dequeue(struct msgbuf *, struct ibuf *);
+
+struct ibuf *
+ibuf_open(size_t len)
+{
+       struct ibuf     *buf;
+
+       if ((buf = calloc(1, sizeof(struct ibuf))) == NULL)
+               return (NULL);
+       if ((buf->buf = malloc(len)) == NULL) {
+               free(buf);
+               return (NULL);
+       }
+       buf->size = buf->max = len;
+       buf->fd = -1;
+
+       return (buf);
+}
+
+struct ibuf *
+ibuf_dynamic(size_t len, size_t max)
+{
+       struct ibuf     *buf;
+
+       if (max < len)
+               return (NULL);
+
+       if ((buf = ibuf_open(len)) == NULL)
+               return (NULL);
+
+       if (max > 0)
+               buf->max = max;
+
+       return (buf);
+}
+
+int
+ibuf_realloc(struct ibuf *buf, size_t len)
+{
+       u_char  *b;
+
+       /* on static buffers max is eq size and so the following fails */
+       if (buf->wpos + len > buf->max) {
+               errno = ERANGE;
+               return (-1);
+       }
+
+       b = realloc(buf->buf, buf->wpos + len);
+       if (b == NULL)
+               return (-1);
+       buf->buf = b;
+       buf->size = buf->wpos + len;
+
+       return (0);
+}
+
+int
+ibuf_add(struct ibuf *buf, const void *data, size_t len)
+{
+       if (buf->wpos + len > buf->size)
+               if (ibuf_realloc(buf, len) == -1)
+                       return (-1);
+
+       memcpy(buf->buf + buf->wpos, data, len);
+       buf->wpos += len;
+       return (0);
+}
+
+void *
+ibuf_reserve(struct ibuf *buf, size_t len)
+{
+       void    *b;
+
+       if (buf->wpos + len > buf->size)
+               if (ibuf_realloc(buf, len) == -1)
+                       return (NULL);
+
+       b = buf->buf + buf->wpos;
+       buf->wpos += len;
+       return (b);
+}
+
+void *
+ibuf_seek(struct ibuf *buf, size_t pos, size_t len)
+{
+       /* only allowed to seek in already written parts */
+       if (pos + len > buf->wpos)
+               return (NULL);
+
+       return (buf->buf + pos);
+}
+
+size_t
+ibuf_size(struct ibuf *buf)
+{
+       return (buf->wpos);
+}
+
+size_t
+ibuf_left(struct ibuf *buf)
+{
+       return (buf->max - buf->wpos);
+}
+
+void
+ibuf_close(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+       ibuf_enqueue(msgbuf, buf);
+}
+
+int
+ibuf_write(struct msgbuf *msgbuf)
+{
+       struct iovec     iov[IOV_MAX];
+       struct ibuf     *buf;
+       unsigned int     i = 0;
+       ssize_t n;
+
+       memset(&iov, 0, sizeof(iov));
+       TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
+               if (i >= IOV_MAX)
+                       break;
+               iov[i].iov_base = buf->buf + buf->rpos;
+               iov[i].iov_len = buf->wpos - buf->rpos;
+               i++;
+       }
+
+again:
+       if ((n = writev(msgbuf->fd, iov, i)) == -1) {
+               if (errno == EINTR)
+                       goto again;
+               if (errno == ENOBUFS)
+                       errno = EAGAIN;
+               return (-1);
+       }
+
+       if (n == 0) {                   /* connection closed */
+               errno = 0;
+               return (0);
+       }
+
+       msgbuf_drain(msgbuf, n);
+
+       return (1);
+}
+
+void
+ibuf_free(struct ibuf *buf)
+{
+       if (buf == NULL)
+               return;
+       free(buf->buf);
+       free(buf);
+}
+
+void
+msgbuf_init(struct msgbuf *msgbuf)
+{
+       msgbuf->queued = 0;
+       msgbuf->fd = -1;
+       TAILQ_INIT(&msgbuf->bufs);
+}
+
+void
+msgbuf_drain(struct msgbuf *msgbuf, size_t n)
+{
+       struct ibuf     *buf, *next;
+
+       for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0;
+           buf = next) {
+               next = TAILQ_NEXT(buf, entry);
+               if (buf->rpos + n >= buf->wpos) {
+                       n -= buf->wpos - buf->rpos;
+                       ibuf_dequeue(msgbuf, buf);
+               } else {
+                       buf->rpos += n;
+                       n = 0;
+               }
+       }
+}
+
+void
+msgbuf_clear(struct msgbuf *msgbuf)
+{
+       struct ibuf     *buf;
+
+       while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL)
+               ibuf_dequeue(msgbuf, buf);
+}
+
+int
+msgbuf_write(struct msgbuf *msgbuf)
+{
+       struct iovec     iov[IOV_MAX];
+       struct ibuf     *buf;
+       unsigned int     i = 0;
+       ssize_t          n;
+       struct msghdr    msg;
+       struct cmsghdr  *cmsg;
+       union {
+               struct cmsghdr  hdr;
+               char            buf[CMSG_SPACE(sizeof(int))];
+       } cmsgbuf;
+
+       memset(&iov, 0, sizeof(iov));
+       memset(&msg, 0, sizeof(msg));
+       memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+       TAILQ_FOREACH(buf, &msgbuf->bufs, entry) {
+               if (i >= IOV_MAX)
+                       break;
+               iov[i].iov_base = buf->buf + buf->rpos;
+               iov[i].iov_len = buf->wpos - buf->rpos;
+               i++;
+               if (buf->fd != -1)
+                       break;
+       }
+
+       msg.msg_iov = iov;
+       msg.msg_iovlen = i;
+
+       if (buf != NULL && buf->fd != -1) {
+               msg.msg_control = (caddr_t)&cmsgbuf.buf;
+               msg.msg_controllen = sizeof(cmsgbuf.buf);
+               cmsg = CMSG_FIRSTHDR(&msg);
+               cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+               cmsg->cmsg_level = SOL_SOCKET;
+               cmsg->cmsg_type = SCM_RIGHTS;
+               *(int *)CMSG_DATA(cmsg) = buf->fd;
+       }
+
+again:
+       if ((n = sendmsg(msgbuf->fd, &msg, 0)) == -1) {
+               if (errno == EINTR)
+                       goto again;
+               if (errno == ENOBUFS)
+                       errno = EAGAIN;
+               return (-1);
+       }
+
+       if (n == 0) {                   /* connection closed */
+               errno = 0;
+               return (0);
+       }
+
+       /*
+        * assumption: fd got sent if sendmsg sent anything
+        * this works because fds are passed one at a time
+        */
+       if (buf != NULL && buf->fd != -1) {
+               close(buf->fd);
+               buf->fd = -1;
+       }
+
+       msgbuf_drain(msgbuf, n);
+
+       return (1);
+}
+
+void
+ibuf_enqueue(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+       TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entry);
+       msgbuf->queued++;
+}
+
+void
+ibuf_dequeue(struct msgbuf *msgbuf, struct ibuf *buf)
+{
+       TAILQ_REMOVE(&msgbuf->bufs, buf, entry);
+
+       if (buf->fd != -1)
+               close(buf->fd);
+
+       msgbuf->queued--;
+       ibuf_free(buf);
+}
diff --git a/lib/imsg.c b/lib/imsg.c
new file mode 100644 (file)
index 0000000..0c1cb82
--- /dev/null
@@ -0,0 +1,302 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "imsg.h"
+
+int     imsg_fd_overhead = 0;
+
+int     imsg_get_fd(struct imsgbuf *);
+
+void
+imsg_init(struct imsgbuf *ibuf, int fd)
+{
+       msgbuf_init(&ibuf->w);
+       memset(&ibuf->r, 0, sizeof(ibuf->r));
+       ibuf->fd = fd;
+       ibuf->w.fd = fd;
+       ibuf->pid = getpid();
+       TAILQ_INIT(&ibuf->fds);
+}
+
+ssize_t
+imsg_read(struct imsgbuf *ibuf)
+{
+       struct msghdr            msg;
+       struct cmsghdr          *cmsg;
+       union {
+               struct cmsghdr hdr;
+               char    buf[CMSG_SPACE(sizeof(int) * 1)];
+       } cmsgbuf;
+       struct iovec             iov;
+       ssize_t                  n = -1;
+       int                      fd;
+       struct imsg_fd          *ifd;
+
+       memset(&msg, 0, sizeof(msg));
+       memset(&cmsgbuf, 0, sizeof(cmsgbuf));
+
+       iov.iov_base = ibuf->r.buf + ibuf->r.wpos;
+       iov.iov_len = sizeof(ibuf->r.buf) - ibuf->r.wpos;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = &cmsgbuf.buf;
+       msg.msg_controllen = sizeof(cmsgbuf.buf);
+
+       if ((ifd = calloc(1, sizeof(struct imsg_fd))) == NULL)
+               return (-1);
+
+again:
+       if (getdtablecount() + imsg_fd_overhead +
+           (int)((CMSG_SPACE(sizeof(int))-CMSG_SPACE(0))/sizeof(int))
+           >= getdtablesize()) {
+               errno = EAGAIN;
+               free(ifd);
+               return (-1);
+       }
+
+       if ((n = recvmsg(ibuf->fd, &msg, 0)) == -1) {
+               if (errno == EINTR)
+                       goto again;
+               goto fail;
+       }
+
+       ibuf->r.wpos += n;
+
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+           cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               if (cmsg->cmsg_level == SOL_SOCKET &&
+                   cmsg->cmsg_type == SCM_RIGHTS) {
+                       int i;
+                       int j;
+
+                       /*
+                        * We only accept one file descriptor.  Due to C
+                        * padding rules, our control buffer might contain
+                        * more than one fd, and we must close them.
+                        */
+                       j = ((char *)cmsg + cmsg->cmsg_len -
+                           (char *)CMSG_DATA(cmsg)) / sizeof(int);
+                       for (i = 0; i < j; i++) {
+                               fd = ((int *)CMSG_DATA(cmsg))[i];
+                               if (ifd != NULL) {
+                                       ifd->fd = fd;
+                                       TAILQ_INSERT_TAIL(&ibuf->fds, ifd,
+                                           entry);
+                                       ifd = NULL;
+                               } else
+                                       close(fd);
+                       }
+               }
+               /* we do not handle other ctl data level */
+       }
+
+fail:
+       free(ifd);
+       return (n);
+}
+
+ssize_t
+imsg_get(struct imsgbuf *ibuf, struct imsg *imsg)
+{
+       size_t                   av, left, datalen;
+
+       av = ibuf->r.wpos;
+
+       if (IMSG_HEADER_SIZE > av)
+               return (0);
+
+       memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr));
+       if (imsg->hdr.len < IMSG_HEADER_SIZE ||
+           imsg->hdr.len > MAX_IMSGSIZE) {
+               errno = ERANGE;
+               return (-1);
+       }
+       if (imsg->hdr.len > av)
+               return (0);
+       datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+       ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE;
+       if (datalen == 0)
+               imsg->data = NULL;
+       else if ((imsg->data = malloc(datalen)) == NULL)
+               return (-1);
+
+       if (imsg->hdr.flags & IMSGF_HASFD)
+               imsg->fd = imsg_get_fd(ibuf);
+       else
+               imsg->fd = -1;
+
+       memcpy(imsg->data, ibuf->r.rptr, datalen);
+
+       if (imsg->hdr.len < av) {
+               left = av - imsg->hdr.len;
+               memmove(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left);
+               ibuf->r.wpos = left;
+       } else
+               ibuf->r.wpos = 0;
+
+       return (datalen + IMSG_HEADER_SIZE);
+}
+
+int
+imsg_compose(struct imsgbuf *ibuf, u_int32_t type, u_int32_t peerid,
+    pid_t pid, int fd, const void *data, u_int16_t datalen)
+{
+       struct ibuf     *wbuf;
+
+       if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+               return (-1);
+
+       if (imsg_add(wbuf, data, datalen) == -1)
+               return (-1);
+
+       wbuf->fd = fd;
+
+       imsg_close(ibuf, wbuf);
+
+       return (1);
+}
+
+int
+imsg_composev(struct imsgbuf *ibuf, u_int32_t type, u_int32_t peerid,
+    pid_t pid, int fd, const struct iovec *iov, int iovcnt)
+{
+       struct ibuf     *wbuf;
+       int              i, datalen = 0;
+
+       for (i = 0; i < iovcnt; i++)
+               datalen += iov[i].iov_len;
+
+       if ((wbuf = imsg_create(ibuf, type, peerid, pid, datalen)) == NULL)
+               return (-1);
+
+       for (i = 0; i < iovcnt; i++)
+               if (imsg_add(wbuf, iov[i].iov_base, iov[i].iov_len) == -1)
+                       return (-1);
+
+       wbuf->fd = fd;
+
+       imsg_close(ibuf, wbuf);
+
+       return (1);
+}
+
+/* ARGSUSED */
+struct ibuf *
+imsg_create(struct imsgbuf *ibuf, u_int32_t type, u_int32_t peerid,
+    pid_t pid, u_int16_t datalen)
+{
+       struct ibuf     *wbuf;
+       struct imsg_hdr  hdr;
+
+       datalen += IMSG_HEADER_SIZE;
+       if (datalen > MAX_IMSGSIZE) {
+               errno = ERANGE;
+               return (NULL);
+       }
+
+       hdr.type = type;
+       hdr.flags = 0;
+       hdr.peerid = peerid;
+       if ((hdr.pid = pid) == 0)
+               hdr.pid = ibuf->pid;
+       if ((wbuf = ibuf_dynamic(datalen, MAX_IMSGSIZE)) == NULL) {
+               return (NULL);
+       }
+       if (imsg_add(wbuf, &hdr, sizeof(hdr)) == -1)
+               return (NULL);
+
+       return (wbuf);
+}
+
+int
+imsg_add(struct ibuf *msg, const void *data, u_int16_t datalen)
+{
+       if (datalen)
+               if (ibuf_add(msg, data, datalen) == -1) {
+                       ibuf_free(msg);
+                       return (-1);
+               }
+       return (datalen);
+}
+
+void
+imsg_close(struct imsgbuf *ibuf, struct ibuf *msg)
+{
+       struct imsg_hdr *hdr;
+
+       hdr = (struct imsg_hdr *)msg->buf;
+
+       hdr->flags &= ~IMSGF_HASFD;
+       if (msg->fd != -1)
+               hdr->flags |= IMSGF_HASFD;
+
+       hdr->len = (u_int16_t)msg->wpos;
+
+       ibuf_close(&ibuf->w, msg);
+}
+
+void
+imsg_free(struct imsg *imsg)
+{
+       free(imsg->data);
+}
+
+int
+imsg_get_fd(struct imsgbuf *ibuf)
+{
+       int              fd;
+       struct imsg_fd  *ifd;
+
+       if ((ifd = TAILQ_FIRST(&ibuf->fds)) == NULL)
+               return (-1);
+
+       fd = ifd->fd;
+       TAILQ_REMOVE(&ibuf->fds, ifd, entry);
+       free(ifd);
+
+       return (fd);
+}
+
+int
+imsg_flush(struct imsgbuf *ibuf)
+{
+       while (ibuf->w.queued)
+               if (msgbuf_write(&ibuf->w) <= 0)
+                       return (-1);
+       return (0);
+}
+
+void
+imsg_clear(struct imsgbuf *ibuf)
+{
+       int     fd;
+
+       msgbuf_clear(&ibuf->w);
+       while ((fd = imsg_get_fd(ibuf)) != -1)
+               close(fd);
+}
diff --git a/lib/imsg.h b/lib/imsg.h
new file mode 100644 (file)
index 0000000..d053d01
--- /dev/null
@@ -0,0 +1,112 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2006, 2007, 2008 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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.
+ */
+
+#ifndef _IMSG_H_
+#define _IMSG_H_
+
+#define IBUF_READ_SIZE         65535
+#define IMSG_HEADER_SIZE       sizeof(struct imsg_hdr)
+#define MAX_IMSGSIZE           16384
+
+struct ibuf {
+       TAILQ_ENTRY(ibuf)        entry;
+       u_char                  *buf;
+       size_t                   size;
+       size_t                   max;
+       size_t                   wpos;
+       size_t                   rpos;
+       int                      fd;
+};
+
+struct msgbuf {
+       TAILQ_HEAD(, ibuf)       bufs;
+       u_int32_t                queued;
+       int                      fd;
+};
+
+struct ibuf_read {
+       u_char                   buf[IBUF_READ_SIZE];
+       u_char                  *rptr;
+       size_t                   wpos;
+};
+
+struct imsg_fd {
+       TAILQ_ENTRY(imsg_fd)    entry;
+       int                     fd;
+};
+
+struct imsgbuf {
+       TAILQ_HEAD(, imsg_fd)    fds;
+       struct ibuf_read         r;
+       struct msgbuf            w;
+       int                      fd;
+       pid_t                    pid;
+};
+
+#define IMSGF_HASFD    1
+
+struct imsg_hdr {
+       u_int32_t        type;
+       u_int16_t        len;
+       u_int16_t        flags;
+       u_int32_t        peerid;
+       u_int32_t        pid;
+};
+
+struct imsg {
+       struct imsg_hdr  hdr;
+       int              fd;
+       void            *data;
+};
+
+
+/* buffer.c */
+struct ibuf    *ibuf_open(size_t);
+struct ibuf    *ibuf_dynamic(size_t, size_t);
+int             ibuf_add(struct ibuf *, const void *, size_t);
+void           *ibuf_reserve(struct ibuf *, size_t);
+void           *ibuf_seek(struct ibuf *, size_t, size_t);
+size_t          ibuf_size(struct ibuf *);
+size_t          ibuf_left(struct ibuf *);
+void            ibuf_close(struct msgbuf *, struct ibuf *);
+int             ibuf_write(struct msgbuf *);
+void            ibuf_free(struct ibuf *);
+void            msgbuf_init(struct msgbuf *);
+void            msgbuf_clear(struct msgbuf *);
+int             msgbuf_write(struct msgbuf *);
+void            msgbuf_drain(struct msgbuf *, size_t);
+
+/* imsg.c */
+void    imsg_init(struct imsgbuf *, int);
+ssize_t         imsg_read(struct imsgbuf *);
+ssize_t         imsg_get(struct imsgbuf *, struct imsg *);
+int     imsg_compose(struct imsgbuf *, u_int32_t, u_int32_t, pid_t,
+           int, const void *, u_int16_t);
+int     imsg_composev(struct imsgbuf *, u_int32_t, u_int32_t,  pid_t,
+           int, const struct iovec *, int);
+struct ibuf *imsg_create(struct imsgbuf *, u_int32_t, u_int32_t, pid_t,
+           u_int16_t);
+int     imsg_add(struct ibuf *, const void *, u_int16_t);
+void    imsg_close(struct imsgbuf *, struct ibuf *);
+void    imsg_free(struct imsg *);
+int     imsg_flush(struct imsgbuf *);
+void    imsg_clear(struct imsgbuf *);
+
+#endif
diff --git a/lib/openbsd-queue.h b/lib/openbsd-queue.h
new file mode 100644 (file)
index 0000000..5e81fdd
--- /dev/null
@@ -0,0 +1,533 @@
+/*     $OpenBSD: queue.h,v 1.43 2015/12/28 19:38:40 millert Exp $      */
+/*     $NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $       */
+
+/*
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)queue.h     8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef        _SYS_QUEUE_H_
+#define        _SYS_QUEUE_H_
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues and XOR simple queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction.  Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * An XOR simple queue is used in the same way as a regular simple queue.
+ * The difference is that the head structure also includes a "cookie" that
+ * is XOR'd with the queue pointer (first, last or next) to generate the
+ * real pointer value.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
+#define _Q_INVALIDATE(a) (a) = ((void *)-1)
+#else
+#define _Q_INVALIDATE(a)
+#endif
+
+/*
+ * Singly-linked List definitions.
+ */
+#define SLIST_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *slh_first; /* first element */                     \
+}
+
+#define        SLIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define SLIST_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *sle_next;  /* next element */                      \
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define        SLIST_FIRST(head)       ((head)->slh_first)
+#define        SLIST_END(head)         NULL
+#define        SLIST_EMPTY(head)       (SLIST_FIRST(head) == SLIST_END(head))
+#define        SLIST_NEXT(elm, field)  ((elm)->field.sle_next)
+
+#define        SLIST_FOREACH(var, head, field)                                 \
+       for((var) = SLIST_FIRST(head);                                  \
+           (var) != SLIST_END(head);                                   \
+           (var) = SLIST_NEXT(var, field))
+
+#define        SLIST_FOREACH_SAFE(var, head, field, tvar)                      \
+       for ((var) = SLIST_FIRST(head);                         \
+           (var) && ((tvar) = SLIST_NEXT(var, field), 1);              \
+           (var) = (tvar))
+
+/*
+ * Singly-linked List functions.
+ */
+#define        SLIST_INIT(head) {                                              \
+       SLIST_FIRST(head) = SLIST_END(head);                            \
+}
+
+#define        SLIST_INSERT_AFTER(slistelm, elm, field) do {                   \
+       (elm)->field.sle_next = (slistelm)->field.sle_next;             \
+       (slistelm)->field.sle_next = (elm);                             \
+} while (0)
+
+#define        SLIST_INSERT_HEAD(head, elm, field) do {                        \
+       (elm)->field.sle_next = (head)->slh_first;                      \
+       (head)->slh_first = (elm);                                      \
+} while (0)
+
+#define        SLIST_REMOVE_AFTER(elm, field) do {                             \
+       (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;  \
+} while (0)
+
+#define        SLIST_REMOVE_HEAD(head, field) do {                             \
+       (head)->slh_first = (head)->slh_first->field.sle_next;          \
+} while (0)
+
+#define SLIST_REMOVE(head, elm, type, field) do {                      \
+       if ((head)->slh_first == (elm)) {                               \
+               SLIST_REMOVE_HEAD((head), field);                       \
+       } else {                                                        \
+               struct type *curelm = (head)->slh_first;                \
+                                                                       \
+               while (curelm->field.sle_next != (elm))                 \
+                       curelm = curelm->field.sle_next;                \
+               curelm->field.sle_next =                                \
+                   curelm->field.sle_next->field.sle_next;             \
+       }                                                               \
+       _Q_INVALIDATE((elm)->field.sle_next);                           \
+} while (0)
+
+/*
+ * List definitions.
+ */
+#define LIST_HEAD(name, type)                                          \
+struct name {                                                          \
+       struct type *lh_first;  /* first element */                     \
+}
+
+#define LIST_HEAD_INITIALIZER(head)                                    \
+       { NULL }
+
+#define LIST_ENTRY(type)                                               \
+struct {                                                               \
+       struct type *le_next;   /* next element */                      \
+       struct type **le_prev;  /* address of previous next element */  \
+}
+
+/*
+ * List access methods.
+ */
+#define        LIST_FIRST(head)                ((head)->lh_first)
+#define        LIST_END(head)                  NULL
+#define        LIST_EMPTY(head)                (LIST_FIRST(head) == LIST_END(head))
+#define        LIST_NEXT(elm, field)           ((elm)->field.le_next)
+
+#define LIST_FOREACH(var, head, field)                                 \
+       for((var) = LIST_FIRST(head);                                   \
+           (var)!= LIST_END(head);                                     \
+           (var) = LIST_NEXT(var, field))
+
+#define        LIST_FOREACH_SAFE(var, head, field, tvar)                       \
+       for ((var) = LIST_FIRST(head);                          \
+           (var) && ((tvar) = LIST_NEXT(var, field), 1);               \
+           (var) = (tvar))
+
+/*
+ * List functions.
+ */
+#define        LIST_INIT(head) do {                                            \
+       LIST_FIRST(head) = LIST_END(head);                              \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do {                    \
+       if (((elm)->field.le_next = (listelm)->field.le_next) != NULL)  \
+               (listelm)->field.le_next->field.le_prev =               \
+                   &(elm)->field.le_next;                              \
+       (listelm)->field.le_next = (elm);                               \
+       (elm)->field.le_prev = &(listelm)->field.le_next;               \
+} while (0)
+
+#define        LIST_INSERT_BEFORE(listelm, elm, field) do {                    \
+       (elm)->field.le_prev = (listelm)->field.le_prev;                \
+       (elm)->field.le_next = (listelm);                               \
+       *(listelm)->field.le_prev = (elm);                              \
+       (listelm)->field.le_prev = &(elm)->field.le_next;               \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do {                                \
+       if (((elm)->field.le_next = (head)->lh_first) != NULL)          \
+               (head)->lh_first->field.le_prev = &(elm)->field.le_next;\
+       (head)->lh_first = (elm);                                       \
+       (elm)->field.le_prev = &(head)->lh_first;                       \
+} while (0)
+
+#define LIST_REMOVE(elm, field) do {                                   \
+       if ((elm)->field.le_next != NULL)                               \
+               (elm)->field.le_next->field.le_prev =                   \
+                   (elm)->field.le_prev;                               \
+       *(elm)->field.le_prev = (elm)->field.le_next;                   \
+       _Q_INVALIDATE((elm)->field.le_prev);                            \
+       _Q_INVALIDATE((elm)->field.le_next);                            \
+} while (0)
+
+#define LIST_REPLACE(elm, elm2, field) do {                            \
+       if (((elm2)->field.le_next = (elm)->field.le_next) != NULL)     \
+               (elm2)->field.le_next->field.le_prev =                  \
+                   &(elm2)->field.le_next;                             \
+       (elm2)->field.le_prev = (elm)->field.le_prev;                   \
+       *(elm2)->field.le_prev = (elm2);                                \
+       _Q_INVALIDATE((elm)->field.le_prev);                            \
+       _Q_INVALIDATE((elm)->field.le_next);                            \
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define SIMPLEQ_HEAD(name, type)                                       \
+struct name {                                                          \
+       struct type *sqh_first; /* first element */                     \
+       struct type **sqh_last; /* addr of last next element */         \
+}
+
+#define SIMPLEQ_HEAD_INITIALIZER(head)                                 \
+       { NULL, &(head).sqh_first }
+
+#define SIMPLEQ_ENTRY(type)                                            \
+struct {                                                               \
+       struct type *sqe_next;  /* next element */                      \
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define        SIMPLEQ_FIRST(head)         ((head)->sqh_first)
+#define        SIMPLEQ_END(head)           NULL
+#define        SIMPLEQ_EMPTY(head)         (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define        SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next)
+
+#define SIMPLEQ_FOREACH(var, head, field)                              \
+       for((var) = SIMPLEQ_FIRST(head);                                \
+           (var) != SIMPLEQ_END(head);                                 \
+           (var) = SIMPLEQ_NEXT(var, field))
+
+#define        SIMPLEQ_FOREACH_SAFE(var, head, field, tvar)                    \
+       for ((var) = SIMPLEQ_FIRST(head);                               \
+           (var) && ((tvar) = SIMPLEQ_NEXT(var, field), 1);            \
+           (var) = (tvar))
+
+/*
+ * Simple queue functions.
+ */
+#define        SIMPLEQ_INIT(head) do {                                         \
+       (head)->sqh_first = NULL;                                       \
+       (head)->sqh_last = &(head)->sqh_first;                          \
+} while (0)
+
+#define SIMPLEQ_INSERT_HEAD(head, elm, field) do {                     \
+       if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)        \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (head)->sqh_first = (elm);                                      \
+} while (0)
+
+#define SIMPLEQ_INSERT_TAIL(head, elm, field) do {                     \
+       (elm)->field.sqe_next = NULL;                                   \
+       *(head)->sqh_last = (elm);                                      \
+       (head)->sqh_last = &(elm)->field.sqe_next;                      \
+} while (0)
+
+#define SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {           \
+       if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+       (listelm)->field.sqe_next = (elm);                              \
+} while (0)
+
+#define SIMPLEQ_REMOVE_HEAD(head, field) do {                  \
+       if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+               (head)->sqh_last = &(head)->sqh_first;                  \
+} while (0)
+
+#define SIMPLEQ_REMOVE_AFTER(head, elm, field) do {                    \
+       if (((elm)->field.sqe_next = (elm)->field.sqe_next->field.sqe_next) \
+           == NULL)                                                    \
+               (head)->sqh_last = &(elm)->field.sqe_next;              \
+} while (0)
+
+#define SIMPLEQ_CONCAT(head1, head2) do {                              \
+       if (!SIMPLEQ_EMPTY((head2))) {                                  \
+               *(head1)->sqh_last = (head2)->sqh_first;                \
+               (head1)->sqh_last = (head2)->sqh_last;                  \
+               SIMPLEQ_INIT((head2));                                  \
+       }                                                               \
+} while (0)
+
+/*
+ * XOR Simple queue definitions.
+ */
+#define XSIMPLEQ_HEAD(name, type)                                      \
+struct name {                                                          \
+       struct type *sqx_first; /* first element */                     \
+       struct type **sqx_last; /* addr of last next element */         \
+       unsigned long sqx_cookie;                                       \
+}
+
+#define XSIMPLEQ_ENTRY(type)                                           \
+struct {                                                               \
+       struct type *sqx_next;  /* next element */                      \
+}
+
+/*
+ * XOR Simple queue access methods.
+ */
+#define XSIMPLEQ_XOR(head, ptr)            ((__typeof(ptr))((head)->sqx_cookie ^ \
+                                       (unsigned long)(ptr)))
+#define        XSIMPLEQ_FIRST(head)        XSIMPLEQ_XOR(head, ((head)->sqx_first))
+#define        XSIMPLEQ_END(head)          NULL
+#define        XSIMPLEQ_EMPTY(head)        (XSIMPLEQ_FIRST(head) == XSIMPLEQ_END(head))
+#define        XSIMPLEQ_NEXT(head, elm, field)    XSIMPLEQ_XOR(head, ((elm)->field.sqx_next))
+
+
+#define XSIMPLEQ_FOREACH(var, head, field)                             \
+       for ((var) = XSIMPLEQ_FIRST(head);                              \
+           (var) != XSIMPLEQ_END(head);                                \
+           (var) = XSIMPLEQ_NEXT(head, var, field))
+
+#define        XSIMPLEQ_FOREACH_SAFE(var, head, field, tvar)                   \
+       for ((var) = XSIMPLEQ_FIRST(head);                              \
+           (var) && ((tvar) = XSIMPLEQ_NEXT(head, var, field), 1);     \
+           (var) = (tvar))
+
+/*
+ * XOR Simple queue functions.
+ */
+#define        XSIMPLEQ_INIT(head) do {                                        \
+       arc4random_buf(&(head)->sqx_cookie, sizeof((head)->sqx_cookie)); \
+       (head)->sqx_first = XSIMPLEQ_XOR(head, NULL);                   \
+       (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first);      \
+} while (0)
+
+#define XSIMPLEQ_INSERT_HEAD(head, elm, field) do {                    \
+       if (((elm)->field.sqx_next = (head)->sqx_first) ==              \
+           XSIMPLEQ_XOR(head, NULL))                                   \
+               (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+       (head)->sqx_first = XSIMPLEQ_XOR(head, (elm));                  \
+} while (0)
+
+#define XSIMPLEQ_INSERT_TAIL(head, elm, field) do {                    \
+       (elm)->field.sqx_next = XSIMPLEQ_XOR(head, NULL);               \
+       *(XSIMPLEQ_XOR(head, (head)->sqx_last)) = XSIMPLEQ_XOR(head, (elm)); \
+       (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next);  \
+} while (0)
+
+#define XSIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {          \
+       if (((elm)->field.sqx_next = (listelm)->field.sqx_next) ==      \
+           XSIMPLEQ_XOR(head, NULL))                                   \
+               (head)->sqx_last = XSIMPLEQ_XOR(head, &(elm)->field.sqx_next); \
+       (listelm)->field.sqx_next = XSIMPLEQ_XOR(head, (elm));          \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_HEAD(head, field) do {                         \
+       if (((head)->sqx_first = XSIMPLEQ_XOR(head,                     \
+           (head)->sqx_first)->field.sqx_next) == XSIMPLEQ_XOR(head, NULL)) \
+               (head)->sqx_last = XSIMPLEQ_XOR(head, &(head)->sqx_first); \
+} while (0)
+
+#define XSIMPLEQ_REMOVE_AFTER(head, elm, field) do {                   \
+       if (((elm)->field.sqx_next = XSIMPLEQ_XOR(head,                 \
+           (elm)->field.sqx_next)->field.sqx_next)                     \
+           == XSIMPLEQ_XOR(head, NULL))                                \
+               (head)->sqx_last =                                      \
+                   XSIMPLEQ_XOR(head, &(elm)->field.sqx_next);         \
+} while (0)
+
+
+/*
+ * Tail queue definitions.
+ */
+#define TAILQ_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *tqh_first; /* first element */                     \
+       struct type **tqh_last; /* addr of last next element */         \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head)                                   \
+       { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *tqe_next;  /* next element */                      \
+       struct type **tqe_prev; /* address of previous next element */  \
+}
+
+/*
+ * Tail queue access methods.
+ */
+#define        TAILQ_FIRST(head)               ((head)->tqh_first)
+#define        TAILQ_END(head)                 NULL
+#define        TAILQ_NEXT(elm, field)          ((elm)->field.tqe_next)
+#define TAILQ_LAST(head, headname)                                     \
+       (*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define TAILQ_PREV(elm, headname, field)                               \
+       (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define        TAILQ_EMPTY(head)                                               \
+       (TAILQ_FIRST(head) == TAILQ_END(head))
+
+#define TAILQ_FOREACH(var, head, field)                                        \
+       for((var) = TAILQ_FIRST(head);                                  \
+           (var) != TAILQ_END(head);                                   \
+           (var) = TAILQ_NEXT(var, field))
+
+#define        TAILQ_FOREACH_SAFE(var, head, field, tvar)                      \
+       for ((var) = TAILQ_FIRST(head);                                 \
+           (var) != TAILQ_END(head) &&                                 \
+           ((tvar) = TAILQ_NEXT(var, field), 1);                       \
+           (var) = (tvar))
+
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field)              \
+       for((var) = TAILQ_LAST(head, headname);                         \
+           (var) != TAILQ_END(head);                                   \
+           (var) = TAILQ_PREV(var, headname, field))
+
+#define        TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)    \
+       for ((var) = TAILQ_LAST(head, headname);                        \
+           (var) != TAILQ_END(head) &&                                 \
+           ((tvar) = TAILQ_PREV(var, headname, field), 1);             \
+           (var) = (tvar))
+
+/*
+ * Tail queue functions.
+ */
+#define        TAILQ_INIT(head) do {                                           \
+       (head)->tqh_first = NULL;                                       \
+       (head)->tqh_last = &(head)->tqh_first;                          \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do {                       \
+       if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)        \
+               (head)->tqh_first->field.tqe_prev =                     \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (head)->tqh_first = (elm);                                      \
+       (elm)->field.tqe_prev = &(head)->tqh_first;                     \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do {                       \
+       (elm)->field.tqe_next = NULL;                                   \
+       (elm)->field.tqe_prev = (head)->tqh_last;                       \
+       *(head)->tqh_last = (elm);                                      \
+       (head)->tqh_last = &(elm)->field.tqe_next;                      \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do {             \
+       if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   &(elm)->field.tqe_next;                             \
+       else                                                            \
+               (head)->tqh_last = &(elm)->field.tqe_next;              \
+       (listelm)->field.tqe_next = (elm);                              \
+       (elm)->field.tqe_prev = &(listelm)->field.tqe_next;             \
+} while (0)
+
+#define        TAILQ_INSERT_BEFORE(listelm, elm, field) do {                   \
+       (elm)->field.tqe_prev = (listelm)->field.tqe_prev;              \
+       (elm)->field.tqe_next = (listelm);                              \
+       *(listelm)->field.tqe_prev = (elm);                             \
+       (listelm)->field.tqe_prev = &(elm)->field.tqe_next;             \
+} while (0)
+
+#define TAILQ_REMOVE(head, elm, field) do {                            \
+       if (((elm)->field.tqe_next) != NULL)                            \
+               (elm)->field.tqe_next->field.tqe_prev =                 \
+                   (elm)->field.tqe_prev;                              \
+       else                                                            \
+               (head)->tqh_last = (elm)->field.tqe_prev;               \
+       *(elm)->field.tqe_prev = (elm)->field.tqe_next;                 \
+       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.tqe_next);                           \
+} while (0)
+
+#define TAILQ_REPLACE(head, elm, elm2, field) do {                     \
+       if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)   \
+               (elm2)->field.tqe_next->field.tqe_prev =                \
+                   &(elm2)->field.tqe_next;                            \
+       else                                                            \
+               (head)->tqh_last = &(elm2)->field.tqe_next;             \
+       (elm2)->field.tqe_prev = (elm)->field.tqe_prev;                 \
+       *(elm2)->field.tqe_prev = (elm2);                               \
+       _Q_INVALIDATE((elm)->field.tqe_prev);                           \
+       _Q_INVALIDATE((elm)->field.tqe_next);                           \
+} while (0)
+
+#define TAILQ_CONCAT(head1, head2, field) do {                         \
+       if (!TAILQ_EMPTY(head2)) {                                      \
+               *(head1)->tqh_last = (head2)->tqh_first;                \
+               (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+               (head1)->tqh_last = (head2)->tqh_last;                  \
+               TAILQ_INIT((head2));                                    \
+       }                                                               \
+} while (0)
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/lib/openbsd-tree.h b/lib/openbsd-tree.h
new file mode 100644 (file)
index 0000000..e6502b1
--- /dev/null
@@ -0,0 +1,748 @@
+/*     $OpenBSD: tree.h,v 1.14 2015/05/25 03:07:49 deraadt Exp $       */
+/*
+ * Copyright 2002 Niels Provos <provos@citi.umich.edu>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef        _SYS_TREE_H_
+#define        _SYS_TREE_H_
+
+/*
+ * This file defines data structures for different types of trees:
+ * splay trees and red-black trees.
+ *
+ * A splay tree is a self-organizing data structure.  Every operation
+ * on the tree causes a splay to happen.  The splay moves the requested
+ * node to the root of the tree and partly rebalances it.
+ *
+ * This has the benefit that request locality causes faster lookups as
+ * the requested nodes move to the top of the tree.  On the other hand,
+ * every lookup causes memory writes.
+ *
+ * The Balance Theorem bounds the total access time for m operations
+ * and n inserts on an initially empty tree as O((m + n)lg n).  The
+ * amortized cost for a sequence of m accesses to a splay tree is O(lg n);
+ *
+ * A red-black tree is a binary search tree with the node color as an
+ * extra attribute.  It fulfills a set of conditions:
+ *     - every search path from the root to a leaf consists of the
+ *       same number of black nodes,
+ *     - each red node (except for the root) has a black parent,
+ *     - each leaf node is black.
+ *
+ * Every operation on a red-black tree is bounded as O(lg n).
+ * The maximum height of a red-black tree is 2lg (n+1).
+ */
+
+#define SPLAY_HEAD(name, type)                                         \
+struct name {                                                          \
+       struct type *sph_root; /* root of the tree */                   \
+}
+
+#define SPLAY_INITIALIZER(root)                                                \
+       { NULL }
+
+#define SPLAY_INIT(root) do {                                          \
+       (root)->sph_root = NULL;                                        \
+} while (0)
+
+#define SPLAY_ENTRY(type)                                              \
+struct {                                                               \
+       struct type *spe_left; /* left element */                       \
+       struct type *spe_right; /* right element */                     \
+}
+
+#define SPLAY_LEFT(elm, field)         (elm)->field.spe_left
+#define SPLAY_RIGHT(elm, field)                (elm)->field.spe_right
+#define SPLAY_ROOT(head)               (head)->sph_root
+#define SPLAY_EMPTY(head)              (SPLAY_ROOT(head) == NULL)
+
+/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
+#define SPLAY_ROTATE_RIGHT(head, tmp, field) do {                      \
+       SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field);  \
+       SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \
+       (head)->sph_root = tmp;                                         \
+} while (0)
+
+#define SPLAY_ROTATE_LEFT(head, tmp, field) do {                       \
+       SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field);  \
+       SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \
+       (head)->sph_root = tmp;                                         \
+} while (0)
+
+#define SPLAY_LINKLEFT(head, tmp, field) do {                          \
+       SPLAY_LEFT(tmp, field) = (head)->sph_root;                      \
+       tmp = (head)->sph_root;                                         \
+       (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);         \
+} while (0)
+
+#define SPLAY_LINKRIGHT(head, tmp, field) do {                         \
+       SPLAY_RIGHT(tmp, field) = (head)->sph_root;                     \
+       tmp = (head)->sph_root;                                         \
+       (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);        \
+} while (0)
+
+#define SPLAY_ASSEMBLE(head, node, left, right, field) do {            \
+       SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
+       SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
+       SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
+       SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
+} while (0)
+
+/* Generates prototypes and inline functions */
+
+#define SPLAY_PROTOTYPE(name, type, field, cmp)                                \
+void name##_SPLAY(struct name *, struct type *);                       \
+void name##_SPLAY_MINMAX(struct name *, int);                          \
+struct type *name##_SPLAY_INSERT(struct name *, struct type *);                \
+struct type *name##_SPLAY_REMOVE(struct name *, struct type *);                \
+                                                                       \
+/* Finds the node with the same key as elm */                          \
+static __inline struct type *                                          \
+name##_SPLAY_FIND(struct name *head, struct type *elm)                 \
+{                                                                      \
+       if (SPLAY_EMPTY(head))                                          \
+               return(NULL);                                           \
+       name##_SPLAY(head, elm);                                        \
+       if ((cmp)(elm, (head)->sph_root) == 0)                          \
+               return (head->sph_root);                                \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+static __inline struct type *                                          \
+name##_SPLAY_NEXT(struct name *head, struct type *elm)                 \
+{                                                                      \
+       name##_SPLAY(head, elm);                                        \
+       if (SPLAY_RIGHT(elm, field) != NULL) {                          \
+               elm = SPLAY_RIGHT(elm, field);                          \
+               while (SPLAY_LEFT(elm, field) != NULL) {                \
+                       elm = SPLAY_LEFT(elm, field);                   \
+               }                                                       \
+       } else                                                          \
+               elm = NULL;                                             \
+       return (elm);                                                   \
+}                                                                      \
+                                                                       \
+static __inline struct type *                                          \
+name##_SPLAY_MIN_MAX(struct name *head, int val)                       \
+{                                                                      \
+       name##_SPLAY_MINMAX(head, val);                                 \
+        return (SPLAY_ROOT(head));                                     \
+}
+
+/* Main splay operation.
+ * Moves node close to the key of elm to top
+ */
+#define SPLAY_GENERATE(name, type, field, cmp)                         \
+struct type *                                                          \
+name##_SPLAY_INSERT(struct name *head, struct type *elm)               \
+{                                                                      \
+    if (SPLAY_EMPTY(head)) {                                           \
+           SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL;    \
+    } else {                                                           \
+           int __comp;                                                 \
+           name##_SPLAY(head, elm);                                    \
+           __comp = (cmp)(elm, (head)->sph_root);                      \
+           if(__comp < 0) {                                            \
+                   SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
+                   SPLAY_RIGHT(elm, field) = (head)->sph_root;         \
+                   SPLAY_LEFT((head)->sph_root, field) = NULL;         \
+           } else if (__comp > 0) {                                    \
+                   SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
+                   SPLAY_LEFT(elm, field) = (head)->sph_root;          \
+                   SPLAY_RIGHT((head)->sph_root, field) = NULL;        \
+           } else                                                      \
+                   return ((head)->sph_root);                          \
+    }                                                                  \
+    (head)->sph_root = (elm);                                          \
+    return (NULL);                                                     \
+}                                                                      \
+                                                                       \
+struct type *                                                          \
+name##_SPLAY_REMOVE(struct name *head, struct type *elm)               \
+{                                                                      \
+       struct type *__tmp;                                             \
+       if (SPLAY_EMPTY(head))                                          \
+               return (NULL);                                          \
+       name##_SPLAY(head, elm);                                        \
+       if ((cmp)(elm, (head)->sph_root) == 0) {                        \
+               if (SPLAY_LEFT((head)->sph_root, field) == NULL) {      \
+                       (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
+               } else {                                                \
+                       __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                       (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
+                       name##_SPLAY(head, elm);                        \
+                       SPLAY_RIGHT((head)->sph_root, field) = __tmp;   \
+               }                                                       \
+               return (elm);                                           \
+       }                                                               \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+void                                                                   \
+name##_SPLAY(struct name *head, struct type *elm)                      \
+{                                                                      \
+       struct type __node, *__left, *__right, *__tmp;                  \
+       int __comp;                                                     \
+\
+       SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+       __left = __right = &__node;                                     \
+\
+       while ((__comp = (cmp)(elm, (head)->sph_root))) {               \
+               if (__comp < 0) {                                       \
+                       __tmp = SPLAY_LEFT((head)->sph_root, field);    \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if ((cmp)(elm, __tmp) < 0){                     \
+                               SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+                               if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKLEFT(head, __right, field);           \
+               } else if (__comp > 0) {                                \
+                       __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if ((cmp)(elm, __tmp) > 0){                     \
+                               SPLAY_ROTATE_LEFT(head, __tmp, field);  \
+                               if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKRIGHT(head, __left, field);           \
+               }                                                       \
+       }                                                               \
+       SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \
+}                                                                      \
+                                                                       \
+/* Splay with either the minimum or the maximum element                        \
+ * Used to find minimum or maximum element in tree.                    \
+ */                                                                    \
+void name##_SPLAY_MINMAX(struct name *head, int __comp) \
+{                                                                      \
+       struct type __node, *__left, *__right, *__tmp;                  \
+\
+       SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
+       __left = __right = &__node;                                     \
+\
+       while (1) {                                                     \
+               if (__comp < 0) {                                       \
+                       __tmp = SPLAY_LEFT((head)->sph_root, field);    \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if (__comp < 0){                                \
+                               SPLAY_ROTATE_RIGHT(head, __tmp, field); \
+                               if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKLEFT(head, __right, field);           \
+               } else if (__comp > 0) {                                \
+                       __tmp = SPLAY_RIGHT((head)->sph_root, field);   \
+                       if (__tmp == NULL)                              \
+                               break;                                  \
+                       if (__comp > 0) {                               \
+                               SPLAY_ROTATE_LEFT(head, __tmp, field);  \
+                               if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
+                                       break;                          \
+                       }                                               \
+                       SPLAY_LINKRIGHT(head, __left, field);           \
+               }                                                       \
+       }                                                               \
+       SPLAY_ASSEMBLE(head, &__node, __left, __right, field);          \
+}
+
+#define SPLAY_NEGINF   -1
+#define SPLAY_INF      1
+
+#define SPLAY_INSERT(name, x, y)       name##_SPLAY_INSERT(x, y)
+#define SPLAY_REMOVE(name, x, y)       name##_SPLAY_REMOVE(x, y)
+#define SPLAY_FIND(name, x, y)         name##_SPLAY_FIND(x, y)
+#define SPLAY_NEXT(name, x, y)         name##_SPLAY_NEXT(x, y)
+#define SPLAY_MIN(name, x)             (SPLAY_EMPTY(x) ? NULL  \
+                                       : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
+#define SPLAY_MAX(name, x)             (SPLAY_EMPTY(x) ? NULL  \
+                                       : name##_SPLAY_MIN_MAX(x, SPLAY_INF))
+
+#define SPLAY_FOREACH(x, name, head)                                   \
+       for ((x) = SPLAY_MIN(name, head);                               \
+            (x) != NULL;                                               \
+            (x) = SPLAY_NEXT(name, head, x))
+
+/* Macros that define a red-black tree */
+#define RB_HEAD(name, type)                                            \
+struct name {                                                          \
+       struct type *rbh_root; /* root of the tree */                   \
+}
+
+#define RB_INITIALIZER(root)                                           \
+       { NULL }
+
+#define RB_INIT(root) do {                                             \
+       (root)->rbh_root = NULL;                                        \
+} while (0)
+
+#define RB_BLACK       0
+#define RB_RED         1
+#define RB_ENTRY(type)                                                 \
+struct {                                                               \
+       struct type *rbe_left;          /* left element */              \
+       struct type *rbe_right;         /* right element */             \
+       struct type *rbe_parent;        /* parent element */            \
+       int rbe_color;                  /* node color */                \
+}
+
+#define RB_LEFT(elm, field)            (elm)->field.rbe_left
+#define RB_RIGHT(elm, field)           (elm)->field.rbe_right
+#define RB_PARENT(elm, field)          (elm)->field.rbe_parent
+#define RB_COLOR(elm, field)           (elm)->field.rbe_color
+#define RB_ROOT(head)                  (head)->rbh_root
+#define RB_EMPTY(head)                 (RB_ROOT(head) == NULL)
+
+#define RB_SET(elm, parent, field) do {                                        \
+       RB_PARENT(elm, field) = parent;                                 \
+       RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL;              \
+       RB_COLOR(elm, field) = RB_RED;                                  \
+} while (0)
+
+#define RB_SET_BLACKRED(black, red, field) do {                                \
+       RB_COLOR(black, field) = RB_BLACK;                              \
+       RB_COLOR(red, field) = RB_RED;                                  \
+} while (0)
+
+#ifndef RB_AUGMENT
+#define RB_AUGMENT(x)  do {} while (0)
+#endif
+
+#define RB_ROTATE_LEFT(head, elm, tmp, field) do {                     \
+       (tmp) = RB_RIGHT(elm, field);                                   \
+       if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) {             \
+               RB_PARENT(RB_LEFT(tmp, field), field) = (elm);          \
+       }                                                               \
+       RB_AUGMENT(elm);                                                \
+       if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {          \
+               if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \
+                       RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \
+               else                                                    \
+                       RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+       } else                                                          \
+               (head)->rbh_root = (tmp);                               \
+       RB_LEFT(tmp, field) = (elm);                                    \
+       RB_PARENT(elm, field) = (tmp);                                  \
+       RB_AUGMENT(tmp);                                                \
+       if ((RB_PARENT(tmp, field)))                                    \
+               RB_AUGMENT(RB_PARENT(tmp, field));                      \
+} while (0)
+
+#define RB_ROTATE_RIGHT(head, elm, tmp, field) do {                    \
+       (tmp) = RB_LEFT(elm, field);                                    \
+       if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) {             \
+               RB_PARENT(RB_RIGHT(tmp, field), field) = (elm);         \
+       }                                                               \
+       RB_AUGMENT(elm);                                                \
+       if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) {          \
+               if ((elm) == RB_LEFT(RB_PARENT(elm, field), field))     \
+                       RB_LEFT(RB_PARENT(elm, field), field) = (tmp);  \
+               else                                                    \
+                       RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
+       } else                                                          \
+               (head)->rbh_root = (tmp);                               \
+       RB_RIGHT(tmp, field) = (elm);                                   \
+       RB_PARENT(elm, field) = (tmp);                                  \
+       RB_AUGMENT(tmp);                                                \
+       if ((RB_PARENT(tmp, field)))                                    \
+               RB_AUGMENT(RB_PARENT(tmp, field));                      \
+} while (0)
+
+/* Generates prototypes and inline functions */
+#define        RB_PROTOTYPE(name, type, field, cmp)                            \
+       RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
+#define        RB_PROTOTYPE_STATIC(name, type, field, cmp)                     \
+       RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr)            \
+attr void name##_RB_INSERT_COLOR(struct name *, struct type *);                \
+attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
+attr struct type *name##_RB_REMOVE(struct name *, struct type *);      \
+attr struct type *name##_RB_INSERT(struct name *, struct type *);      \
+attr struct type *name##_RB_FIND(struct name *, struct type *);                \
+attr struct type *name##_RB_NFIND(struct name *, struct type *);       \
+attr struct type *name##_RB_NEXT(struct type *);                       \
+attr struct type *name##_RB_PREV(struct type *);                       \
+attr struct type *name##_RB_MINMAX(struct name *, int);                        \
+                                                                       \
+
+/* Main rb operation.
+ * Moves node close to the key of elm to top
+ */
+#define        RB_GENERATE(name, type, field, cmp)                             \
+       RB_GENERATE_INTERNAL(name, type, field, cmp,)
+#define        RB_GENERATE_STATIC(name, type, field, cmp)                      \
+       RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static)
+#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr)             \
+attr void                                                              \
+name##_RB_INSERT_COLOR(struct name *head, struct type *elm)            \
+{                                                                      \
+       struct type *parent, *gparent, *tmp;                            \
+       while ((parent = RB_PARENT(elm, field)) &&                      \
+           RB_COLOR(parent, field) == RB_RED) {                        \
+               gparent = RB_PARENT(parent, field);                     \
+               if (parent == RB_LEFT(gparent, field)) {                \
+                       tmp = RB_RIGHT(gparent, field);                 \
+                       if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \
+                               RB_COLOR(tmp, field) = RB_BLACK;        \
+                               RB_SET_BLACKRED(parent, gparent, field);\
+                               elm = gparent;                          \
+                               continue;                               \
+                       }                                               \
+                       if (RB_RIGHT(parent, field) == elm) {           \
+                               RB_ROTATE_LEFT(head, parent, tmp, field);\
+                               tmp = parent;                           \
+                               parent = elm;                           \
+                               elm = tmp;                              \
+                       }                                               \
+                       RB_SET_BLACKRED(parent, gparent, field);        \
+                       RB_ROTATE_RIGHT(head, gparent, tmp, field);     \
+               } else {                                                \
+                       tmp = RB_LEFT(gparent, field);                  \
+                       if (tmp && RB_COLOR(tmp, field) == RB_RED) {    \
+                               RB_COLOR(tmp, field) = RB_BLACK;        \
+                               RB_SET_BLACKRED(parent, gparent, field);\
+                               elm = gparent;                          \
+                               continue;                               \
+                       }                                               \
+                       if (RB_LEFT(parent, field) == elm) {            \
+                               RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                               tmp = parent;                           \
+                               parent = elm;                           \
+                               elm = tmp;                              \
+                       }                                               \
+                       RB_SET_BLACKRED(parent, gparent, field);        \
+                       RB_ROTATE_LEFT(head, gparent, tmp, field);      \
+               }                                                       \
+       }                                                               \
+       RB_COLOR(head->rbh_root, field) = RB_BLACK;                     \
+}                                                                      \
+                                                                       \
+attr void                                                              \
+name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
+{                                                                      \
+       struct type *tmp;                                               \
+       while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) &&     \
+           elm != RB_ROOT(head)) {                                     \
+               if (RB_LEFT(parent, field) == elm) {                    \
+                       tmp = RB_RIGHT(parent, field);                  \
+                       if (RB_COLOR(tmp, field) == RB_RED) {           \
+                               RB_SET_BLACKRED(tmp, parent, field);    \
+                               RB_ROTATE_LEFT(head, parent, tmp, field);\
+                               tmp = RB_RIGHT(parent, field);          \
+                       }                                               \
+                       if ((RB_LEFT(tmp, field) == NULL ||             \
+                           RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+                           (RB_RIGHT(tmp, field) == NULL ||            \
+                           RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+                               RB_COLOR(tmp, field) = RB_RED;          \
+                               elm = parent;                           \
+                               parent = RB_PARENT(elm, field);         \
+                       } else {                                        \
+                               if (RB_RIGHT(tmp, field) == NULL ||     \
+                                   RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
+                                       struct type *oleft;             \
+                                       if ((oleft = RB_LEFT(tmp, field)))\
+                                               RB_COLOR(oleft, field) = RB_BLACK;\
+                                       RB_COLOR(tmp, field) = RB_RED;  \
+                                       RB_ROTATE_RIGHT(head, tmp, oleft, field);\
+                                       tmp = RB_RIGHT(parent, field);  \
+                               }                                       \
+                               RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+                               RB_COLOR(parent, field) = RB_BLACK;     \
+                               if (RB_RIGHT(tmp, field))               \
+                                       RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
+                               RB_ROTATE_LEFT(head, parent, tmp, field);\
+                               elm = RB_ROOT(head);                    \
+                               break;                                  \
+                       }                                               \
+               } else {                                                \
+                       tmp = RB_LEFT(parent, field);                   \
+                       if (RB_COLOR(tmp, field) == RB_RED) {           \
+                               RB_SET_BLACKRED(tmp, parent, field);    \
+                               RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                               tmp = RB_LEFT(parent, field);           \
+                       }                                               \
+                       if ((RB_LEFT(tmp, field) == NULL ||             \
+                           RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
+                           (RB_RIGHT(tmp, field) == NULL ||            \
+                           RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
+                               RB_COLOR(tmp, field) = RB_RED;          \
+                               elm = parent;                           \
+                               parent = RB_PARENT(elm, field);         \
+                       } else {                                        \
+                               if (RB_LEFT(tmp, field) == NULL ||      \
+                                   RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
+                                       struct type *oright;            \
+                                       if ((oright = RB_RIGHT(tmp, field)))\
+                                               RB_COLOR(oright, field) = RB_BLACK;\
+                                       RB_COLOR(tmp, field) = RB_RED;  \
+                                       RB_ROTATE_LEFT(head, tmp, oright, field);\
+                                       tmp = RB_LEFT(parent, field);   \
+                               }                                       \
+                               RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
+                               RB_COLOR(parent, field) = RB_BLACK;     \
+                               if (RB_LEFT(tmp, field))                \
+                                       RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
+                               RB_ROTATE_RIGHT(head, parent, tmp, field);\
+                               elm = RB_ROOT(head);                    \
+                               break;                                  \
+                       }                                               \
+               }                                                       \
+       }                                                               \
+       if (elm)                                                        \
+               RB_COLOR(elm, field) = RB_BLACK;                        \
+}                                                                      \
+                                                                       \
+attr struct type *                                                     \
+name##_RB_REMOVE(struct name *head, struct type *elm)                  \
+{                                                                      \
+       struct type *child, *parent, *old = elm;                        \
+       int color;                                                      \
+       if (RB_LEFT(elm, field) == NULL)                                \
+               child = RB_RIGHT(elm, field);                           \
+       else if (RB_RIGHT(elm, field) == NULL)                          \
+               child = RB_LEFT(elm, field);                            \
+       else {                                                          \
+               struct type *left;                                      \
+               elm = RB_RIGHT(elm, field);                             \
+               while ((left = RB_LEFT(elm, field)))                    \
+                       elm = left;                                     \
+               child = RB_RIGHT(elm, field);                           \
+               parent = RB_PARENT(elm, field);                         \
+               color = RB_COLOR(elm, field);                           \
+               if (child)                                              \
+                       RB_PARENT(child, field) = parent;               \
+               if (parent) {                                           \
+                       if (RB_LEFT(parent, field) == elm)              \
+                               RB_LEFT(parent, field) = child;         \
+                       else                                            \
+                               RB_RIGHT(parent, field) = child;        \
+                       RB_AUGMENT(parent);                             \
+               } else                                                  \
+                       RB_ROOT(head) = child;                          \
+               if (RB_PARENT(elm, field) == old)                       \
+                       parent = elm;                                   \
+               (elm)->field = (old)->field;                            \
+               if (RB_PARENT(old, field)) {                            \
+                       if (RB_LEFT(RB_PARENT(old, field), field) == old)\
+                               RB_LEFT(RB_PARENT(old, field), field) = elm;\
+                       else                                            \
+                               RB_RIGHT(RB_PARENT(old, field), field) = elm;\
+                       RB_AUGMENT(RB_PARENT(old, field));              \
+               } else                                                  \
+                       RB_ROOT(head) = elm;                            \
+               RB_PARENT(RB_LEFT(old, field), field) = elm;            \
+               if (RB_RIGHT(old, field))                               \
+                       RB_PARENT(RB_RIGHT(old, field), field) = elm;   \
+               if (parent) {                                           \
+                       left = parent;                                  \
+                       do {                                            \
+                               RB_AUGMENT(left);                       \
+                       } while ((left = RB_PARENT(left, field)));      \
+               }                                                       \
+               goto color;                                             \
+       }                                                               \
+       parent = RB_PARENT(elm, field);                                 \
+       color = RB_COLOR(elm, field);                                   \
+       if (child)                                                      \
+               RB_PARENT(child, field) = parent;                       \
+       if (parent) {                                                   \
+               if (RB_LEFT(parent, field) == elm)                      \
+                       RB_LEFT(parent, field) = child;                 \
+               else                                                    \
+                       RB_RIGHT(parent, field) = child;                \
+               RB_AUGMENT(parent);                                     \
+       } else                                                          \
+               RB_ROOT(head) = child;                                  \
+color:                                                                 \
+       if (color == RB_BLACK)                                          \
+               name##_RB_REMOVE_COLOR(head, parent, child);            \
+       return (old);                                                   \
+}                                                                      \
+                                                                       \
+/* Inserts a node into the RB tree */                                  \
+attr struct type *                                                     \
+name##_RB_INSERT(struct name *head, struct type *elm)                  \
+{                                                                      \
+       struct type *tmp;                                               \
+       struct type *parent = NULL;                                     \
+       int comp = 0;                                                   \
+       tmp = RB_ROOT(head);                                            \
+       while (tmp) {                                                   \
+               parent = tmp;                                           \
+               comp = (cmp)(elm, parent);                              \
+               if (comp < 0)                                           \
+                       tmp = RB_LEFT(tmp, field);                      \
+               else if (comp > 0)                                      \
+                       tmp = RB_RIGHT(tmp, field);                     \
+               else                                                    \
+                       return (tmp);                                   \
+       }                                                               \
+       RB_SET(elm, parent, field);                                     \
+       if (parent != NULL) {                                           \
+               if (comp < 0)                                           \
+                       RB_LEFT(parent, field) = elm;                   \
+               else                                                    \
+                       RB_RIGHT(parent, field) = elm;                  \
+               RB_AUGMENT(parent);                                     \
+       } else                                                          \
+               RB_ROOT(head) = elm;                                    \
+       name##_RB_INSERT_COLOR(head, elm);                              \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+/* Finds the node with the same key as elm */                          \
+attr struct type *                                                     \
+name##_RB_FIND(struct name *head, struct type *elm)                    \
+{                                                                      \
+       struct type *tmp = RB_ROOT(head);                               \
+       int comp;                                                       \
+       while (tmp) {                                                   \
+               comp = cmp(elm, tmp);                                   \
+               if (comp < 0)                                           \
+                       tmp = RB_LEFT(tmp, field);                      \
+               else if (comp > 0)                                      \
+                       tmp = RB_RIGHT(tmp, field);                     \
+               else                                                    \
+                       return (tmp);                                   \
+       }                                                               \
+       return (NULL);                                                  \
+}                                                                      \
+                                                                       \
+/* Finds the first node greater than or equal to the search key */     \
+attr struct type *                                                     \
+name##_RB_NFIND(struct name *head, struct type *elm)                   \
+{                                                                      \
+       struct type *tmp = RB_ROOT(head);                               \
+       struct type *res = NULL;                                        \
+       int comp;                                                       \
+       while (tmp) {                                                   \
+               comp = cmp(elm, tmp);                                   \
+               if (comp < 0) {                                         \
+                       res = tmp;                                      \
+                       tmp = RB_LEFT(tmp, field);                      \
+               }                                                       \
+               else if (comp > 0)                                      \
+                       tmp = RB_RIGHT(tmp, field);                     \
+               else                                                    \
+                       return (tmp);                                   \
+       }                                                               \
+       return (res);                                                   \
+}                                                                      \
+                                                                       \
+/* ARGSUSED */                                                         \
+attr struct type *                                                     \
+name##_RB_NEXT(struct type *elm)                                       \
+{                                                                      \
+       if (RB_RIGHT(elm, field)) {                                     \
+               elm = RB_RIGHT(elm, field);                             \
+               while (RB_LEFT(elm, field))                             \
+                       elm = RB_LEFT(elm, field);                      \
+       } else {                                                        \
+               if (RB_PARENT(elm, field) &&                            \
+                   (elm == RB_LEFT(RB_PARENT(elm, field), field)))     \
+                       elm = RB_PARENT(elm, field);                    \
+               else {                                                  \
+                       while (RB_PARENT(elm, field) &&                 \
+                           (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
+                               elm = RB_PARENT(elm, field);            \
+                       elm = RB_PARENT(elm, field);                    \
+               }                                                       \
+       }                                                               \
+       return (elm);                                                   \
+}                                                                      \
+                                                                       \
+/* ARGSUSED */                                                         \
+attr struct type *                                                     \
+name##_RB_PREV(struct type *elm)                                       \
+{                                                                      \
+       if (RB_LEFT(elm, field)) {                                      \
+               elm = RB_LEFT(elm, field);                              \
+               while (RB_RIGHT(elm, field))                            \
+                       elm = RB_RIGHT(elm, field);                     \
+       } else {                                                        \
+               if (RB_PARENT(elm, field) &&                            \
+                   (elm == RB_RIGHT(RB_PARENT(elm, field), field)))    \
+                       elm = RB_PARENT(elm, field);                    \
+               else {                                                  \
+                       while (RB_PARENT(elm, field) &&                 \
+                           (elm == RB_LEFT(RB_PARENT(elm, field), field)))\
+                               elm = RB_PARENT(elm, field);            \
+                       elm = RB_PARENT(elm, field);                    \
+               }                                                       \
+       }                                                               \
+       return (elm);                                                   \
+}                                                                      \
+                                                                       \
+attr struct type *                                                     \
+name##_RB_MINMAX(struct name *head, int val)                           \
+{                                                                      \
+       struct type *tmp = RB_ROOT(head);                               \
+       struct type *parent = NULL;                                     \
+       while (tmp) {                                                   \
+               parent = tmp;                                           \
+               if (val < 0)                                            \
+                       tmp = RB_LEFT(tmp, field);                      \
+               else                                                    \
+                       tmp = RB_RIGHT(tmp, field);                     \
+       }                                                               \
+       return (parent);                                                \
+}
+
+#define RB_NEGINF      -1
+#define RB_INF 1
+
+#define RB_INSERT(name, x, y)  name##_RB_INSERT(x, y)
+#define RB_REMOVE(name, x, y)  name##_RB_REMOVE(x, y)
+#define RB_FIND(name, x, y)    name##_RB_FIND(x, y)
+#define RB_NFIND(name, x, y)   name##_RB_NFIND(x, y)
+#define RB_NEXT(name, x, y)    name##_RB_NEXT(y)
+#define RB_PREV(name, x, y)    name##_RB_PREV(y)
+#define RB_MIN(name, x)                name##_RB_MINMAX(x, RB_NEGINF)
+#define RB_MAX(name, x)                name##_RB_MINMAX(x, RB_INF)
+
+#define RB_FOREACH(x, name, head)                                      \
+       for ((x) = RB_MIN(name, head);                                  \
+            (x) != NULL;                                               \
+            (x) = name##_RB_NEXT(x))
+
+#define RB_FOREACH_SAFE(x, name, head, y)                              \
+       for ((x) = RB_MIN(name, head);                                  \
+           ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1);              \
+            (x) = (y))
+
+#define RB_FOREACH_REVERSE(x, name, head)                              \
+       for ((x) = RB_MAX(name, head);                                  \
+            (x) != NULL;                                               \
+            (x) = name##_RB_PREV(x))
+
+#define RB_FOREACH_REVERSE_SAFE(x, name, head, y)                      \
+       for ((x) = RB_MAX(name, head);                                  \
+           ((x) != NULL) && ((y) = name##_RB_PREV(x), 1);              \
+            (x) = (y))
+
+#endif /* _SYS_TREE_H_ */