/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>

#include <sys/stropts.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#include <netinet/if_ether.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <tsol/label.h>
#include <sys/tsol/tndb.h>
#include <sys/tsol/label_macro.h>

#include "snoop.h"


/*
 * IPv6 extension header masks.  These are used by the print_ipv6_extensions()
 * function to return information to the caller about which extension headers
 * were processed.  This can be useful if the caller wants to know if the
 * packet is an IPv6 fragment, for example.
 */
#define	SNOOP_HOPOPTS	0x01U
#define	SNOOP_ROUTING	0x02U
#define	SNOOP_DSTOPTS	0x04U
#define	SNOOP_FRAGMENT	0x08U
#define	SNOOP_AH	0x10U
#define	SNOOP_ESP	0x20U
#define	SNOOP_IPV6	0x40U

static void prt_routing_hdr(int, const struct ip6_rthdr *);
static void prt_fragment_hdr(int, const struct ip6_frag *);
static void prt_hbh_options(int, const struct ip6_hbh *);
static void prt_dest_options(int, const struct ip6_dest *);
static void print_route(const uchar_t *);
static void print_ipoptions(const uchar_t *, int);
static void print_ripso(const uchar_t *);
static void print_cipso(const uchar_t *);

/* Keep track of how many nested IP headers we have. */
unsigned int encap_levels;
unsigned int total_encap_levels = 1;

int
interpret_ip(int flags, const struct ip *ip, int fraglen)
{
	uchar_t *data;
	char buff[24];
	boolean_t isfrag = B_FALSE;
	boolean_t morefrag;
	uint16_t fragoffset;
	int hdrlen;
	uint16_t iplen, uitmp;

	if (ip->ip_v == IPV6_VERSION) {
		iplen = interpret_ipv6(flags, (ip6_t *)ip, fraglen);
		return (iplen);
	}

	if (encap_levels == 0)
		total_encap_levels = 0;
	encap_levels++;
	total_encap_levels++;

	hdrlen = ip->ip_hl * 4;
	data = ((uchar_t *)ip) + hdrlen;
	iplen = ntohs(ip->ip_len) - hdrlen;
	fraglen -= hdrlen;
	if (fraglen > iplen)
		fraglen = iplen;
	if (fraglen < 0) {
		(void) snprintf(get_sum_line(), MAXLINE,
		    "IP truncated: header missing %d bytes", -fraglen);
		encap_levels--;
		return (fraglen + iplen);
	}
	/*
	 * We flag this as a fragment if the more fragments bit is set, or
	 * if the fragment offset is non-zero.
	 */
	morefrag = (ntohs(ip->ip_off) & IP_MF) == 0 ? B_FALSE : B_TRUE;
	fragoffset = (ntohs(ip->ip_off) & 0x1FFF) * 8;
	if (morefrag || fragoffset != 0)
		isfrag = B_TRUE;

	src_name = addrtoname(AF_INET, &ip->ip_src);
	dst_name = addrtoname(AF_INET, &ip->ip_dst);

	if (flags & F_SUM) {
		if (isfrag) {
			(void) snprintf(get_sum_line(), MAXLINE,
			    "%s IP fragment ID=%d Offset=%-4d MF=%d TOS=0x%x "
			    "TTL=%d",
			    getproto(ip->ip_p),
			    ntohs(ip->ip_id),
			    fragoffset,
			    morefrag,
			    ip->ip_tos,
			    ip->ip_ttl);
		} else {
			(void) strlcpy(buff, inet_ntoa(ip->ip_dst),
			    sizeof (buff));
			uitmp = ntohs(ip->ip_len);
			(void) snprintf(get_sum_line(), MAXLINE,
			    "IP  D=%s S=%s LEN=%u%s, ID=%d, TOS=0x%x, TTL=%d",
			    buff,
			    inet_ntoa(ip->ip_src),
			    uitmp,
			    iplen > fraglen ? "?" : "",
			    ntohs(ip->ip_id),
			    ip->ip_tos,
			    ip->ip_ttl);
		}
	}

	if (flags & F_DTAIL) {
		show_header("IP:   ", "IP Header", iplen);
		show_space();
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Version = %d", ip->ip_v);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Header length = %d bytes", hdrlen);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Type of service = 0x%02x", ip->ip_tos);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "      xxx. .... = %d (precedence)",
		    ip->ip_tos >> 5);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "      %s", getflag(ip->ip_tos, IPTOS_LOWDELAY,
		    "low delay", "normal delay"));
		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
		    getflag(ip->ip_tos, IPTOS_THROUGHPUT,
		    "high throughput", "normal throughput"));
		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
		    getflag(ip->ip_tos, IPTOS_RELIABILITY,
		    "high reliability", "normal reliability"));
		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
		    getflag(ip->ip_tos, IPTOS_ECT,
		    "ECN capable transport", "not ECN capable transport"));
		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
		    getflag(ip->ip_tos, IPTOS_CE,
		    "ECN congestion experienced",
		    "no ECN congestion experienced"));
		/* warning: ip_len is signed in netinet/ip.h */
		uitmp = ntohs(ip->ip_len);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Total length = %u bytes%s", uitmp,
		    iplen > fraglen ? " -- truncated" : "");
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Identification = %d", ntohs(ip->ip_id));
		/* warning: ip_off is signed in netinet/ip.h */
		uitmp = ntohs(ip->ip_off);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Flags = 0x%x", uitmp >> 12);
		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
		    getflag(uitmp >> 8, IP_DF >> 8,
		    "do not fragment", "may fragment"));
		(void) snprintf(get_line(0, 0), get_line_remain(), "      %s",
		    getflag(uitmp >> 8, IP_MF >> 8,
		    "more fragments", "last fragment"));
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Fragment offset = %u bytes",
		    fragoffset);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Time to live = %d seconds/hops",
		    ip->ip_ttl);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Protocol = %d (%s)", ip->ip_p,
		    getproto(ip->ip_p));
		/*
		 * XXX need to compute checksum and print whether it's correct
		 */
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Header checksum = %04x",
		    ntohs(ip->ip_sum));
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Source address = %s, %s",
		    inet_ntoa(ip->ip_src), addrtoname(AF_INET, &ip->ip_src));
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Destination address = %s, %s",
		    inet_ntoa(ip->ip_dst), addrtoname(AF_INET, &ip->ip_dst));

		/* Print IP options - if any */

		print_ipoptions((const uchar_t *)(ip + 1),
		    hdrlen - sizeof (struct ip));
		show_space();
	}

	/*
	 * If we are in detail mode, and this is not the first fragment of
	 * a fragmented packet, print out a little line stating this.
	 * Otherwise, go to the next protocol layer only if this is not a
	 * fragment, or we are in detail mode and this is the first fragment
	 * of a fragmented packet.
	 */
	if (flags & F_DTAIL && fragoffset != 0) {
		(void) snprintf(get_detail_line(0, 0), MAXLINE,
		    "%s:  [%d byte(s) of data, continuation of IP ident=%d]",
		    getproto(ip->ip_p),
		    iplen,
		    ntohs(ip->ip_id));
	} else if (!isfrag || (flags & F_DTAIL) && isfrag && fragoffset == 0) {
		/* go to the next protocol layer */

		if (fraglen > 0) {
			switch (ip->ip_p) {
			case IPPROTO_IP:
				break;
			case IPPROTO_ENCAP:
				(void) interpret_ip(flags,
				    /* LINTED: alignment */
				    (const struct ip *)data, fraglen);
				break;
			case IPPROTO_ICMP:
				(void) interpret_icmp(flags,
				    /* LINTED: alignment */
				    (struct icmp *)data, iplen, fraglen);
				break;
			case IPPROTO_IGMP:
				interpret_igmp(flags, data, iplen, fraglen);
				break;
			case IPPROTO_GGP:
				break;
			case IPPROTO_TCP:
				(void) interpret_tcp(flags,
				    (struct tcphdr *)data, iplen, fraglen);
				break;

			case IPPROTO_ESP:
				(void) interpret_esp(flags, data, iplen,
				    fraglen);
				break;
			case IPPROTO_AH:
				(void) interpret_ah(flags, data, iplen,
				    fraglen);
				break;

			case IPPROTO_OSPF:
				interpret_ospf(flags, data, iplen, fraglen);
				break;

			case IPPROTO_EGP:
			case IPPROTO_PUP:
				break;
			case IPPROTO_UDP:
				(void) interpret_udp(flags,
				    (struct udphdr *)data, iplen, fraglen);
				break;

			case IPPROTO_IDP:
			case IPPROTO_HELLO:
			case IPPROTO_ND:
			case IPPROTO_RAW:
				break;
			case IPPROTO_IPV6:	/* IPV6 encap */
				/* LINTED: alignment */
				(void) interpret_ipv6(flags, (ip6_t *)data,
				    iplen);
				break;
			case IPPROTO_SCTP:
				(void) interpret_sctp(flags,
				    (struct sctp_hdr *)data, iplen, fraglen);
				break;
			}
		}
	}

	encap_levels--;
	return (iplen);
}

