root/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_dhcpv6.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2017 Gary Mills
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Dynamic Host Configuration Protocol version 6, for IPv6.  Supports
 * RFCs 3315, 3319, 3646, 3898, 4075, 4242, 4280, 4580, 4649, and 4704.
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/dhcp6.h>
#include <arpa/inet.h>
#include <dhcp_impl.h>
#include <dhcp_inittab.h>

#include "snoop.h"

static const char *mtype_to_str(uint8_t);
static const char *option_to_str(uint8_t);
static const char *duidtype_to_str(uint16_t);
static const char *status_to_str(uint16_t);
static const char *entr_to_str(uint32_t);
static const char *reconf_to_str(uint8_t);
static const char *authproto_to_str(uint8_t);
static const char *authalg_to_str(uint8_t, uint8_t);
static const char *authrdm_to_str(uint8_t);
static const char *cwhat_to_str(uint8_t);
static const char *catype_to_str(uint8_t);
static void show_hex(const uint8_t *, int, const char *);
static void show_ascii(const uint8_t *, int, const char *);
static void show_address(const char *, const void *);
static void show_options(const uint8_t *, int);

int
interpret_dhcpv6(int flags, const uint8_t *data, int len)
{
        int olen = len;
        char *line, *lstart;
        dhcpv6_relay_t d6r;
        dhcpv6_message_t d6m;
        uint_t optlen;
        uint16_t statuscode;

        if (len <= 0) {
                (void) strlcpy(get_sum_line(), "DHCPv6?", MAXLINE);
                return (0);
        }
        if (flags & F_SUM) {
                uint_t ias;
                dhcpv6_option_t *d6o;
                in6_addr_t link, peer;
                char linkstr[INET6_ADDRSTRLEN];
                char peerstr[INET6_ADDRSTRLEN];

                line = lstart = get_sum_line();
                line += snprintf(line, MAXLINE, "DHCPv6 %s",
                    mtype_to_str(data[0]));
                if (data[0] == DHCPV6_MSG_RELAY_FORW ||
                    data[0] == DHCPV6_MSG_RELAY_REPL) {
                        if (len < sizeof (d6r)) {
                                (void) strlcpy(line, "?",
                                    MAXLINE - (line - lstart));
                                return (olen);
                        }
                        /* Not much in DHCPv6 is aligned. */
                        (void) memcpy(&d6r, data, sizeof (d6r));
                        (void) memcpy(&link, d6r.d6r_linkaddr, sizeof (link));
                        (void) memcpy(&peer, d6r.d6r_peeraddr, sizeof (peer));
                        line += snprintf(line, MAXLINE - (line - lstart),
                            " HC=%d link=%s peer=%s", d6r.d6r_hop_count,
                            inet_ntop(AF_INET6, &link, linkstr,
                            sizeof (linkstr)),
                            inet_ntop(AF_INET6, &peer, peerstr,
                            sizeof (peerstr)));
                        data += sizeof (d6r);
                        len -= sizeof (d6r);
                } else {
                        if (len < sizeof (d6m)) {
                                (void) strlcpy(line, "?",
                                    MAXLINE - (line - lstart));
                                return (olen);
                        }
                        (void) memcpy(&d6m, data, sizeof (d6m));
                        line += snprintf(line, MAXLINE - (line - lstart),
                            " xid=%x", DHCPV6_GET_TRANSID(&d6m));
                        data += sizeof (d6m);
                        len -= sizeof (d6m);
                }
                ias = 0;
                d6o = NULL;
                while ((d6o = dhcpv6_find_option(data, len, d6o,
                    DHCPV6_OPT_IA_NA, NULL)) != NULL)
                        ias++;
                if (ias > 0)
                        line += snprintf(line, MAXLINE - (line - lstart),
                            " IAs=%u", ias);
                d6o = dhcpv6_find_option(data, len, NULL,
                    DHCPV6_OPT_STATUS_CODE, &optlen);
                optlen -= sizeof (*d6o);
                if (d6o != NULL && optlen >= sizeof (statuscode)) {
                        (void) memcpy(&statuscode, d6o + 1,
                            sizeof (statuscode));
                        line += snprintf(line, MAXLINE - (line - lstart),
                            " status=%u", ntohs(statuscode));
                        optlen -= sizeof (statuscode);
                        if (optlen > 0) {
                                line += snprintf(line,
                                    MAXLINE - (line - lstart), " \"%.*s\"",
                                    optlen, (char *)(d6o + 1) + 2);
                        }
                }
                d6o = dhcpv6_find_option(data, len, NULL,
                    DHCPV6_OPT_RELAY_MSG, &optlen);
                optlen -= sizeof (*d6o);
                if (d6o != NULL && optlen >= 1) {
                        line += snprintf(line, MAXLINE - (line - lstart),
                            " relay=%s", mtype_to_str(*(uint8_t *)(d6o + 1)));
                }
        } else if (flags & F_DTAIL) {
                show_header("DHCPv6: ",
                    "Dynamic Host Configuration Protocol Version 6", len);
                show_space();
                (void) snprintf(get_line(0, 0), get_line_remain(),
                    "Message type (msg-type) = %u (%s)", data[0],
                    mtype_to_str(data[0]));
                if (data[0] == DHCPV6_MSG_RELAY_FORW ||
                    data[0] == DHCPV6_MSG_RELAY_REPL) {
                        if (len < sizeof (d6r)) {
                                (void) strlcpy(get_line(0, 0), "Truncated",
                                    get_line_remain());
                                return (olen);
                        }
                        (void) memcpy(&d6r, data, sizeof (d6r));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "Hop count = %u", d6r.d6r_hop_count);
                        show_address("Link address", d6r.d6r_linkaddr);
                        show_address("Peer address", d6r.d6r_peeraddr);
                        data += sizeof (d6r);
                        len -= sizeof (d6r);
                } else {
                        if (len < sizeof (d6m)) {
                                (void) strlcpy(get_line(0, 0), "Truncated",
                                    get_line_remain());
                                return (olen);
                        }
                        (void) memcpy(&d6m, data, sizeof (d6m));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "Transaction ID = %x", DHCPV6_GET_TRANSID(&d6m));
                        data += sizeof (d6m);
                        len -= sizeof (d6m);
                }
                show_space();
                show_options(data, len);
                show_space();
        }
        return (olen);
}

