root/usr.sbin/rtadvctl/rtadvctl.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (C) 2011 Hiroki Sato <hrs@FreeBSD.org>
 * 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 PROJECT 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 PROJECT 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.
 *
 */

#include <sys/param.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet6/in6_var.h>
#include <netinet6/nd6.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <inttypes.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <time.h>
#include <err.h>

#include "pathnames.h"
#include "rtadvd.h"
#include "if.h"
#include "timer_subr.h"
#include "timer.h"
#include "control.h"
#include "control_client.h"

#define RA_IFSTATUS_INACTIVE    0
#define RA_IFSTATUS_RA_RECV     1
#define RA_IFSTATUS_RA_SEND     2

static int vflag = LOG_ERR;

static void     usage(void);

static int      action_propset(char *);
static int      action_propget(char *, struct ctrl_msg_pl *);
static int      action_plgeneric(int, char *, char *);

static int      action_enable(int, char **);
static int      action_disable(int, char **);
static int      action_reload(int, char **);
static int      action_echo(int, char **);
static int      action_version(int, char **);
static int      action_shutdown(int, char **);

static int      action_show(int, char **);
static int      action_show_prefix(struct prefix *);
static int      action_show_rtinfo(struct rtinfo *);
static int      action_show_rdnss(void *);
static int      action_show_dnssl(void *);
static void     action_show_pref64(void *);

static int      csock_client_open(struct sockinfo *);
static size_t   dname_labeldec(char *, size_t, const char *);
static void     mysyslog(int, const char *, ...);

static const char *rtpref_str[] = {
        "medium",               /* 00 */
        "high",                 /* 01 */
        "rsv",                  /* 10 */
        "low"                   /* 11 */
};

static struct dispatch_table {
        const char      *dt_comm;
        int (*dt_act)(int, char **);
} dtable[] = {
        { "show", action_show },
        { "reload", action_reload },
        { "shutdown", action_shutdown },
        { "enable", action_enable },
        { "disable", action_disable },
        { NULL, NULL },
        { "echo", action_echo },
        { "version", action_version },
        { NULL, NULL },
};

static char errmsgbuf[1024];
static char *errmsg = NULL;

static void
mysyslog(int priority, const char * restrict fmt, ...)
{
        va_list ap;

        if (vflag >= priority) {
                va_start(ap, fmt);
                vfprintf(stderr, fmt, ap);
                fprintf(stderr, "\n");
                va_end(ap);
        }
}

static void
usage(void)
{
        int i;

        for (i = 0; (size_t)i < nitems(dtable); i++) {
                if (dtable[i].dt_comm == NULL)
                        break;
                printf("%s\n", dtable[i].dt_comm);
        }

        exit(1);
}

int
main(int argc, char *argv[])
{
        int i;
        int ch;
        int (*action)(int, char **) = NULL;
        int error;

        while ((ch = getopt(argc, argv, "Dv")) != -1) {
                switch (ch) {
                case 'D':
                        vflag = LOG_DEBUG;
                        break;
                case 'v':
                        vflag++;
                        break;
                default:
                        usage();
                }
        }
        argc -= optind;
        argv += optind;

        if (argc == 0)
                usage();

        for (i = 0; (size_t)i < nitems(dtable); i++) {
                if (dtable[i].dt_comm == NULL ||
                    strcmp(dtable[i].dt_comm, argv[0]) == 0) {
                        action = dtable[i].dt_act;
                        break;
                }
        }

        if (action == NULL)
                usage();

        error = (dtable[i].dt_act)(--argc, ++argv);
        if (error) {
                fprintf(stderr, "%s failed", dtable[i].dt_comm);
                if (errmsg != NULL)
                        fprintf(stderr, ": %s", errmsg);
                fprintf(stderr, ".\n");
        }

        return (error);
}