int
interpret_ipv6(int flags, const ip6_t *ip6h, int fraglen)
{
	uint8_t *data;
	int hdrlen, iplen;
	int version, flow, class;
	uchar_t proto;
	boolean_t isfrag = B_FALSE;
	uint8_t extmask;
	/*
	 * The print_srcname and print_dstname strings are the hostname
	 * parts of the verbose IPv6 header output, including the comma
	 * and the space after the litteral address strings.
	 */
	char print_srcname[MAXHOSTNAMELEN + 2];
	char print_dstname[MAXHOSTNAMELEN + 2];
	char src_addrstr[INET6_ADDRSTRLEN];
	char dst_addrstr[INET6_ADDRSTRLEN];

	iplen = ntohs(ip6h->ip6_plen);
	hdrlen = IPV6_HDR_LEN;
	fraglen -= hdrlen;
	if (fraglen < 0)
		return (fraglen + hdrlen);
	data = ((uint8_t *)ip6h) + hdrlen;

	proto = ip6h->ip6_nxt;

	src_name = addrtoname(AF_INET6, &ip6h->ip6_src);
	dst_name = addrtoname(AF_INET6, &ip6h->ip6_dst);

	/*
	 * Use endian-aware masks to extract traffic class and
	 * flowinfo.  Also, flowinfo is now 20 bits and class 8
	 * rather than 24 and 4.
	 */
	class = ntohl((ip6h->ip6_vcf & IPV6_FLOWINFO_TCLASS) >> 20);
	flow = ntohl(ip6h->ip6_vcf & IPV6_FLOWINFO_FLOWLABEL);

	/*
	 * NOTE: the F_SUM and F_DTAIL flags are mutually exclusive,
	 * so the code within the first part of the following if statement
	 * will not affect the detailed printing of the packet.
	 */
	if (flags & F_SUM) {
		(void) snprintf(get_sum_line(), MAXLINE,
		    "IPv6  S=%s D=%s LEN=%d HOPS=%d CLASS=0x%x FLOW=0x%x",
		    src_name, dst_name, iplen, ip6h->ip6_hops, class, flow);
	} else if (flags & F_DTAIL) {

		(void) inet_ntop(AF_INET6, &ip6h->ip6_src, src_addrstr,
		    INET6_ADDRSTRLEN);
		(void) inet_ntop(AF_INET6, &ip6h->ip6_dst, dst_addrstr,
		    INET6_ADDRSTRLEN);

		version = ntohl(ip6h->ip6_vcf) >> 28;

		if (strcmp(src_name, src_addrstr) == 0) {
			print_srcname[0] = '\0';
		} else {
			snprintf(print_srcname, sizeof (print_srcname),
			    ", %s", src_name);
		}

		if (strcmp(dst_name, dst_addrstr) == 0) {
			print_dstname[0] = '\0';
		} else {
			snprintf(print_dstname, sizeof (print_dstname),
			    ", %s", dst_name);
		}

		show_header("IPv6:   ", "IPv6 Header", iplen);
		show_space();

		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Version = %d", version);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Traffic Class = %d", class);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Flow label = 0x%x", flow);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Payload length = %d", iplen);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Next Header = %d (%s)", proto,
		    getproto(proto));
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Hop Limit = %d", ip6h->ip6_hops);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Source address = %s%s", src_addrstr, print_srcname);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Destination address = %s%s", dst_addrstr, print_dstname);

		show_space();
	}

	/*
	 * Print IPv6 Extension Headers, or skip them in the summary case.
	 * Set isfrag to true if one of the extension headers encounterred
	 * was a fragment header.
	 */
	if (proto == IPPROTO_HOPOPTS || proto == IPPROTO_DSTOPTS ||
	    proto == IPPROTO_ROUTING || proto == IPPROTO_FRAGMENT) {
		extmask = print_ipv6_extensions(flags, &data, &proto, &iplen,
		    &fraglen);
		if ((extmask & SNOOP_FRAGMENT) != 0) {
			isfrag = B_TRUE;
		}
	}

	/*
	 * We only want to print upper layer information if this is not
	 * a fragment, or if we're printing in detail.  Note that the
	 * proto variable will be set to IPPROTO_NONE if this is a fragment
	 * with a non-zero fragment offset.
	 */
	if (!isfrag || flags & F_DTAIL) {
		/* go to the next protocol layer */

		switch (proto) {
		case IPPROTO_IP:
			break;
		case IPPROTO_ENCAP:
			/* LINTED: alignment */
			(void) interpret_ip(flags, (const struct ip *)data,
			    fraglen);
			break;
		case IPPROTO_ICMPV6:
			/* LINTED: alignment */
			(void) interpret_icmpv6(flags, (icmp6_t *)data, iplen,
			    fraglen);
			break;
		case IPPROTO_IGMP:
			interpret_igmp(flags, data, iplen, fraglen);
			break;
		case IPPROTO_GGP:
			break;
		case IPPROTO_TCP:
			(void) interpret_tcp(flags, (struct tcphdr *)data,
			    iplen, fraglen);
			break;
		case IPPROTO_ESP:
			(void) interpret_esp(flags, data, iplen, fraglen);
			break;
		case IPPROTO_AH:
			(void) interpret_ah(flags, data, iplen, fraglen);
			break;
		case IPPROTO_EGP:
		case IPPROTO_PUP:
			break;
		case IPPROTO_UDP:
			(void) interpret_udp(flags, (struct udphdr *)data,
			    iplen, fraglen);
			break;
		case IPPROTO_IDP:
		case IPPROTO_HELLO:
		case IPPROTO_ND:
		case IPPROTO_RAW:
			break;
		case IPPROTO_IPV6:
			/* LINTED: alignment */
			(void) interpret_ipv6(flags, (const ip6_t *)data,
			    iplen);
			break;
		case IPPROTO_SCTP:
			(void) interpret_sctp(flags, (struct sctp_hdr *)data,
			    iplen, fraglen);
			break;
		case IPPROTO_OSPF:
			interpret_ospf6(flags, data, iplen, fraglen);
			break;
		}
	}

	return (iplen);
}