static const char *
mtype_to_str(uint8_t mtype)
{
        switch (mtype) {
        case DHCPV6_MSG_SOLICIT:
                return ("Solicit");
        case DHCPV6_MSG_ADVERTISE:
                return ("Advertise");
        case DHCPV6_MSG_REQUEST:
                return ("Request");
        case DHCPV6_MSG_CONFIRM:
                return ("Confirm");
        case DHCPV6_MSG_RENEW:
                return ("Renew");
        case DHCPV6_MSG_REBIND:
                return ("Rebind");
        case DHCPV6_MSG_REPLY:
                return ("Reply");
        case DHCPV6_MSG_RELEASE:
                return ("Release");
        case DHCPV6_MSG_DECLINE:
                return ("Decline");
        case DHCPV6_MSG_RECONFIGURE:
                return ("Reconfigure");
        case DHCPV6_MSG_INFO_REQ:
                return ("Information-Request");
        case DHCPV6_MSG_RELAY_FORW:
                return ("Relay-Forward");
        case DHCPV6_MSG_RELAY_REPL:
                return ("Relay-Reply");
        default:
                return ("Unknown");
        }
}

static const char *
option_to_str(uint8_t mtype)
{
        switch (mtype) {
        case DHCPV6_OPT_CLIENTID:
                return ("Client Identifier");
        case DHCPV6_OPT_SERVERID:
                return ("Server Identifier");
        case DHCPV6_OPT_IA_NA:
                return ("Identity Association for Non-temporary Addresses");
        case DHCPV6_OPT_IA_TA:
                return ("Identity Association for Temporary Addresses");
        case DHCPV6_OPT_IAADDR:
                return ("IA Address");
        case DHCPV6_OPT_ORO:
                return ("Option Request");
        case DHCPV6_OPT_PREFERENCE:
                return ("Preference");
        case DHCPV6_OPT_ELAPSED_TIME:
                return ("Elapsed Time");
        case DHCPV6_OPT_RELAY_MSG:
                return ("Relay Message");
        case DHCPV6_OPT_AUTH:
                return ("Authentication");
        case DHCPV6_OPT_UNICAST:
                return ("Server Unicast");
        case DHCPV6_OPT_STATUS_CODE:
                return ("Status Code");
        case DHCPV6_OPT_RAPID_COMMIT:
                return ("Rapid Commit");
        case DHCPV6_OPT_USER_CLASS:
                return ("User Class");
        case DHCPV6_OPT_VENDOR_CLASS:
                return ("Vendor Class");
        case DHCPV6_OPT_VENDOR_OPT:
                return ("Vendor-specific Information");
        case DHCPV6_OPT_INTERFACE_ID:
                return ("Interface-Id");
        case DHCPV6_OPT_RECONF_MSG:
                return ("Reconfigure Message");
        case DHCPV6_OPT_RECONF_ACC:
                return ("Reconfigure Accept");
        case DHCPV6_OPT_SIP_NAMES:
                return ("SIP Servers Domain Name List");
        case DHCPV6_OPT_SIP_ADDR:
                return ("SIP Servers IPv6 Address List");
        case DHCPV6_OPT_DNS_ADDR:
                return ("DNS Recursive Name Server");
        case DHCPV6_OPT_DNS_SEARCH:
                return ("Domain Search List");
        case DHCPV6_OPT_IA_PD:
                return ("Identity Association for Prefix Delegation");
        case DHCPV6_OPT_IAPREFIX:
                return ("IA_PD Prefix");
        case DHCPV6_OPT_NIS_SERVERS:
                return ("Network Information Service Servers");
        case DHCPV6_OPT_NIS_DOMAIN:
                return ("Network Information Service Domain Name");
        case DHCPV6_OPT_SNTP_SERVERS:
                return ("Simple Network Time Protocol Servers");
        case DHCPV6_OPT_INFO_REFTIME:
                return ("Information Refresh Time");
        case DHCPV6_OPT_BCMCS_SRV_D:
                return ("BCMCS Controller Domain Name List");
        case DHCPV6_OPT_BCMCS_SRV_A:
                return ("BCMCS Controller IPv6 Address");
        case DHCPV6_OPT_GEOCONF_CVC:
                return ("Civic Location");
        case DHCPV6_OPT_REMOTE_ID:
                return ("Relay Agent Remote-ID");
        case DHCPV6_OPT_SUBSCRIBER:
                return ("Relay Agent Subscriber-ID");
        case DHCPV6_OPT_CLIENT_FQDN:
                return ("Client FQDN");
        default:
                return ("Unknown");
        }
}