static int
csock_client_open(struct sockinfo *s)
{
        struct sockaddr_un sun;

        if ((s->si_fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
                err(1, "cannot open control socket.");

        memset(&sun, 0, sizeof(sun));
        sun.sun_family = AF_UNIX;
        sun.sun_len = sizeof(sun);
        strlcpy(sun.sun_path, s->si_name, sizeof(sun.sun_path));

        if (connect(s->si_fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
                err(1, "connect: %s", s->si_name);

        mysyslog(LOG_DEBUG,
            "<%s> connected to %s", __func__, sun.sun_path);

        return (0);
}

static int
action_plgeneric(int action, char *plstr, char *buf)
{
        struct ctrl_msg_hdr *cm;
        struct ctrl_msg_pl cp;
        struct sockinfo *s;
        char *msg;
        char *p;
        char *q;

        s = &ctrlsock;
        csock_client_open(s);

        cm = (struct ctrl_msg_hdr *)buf;
        msg = (char *)buf + sizeof(*cm);

        cm->cm_version = CM_VERSION;
        cm->cm_type = action;
        cm->cm_len = sizeof(*cm);

        if (plstr != NULL) {
                memset(&cp, 0, sizeof(cp));
                p = strchr(plstr, ':');
                q = strchr(plstr, '=');
                if (p != NULL && q != NULL && p > q)
                        return (1);

                if (p == NULL) {                /* No : */
                        cp.cp_ifname = NULL;
                        cp.cp_key = plstr;
                } else if  (p == plstr) {       /* empty */
                        cp.cp_ifname = NULL;
                        cp.cp_key = plstr + 1;
                } else {
                        *p++ = '\0';
                        cp.cp_ifname = plstr;
                        cp.cp_key = p;
                }
                if (q == NULL)
                        cp.cp_val = NULL;
                else {
                        *q++ = '\0';
                        cp.cp_val = q;
                }
                cm->cm_len += cm_pl2bin(msg, &cp);

                mysyslog(LOG_DEBUG, "<%s> key=%s, val_len=%d, ifname=%s",
                    __func__,cp.cp_key, cp.cp_val_len, cp.cp_ifname);
        }

        return (cm_handler_client(s->si_fd, CM_STATE_MSG_DISPATCH, buf));
}

static int
action_propget(char *argv, struct ctrl_msg_pl *cp)
{
        int error;
        struct ctrl_msg_hdr *cm;
        char buf[CM_MSG_MAXLEN];
        char *msg;

        memset(cp, 0, sizeof(*cp));
        cm = (struct ctrl_msg_hdr *)buf;
        msg = (char *)buf + sizeof(*cm);

        error = action_plgeneric(CM_TYPE_REQ_GET_PROP, argv, buf);
        if (error || cm->cm_len <= sizeof(*cm))
                return (1);

        cm_bin2pl(msg, cp);
        mysyslog(LOG_DEBUG, "<%s> type=%d, len=%d",
            __func__, cm->cm_type, cm->cm_len);
        mysyslog(LOG_DEBUG, "<%s> key=%s, val_len=%d, ifname=%s",
            __func__,cp->cp_key, cp->cp_val_len, cp->cp_ifname);

        return (0);
}

static int
action_propset(char *argv)
{
        char buf[CM_MSG_MAXLEN];

        return (action_plgeneric(CM_TYPE_REQ_SET_PROP, argv, buf));
}

static int
action_disable(int argc, char **argv)
{
        char *action_argv;
        char argv_disable[IFNAMSIZ + sizeof(":disable=")];
        int i;
        int error;

        if (argc < 1)
                return (1);

        error = 0;
        for (i = 0; i < argc; i++) {
                sprintf(argv_disable, "%s:disable=", argv[i]);
                action_argv = argv_disable;
                error += action_propset(action_argv);
        }

        return (error);
}

static int
action_enable(int argc, char **argv)
{
        char *action_argv;
        char argv_enable[IFNAMSIZ + sizeof(":enable=")];
        int i;
        int error;

        if (argc < 1)
                return (1);

        error = 0;
        for (i = 0; i < argc; i++) {
                sprintf(argv_enable, "%s:enable=", argv[i]);
                action_argv = argv_enable;
                error += action_propset(action_argv);
        }

        return (error);
}

static int
action_reload(int argc, char **argv)
{
        char *action_argv;
        char argv_reload[IFNAMSIZ + sizeof(":reload=")];
        int i;
        int error;

        if (argc == 0) {
                action_argv = strdup(":reload=");
                return (action_propset(action_argv));
        }

        error = 0;
        for (i = 0; i < argc; i++) {
                sprintf(argv_reload, "%s:reload=", argv[i]);
                action_argv = argv_reload;
                error += action_propset(action_argv);
        }

        return (error);
}

static int
action_echo(int argc __unused, char **argv __unused)
{
        char *action_argv;

        action_argv = strdup("echo");
        return (action_propset(action_argv));
}

static int
action_shutdown(int argc __unused, char **argv __unused)
{
        char *action_argv;

        action_argv = strdup("shutdown");
        return (action_propset(action_argv));
}

/* XXX */
static int
action_version(int argc __unused, char **argv __unused)
{
        char *action_argv;
        struct ctrl_msg_pl cp;
        int error;

        action_argv = strdup(":version=");
        error = action_propget(action_argv, &cp);
        if (error)
                return (error);

        printf("version=%s\n", cp.cp_val);
        return (0);
}

static int
action_show(int argc, char **argv)
{
        char *action_argv;
        char argv_ifilist[sizeof(":ifilist=")] = ":ifilist=";
        char argv_ifi[IFNAMSIZ + sizeof(":ifi=")];
        char argv_rai[IFNAMSIZ + sizeof(":rai=")];
        char argv_rti[IFNAMSIZ + sizeof(":rti=")];
        char argv_pfx[IFNAMSIZ + sizeof(":pfx=")];
        char argv_ifi_ra_timer[IFNAMSIZ + sizeof(":ifi_ra_timer=")];
        char argv_rdnss[IFNAMSIZ + sizeof(":rdnss=")];
        char argv_dnssl[IFNAMSIZ + sizeof(":dnssl=")];
        char argv_pref64[IFNAMSIZ + sizeof(":pref64=")];
        char ssbuf[SSBUFLEN];

        struct timespec now, ts0, ts;
        struct ctrl_msg_pl cp;
        struct ifinfo *ifi;
        TAILQ_HEAD(, ifinfo) ifl = TAILQ_HEAD_INITIALIZER(ifl);
        char *endp;
        char *p;
        int error;
        int i;
        int len;

        if (argc == 0) {
                action_argv = argv_ifilist;
                error = action_propget(action_argv, &cp);
                if (error)
                        return (error);

                p = cp.cp_val;
                endp = p + cp.cp_val_len;
                while (p < endp) {
                        ifi = malloc(sizeof(*ifi));
                        if (ifi == NULL)
                                return (1);
                        memset(ifi, 0, sizeof(*ifi));

                        strcpy(ifi->ifi_ifname, p);
                        ifi->ifi_ifindex = if_nametoindex(ifi->ifi_ifname);
                        TAILQ_INSERT_TAIL(&ifl, ifi, ifi_next);
                        p += strlen(ifi->ifi_ifname) + 1;
                }
        } else {
                for (i = 0; i < argc; i++) {
                        ifi = malloc(sizeof(*ifi));
                        if (ifi == NULL)
                                return (1);
                        memset(ifi, 0, sizeof(*ifi));

                        strcpy(ifi->ifi_ifname, argv[i]);
                        ifi->ifi_ifindex = if_nametoindex(ifi->ifi_ifname);
                        if (ifi->ifi_ifindex == 0) {
                                sprintf(errmsgbuf, "invalid interface %s",
                                    ifi->ifi_ifname);
                                errmsg = errmsgbuf;
                                return (1);
                        }

                        TAILQ_INSERT_TAIL(&ifl, ifi, ifi_next);
                }
        }

        clock_gettime(CLOCK_REALTIME_FAST, &now);
        clock_gettime(CLOCK_MONOTONIC_FAST, &ts);
        TS_SUB(&now, &ts, &ts0);

        TAILQ_FOREACH(ifi, &ifl, ifi_next) {
                struct ifinfo *ifi_s;
                struct rtadvd_timer *rat;
                struct rainfo *rai;
                struct rtinfo *rti;
                struct prefix *pfx;
                int c;
                int ra_ifstatus;

                sprintf(argv_ifi, "%s:ifi=", ifi->ifi_ifname);
                action_argv = argv_ifi;
                error = action_propget(action_argv, &cp);
                if (error)
                        return (error);
                ifi_s = (struct ifinfo *)cp.cp_val;

                if (!(ifi_s->ifi_persist) && vflag < LOG_NOTICE)
                        continue;

                printf("%s: flags=<", ifi->ifi_ifname);

                c = 0;
                if (ifi_s->ifi_ifindex == 0)
                        c += printf("NONEXISTENT");
                else
                        c += printf("%s", (ifi_s->ifi_flags & IFF_UP) ?
                            "UP" : "DOWN");
                switch (ifi_s->ifi_state) {
                case IFI_STATE_CONFIGURED:
                        c += printf("%s%s", (c) ? "," : "", "CONFIGURED");
                        break;
                case IFI_STATE_TRANSITIVE:
                        c += printf("%s%s", (c) ? "," : "", "TRANSITIVE");
                        break;
                }
                if (ifi_s->ifi_persist)
                        c += printf("%s%s", (c) ? "," : "", "PERSIST");
                printf(">");

                ra_ifstatus = RA_IFSTATUS_INACTIVE;
                if ((ifi_s->ifi_flags & IFF_UP) &&
                    ((ifi_s->ifi_state == IFI_STATE_CONFIGURED) ||
                        (ifi_s->ifi_state == IFI_STATE_TRANSITIVE))) {
                        /*
                         * RA_RECV: ND6_IFF_ACCEPT_RTADV
                         * RA_SEND: ip6.forwarding
                         */
                        if (ifi_s->ifi_nd_flags & ND6_IFF_ACCEPT_RTADV)
                                ra_ifstatus = RA_IFSTATUS_RA_RECV;
                        else if (getinet6sysctl(IPV6CTL_FORWARDING))
                                ra_ifstatus = RA_IFSTATUS_RA_SEND;
                        else
                                ra_ifstatus = RA_IFSTATUS_INACTIVE;
                }

                c = 0;
                printf(" status=<");
                if (ra_ifstatus == RA_IFSTATUS_INACTIVE)
                        printf("%s%s", (c) ? "," : "", "INACTIVE");
                else if (ra_ifstatus == RA_IFSTATUS_RA_RECV)
                        printf("%s%s", (c) ? "," : "", "RA_RECV");
                else if (ra_ifstatus == RA_IFSTATUS_RA_SEND)
                        printf("%s%s", (c) ? "," : "", "RA_SEND");
                printf("> ");

                switch (ifi_s->ifi_state) {
                case IFI_STATE_CONFIGURED:
                case IFI_STATE_TRANSITIVE:
                        break;
                default:
                        printf("\n");
                        continue;
                }

                printf("mtu %d\n", ifi_s->ifi_phymtu);

                sprintf(argv_rai, "%s:rai=", ifi->ifi_ifname);
                action_argv = argv_rai;

                error = action_propget(action_argv, &cp);
                if (error)
                        continue;

                rai = (struct rainfo *)cp.cp_val;

                printf("\tDefaultLifetime: %s",
                    sec2str(rai->rai_lifetime, ssbuf));
                if (ra_ifstatus != RA_IFSTATUS_RA_SEND &&
                    rai->rai_lifetime == 0)
                        printf(" (RAs will be sent with zero lifetime)");

                printf("\n");

                printf("\tMinAdvInterval/MaxAdvInterval: ");
                printf("%s/", sec2str(rai->rai_mininterval, ssbuf));
                printf("%s\n", sec2str(rai->rai_maxinterval, ssbuf));
                if (rai->rai_linkmtu)
                        printf("\tAdvLinkMTU: %d", rai->rai_linkmtu);
                else
                        printf("\tAdvLinkMTU: <none>");

                printf(", ");

                printf("Flags: ");
                if (rai->rai_managedflg || rai->rai_otherflg) {
                        printf("%s", rai->rai_managedflg ? "M" : "");
                        printf("%s", rai->rai_otherflg ? "O" : "");
                } else
                        printf("<none>");

                printf(", ");

                printf("Preference: %s\n",
                    rtpref_str[(rai->rai_rtpref >> 3) & 0xff]);

                printf("\tReachableTime: %s, ",
                    sec2str(rai->rai_reachabletime, ssbuf));
                printf("RetransTimer: %s, "
                    "CurHopLimit: %d\n",
                    sec2str(rai->rai_retranstimer, ssbuf),
                    rai->rai_hoplimit);
                printf("\tAdvIfPrefixes: %s\n",
                    rai->rai_advifprefix ? "yes" : "no");

                /* RA timer */
                rat = NULL;
                if (ifi_s->ifi_ra_timer != NULL) {
                        sprintf(argv_ifi_ra_timer, "%s:ifi_ra_timer=",
                            ifi->ifi_ifname);
                        action_argv = argv_ifi_ra_timer;

                        error = action_propget(action_argv, &cp);
                        if (error)
                                return (error);

                        rat = (struct rtadvd_timer *)cp.cp_val;
                }
                printf("\tNext RA send: ");
                if (rat == NULL)
                        printf("never\n");
                else {
                        ts.tv_sec = rat->rat_tm.tv_sec + ts0.tv_sec;
                        printf("%s", ctime(&ts.tv_sec));
                }
                printf("\tLast RA send: ");
                if (ifi_s->ifi_ra_lastsent.tv_sec == 0)
                        printf("never\n");
                else {
                        ts.tv_sec = ifi_s->ifi_ra_lastsent.tv_sec + ts0.tv_sec;
                        printf("%s", ctime(&ts.tv_sec));
                }
                if (rai->rai_clockskew)
                        printf("\tClock skew: %" PRIu16 "sec\n",
                            rai->rai_clockskew);

                if (vflag < LOG_WARNING)
                        continue;

                /* route information */
                sprintf(argv_rti, "%s:rti=", ifi->ifi_ifname);
                action_argv = argv_rti;
                error = action_propget(action_argv, &cp);
                if (error)
                        return (error);

                rti = (struct rtinfo *)cp.cp_val;
                len = cp.cp_val_len / sizeof(*rti);
                if (len > 0) {
                        printf("\tRoute Info:\n");

                        for (i = 0; i < len; i++)
                                action_show_rtinfo(&rti[i]);
                }

                /* prefix information */
                sprintf(argv_pfx, "%s:pfx=", ifi->ifi_ifname);
                action_argv = argv_pfx;

                error = action_propget(action_argv, &cp);
                if (error)
                        continue;

                pfx = (struct prefix *)cp.cp_val;
                len = cp.cp_val_len / sizeof(*pfx);

                if (len > 0) {
                        printf("\tPrefixes (%d):\n", len);

                        for (i = 0; i < len; i++)
                                action_show_prefix(&pfx[i]);
                }

                /* RDNSS information */
                sprintf(argv_rdnss, "%s:rdnss=", ifi->ifi_ifname);
                action_argv = argv_rdnss;

                error = action_propget(action_argv, &cp);
                if (error)
                        continue;

                len = *((uint16_t *)cp.cp_val);

                if (len > 0) {
                        printf("\tRDNSS entries:\n");
                        action_show_rdnss(cp.cp_val);
                }

                /* DNSSL information */
                sprintf(argv_dnssl, "%s:dnssl=", ifi->ifi_ifname);
                action_argv = argv_dnssl;

                error = action_propget(action_argv, &cp);
                if (error)
                        continue;

                len = *((uint16_t *)cp.cp_val);

                if (len > 0) {
                        printf("\tDNSSL entries:\n");
                        action_show_dnssl(cp.cp_val);
                }

                /* PREF64 information */
                sprintf(argv_pref64, "%s:pref64=", ifi->ifi_ifname);
                action_argv = argv_pref64;

                error = action_propget(action_argv, &cp);
                if (error)
                        continue;

                len = *((uint16_t *)cp.cp_val);

                if (len > 0) {
                        printf("\tPREF64:\n");
                        action_show_pref64(cp.cp_val);
                }

                if (vflag < LOG_NOTICE)
                        continue;

                printf("\n");

                printf("\tCounters\n"
                    "\t RA burst counts: %" PRIu16 " (interval: %s)\n"
                    "\t RS wait counts: %" PRIu16 "\n",
                    ifi_s->ifi_burstcount,
                    sec2str(ifi_s->ifi_burstinterval, ssbuf),
                    ifi_s->ifi_rs_waitcount);

                printf("\tOutputs\n"
                    "\t RA: %" PRIu64 "\n", ifi_s->ifi_raoutput);

                printf("\tInputs\n"
                    "\t RA: %" PRIu64 " (normal)\n"
                    "\t RA: %" PRIu64 " (inconsistent)\n"
                    "\t RS: %" PRIu64 "\n",
                    ifi_s->ifi_rainput,
                    ifi_s->ifi_rainconsistent,
                    ifi_s->ifi_rsinput);

                printf("\n");

#if 0   /* Not implemented yet */
                printf("\tReceived RAs:\n");
#endif
        }

        return (0);
}

static int
action_show_rtinfo(struct rtinfo *rti)
{
        char ntopbuf[INET6_ADDRSTRLEN];
        char ssbuf[SSBUFLEN];

        printf("\t  %s/%d (pref: %s, ltime: %s)\n",
            inet_ntop(AF_INET6, &rti->rti_prefix,
                ntopbuf, sizeof(ntopbuf)),
            rti->rti_prefixlen,
            rtpref_str[0xff & (rti->rti_rtpref >> 3)],
            (rti->rti_ltime == ND6_INFINITE_LIFETIME) ?
            "infinity" : sec2str(rti->rti_ltime, ssbuf));

        return (0);
}

static int
action_show_prefix(struct prefix *pfx)
{
        char ntopbuf[INET6_ADDRSTRLEN];
        char ssbuf[SSBUFLEN];
        struct timespec now;

        clock_gettime(CLOCK_MONOTONIC_FAST, &now);
        printf("\t  %s/%d", inet_ntop(AF_INET6, &pfx->pfx_prefix,
                ntopbuf, sizeof(ntopbuf)), pfx->pfx_prefixlen);

        printf(" (");
        switch (pfx->pfx_origin) {
        case PREFIX_FROM_KERNEL:
                printf("KERNEL");
                break;
        case PREFIX_FROM_CONFIG:
                printf("CONFIG");
                break;
        case PREFIX_FROM_DYNAMIC:
                printf("DYNAMIC");
                break;
        }

        printf(",");

        printf(" vltime=%s",
            (pfx->pfx_validlifetime == ND6_INFINITE_LIFETIME) ?
            "infinity" : sec2str(pfx->pfx_validlifetime, ssbuf));

        if (pfx->pfx_vltimeexpire > 0)
                printf("(expire: %s)",
                    ((long)pfx->pfx_vltimeexpire > now.tv_sec) ?
                    sec2str(pfx->pfx_vltimeexpire - now.tv_sec, ssbuf) :
                    "0");

        printf(",");

        printf(" pltime=%s",
            (pfx->pfx_preflifetime == ND6_INFINITE_LIFETIME) ?
            "infinity" : sec2str(pfx->pfx_preflifetime, ssbuf));

        if (pfx->pfx_pltimeexpire > 0)
                printf("(expire %s)",
                    ((long)pfx->pfx_pltimeexpire > now.tv_sec) ?
                    sec2str(pfx->pfx_pltimeexpire - now.tv_sec, ssbuf) :
                    "0");

        printf(",");

        printf(" flags=");
        if (pfx->pfx_onlinkflg || pfx->pfx_autoconfflg) {
                printf("%s", pfx->pfx_onlinkflg ? "L" : "");
                printf("%s", pfx->pfx_autoconfflg ? "A" : "");
        } else
                printf("<none>");

        if (pfx->pfx_timer) {
                struct timespec *rest;

                rest = rtadvd_timer_rest(pfx->pfx_timer);
                if (rest) { /* XXX: what if not? */
                        printf(" expire=%s", sec2str(rest->tv_sec, ssbuf));
                }
        }

        printf(")\n");

        return (0);
}

static int
action_show_rdnss(void *msg)
{
        struct rdnss *rdn;
        struct rdnss_addr *rda;
        uint16_t *rdn_cnt;
        uint16_t *rda_cnt;
        int i;
        int j;
        char *p;
        uint32_t        ltime;
        char ntopbuf[INET6_ADDRSTRLEN];
        char ssbuf[SSBUFLEN];

        p = msg;
        rdn_cnt = (uint16_t *)p;
        p += sizeof(*rdn_cnt);

        if (*rdn_cnt > 0) {
                for (i = 0; i < *rdn_cnt; i++) {
                        rdn = (struct rdnss *)p;
                        ltime = rdn->rd_ltime;
                        p += sizeof(*rdn);

                        rda_cnt = (uint16_t *)p;
                        p += sizeof(*rda_cnt);
                        if (*rda_cnt > 0)
                                for (j = 0; j < *rda_cnt; j++) {
                                        rda = (struct rdnss_addr *)p;
                                        printf("\t  %s (ltime=%s)\n",
                                            inet_ntop(AF_INET6,
                                                &rda->ra_dns,
                                                ntopbuf,
                                                sizeof(ntopbuf)),
                                            sec2str(ltime, ssbuf));
                                        p += sizeof(*rda);
                                }
                }
        }

        return (0);
}

static int
action_show_dnssl(void *msg)
{
        struct dnssl *dns;
        struct dnssl_addr *dna;
        uint16_t *dns_cnt;
        uint16_t *dna_cnt;
        int i;
        int j;
        char *p;
        uint32_t ltime;
        char hbuf[NI_MAXHOST];
        char ssbuf[SSBUFLEN];

        p = msg;
        dns_cnt = (uint16_t *)p;
        p += sizeof(*dns_cnt);

        if (*dns_cnt > 0) {
                for (i = 0; i < *dns_cnt; i++) {
                        dns = (struct dnssl *)p;
                        ltime = dns->dn_ltime;
                        p += sizeof(*dns);

                        dna_cnt = (uint16_t *)p;
                        p += sizeof(*dna_cnt);
                        if (*dna_cnt > 0)
                                for (j = 0; j < *dna_cnt; j++) {
                                        dna = (struct dnssl_addr *)p;
                                        dname_labeldec(hbuf, sizeof(hbuf),
                                            dna->da_dom);
                                        printf("\t  %s (ltime=%s)\n",
                                            hbuf, sec2str(ltime, ssbuf));
                                        p += sizeof(*dna);
                                }
                }
        }

        return (0);
}

static void
action_show_pref64(void *msg)
{
        struct pref64 *prf64;
        uint16_t *prf64_cnt;
        char ntopbuf[INET6_ADDRSTRLEN];
        char ssbuf[SSBUFLEN];
        char *p;
        int i;
        uint16_t prf64len;

        p = msg;
        prf64_cnt = (uint16_t *)p;
        p += sizeof(*prf64_cnt);

        for (i = 0; i < *prf64_cnt; i++) {
                prf64 = (struct pref64 *)p;

                /* RFC 8781 Section 4: Map PLC values to prefix lengths */
                prf64len = (prf64->p64_plc == 0) ? 96 : 72 - (8 * prf64->p64_plc);
                printf("\t  %s/%d (ltime: %s)\n",
                    inet_ntop(AF_INET6, &prf64->p64_prefix,
                        ntopbuf, sizeof(ntopbuf)),
                    prf64len, sec2str(prf64->p64_sl, ssbuf));

                p += sizeof(*prf64);
        }
}

/* Decode domain name label encoding in RFC 1035 Section 3.1 */
static size_t
dname_labeldec(char *dst, size_t dlen, const char *src)
{
        size_t len;
        const char *src_origin;
        const char *src_last;
        const char *dst_origin;

        src_origin = src;
        src_last = strchr(src, '\0');
        dst_origin = dst;
        memset(dst, '\0', dlen);
        while (src && (len = (uint8_t)(*src++) & 0x3f) &&
            (src + len) <= src_last) {
                if (dst != dst_origin)
                        *dst++ = '.';
                mysyslog(LOG_DEBUG, "<%s> labellen = %zd", __func__, len);
                memcpy(dst, src, len);
                src += len;
                dst += len;
        }
        *dst = '\0';

        return (src - src_origin);
}