/*
 * ip_ext: data including the extension header.
 * iplen: length of the data remaining in the packet.
 * Returns a mask of IPv6 extension headers it processed.
 */
uint8_t
print_ipv6_extensions(int flags, uint8_t **hdr, uint8_t *next, int *iplen,
    int *fraglen)
{
	uint8_t *data_ptr;
	uchar_t proto = *next;
	boolean_t is_extension_header;
	struct ip6_hbh *ipv6ext_hbh;
	struct ip6_dest *ipv6ext_dest;
	struct ip6_rthdr *ipv6ext_rthdr;
	struct ip6_frag *ipv6ext_frag;
	uint32_t exthdrlen;
	uint8_t extmask = 0;

	if ((hdr == NULL) || (*hdr == NULL) || (next == NULL) || (iplen == 0))
		return (0);

	data_ptr = *hdr;
	is_extension_header = B_TRUE;
	while (is_extension_header) {

		/*
		 * There must be at least enough data left to read the
		 * next header and header length fields from the next
		 * header.
		 */
		if (*fraglen < 2) {
			return (extmask);
		}

		switch (proto) {
		case IPPROTO_HOPOPTS:
			ipv6ext_hbh = (struct ip6_hbh *)data_ptr;
			exthdrlen = 8 + ipv6ext_hbh->ip6h_len * 8;
			if (*fraglen <= exthdrlen) {
				return (extmask);
			}
			prt_hbh_options(flags, ipv6ext_hbh);
			extmask |= SNOOP_HOPOPTS;
			proto = ipv6ext_hbh->ip6h_nxt;
			break;
		case IPPROTO_DSTOPTS:
			ipv6ext_dest = (struct ip6_dest *)data_ptr;
			exthdrlen = 8 + ipv6ext_dest->ip6d_len * 8;
			if (*fraglen <= exthdrlen) {
				return (extmask);
			}
			prt_dest_options(flags, ipv6ext_dest);
			extmask |= SNOOP_DSTOPTS;
			proto = ipv6ext_dest->ip6d_nxt;
			break;
		case IPPROTO_ROUTING:
			ipv6ext_rthdr = (struct ip6_rthdr *)data_ptr;
			exthdrlen = 8 + ipv6ext_rthdr->ip6r_len * 8;
			if (*fraglen <= exthdrlen) {
				return (extmask);
			}
			prt_routing_hdr(flags, ipv6ext_rthdr);
			extmask |= SNOOP_ROUTING;
			proto = ipv6ext_rthdr->ip6r_nxt;
			break;
		case IPPROTO_FRAGMENT:
			/* LINTED: alignment */
			ipv6ext_frag = (struct ip6_frag *)data_ptr;
			exthdrlen = sizeof (struct ip6_frag);
			if (*fraglen <= exthdrlen) {
				return (extmask);
			}
			prt_fragment_hdr(flags, ipv6ext_frag);
			extmask |= SNOOP_FRAGMENT;
			/*
			 * If this is not the first fragment, forget about
			 * the rest of the packet, snoop decoding is
			 * stateless.
			 */
			if ((ipv6ext_frag->ip6f_offlg & IP6F_OFF_MASK) != 0)
				proto = IPPROTO_NONE;
			else
				proto = ipv6ext_frag->ip6f_nxt;
			break;
		default:
			is_extension_header = B_FALSE;
			break;
		}

		if (is_extension_header) {
			*iplen -= exthdrlen;
			*fraglen -= exthdrlen;
			data_ptr += exthdrlen;
		}
	}

	*next = proto;
	*hdr = data_ptr;
	return (extmask);
}