static const char *
duidtype_to_str(uint16_t dtype)
{
        switch (dtype) {
        case DHCPV6_DUID_LLT:
                return ("Link-layer Address Plus Time");
        case DHCPV6_DUID_EN:
                return ("Enterprise Number");
        case DHCPV6_DUID_LL:
                return ("Link-layer Address");
        default:
                return ("Unknown");
        }
}

static const char *
status_to_str(uint16_t status)
{
        switch (status) {
        case DHCPV6_STAT_SUCCESS:
                return ("Success");
        case DHCPV6_STAT_UNSPECFAIL:
                return ("Failure, reason unspecified");
        case DHCPV6_STAT_NOADDRS:
                return ("No addresses for IAs");
        case DHCPV6_STAT_NOBINDING:
                return ("Client binding unavailable");
        case DHCPV6_STAT_NOTONLINK:
                return ("Prefix not on link");
        case DHCPV6_STAT_USEMCAST:
                return ("Use multicast");
        case DHCPV6_STAT_NOPREFIX:
                return ("No prefix available");
        default:
                return ("Unknown");
        }
}

static const char *
entr_to_str(uint32_t entr)
{
        switch (entr) {
        case DHCPV6_SUN_ENT:
                return ("Sun Microsystems");
        default:
                return ("Unknown");
        }
}

static const char *
reconf_to_str(uint8_t msgtype)
{
        switch (msgtype) {
        case DHCPV6_RECONF_RENEW:
                return ("Renew");
        case DHCPV6_RECONF_INFO:
                return ("Information-request");
        default:
                return ("Unknown");
        }
}

static const char *
authproto_to_str(uint8_t aproto)
{
        switch (aproto) {
        case DHCPV6_PROTO_DELAYED:
                return ("Delayed");
        case DHCPV6_PROTO_RECONFIG:
                return ("Reconfigure Key");
        default:
                return ("Unknown");
        }
}

static const char *
authalg_to_str(uint8_t aproto, uint8_t aalg)
{
        switch (aproto) {
        case DHCPV6_PROTO_DELAYED:
        case DHCPV6_PROTO_RECONFIG:
                switch (aalg) {
                case DHCPV6_ALG_HMAC_MD5:
                        return ("HMAC-MD5 Signature");
                default:
                        return ("Unknown");
                }
                break;
        default:
                return ("Unknown");
        }
}

static const char *
authrdm_to_str(uint8_t ardm)
{
        switch (ardm) {
        case DHCPV6_RDM_MONOCNT:
                return ("Monotonic Counter");
        default:
                return ("Unknown");
        }
}