static void
print_ipoptions(const uchar_t *opt, int optlen)
{
	int len;
	int remain;
	char *line;
	const char *truncstr;

	if (optlen <= 0) {
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "No options");
		return;
	}

	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Options: (%d bytes)", optlen);

	while (optlen > 0) {
		line = get_line(0, 0);
		remain = get_line_remain();
		len = opt[1];
		truncstr = len > optlen ? "?" : "";
		switch (opt[0]) {
		case IPOPT_EOL:
			(void) strlcpy(line, "  - End of option list", remain);
			return;
		case IPOPT_NOP:
			(void) strlcpy(line, "  - No op", remain);
			len = 1;
			break;
		case IPOPT_RR:
			(void) snprintf(line, remain,
			    "  - Record route (%d bytes%s)", len, truncstr);
			print_route(opt);
			break;
		case IPOPT_TS:
			(void) snprintf(line, remain,
			    "  - Time stamp (%d bytes%s)", len, truncstr);
			break;
		case IPOPT_SECURITY:
			(void) snprintf(line, remain, "  - RIPSO (%d bytes%s)",
			    len, truncstr);
			print_ripso(opt);
			break;
		case IPOPT_COMSEC:
			(void) snprintf(line, remain, "  - CIPSO (%d bytes%s)",
			    len, truncstr);
			print_cipso(opt);
			break;
		case IPOPT_LSRR:
			(void) snprintf(line, remain,
			    "  - Loose source route (%d bytes%s)", len,
			    truncstr);
			print_route(opt);
			break;
		case IPOPT_SATID:
			(void) snprintf(line, remain,
			    "  - SATNET Stream id (%d bytes%s)",
			    len, truncstr);
			break;
		case IPOPT_SSRR:
			(void) snprintf(line, remain,
			    "  - Strict source route, (%d bytes%s)", len,
			    truncstr);
			print_route(opt);
			break;
		default:
			(void) snprintf(line, remain,
			    "  - Option %d (unknown - %d bytes%s) %s",
			    opt[0], len, truncstr,
			    tohex((char *)&opt[2], len - 2));
			break;
		}
		if (len <= 0) {
			(void) snprintf(line, remain,
			    "  - Incomplete option len %d", len);
			break;
		}
		opt += len;
		optlen -= len;
	}
}

static void
print_route(const uchar_t *opt)
{
	int len, pointer, remain;
	struct in_addr addr;
	char *line;

	len = opt[1];
	pointer = opt[2];

	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "    Pointer = %d", pointer);

	pointer -= IPOPT_MINOFF;
	opt += (IPOPT_OFFSET + 1);
	len -= (IPOPT_OFFSET + 1);

	while (len > 0) {
		line = get_line(0, 0);
		remain = get_line_remain();
		memcpy((char *)&addr, opt, sizeof (addr));
		if (addr.s_addr == INADDR_ANY)
			(void) strlcpy(line, "      -", remain);
		else
			(void) snprintf(line, remain, "      %s",
			    addrtoname(AF_INET, &addr));
		if (pointer == 0)
			(void) strlcat(line, "  <-- (current)", remain);

		opt += sizeof (addr);
		len -= sizeof (addr);
		pointer -= sizeof (addr);
	}
}

char *
getproto(int p)
{
	switch (p) {
	case IPPROTO_HOPOPTS:	return ("IPv6-HopOpts");
	case IPPROTO_IPV6:	return ("IPv6");
	case IPPROTO_ROUTING:	return ("IPv6-Route");
	case IPPROTO_FRAGMENT:	return ("IPv6-Frag");
	case IPPROTO_RSVP:	return ("RSVP");
	case IPPROTO_ENCAP:	return ("IP-in-IP");
	case IPPROTO_AH:	return ("AH");
	case IPPROTO_ESP:	return ("ESP");
	case IPPROTO_ICMP:	return ("ICMP");
	case IPPROTO_ICMPV6:	return ("ICMPv6");
	case IPPROTO_DSTOPTS:	return ("IPv6-DstOpts");
	case IPPROTO_IGMP:	return ("IGMP");
	case IPPROTO_GGP:	return ("GGP");
	case IPPROTO_TCP:	return ("TCP");
	case IPPROTO_EGP:	return ("EGP");
	case IPPROTO_PUP:	return ("PUP");
	case IPPROTO_UDP:	return ("UDP");
	case IPPROTO_IDP:	return ("IDP");
	case IPPROTO_HELLO:	return ("HELLO");
	case IPPROTO_ND:	return ("ND");
	case IPPROTO_EON:	return ("EON");
	case IPPROTO_RAW:	return ("RAW");
	case IPPROTO_OSPF:	return ("OSPF");
	default:		return ("");
	}
}

static void
prt_routing_hdr(int flags, const struct ip6_rthdr *ipv6ext_rthdr)
{
	uint8_t nxt_hdr;
	uint8_t type;
	uint32_t len;
	uint8_t segleft;
	uint32_t numaddrs;
	int i;
	struct ip6_rthdr0 *ipv6ext_rthdr0;
	struct in6_addr *addrs;
	char addr[INET6_ADDRSTRLEN];

	/* in summary mode, we don't do anything. */
	if (flags & F_SUM) {
		return;
	}

	nxt_hdr = ipv6ext_rthdr->ip6r_nxt;
	type = ipv6ext_rthdr->ip6r_type;
	len = 8 * (ipv6ext_rthdr->ip6r_len + 1);
	segleft = ipv6ext_rthdr->ip6r_segleft;

	show_header("IPv6-Route:  ", "IPv6 Routing Header", 0);
	show_space();

	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Next header = %d (%s)", nxt_hdr, getproto(nxt_hdr));
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Header length = %d", len);
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Routing type = %d", type);
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Segments left = %d", segleft);

	if (type == IPV6_RTHDR_TYPE_0) {
		/*
		 * XXX This loop will print all addresses in the routing header,
		 * XXX not just the segments left.
		 * XXX (The header length field is twice the number of
		 * XXX addresses)
		 * XXX At some future time, we may want to change this
		 * XXX to differentiate between the hops yet to do
		 * XXX and the hops already taken.
		 */
		/* LINTED: alignment */
		ipv6ext_rthdr0 = (struct ip6_rthdr0 *)ipv6ext_rthdr;
		numaddrs = ipv6ext_rthdr0->ip6r0_len / 2;
		addrs = (struct in6_addr *)(ipv6ext_rthdr0 + 1);
		for (i = 0; i < numaddrs; i++) {
			(void) inet_ntop(AF_INET6, &addrs[i], addr,
			    INET6_ADDRSTRLEN);
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "address[%d]=%s", i, addr);
		}
	}

	show_space();
}

static void
prt_fragment_hdr(int flags, const struct ip6_frag *ipv6ext_frag)
{
	boolean_t morefrag;
	uint16_t fragoffset;
	uint8_t nxt_hdr;
	uint32_t fragident;

	/* extract the various fields from the fragment header */
	nxt_hdr = ipv6ext_frag->ip6f_nxt;
	morefrag = (ipv6ext_frag->ip6f_offlg & IP6F_MORE_FRAG) == 0
	    ? B_FALSE : B_TRUE;
	fragoffset = ntohs(ipv6ext_frag->ip6f_offlg & IP6F_OFF_MASK);
	fragident = ntohl(ipv6ext_frag->ip6f_ident);

	if (flags & F_SUM) {
		(void) snprintf(get_sum_line(), MAXLINE,
		    "IPv6 fragment ID=%u Offset=%-4d MF=%d",
		    fragident,
		    fragoffset,
		    morefrag);
	} else { /* F_DTAIL */
		show_header("IPv6-Frag:  ", "IPv6 Fragment Header", 0);
		show_space();

		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Next Header = %d (%s)", nxt_hdr, getproto(nxt_hdr));
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Fragment Offset = %d", fragoffset);
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "More Fragments Flag = %s", morefrag ? "true" : "false");
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Identification = %u", fragident);

		show_space();
	}
}

static void
print_ip6opt_ls(const uchar_t *data, unsigned int op_len)
{
	uint32_t doi;
	uint8_t sotype, solen;
	uint16_t value, value2;
	char *cp;
	int remlen;
	boolean_t printed;

	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Labeled Security Option len = %u bytes%s", op_len,
	    op_len < sizeof (uint32_t) || (op_len & 1) != 0 ? "?" : "");
	if (op_len < sizeof (uint32_t))
		return;
	GETINT32(doi, data);
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "    DOI = %d (%s)", doi, doi == IP6LS_DOI_V4 ? "IPv4" : "???");
	op_len -= sizeof (uint32_t);
	while (op_len > 0) {
		GETINT8(sotype, data);
		if (op_len < 2) {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "    truncated %u suboption (no len)", sotype);
			break;
		}
		GETINT8(solen, data);
		if (solen < 2 || solen > op_len) {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "    bad %u suboption (len 2 <= %u <= %u)",
			    sotype, solen, op_len);
			if (solen < 2)
				solen = 2;
			if (solen > op_len)
				solen = op_len;
		}
		op_len -= solen;
		solen -= 2;
		cp = get_line(0, 0);
		remlen = get_line_remain();
		(void) strlcpy(cp, "    ", remlen);
		cp += 4;
		remlen -= 4;
		printed = B_TRUE;
		switch (sotype) {
		case IP6LS_TT_LEVEL:
			if (solen != 2) {
				printed = B_FALSE;
				break;
			}
			GETINT16(value, data);
			(void) snprintf(cp, remlen, "Level %u", value);
			solen = 0;
			break;
		case IP6LS_TT_VECTOR:
			(void) strlcpy(cp, "Bit-Vector: ", remlen);
			remlen -= strlen(cp);
			cp += strlen(cp);
			while (solen > 1) {
				GETINT16(value, data);
				solen -= 2;
				(void) snprintf(cp, remlen, "%04x", value);
				remlen -= strlen(cp);
				cp += strlen(cp);
			}
			break;
		case IP6LS_TT_ENUM:
			(void) strlcpy(cp, "Enumeration:", remlen);
			remlen -= strlen(cp);
			cp += strlen(cp);
			while (solen > 1) {
				GETINT16(value, data);
				solen -= 2;
				(void) snprintf(cp, remlen, " %u", value);
				remlen -= strlen(cp);
				cp += strlen(cp);
			}
			break;
		case IP6LS_TT_RANGES:
			(void) strlcpy(cp, "Ranges:", remlen);
			remlen -= strlen(cp);
			cp += strlen(cp);
			while (solen > 3) {
				GETINT16(value, data);
				GETINT16(value2, data);
				solen -= 4;
				(void) snprintf(cp, remlen, " %u-%u", value,
				    value2);
				remlen -= strlen(cp);
				cp += strlen(cp);
			}
			break;
		case IP6LS_TT_V4:
			(void) strlcpy(cp, "IPv4 Option", remlen);
			print_ipoptions(data, solen);
			solen = 0;
			break;
		case IP6LS_TT_DEST:
			(void) snprintf(cp, remlen,
			    "Destination-Only Data length %u", solen);
			solen = 0;
			break;
		default:
			(void) snprintf(cp, remlen,
			    "    unknown %u suboption (len %u)", sotype, solen);
			solen = 0;
			break;
		}
		if (solen != 0) {
			if (printed) {
				cp = get_line(0, 0);
				remlen = get_line_remain();
			}
			(void) snprintf(cp, remlen,
			    "    malformed %u suboption (remaining %u)",
			    sotype, solen);
			data += solen;
		}
	}
}