static const char *
cwhat_to_str(uint8_t what)
{
        switch (what) {
        case DHCPV6_CWHAT_SERVER:
                return ("Server");
        case DHCPV6_CWHAT_NETWORK:
                return ("Network");
        case DHCPV6_CWHAT_CLIENT:
                return ("Client");
        default:
                return ("Unknown");
        }
}

static const char *
catype_to_str(uint8_t catype)
{
        switch (catype) {
        case CIVICADDR_LANG:
                return ("Language; RFC 2277");
        case CIVICADDR_A1:
                return ("National division (state)");
        case CIVICADDR_A2:
                return ("County");
        case CIVICADDR_A3:
                return ("City");
        case CIVICADDR_A4:
                return ("City division");
        case CIVICADDR_A5:
                return ("Neighborhood");
        case CIVICADDR_A6:
                return ("Street group");
        case CIVICADDR_PRD:
                return ("Leading street direction");
        case CIVICADDR_POD:
                return ("Trailing street suffix");
        case CIVICADDR_STS:
                return ("Street suffix or type");
        case CIVICADDR_HNO:
                return ("House number");
        case CIVICADDR_HNS:
                return ("House number suffix");
        case CIVICADDR_LMK:
                return ("Landmark");
        case CIVICADDR_LOC:
                return ("Additional location information");
        case CIVICADDR_NAM:
                return ("Name/occupant");
        case CIVICADDR_PC:
                return ("Postal Code/ZIP");
        case CIVICADDR_BLD:
                return ("Building");
        case CIVICADDR_UNIT:
                return ("Unit/apt/suite");
        case CIVICADDR_FLR:
                return ("Floor");
        case CIVICADDR_ROOM:
                return ("Room number");
        case CIVICADDR_TYPE:
                return ("Place type");
        case CIVICADDR_PCN:
                return ("Postal community name");
        case CIVICADDR_POBOX:
                return ("Post office box");
        case CIVICADDR_ADDL:
                return ("Additional code");
        case CIVICADDR_SEAT:
                return ("Seat/desk");
        case CIVICADDR_ROAD:
                return ("Primary road or street");
        case CIVICADDR_RSEC:
                return ("Road section");
        case CIVICADDR_RBRA:
                return ("Road branch");
        case CIVICADDR_RSBR:
                return ("Road sub-branch");
        case CIVICADDR_SPRE:
                return ("Street name pre-modifier");
        case CIVICADDR_SPOST:
                return ("Street name post-modifier");
        case CIVICADDR_SCRIPT:
                return ("Script");
        default:
                return ("Unknown");
        }
}

static void
show_hex(const uint8_t *data, int len, const char *name)
{
        char buffer[16 * 3 + 1];
        int nlen;
        int i;
        char sep;

        nlen = strlen(name);
        sep = '=';
        while (len > 0) {
                for (i = 0; i < 16 && i < len; i++)
                        (void) snprintf(buffer + 3 * i, 4, " %02x", *data++);
                (void) snprintf(get_line(0, 0), get_line_remain(), "%*s %c%s",
                    nlen, name, sep, buffer);
                name = "";
                sep = ' ';
                len -= i;
        }
}

static void
show_ascii(const uint8_t *data, int len, const char *name)
{
        char buffer[64], *bp;
        int nlen;
        int i;
        char sep;

        nlen = strlen(name);
        sep = '=';
        while (len > 0) {
                bp = buffer;
                for (i = 0; i < sizeof (buffer) - 4 && len > 0; len--) {
                        if (!isascii(*data) || !isprint(*data))
                                bp += snprintf(bp, 5, "\\%03o", *data++);
                        else
                                *bp++;
                }
                *bp = '\0';
                (void) snprintf(get_line(0, 0), get_line_remain(),
                    "%*s %c \"%s\"", nlen, name, sep, buffer);
                sep = ' ';
                name = "";
        }
}

static void
show_address(const char *addrname, const void *aptr)
{
        char *hname;
        char addrstr[INET6_ADDRSTRLEN];
        in6_addr_t addr;

        (void) memcpy(&addr, aptr, sizeof (in6_addr_t));
        (void) inet_ntop(AF_INET6, &addr, addrstr, sizeof (addrstr));
        hname = addrtoname(AF_INET6, &addr);
        if (strcmp(hname, addrstr) == 0) {
                (void) snprintf(get_line(0, 0), get_line_remain(), "%s = %s",
                    addrname, addrstr);
        } else {
                (void) snprintf(get_line(0, 0), get_line_remain(),
                    "%s = %s (%s)", addrname, addrstr, hname);
        }
}