static void
prt_hbh_options(int flags, const struct ip6_hbh *ipv6ext_hbh)
{
	const uint8_t *data, *ndata;
	uint32_t len;
	uint8_t op_type;
	uint8_t op_len;
	uint8_t nxt_hdr;

	/* in summary mode, we don't do anything. */
	if (flags & F_SUM) {
		return;
	}

	show_header("IPv6-HopOpts:  ", "IPv6 Hop-by-Hop Options Header", 0);
	show_space();

	/*
	 * Store the lengh of this ext hdr in bytes.  The caller has
	 * ensured that there is at least len bytes of data left.
	 */
	len = ipv6ext_hbh->ip6h_len * 8 + 8;

	ndata = (const uint8_t *)ipv6ext_hbh + 2;
	len -= 2;

	nxt_hdr = ipv6ext_hbh->ip6h_nxt;
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Next Header = %u (%s)", nxt_hdr, getproto(nxt_hdr));

	while (len > 0) {
		data = ndata;
		GETINT8(op_type, data);
		/* This is the only one-octet IPv6 option */
		if (op_type == IP6OPT_PAD1) {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "pad1 option ");
			len--;
			ndata = data;
			continue;
		}
		GETINT8(op_len, data);
		if (len < 2 || op_len + 2 > len) {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Error: option %u truncated (%u + 2 > %u)",
			    op_type, op_len, len);
			op_len = len - 2;
			/*
			 * Continue processing the malformed option so that we
			 * can display as much as possible.
			 */
		}

		/* advance pointers to the next option */
		len -= op_len + 2;
		ndata = data + op_len;

		/* process this option */
		switch (op_type) {
		case IP6OPT_PADN:
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "padN option len = %u", op_len);
			break;
		case IP6OPT_JUMBO: {
			uint32_t payload_len;

			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Jumbo Payload Option len = %u bytes%s", op_len,
			    op_len == sizeof (uint32_t) ? "" : "?");
			if (op_len == sizeof (uint32_t)) {
				GETINT32(payload_len, data);
				(void) snprintf(get_line(0, 0),
				    get_line_remain(),
				    "Jumbo Payload Length = %u bytes",
				    payload_len);
			}
			break;
		}
		case IP6OPT_ROUTER_ALERT: {
			uint16_t value;
			const char *label[] = {"MLD", "RSVP", "AN"};

			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Router Alert Option len = %u bytes%s", op_len,
			    op_len == sizeof (uint16_t) ? "" : "?");
			if (op_len == sizeof (uint16_t)) {
				GETINT16(value, data);
				(void) snprintf(get_line(0, 0),
				    get_line_remain(),
				    "Alert Type = %d (%s)", value,
				    value < sizeof (label) / sizeof (label[0]) ?
				    label[value] : "???");
			}
			break;
		}
		case IP6OPT_LS:
			print_ip6opt_ls(data, op_len);
			break;
		default:
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Option type = %u, len = %u", op_type, op_len);
			break;
		}
	}

	show_space();
}

static void
prt_dest_options(int flags, const struct ip6_dest *ipv6ext_dest)
{
	const uint8_t *data, *ndata;
	uint32_t len;
	uint8_t op_type;
	uint32_t op_len;
	uint8_t nxt_hdr;
	uint8_t value;

	/* in summary mode, we don't do anything. */
	if (flags & F_SUM) {
		return;
	}

	show_header("IPv6-DstOpts:  ", "IPv6 Destination Options Header", 0);
	show_space();

	/*
	 * Store the length of this ext hdr in bytes.  The caller has
	 * ensured that there is at least len bytes of data left.
	 */
	len = ipv6ext_dest->ip6d_len * 8 + 8;

	ndata = (const uint8_t *)ipv6ext_dest + 2; /* skip hdr/len */
	len -= 2;

	nxt_hdr = ipv6ext_dest->ip6d_nxt;
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Next Header = %u (%s)", nxt_hdr, getproto(nxt_hdr));

	while (len > 0) {
		data = ndata;
		GETINT8(op_type, data);
		if (op_type == IP6OPT_PAD1) {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "pad1 option ");
			len--;
			ndata = data;
			continue;
		}
		GETINT8(op_len, data);
		if (len < 2 || op_len + 2 > len) {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Error: option %u truncated (%u + 2 > %u)",
			    op_type, op_len, len);
			op_len = len - 2;
			/*
			 * Continue processing the malformed option so that we
			 * can display as much as possible.
			 */
		}

		/* advance pointers to the next option */
		len -= op_len + 2;
		ndata = data + op_len;

		/* process this option */
		switch (op_type) {
		case IP6OPT_PADN:
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "padN option len = %u", op_len);
			break;
		case IP6OPT_TUNNEL_LIMIT:
			GETINT8(value, data);
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "tunnel encapsulation limit len = %d, value = %d",
			    op_len, value);
			break;
		case IP6OPT_LS:
			print_ip6opt_ls(data, op_len);
			break;
		default:
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Option type = %u, len = %u", op_type, op_len);
			break;
		}
	}

	show_space();
}

#define	ALABEL_MAXLEN	256

static char ascii_label[ALABEL_MAXLEN];
static char *plabel = ascii_label;

struct snoop_pair {
	int val;
	const char *name;
};

static struct snoop_pair ripso_class_tbl[] = {
	TSOL_CL_TOP_SECRET,	"TOP SECRET",
	TSOL_CL_SECRET,		"SECRET",
	TSOL_CL_CONFIDENTIAL,	"CONFIDENTIAL",
	TSOL_CL_UNCLASSIFIED,	"UNCLASSIFIED",
	-1,			NULL
};

static struct snoop_pair ripso_prot_tbl[] = {
	TSOL_PA_GENSER,		"GENSER",
	TSOL_PA_SIOP_ESI,	"SIOP-ESI",
	TSOL_PA_SCI,		"SCI",
	TSOL_PA_NSA,		"NSA",
	TSOL_PA_DOE,		"DOE",
	0x04,			"UNASSIGNED",
	0x02,			"UNASSIGNED",
	-1,			NULL
};

static struct snoop_pair *
get_pair_byval(struct snoop_pair pairlist[], int val)
{
	int i;

	for (i = 0; pairlist[i].name != NULL; i++)
		if (pairlist[i].val == val)
			return (&pairlist[i]);
	return (NULL);
}

static void
print_ripso(const uchar_t *opt)
{
	struct snoop_pair *ripso_class;
	int i, index, prot_len;
	boolean_t first_prot;
	char line[100], *ptr;

	prot_len = opt[1] - 3;
	if (prot_len < 0)
		return;

	show_header("RIPSO:  ", "Revised IP Security Option", 0);
	show_space();

	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Type = Basic Security Option (%d), Length = %d", opt[0], opt[1]);

	/*
	 * Display Classification Level
	 */
	ripso_class = get_pair_byval(ripso_class_tbl, (int)opt[2]);
	if (ripso_class != NULL)
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Classification = Unknown (0x%02x)", opt[2]);
	else
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Classification = %s (0x%02x)",
		    ripso_class->name, ripso_class->val);

	/*
	 * Display Protection Authority Flags
	 */
	(void) snprintf(line, sizeof (line), "Protection Authority = ");
	ptr = line;
	first_prot = B_TRUE;
	for (i = 0; i < prot_len; i++) {
		index = 0;
		while (ripso_prot_tbl[index].name != NULL) {
			if (opt[3 + i] & ripso_prot_tbl[index].val) {
				ptr = strchr(ptr, 0);
				if (!first_prot) {
					(void) strlcpy(ptr, ", ",
					    sizeof (line) - (ptr - line));
					ptr = strchr(ptr, 0);
				}
				(void) snprintf(ptr,
				    sizeof (line) - (ptr - line),
				    "%s (0x%02x)",
				    ripso_prot_tbl[index].name,
				    ripso_prot_tbl[index].val);
			}
			index++;
		}
		if ((opt[3 + i] & 1) == 0)
			break;
	}
	if (!first_prot)
		(void) snprintf(get_line(0, 0), get_line_remain(), "%s", line);
	else
		(void) snprintf(get_line(0, 0), get_line_remain(), "%sNone",
		    line);
}

#define	CIPSO_GENERIC_ARRAY_LEN	200

/*
 * Return 1 if CIPSO SL and Categories are all 1's; 0 otherwise.
 *
 * Note: opt starts with "Tag Type":
 *
 * |tag_type(1)|tag_length(1)|align(1)|sl(1)|categories(variable)|
 *
 */
static boolean_t
cipso_high(const uchar_t *opt)
{
	int i;

	if (((int)opt[1] + 6) < IP_MAX_OPT_LENGTH)
		return (B_FALSE);
	for (i = 0; i < ((int)opt[1] - 3); i++)
		if (opt[3 + i] != 0xff)
			return (B_FALSE);
	return (B_TRUE);
}

/*
 * Converts CIPSO label to SL.
 *
 * Note: opt starts with "Tag Type":
 *
 * |tag_type(1)|tag_length(1)|align(1)|sl(1)|categories(variable)|
 *
 */
static void
cipso2sl(const uchar_t *opt, bslabel_t *sl, int *high)
{
	int i, taglen;
	uchar_t *q = (uchar_t *)&((_bslabel_impl_t *)sl)->compartments;

	*high = 0;
	taglen = opt[1];
	memset((caddr_t)sl, 0, sizeof (bslabel_t));

	if (cipso_high(opt)) {
		BSLHIGH(sl);
		*high = 1;
	} else {
		LCLASS_SET((_bslabel_impl_t *)sl, opt[3]);
		for (i = 0; i < taglen - TSOL_TT1_MIN_LENGTH; i++)
			q[i] = opt[TSOL_TT1_MIN_LENGTH + i];
	}
	SETBLTYPE(sl, SUN_SL_ID);
}