static void
nest_options(const uint8_t *data, uint_t olen, char *prefix, char *title)
{
        char *str, *oldnest, *oldprefix;

        if (olen <= 0)
                return;
        oldprefix = prot_prefix;
        oldnest = prot_nest_prefix;
        str = malloc(strlen(prot_nest_prefix) + strlen(prot_prefix) + 1);
        if (str == NULL) {
                prot_nest_prefix = prot_prefix;
        } else {
                (void) sprintf(str, "%s%s", prot_nest_prefix, prot_prefix);
                prot_nest_prefix = str;
        }
        show_header(prefix, title, 0);
        show_options(data, olen);
        free(str);
        prot_prefix = oldprefix;
        prot_nest_prefix = oldnest;
}

static void
show_options(const uint8_t *data, int len)
{
        dhcpv6_option_t d6o;
        uint_t olen;
        uint16_t val16;
        uint16_t type;
        uint32_t val32;
        const uint8_t *ostart;
        char *str, *sp;
        char *oldnest;

        /*
         * Be very careful with negative numbers; ANSI signed/unsigned
         * comparison doesn't work as expected.
         */
        while (len >= (signed)sizeof (d6o)) {
                (void) memcpy(&d6o, data, sizeof (d6o));
                d6o.d6o_code = ntohs(d6o.d6o_code);
                d6o.d6o_len = olen = ntohs(d6o.d6o_len);
                (void) snprintf(get_line(0, 0), get_line_remain(),
                    "Option Code = %u (%s)", d6o.d6o_code,
                    option_to_str(d6o.d6o_code));
                ostart = data += sizeof (d6o);
                len -= sizeof (d6o);
                if (olen > len) {
                        (void) strlcpy(get_line(0, 0), "Option truncated",
                            get_line_remain());
                        olen = len;
                }
                switch (d6o.d6o_code) {
                case DHCPV6_OPT_CLIENTID:
                case DHCPV6_OPT_SERVERID:
                        if (olen < sizeof (val16))
                                break;
                        (void) memcpy(&val16, data, sizeof (val16));
                        data += sizeof (val16);
                        olen -= sizeof (val16);
                        type = ntohs(val16);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  DUID Type = %u (%s)", type,
                            duidtype_to_str(type));
                        if (type == DHCPV6_DUID_LLT || type == DHCPV6_DUID_LL) {
                                if (olen < sizeof (val16))
                                        break;
                                (void) memcpy(&val16, data, sizeof (val16));
                                data += sizeof (val16);
                                olen -= sizeof (val16);
                                val16 = ntohs(val16);
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  Hardware Type = %u (%s)", val16,
                                    arp_htype(val16));
                        }
                        if (type == DHCPV6_DUID_LLT) {
                                time_t timevalue;

                                if (olen < sizeof (val32))
                                        break;
                                (void) memcpy(&val32, data, sizeof (val32));
                                data += sizeof (val32);
                                olen -= sizeof (val32);
                                timevalue = ntohl(val32) + DUID_TIME_BASE;
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  Time = %lu (%.24s)", ntohl(val32),
                                    ctime(&timevalue));
                        }
                        if (type == DHCPV6_DUID_EN) {
                                if (olen < sizeof (val32))
                                        break;
                                (void) memcpy(&val32, data, sizeof (val32));
                                data += sizeof (val32);
                                olen -= sizeof (val32);
                                val32 = ntohl(val32);
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  Enterprise Number = %lu (%s)", val32,
                                    entr_to_str(val32));
                        }
                        if (olen == 0)
                                break;
                        if ((str = malloc(olen * 3)) == NULL)
                                pr_err("interpret_dhcpv6: no mem");
                        sp = str + snprintf(str, 3, "%02x", *data++);
                        while (--olen > 0) {
                                *sp++ = (type == DHCPV6_DUID_LLT ||
                                    type == DHCPV6_DUID_LL) ? ':' : ' ';
                                sp = sp + snprintf(sp, 3, "%02x", *data++);
                        }
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            (type == DHCPV6_DUID_LLT ||
                            type == DHCPV6_DUID_LL) ?
                            "  Link Layer Address = %s" :
                            "  Identifier = %s", str);
                        free(str);
                        break;
                case DHCPV6_OPT_IA_NA:
                case DHCPV6_OPT_IA_PD: {
                        dhcpv6_ia_na_t d6in;

                        if (olen < sizeof (d6in) - sizeof (d6o))
                                break;
                        (void) memcpy(&d6in, data - sizeof (d6o),
                            sizeof (d6in));
                        data += sizeof (d6in) - sizeof (d6o);
                        olen -= sizeof (d6in) - sizeof (d6o);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  IAID = %u", ntohl(d6in.d6in_iaid));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  T1 (renew) = %u seconds", ntohl(d6in.d6in_t1));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  T2 (rebind) = %u seconds", ntohl(d6in.d6in_t2));
                        nest_options(data, olen, "IA: ",
                            "Identity Association");
                        break;
                }
                case DHCPV6_OPT_IA_TA: {
                        dhcpv6_ia_ta_t d6it;

                        if (olen < sizeof (d6it) - sizeof (d6o))
                                break;
                        (void) memcpy(&d6it, data - sizeof (d6o),
                            sizeof (d6it));
                        data += sizeof (d6it) - sizeof (d6o);
                        olen -= sizeof (d6it) - sizeof (d6o);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  IAID = %u", ntohl(d6it.d6it_iaid));
                        nest_options(data, olen, "IA: ",
                            "Identity Association");
                        break;
                }
                case DHCPV6_OPT_IAADDR: {
                        dhcpv6_iaaddr_t d6ia;

                        if (olen < sizeof (d6ia) - sizeof (d6o))
                                break;
                        (void) memcpy(&d6ia, data - sizeof (d6o),
                            sizeof (d6ia));
                        data += sizeof (d6ia) - sizeof (d6o);
                        olen -= sizeof (d6ia) - sizeof (d6o);
                        show_address("  Address", &d6ia.d6ia_addr);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Preferred lifetime = %u seconds",
                            ntohl(d6ia.d6ia_preflife));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Valid lifetime = %u seconds",
                            ntohl(d6ia.d6ia_vallife));
                        nest_options(data, olen, "ADDR: ", "Address");
                        break;
                }
                case DHCPV6_OPT_ORO:
                        while (olen >= sizeof (val16)) {
                                (void) memcpy(&val16, data, sizeof (val16));
                                val16 = ntohs(val16);
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  Requested Option Code = %u (%s)", val16,
                                    option_to_str(val16));
                                data += sizeof (val16);
                                olen -= sizeof (val16);
                        }
                        break;
                case DHCPV6_OPT_PREFERENCE:
                        if (olen > 0) {
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    *data == 255 ?
                                    "  Preference = %u (immediate)" :
                                    "  Preference = %u", *data);
                        }
                        break;
                case DHCPV6_OPT_ELAPSED_TIME:
                        if (olen == sizeof (val16)) {
                                (void) memcpy(&val16, data, sizeof (val16));
                                val16 = ntohs(val16);
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  Elapsed Time = %u.%02u seconds",
                                    val16 / 100, val16 % 100);
                        }
                        break;
                case DHCPV6_OPT_RELAY_MSG:
                        if (olen > 0) {
                                oldnest = prot_nest_prefix;
                                prot_nest_prefix = prot_prefix;
                                (void) interpret_dhcpv6(F_DTAIL, data, olen);
                                prot_prefix = prot_nest_prefix;
                                prot_nest_prefix = oldnest;
                        }
                        break;
                case DHCPV6_OPT_AUTH: {
                        dhcpv6_auth_t d6a;

                        if (olen < DHCPV6_AUTH_SIZE - sizeof (d6o))
                                break;
                        (void) memcpy(&d6a, data - sizeof (d6o),
                            DHCPV6_AUTH_SIZE);
                        data += DHCPV6_AUTH_SIZE - sizeof (d6o);
                        olen += DHCPV6_AUTH_SIZE - sizeof (d6o);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Protocol = %u (%s)", d6a.d6a_proto,
                            authproto_to_str(d6a.d6a_proto));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Algorithm = %u (%s)", d6a.d6a_alg,
                            authalg_to_str(d6a.d6a_proto, d6a.d6a_alg));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Replay Detection Method = %u (%s)", d6a.d6a_rdm,
                            authrdm_to_str(d6a.d6a_rdm));
                        show_hex(d6a.d6a_replay, sizeof (d6a.d6a_replay),
                            "  RDM Data");
                        if (olen > 0)
                                show_hex(data, olen, "  Auth Info");
                        break;
                }
                case DHCPV6_OPT_UNICAST:
                        if (olen >= sizeof (in6_addr_t))
                                show_address("  Server Address", data);
                        break;
                case DHCPV6_OPT_STATUS_CODE:
                        if (olen < sizeof (val16))
                                break;
                        (void) memcpy(&val16, data, sizeof (val16));
                        val16 = ntohs(val16);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Status Code = %u (%s)", val16,
                            status_to_str(val16));
                        data += sizeof (val16);
                        olen -= sizeof (val16);
                        if (olen > 0)
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(), "  Text = \"%.*s\"",
                                    olen, data);
                        break;
                case DHCPV6_OPT_VENDOR_CLASS:
                        if (olen < sizeof (val32))
                                break;
                        (void) memcpy(&val32, data, sizeof (val32));
                        data += sizeof (val32);
                        olen -= sizeof (val32);
                        val32 = ntohl(val32);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Enterprise Number = %lu (%s)", val32,
                            entr_to_str(val32));
                        /* FALLTHROUGH */
                case DHCPV6_OPT_USER_CLASS:
                        while (olen >= sizeof (val16)) {
                                (void) memcpy(&val16, data, sizeof (val16));
                                data += sizeof (val16);
                                olen -= sizeof (val16);
                                val16 = ntohs(val16);
                                if (val16 > olen) {
                                        (void) strlcpy(get_line(0, 0),
                                            "  Truncated class",
                                            get_line_remain());
                                        val16 = olen;
                                }
                                show_hex(data, olen, "  Class");
                                data += val16;
                                olen -= val16;
                        }
                        break;
                case DHCPV6_OPT_VENDOR_OPT: {
                        dhcpv6_option_t sd6o;

                        if (olen < sizeof (val32))
                                break;
                        (void) memcpy(&val32, data, sizeof (val32));
                        data += sizeof (val32);
                        olen -= sizeof (val32);
                        val32 = ntohl(val32);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Enterprise Number = %lu (%s)", val32,
                            entr_to_str(val32));
                        while (olen >= sizeof (sd6o)) {
                                (void) memcpy(&sd6o, data, sizeof (sd6o));
                                sd6o.d6o_code = ntohs(sd6o.d6o_code);
                                sd6o.d6o_len = ntohs(sd6o.d6o_len);
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  Vendor Option Code = %u", d6o.d6o_code);
                                data += sizeof (d6o);
                                olen -= sizeof (d6o);
                                if (sd6o.d6o_len > olen) {
                                        (void) strlcpy(get_line(0, 0),
                                            "  Vendor Option truncated",
                                            get_line_remain());
                                        sd6o.d6o_len = olen;
                                }
                                if (sd6o.d6o_len > 0) {
                                        show_hex(data, sd6o.d6o_len,
                                            "    Data");
                                        data += sd6o.d6o_len;
                                        olen -= sd6o.d6o_len;
                                }
                        }
                        break;
                }
                case DHCPV6_OPT_REMOTE_ID:
                        if (olen < sizeof (val32))
                                break;
                        (void) memcpy(&val32, data, sizeof (val32));
                        data += sizeof (val32);
                        olen -= sizeof (val32);
                        val32 = ntohl(val32);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Enterprise Number = %lu (%s)", val32,
                            entr_to_str(val32));
                        /* FALLTHROUGH */
                case DHCPV6_OPT_INTERFACE_ID:
                case DHCPV6_OPT_SUBSCRIBER:
                        if (olen > 0)
                                show_hex(data, olen, "  ID");
                        break;
                case DHCPV6_OPT_RECONF_MSG:
                        if (olen > 0) {
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  Message Type = %u (%s)", *data,
                                    reconf_to_str(*data));
                        }
                        break;
                case DHCPV6_OPT_SIP_NAMES:
                case DHCPV6_OPT_DNS_SEARCH:
                case DHCPV6_OPT_NIS_DOMAIN:
                case DHCPV6_OPT_BCMCS_SRV_D: {
                        dhcp_symbol_t *symp;
                        char *sp2;

                        symp = inittab_getbycode(
                            ITAB_CAT_STANDARD | ITAB_CAT_V6, ITAB_CONS_SNOOP,
                            d6o.d6o_code);
                        if (symp != NULL) {
                                str = inittab_decode(symp, data, olen, B_TRUE);
                                if (str != NULL) {
                                        sp = str;
                                        do {
                                                sp2 = strchr(sp, ' ');
                                                if (sp2 != NULL)
                                                        *sp2++ = '\0';
                                                (void) snprintf(get_line(0, 0),
                                                    get_line_remain(),
                                                    "  Name = %s", sp);
                                        } while ((sp = sp2) != NULL);
                                        free(str);
                                }
                                free(symp);
                        }
                        break;
                }
                case DHCPV6_OPT_SIP_ADDR:
                case DHCPV6_OPT_DNS_ADDR:
                case DHCPV6_OPT_NIS_SERVERS:
                case DHCPV6_OPT_SNTP_SERVERS:
                case DHCPV6_OPT_BCMCS_SRV_A:
                        while (olen >= sizeof (in6_addr_t)) {
                                show_address("  Address", data);
                                data += sizeof (in6_addr_t);
                                olen -= sizeof (in6_addr_t);
                        }
                        break;
                case DHCPV6_OPT_IAPREFIX: {
                        dhcpv6_iaprefix_t d6ip;

                        if (olen < DHCPV6_IAPREFIX_SIZE - sizeof (d6o))
                                break;
                        (void) memcpy(&d6ip, data - sizeof (d6o),
                            DHCPV6_IAPREFIX_SIZE);
                        data += DHCPV6_IAPREFIX_SIZE - sizeof (d6o);
                        olen -= DHCPV6_IAPREFIX_SIZE - sizeof (d6o);
                        show_address("  Prefix", d6ip.d6ip_addr);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Preferred lifetime = %u seconds",
                            ntohl(d6ip.d6ip_preflife));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Valid lifetime = %u seconds",
                            ntohl(d6ip.d6ip_vallife));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Prefix length = %u", d6ip.d6ip_preflen);
                        nest_options(data, olen, "ADDR: ", "Address");
                        break;
                }
                case DHCPV6_OPT_INFO_REFTIME:
                        if (olen < sizeof (val32))
                                break;
                        (void) memcpy(&val32, data, sizeof (val32));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Refresh Time = %lu seconds", ntohl(val32));
                        break;
                case DHCPV6_OPT_GEOCONF_CVC: {
                        dhcpv6_civic_t d6c;
                        int solen;

                        if (olen < DHCPV6_CIVIC_SIZE - sizeof (d6o))
                                break;
                        (void) memcpy(&d6c, data - sizeof (d6o),
                            DHCPV6_CIVIC_SIZE);
                        data += DHCPV6_CIVIC_SIZE - sizeof (d6o);
                        olen -= DHCPV6_CIVIC_SIZE - sizeof (d6o);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  What Location = %u (%s)", d6c.d6c_what,
                            cwhat_to_str(d6c.d6c_what));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Country Code = %.*s", sizeof (d6c.d6c_cc),
                            d6c.d6c_cc);
                        while (olen >= 2) {
                                (void) snprintf(get_line(0, 0),
                                    get_line_remain(),
                                    "  CA Element = %u (%s)", *data,
                                    catype_to_str(*data));
                                solen = data[1];
                                data += 2;
                                olen -= 2;
                                if (solen > olen) {
                                        (void) strlcpy(get_line(0, 0),
                                            "  CA Element truncated",
                                            get_line_remain());
                                        solen = olen;
                                }
                                if (solen > 0) {
                                        show_ascii(data, solen, "  CA Data");
                                        data += solen;
                                        olen -= solen;
                                }
                        }
                        break;
                }
                case DHCPV6_OPT_CLIENT_FQDN: {
                        dhcp_symbol_t *symp;

                        if (olen == 0)
                                break;
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "  Flags = %02x", *data);
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "        %s", getflag(*data, DHCPV6_FQDNF_S,
                            "Perform AAAA RR updates", "No AAAA RR updates"));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "        %s", getflag(*data, DHCPV6_FQDNF_O,
                            "Server override updates",
                            "No server override updates"));
                        (void) snprintf(get_line(0, 0), get_line_remain(),
                            "        %s", getflag(*data, DHCPV6_FQDNF_N,
                            "Server performs no updates",
                            "Server performs updates"));
                        symp = inittab_getbycode(
                            ITAB_CAT_STANDARD | ITAB_CAT_V6, ITAB_CONS_SNOOP,
                            d6o.d6o_code);
                        if (symp != NULL) {
                                str = inittab_decode(symp, data, olen, B_TRUE);
                                if (str != NULL) {
                                        (void) snprintf(get_line(0, 0),
                                            get_line_remain(),
                                            "  FQDN = %s", str);
                                        free(str);
                                }
                                free(symp);
                        }
                        break;
                }
                }
                data = ostart + d6o.d6o_len;
                len -= d6o.d6o_len;
        }
        if (len != 0) {
                (void) strlcpy(get_line(0, 0), "Option entry truncated",
                    get_line_remain());
        }
}