static int
interpret_cipso_tagtype1(const uchar_t *opt)
{
	int i, taglen, ishigh;
	bslabel_t sl;
	char line[CIPSO_GENERIC_ARRAY_LEN], *ptr;

	taglen = opt[1];
	if (taglen < TSOL_TT1_MIN_LENGTH ||
	    taglen > TSOL_TT1_MAX_LENGTH)
		return (taglen);

	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Tag Type = %d, Tag Length = %d", opt[0], opt[1]);
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Sensitivity Level = 0x%02x", opt[3]);
	ptr = line;
	for (i = 0; i < taglen - TSOL_TT1_MIN_LENGTH; i++) {
		(void) snprintf(ptr, sizeof (line) - (ptr - line), "%02x",
		    opt[TSOL_TT1_MIN_LENGTH + i]);
		ptr = strchr(ptr, 0);
	}
	if (i != 0) {
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Categories = ");
		(void) snprintf(get_line(0, 0), get_line_remain(), "\t%s",
		    line);
	} else {
		(void) snprintf(get_line(0, 0), get_line_remain(),
		    "Categories = None");
	}
	cipso2sl(opt, &sl, &ishigh);
	if (is_system_labeled()) {
		if (bsltos(&sl, &plabel, ALABEL_MAXLEN,
		    LONG_CLASSIFICATION|LONG_WORDS|VIEW_INTERNAL) < 0) {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "The Sensitivity Level and Categories can't be "
			    "mapped to a valid SL");
		} else {
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "The Sensitivity Level and Categories are mapped "
			    "to the SL:");
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "\t%s", ascii_label);
		}
	}
	return (taglen);
}

/*
 * The following struct definition #define's are copied from TS1.x. They are
 * not used here (except TTYPE_3_MAX_TOKENS), but included as a reference for
 * the tag type 3 packet format.
 */
#define	TTYPE_3_MAX_TOKENS	7

/*
 * Display CIPSO tag type 3 which is defined by MAXSIX.
 */
static int
interpret_cipso_tagtype3(const uchar_t *opt)
{
	uchar_t tagtype;
	int index, numtokens, taglen;
	uint16_t mask;
	uint32_t token;
	static const char *name[] = {
		"SL",
		"NCAV",
		"INTEG",
		"SID",
		"undefined",
		"undefined",
		"IL",
		"PRIVS",
		"LUID",
		"PID",
		"IDS",
		"ACL"
	};

	tagtype = *opt++;
	(void) memcpy(&mask, opt + 3, sizeof (mask));
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Tag Type = %d (MAXSIX)", tagtype);
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Generation = 0x%02x%02x%02x, Mask = 0x%04x", opt[0], opt[1],
	    opt[2], mask);
	opt += 3 + sizeof (mask);

	/*
	 * Display tokens
	 */
	numtokens = 0;
	index = 0;
	while (mask != 0 && numtokens < TTYPE_3_MAX_TOKENS) {
		if (mask & 0x0001) {
			(void) memcpy(&token, opt, sizeof (token));
			opt += sizeof (token);
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Attribute = %s, Token = 0x%08x",
			    index < sizeof (name) / sizeof (*name) ?
			    name[index] : "unknown", token);
			numtokens++;
		}
		mask = mask >> 1;
		index++;
	}

	taglen = 6 + numtokens * 4;
	return (taglen);
}

static void
print_cipso(const uchar_t *opt)
{
	int optlen, taglen, tagnum;
	uint32_t doi;
	char line[CIPSO_GENERIC_ARRAY_LEN];
	char *oldnest;

	optlen = opt[1];
	if (optlen < TSOL_CIPSO_MIN_LENGTH || optlen > TSOL_CIPSO_MAX_LENGTH)
		return;

	oldnest = prot_nest_prefix;
	prot_nest_prefix = prot_prefix;
	show_header("CIPSO:  ", "Common IP Security Option", 0);
	show_space();

	/*
	 * Display CIPSO Header
	 */
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Type = CIPSO (%d), Length = %d", opt[0], opt[1]);
	(void) memcpy(&doi, opt + 2, sizeof (doi));
	(void) snprintf(get_line(0, 0), get_line_remain(),
	    "Domain of Interpretation = %u", (unsigned)ntohl(doi));

	if (opt[1] == TSOL_CIPSO_MIN_LENGTH) {	/* no tags */
		show_space();
		prot_prefix = prot_nest_prefix;
		prot_nest_prefix = oldnest;
		return;
	}
	optlen -= TSOL_CIPSO_MIN_LENGTH;
	opt += TSOL_CIPSO_MIN_LENGTH;

	/*
	 * Display Each Tag
	 */
	tagnum = 1;
	while (optlen >= TSOL_TT1_MIN_LENGTH) {
		(void) snprintf(line, sizeof (line), "Tag# %d", tagnum);
		show_header("CIPSO:  ", line, 0);
		/*
		 * We handle tag type 1 and 3 only. Note, tag type 3
		 * is MAXSIX defined.
		 */
		switch (opt[0]) {
		case 1:
			taglen = interpret_cipso_tagtype1(opt);
			break;
		case 3:
			taglen = interpret_cipso_tagtype3(opt);
			break;
		default:
			(void) snprintf(get_line(0, 0), get_line_remain(),
			    "Unknown Tag Type %d", opt[0]);
			show_space();
			prot_prefix = prot_nest_prefix;
			prot_nest_prefix = oldnest;
			return;
		}

		/*
		 * Move to the next tag
		 */
		if (taglen <= 0)
			break;
		optlen -= taglen;
		opt += taglen;
		tagnum++;
	}
	show_space();
	prot_prefix = prot_nest_prefix;
	prot_nest_prefix = oldnest;
}