/*
 *	Copyright (c) 1995, 1996, Sun Microsystems, Inc.
 *
 * This software is provided by Sun ``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 Sun Microsystems be liable for any direct, indirect,
 * incidental, special, exemplary, or consequential damages.
 *
 * This software is not a product, and is provided for evaluation purposes
 * only.
 *
 * This software may not be resold without the express permission of
 * Sun Microsystems.
 *
 * Feedback on this tool may be sent to packet-shell-owner@sunroof.eng.sun.com.
 *
 * Packet Shell
 */

#pragma ident	"@(#)ipv6-icmp.c	1.33	99/05/28 SMI"

#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <tcl.h>
#include <strings.h>
#include <ctype.h>
#include <ctype.h>
#include <stdlib.h>
#include "ipv6.h"
#include "icmp6.h"
#include "proto.h"
#include "name2int.h"

static bool_t
verify_cksum(struct packet *pckt, int element);

static char *proto_name;
static int nd_option_offset(char *start, int pktlen, int type, int index);
static char *bytes_to_colon_hex(char *dest, unsigned char *start, int len);

/*
 * In a better world, some header file would define these...
 */
struct ether_addr *ether_aton(char *);
char *ether_ntoa(struct ether_addr *);
int ether_ntohost(char *, struct ether_addr *);
int ether_hostton(char *, struct ether_addr *);


#define	NICMP6_TYPES 14
static struct nameint typetbl[NICMP6_TYPES] = {
	{ "unreachable", ICMP6_DST_UNREACH },
	{ "packet too big", ICMP6_PACKET_TOO_BIG },
	{ "time exceeded", ICMP6_TIME_EXCEEDED },
	{ "parameter problem", ICMP6_PARAM_PROB },
	{ "echo request", ICMP6_ECHO_REQUEST },
	{ "echo reply", ICMP6_ECHO_REPLY },
	{ "group query", ICMP6_MEMBERSHIP_QUERY },
	{ "group report", ICMP6_MEMBERSHIP_REPORT },
	{ "group term", ICMP6_MEMBERSHIP_REDUCTION },
	{ "router solicitation", ND_ROUTER_SOLICIT },
	{ "router advertisement", ND_ROUTER_ADVERT },
	{ "neighbor solicitation", ND_NEIGHBOR_SOLICIT },
	{ "neighbor advertisement", ND_NEIGHBOR_ADVERT },
	{ "redirect", ND_REDIRECT }
};

#define	NICMP6_UNREACH_CODES	5
static struct nameint unreachtbl[NICMP6_UNREACH_CODES] = {
	{ "no route", ICMP6_DST_UNREACH_NOROUTE },
	{ "prohibited", ICMP6_DST_UNREACH_ADMIN },
	{ "not neighbor", ICMP6_DST_UNREACH_NOTNEIGHBOR },
	{ "address", ICMP6_DST_UNREACH_ADDR },
	{ "port", ICMP6_DST_UNREACH_NOPORT }
};

/* no codes for PKT2BIG */

#define	NICMP6_TIMXCEED_CODES	2
static struct nameint timetbl[NICMP6_TIMXCEED_CODES] = {
	{ "in transit", ICMP6_TIME_EXCEED_TRANSIT },
	{ "in reassembly", ICMP6_TIME_EXCEED_REASSEMBLY }
};

#define	NICMP6_PARAMPROB_CODES	3
static struct nameint paramtbl[NICMP6_PARAMPROB_CODES] = {
	{ "bad header", ICMP6_PARAMPROB_HEADER },
	{ "unknown header", ICMP6_PARAMPROB_NEXTHEADER },
	{ "unknown option", ICMP6_PARAMPROB_OPTION }
};

/* no codes for echo and group ICMPs */

static char *
icmp_type_name(int type)
{
	char *p;

	p = int2name(type, typetbl, NICMP6_TYPES);
	if (p == NULL) {
		p = "<invalid-ICMP6>";
	}
	return (p);
}

static char *
icmp_code_name(int type, int code)
{
	char codestr[3];

	switch (type) {
	case ICMP6_DST_UNREACH:
		return (int2name(code, unreachtbl, NICMP6_UNREACH_CODES));
	case ICMP6_TIME_EXCEEDED:
		return (int2name(code, timetbl, NICMP6_TIMXCEED_CODES));
	case ICMP6_PARAM_PROB:
		return (int2name(code, paramtbl, NICMP6_UNREACH_CODES));
	case ICMP6_PACKET_TOO_BIG:
	case ICMP6_ECHO_REQUEST:
	case ICMP6_ECHO_REPLY:
	case ICMP6_MEMBERSHIP_QUERY:
	case ICMP6_MEMBERSHIP_REPORT:
	case ICMP6_MEMBERSHIP_REDUCTION:
	case ND_ROUTER_SOLICIT:
	case ND_ROUTER_ADVERT:
	case ND_NEIGHBOR_SOLICIT:
	case ND_NEIGHBOR_ADVERT:
	case ND_REDIRECT:
	default:
		/*
		 * For the above ICMP types which only defined for code 0, and
		 * for unknown ICMP types, simply print out the hexadecimal
		 * code value.
		 */
		(void) sprintf(codestr, "0x%2.2x", code);
		return (strdup(codestr));
	}
}

/*
 *  String to number conversion routine
 */
static int
icmp6a2n(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	int num;
	char buf[1024];

	if (argc != 3) {
		Tcl_AppendResult(interp,
		    "Usage: ipv6-icmp.a2n <type|code> <string>", NULL);
		return (TCL_ERROR);
	}
	if (strcmp(argv[1], "type") == 0) {
		if (name2int(argv[2], &num, typetbl, NICMP6_TYPES) == 1)
		(void) sprintf(buf, "%d", num);
		else
		(void) sprintf(buf, "not found : %s", argv[2]);
		Tcl_AppendResult(interp, buf, NULL);
		return (TCL_OK);
	} else {
		Tcl_AppendResult(interp, "Bad type : ", argv[1], NULL);
		return (TCL_ERROR);
	}
}

static bool_t
verify_cksum(struct packet *pckt, int element)
{
	struct pseudo_ip6	pseudo_hdr;
	struct icmp6_hdr	*icmp6hdr_ptr;
	struct routeopt6	*rhdr;
	struct ip6		*ip6h;
	uint16_t		sum = 0;
	int ipv6_index = -1;
	int rthdr_index = -1;
	int len;
	int i, j;

	len = pckt->p_len - pckt->p_tbl[element].pt_offset;

	/*
	 * Look for an ipv6 header.  If found, we must adjust
	 * checksum by the pseudo-header.
	 */
	for (i = element - 1; i >= 0; i--) {
		if (strcmp(pckt->p_tbl[i].pt_name, "ipv6") == 0) {
			ipv6_index = i;
			ip6h = (struct ip6 *)(pckt->p_buf +
			    pckt->p_tbl[ipv6_index].pt_offset);
			break;
		}
	}

	/* If there's no IPv6 header, skip to the icmpv6 checksum. */
	if (ipv6_index == -1) {
		sum = ~sum;
		goto vfy_icmpv6_cksum;
	}

	/* build the pseudo header */
	bzero(&pseudo_hdr, sizeof (struct pseudo_ip6));
	(void) memcpy(&pseudo_hdr.pseudo_ip6_src, &ip6h->ip6_src,
	    sizeof (struct ip6_addr));
	(void) memcpy(&pseudo_hdr.pseudo_ip6_dst, &ip6h->ip6_dst,
	    sizeof (struct ip6_addr));
	pseudo_hdr.pseudo_ip6_len = htons((ushort_t)len);
	pseudo_hdr.pseudo_ip6_p = 58;

	/*
	 * Look for a routing header.  If found, we must
	 * examine it.  If routing type 0 and > 0 segments
	 * left, we must replace the dest addr in the pseudo
	 * header with the last entry in the routing header.
	 */
	for (j = element - 1; j > ipv6_index; j--) {
		if (strcmp(pckt->p_tbl[j].pt_name, "ipv6-route") == 0) {
			rhdr = (struct routeopt6 *)(pckt->p_buf +
			    pckt->p_tbl[j].pt_offset);
			if ((rhdr->routeopt6_routingtype == 0) &&
			    (rhdr->routeopt6_segmentsleft > 0)) {
				rthdr_index = j;
			}
			break;
		}
	}

	if (rthdr_index != -1) {
		(void) memcpy(&pseudo_hdr.pseudo_ip6_dst,
		    (void *)((char *)rhdr + rhdr->routeopt6_hdrextlen * 8 - 8),
		    sizeof (struct ip6_addr));
	}

	/* calculate the pseudo header checksum */
	(void) ip_cksum(&sum, (uchar_t *)&pseudo_hdr,
	    sizeof (struct pseudo_ip6));

vfy_icmpv6_cksum:

	/* calculate the checksum over the icmp6 header and the icmp6 data */
	icmp6hdr_ptr = (struct icmp6_hdr *)(pckt->p_buf +
	    pckt->p_tbl[element].pt_offset);
	sum = ~sum;
	ip_cksum(&sum, (uchar_t *)icmp6hdr_ptr, len);

	if (sum == 0) {
		return (1);
	} else {
		return (0);
	}
}

/*
 * Initialization
 */
int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	/*
	 * add support for a2n function
	 */
	(void) Tcl_CreateCommand(interp, "ipv6-icmp.a2n", icmp6a2n, NULL, NULL);

	/*
	 * Register protocol specific ancilliary commands, if any
	 */
	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: malloc: no space",
		    NULL);
		return (0);
	}
	return (1);
}

#if 0
int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	return (TCL_ERROR);
}

int
prclose(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	return (TCL_ERROR);
}
#endif

/*
 * Create a "default" packet.
 */
int
pinit(ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct icmp6_hdr icmp6hdr;

	if (pckt->p_len == 0)
		pckt->p_len = sizeof (struct icmp6_hdr);

	if (ptbl_update(pckt, 1, NULL, pckt->p_len) == 0)
		return (TCL_ERROR);

	/* Set the default type to an echo request */
	icmp6hdr.icmp6_type = ICMP6_ECHO_REQUEST;
	bcopy(&icmp6hdr, pckt->p_buf, sizeof (struct icmp6_hdr));

	return (TCL_OK);
}


int
decode(struct packet *pkt, int element, char **name, int *next_offset)
{
	struct icmp6_hdr		icpkt;

	(void) memcpy(&icpkt, pkt->p_buf + pkt->p_tbl[element].pt_offset,
	    sizeof (icpkt));
	if (icpkt.icmp6_type == ICMP6_ECHO_REPLY ||
	    icpkt.icmp6_type == ICMP6_ECHO_REQUEST) {
		*name = "data";
		*next_offset = pkt->p_tbl[element].pt_offset + 8;
		return (1);
	}
	if (icpkt.icmp6_type < 128) {
		/*
		 * This is an ICMP6 error message.  The packet is enclosed
		 * beginning at 8 bytes.  Always an IP6 header to start if off?
		 */

		/* Make sure that something actually follows the header. */

		if ((pkt->p_tbl[element].pt_offset + 8) < pkt->p_len) {
			*name = "ipv6";
			*next_offset = pkt->p_tbl[element].pt_offset + 8;
			return (1);
		}
	}
	*name = "end";
	*next_offset = pkt->p_len;
	return (0);
}

/*
 * Get protocol fields.	 There are lots of these, since we have to include
 * all of the options as well.
 */
int
get(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct ether_addr		eaddr;
	struct icmp6_hdr		icpkt;
	struct nd_neighbor_solicit	ndpkt;
	struct nd_neighbor_advert	napkt;
	struct nd_redirect		redpkt;
	struct nd_opt_hdr		*opthdr;
	struct nd_opt_prefix_info	prefixhdr;
	char		*icmp_start;
	int		icmp_len;
	char		tmp[128];
	int		poffset;
	uint32_t	mtu;

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget <pckt>", proto_name,
		    " <item>", NULL);
		return (TCL_ERROR);
	}

	icmp_start = pckt->p_buf + pckt->p_tbl[element].pt_offset;
	icmp_len = pckt->p_tbl[element+1].pt_offset -
	    pckt->p_tbl[element].pt_offset;

	(void) memcpy(&icpkt, icmp_start, sizeof (icpkt));
	if (strcmp(argv[0], "type") == 0) {
		if (int2name(icpkt.icmp6_type, typetbl, NICMP6_TYPES)) {
			(void) sprintf(tmp, "%s",
			    icmp_type_name(icpkt.icmp6_type));
			Tcl_AppendResult(interp, tmp, NULL);
			return (TCL_OK);
		}
		(void) sprintf(tmp, "0x%x", icpkt.icmp6_type);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "code") == 0) {
		(void) sprintf(tmp, "0x%x", icpkt.icmp6_code);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "cksum") == 0) {
		(void) sprintf(tmp, "0x%hx", ntohs(icpkt.icmp6_cksum));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "cksum-valid") == 0) {
		if (verify_cksum(pckt, element)) {
			Tcl_AppendResult(interp, "valid", NULL);
		} else {
			Tcl_AppendResult(interp, "invalid", NULL);
		}
	} else if (strcmp(argv[0], "id") == 0) {
		(void) sprintf(tmp, "0x%hx", htons(icpkt.icmp6_id));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "seq") == 0) {
		(void) sprintf(tmp, "0x%hx", htons(icpkt.icmp6_seq));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "pointer") == 0) {
		(void) sprintf(tmp, "0x%x", htonl(icpkt.icmp6_pptr));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "word1") == 0) {
		(void) sprintf(tmp, "0x%x", htonl(icpkt.icmp6_data32[0]));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "target") == 0) {
		if (icmp_len >= sizeof (ndpkt)) {
			(void) memcpy(&ndpkt, icmp_start, sizeof (ndpkt));
			psh_addr2ascii(AF_INET6, (void *)&ndpkt.nd_ns_target,
			    tmp);
			Tcl_AppendResult(interp, tmp, NULL);
		} else {
			Tcl_AppendResult(interp, "No target found", NULL);
			return (TCL_ERROR);
		}
	} else if (strcmp(argv[0], "redirect_target") == 0) {
		(void) memcpy(&redpkt, icmp_start, sizeof (redpkt));
		psh_addr2ascii(AF_INET6, (void *)&redpkt.nd_rd_target, tmp);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "redirect_dest") == 0) {
		(void) memcpy(&redpkt, icmp_start, sizeof (redpkt));
		psh_addr2ascii(AF_INET6, (void *)&redpkt.nd_rd_dst, tmp);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "router") == 0) {
		(void) memcpy(&napkt, icmp_start, sizeof (napkt));
		if (napkt.nd_na_flags_reserved & ND_NA_FLAG_ROUTER)
			Tcl_AppendResult(interp, "on", NULL);
		else
			Tcl_AppendResult(interp, "off", NULL);
	} else if (strcmp(argv[0], "solicited") == 0) {
		(void) memcpy(&napkt, icmp_start, sizeof (napkt));
		if (napkt.nd_na_flags_reserved & ND_NA_FLAG_SOLICITED)
			Tcl_AppendResult(interp, "on", NULL);
		else
			Tcl_AppendResult(interp, "off", NULL);
	} else if (strcmp(argv[0], "override") == 0) {
		(void) memcpy(&napkt, icmp_start, sizeof (napkt));
		if (napkt.nd_na_flags_reserved & ND_NA_FLAG_OVERRIDE)
			Tcl_AppendResult(interp, "on", NULL);
		else
			Tcl_AppendResult(interp, "off", NULL);
	} else if (strcmp(argv[0], "slla") == 0) {
		poffset = nd_option_offset(icmp_start, icmp_len,
		    ND_OPT_SOURCE_LINKADDR, 0);
		if (poffset < 0) {
			Tcl_AppendResult(interp, "No slla option found", NULL);
			return (TCL_ERROR);
		}
		/*
		 *  nd_opt_hdr contains only 8-bit values;
		 *  no alignment necessary
		 */
		opthdr = (struct nd_opt_hdr *)(icmp_start + poffset);
		if (opthdr->nd_opt_len == sizeof (eaddr)) {
			(void) memcpy(&eaddr, (char *)(opthdr + 1),
			    sizeof (eaddr));
			(void) sprintf(tmp, "%x %s", 1, ether_ntoa(&eaddr));
			Tcl_AppendResult(interp, tmp, NULL);
		} else {
			(void) sprintf(tmp, "%x ", opthdr->nd_opt_len);
			Tcl_AppendResult(interp, tmp, NULL);
			(void) bytes_to_colon_hex(tmp, (uchar_t *)(opthdr + 1),
			    (opthdr->nd_opt_len * 8) - 2);
			Tcl_AppendResult(interp, tmp, NULL);

		}
	} else if (strcmp(argv[0], "tlla") == 0) {
		poffset = nd_option_offset(icmp_start, icmp_len,
		    ND_OPT_TARGET_LINKADDR, 0);
		if (poffset < 0) {
			Tcl_AppendResult(interp, "No tlla option found", NULL);
			return (TCL_ERROR);
		}
		/*
		 * nd_opt_hdr contains only 8-bit values;
		 * no alignment necessary
		 */
		opthdr = (struct nd_opt_hdr *)(icmp_start + poffset);
		if (opthdr->nd_opt_len == sizeof (eaddr)) {
			(void) memcpy(&eaddr, (char *)(opthdr + 1),
			    sizeof (eaddr));
			(void) sprintf(tmp, "%d %s", 1, ether_ntoa(&eaddr));
			Tcl_AppendResult(interp, tmp, NULL);
		} else {
			(void) sprintf(tmp, "%d ", opthdr->nd_opt_len);
			Tcl_AppendResult(interp, tmp, NULL);
			(void) bytes_to_colon_hex(tmp, (uchar_t *)(opthdr + 1),
			    (opthdr->nd_opt_len * 8) - 2);
			Tcl_AppendResult(interp, tmp, NULL);
		}
	} else if ((strcmp(argv[0], "prefix") == 0) ||
	    (strcmp(argv[0], "prefix-length") == 0) ||
	    (strcmp(argv[0], "prefix-vlife") == 0) ||
	    (strcmp(argv[0], "prefix-plife") == 0) ||
	    (strcmp(argv[0], "prefix-la") == 0)) {
		poffset = nd_option_offset(icmp_start, icmp_len,
		    ND_OPT_PREFIX_INFORMATION, 0);
		if (poffset < 0) {
			Tcl_AppendResult(interp, "No prefix information ",
			    "option found", NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&prefixhdr, icmp_start + poffset,
		    sizeof (prefixhdr));
		if (strcmp(argv[0], "prefix") == 0) {
			psh_addr2ascii(AF_INET6,
			    (void *)&prefixhdr.nd_opt_pi_prefix, tmp);
		} else if (strcmp(argv[0], "prefix-length") == 0) {
			(void) sprintf(tmp, "%u",
			    prefixhdr.nd_opt_pi_prefix_len);
		} else if (strcmp(argv[0], "prefix-vlife") == 0) {
			(void) sprintf(tmp, "%x",
			    ntohl(prefixhdr.nd_opt_pi_valid_time));
		} else if (strcmp(argv[0], "prefix-plife") == 0) {
			(void) sprintf(tmp, "%x",
			    ntohl(prefixhdr.nd_opt_pi_preferred_time));
		} else if (strcmp(argv[0], "prefix-la") == 0) {
			(void) sprintf(tmp, "%x:%x",
			    prefixhdr.nd_opt_pi_flags_reserved &
			    ND_OPT_PI_FLAG_ONLINK,
			    prefixhdr.nd_opt_pi_flags_reserved &
			    ND_OPT_PI_FLAG_AUTO);
		}
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "redirect-length") == 0) {
		poffset = nd_option_offset(icmp_start, icmp_len,
		    ND_OPT_REDIRECTED_HEADER, 0);
		if (poffset < 0) {
			Tcl_AppendResult(interp, "No redirected header ",
			    "option found", NULL);
			return (TCL_ERROR);
		}
		opthdr = (struct nd_opt_hdr *)(icmp_start + poffset);
		(void) sprintf(tmp, "%d", opthdr->nd_opt_len);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "mtu") == 0) {
		poffset = nd_option_offset(icmp_start, icmp_len,
		    ND_OPT_MTU, 0);
		if (poffset < 0) {
			Tcl_AppendResult(interp, "No MTU option found",
			    NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&mtu, icmp_start + poffset + 4, 4);
		(void) sprintf(tmp, "%u", ntohl(mtu));
		Tcl_AppendResult(interp, tmp, NULL);
	} else {
		Tcl_AppendResult(interp, "Unknown ", proto_name, " field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

/*
 * Set a protocol element
 */
int
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct icmp6_hdr	   icpkt;
	struct ip6_addr		   addr;
	struct ip6		   *ip6h;
	struct routeopt6	   *rhdr;
	struct nd_router_advert	   rapkt;
	struct nd_opt_prefix_info  prefix_opt;
	struct ether_addr	   *eap, eaddr;
	int			   val, i, j;
	int			   protolen;
	int			   poffset;
	char			   *p;
	int			   index;
	char			   buf[128];

	protolen = pckt->p_tbl[element+1].pt_offset -
	    pckt->p_tbl[element].pt_offset;
	if (argc == 1 && strcmp(argv[0], "cksum") == 0) {
		/*
		 * Special case must come before general usage check.
		 * If no value is given, set cksum to the correct value.
		 * First, zero out the checksum field.
		 */
		uint16_t sum = 0;
		int len;
		struct pseudo_ip6 pseudo_hdr;
		int ipv6_index = -1;
		int rthdr_index = -1;
		struct icmp6_hdr *icmp6hdr_ptr;

		len = pckt->p_len - pckt->p_tbl[element].pt_offset;

		/*
		 * Look for an ipv6 header.  If found, we must adjust
		 * checksum by the pseudo-header.
		 */
		for (i = element - 1; i >= 0; i--) {
			if (strcmp(pckt->p_tbl[i].pt_name, "ipv6") == 0) {
				ipv6_index = i;
				ip6h = (struct ip6 *)(pckt->p_buf +
				    pckt->p_tbl[ipv6_index].pt_offset);
				break;
			}
		}

		/* If there's no IPv6 header, skip to the icmpv6 checksum. */
		if (ipv6_index == -1) {
			sum = ~sum;
			goto icmpv6_cksum;
		}

		/* build the pseudo header */
		bzero(&pseudo_hdr, sizeof (struct pseudo_ip6));
		(void) memcpy(&pseudo_hdr.pseudo_ip6_src, &ip6h->ip6_src,
		    sizeof (struct ip6_addr));
		(void) memcpy(&pseudo_hdr.pseudo_ip6_dst, &ip6h->ip6_dst,
		    sizeof (struct ip6_addr));
		pseudo_hdr.pseudo_ip6_len = htons((ushort_t)len);
		pseudo_hdr.pseudo_ip6_p = 58;

		/*
		 * Look for a routing header.  If found, we must
		 * examine it.  If routing type 0 and > 0 segments
		 * left, we must replace the dest addr in the pseudo
		 * header with the last entry in the routing header.
		 */
		for (j = element - 1; j > ipv6_index; j--) {
			if (strcmp(pckt->p_tbl[j].pt_name, "ipv6-route") == 0) {
				rhdr = (struct routeopt6 *)(pckt->p_buf +
				    pckt->p_tbl[j].pt_offset);
				if ((rhdr->routeopt6_routingtype == 0) &&
				    (rhdr->routeopt6_segmentsleft > 0)) {
					rthdr_index = j;
				}
				break;
			}
		}

		if (rthdr_index != -1) {
			(void) memcpy(&pseudo_hdr.pseudo_ip6_dst,
			    (void *)((char *)rhdr +
			    rhdr->routeopt6_hdrextlen * 8 - 8),
			    sizeof (struct ip6_addr));
		}

		/* calculate the pseudo header checksum */
		(void) ip_cksum(&sum, (uchar_t *)&pseudo_hdr,
		    sizeof (struct pseudo_ip6));

icmpv6_cksum:
		/*
		 * calculate the checksum over the icmp6 header
		 * and the icmp6 data
		 */
		icmp6hdr_ptr = (struct icmp6_hdr *)(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset);
		icmp6hdr_ptr->icmp6_cksum = 0;
		sum = ~sum;
		ip_cksum(&sum, (uchar_t *)icmp6hdr_ptr, len);

		icmp6hdr_ptr->icmp6_cksum = htons(sum);
		return (TCL_OK);
	}
	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: pset ", proto_name,
		    " <pckt> <item> <value>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&icpkt, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (icpkt));
	if (strcmp(argv[0], "type") == 0) {
		if (isdigit(*argv[1])) {
			if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
				goto type_error;
			}
			if (val > 255 || val < 0) {
				goto type_error;
			}
		} else if (!name2int(argv[1], &val, typetbl, NICMP6_TYPES)) {
type_error:
			Tcl_AppendResult(interp, "Unrecognized ", proto_name,
			    " type value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		/*
		 *  If needed, add extra length
		 */
		if ((val == ND_NEIGHBOR_SOLICIT) ||
		    (val == ND_NEIGHBOR_ADVERT)) {
			if (ptbl_fix_length(pckt, element,
			    sizeof (struct nd_neighbor_advert)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": Too little space for ",
				    argv[1], " type", NULL);
				return (TCL_ERROR);
			}
		}
		if (val == ND_ROUTER_ADVERT) {
			if (ptbl_fix_length(pckt, element,
			    sizeof (struct nd_router_advert)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": Too little space for ",
				    argv[1], " type", NULL);
				return (TCL_ERROR);
			}
		}

		/* now for the REDIRECT type */
		if ((val == ND_REDIRECT)) {
			if (ptbl_fix_length(pckt, element,
			    sizeof (struct nd_router_advert)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": Too little space for ",
				    argv[1], " type", NULL);
				return (TCL_ERROR);
			}
		}
		icpkt.icmp6_type = (uchar_t)val;
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "code") == 0) {
		if (isdigit(*argv[1])) {
			if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
				goto code_error;
			}
			if (val > 255 || val < 0) {
				goto code_error;
			}
/* XXX should allow symbollic code representation but too hard for now */
/*
		} else if (!name2int(argv[1], &val, codetbl, NICMP6_TYPES)) {
*/
		} else {
code_error:
			Tcl_AppendResult(interp, "Unrecognized ", proto_name,
			    " type value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp6_code = (uchar_t)val;
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "cksum") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK ||
		    val > 65535 || val < 0) {
			Tcl_AppendResult(interp, "Bad checksum value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp6_cksum = htons(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "id") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK ||
		    val > 65535 || val < 0) {
			Tcl_AppendResult(interp, "Bad value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp6_id = htons(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "seq") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK ||
		    val > 65535 || val < 0) {
			Tcl_AppendResult(interp, "Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		icpkt.icmp6_seq = htons(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "solicited") == 0) {
		if (icpkt.icmp6_type != ND_NEIGHBOR_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no solicited field", NULL);
			return (TCL_ERROR);
		}
		if (strcmp(argv[1], "on") == 0) {
			*(icpkt.icmp6_data32) |= htonl(0x40000000);
		} else if (strcmp(argv[1], "off") == 0) {
			*(icpkt.icmp6_data32) &= htonl(~0x40000000);
		} else {
			Tcl_AppendResult(interp, "pset ", proto_name, ": ",
			    "Bad value ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "router") == 0) {
		if (icpkt.icmp6_type != ND_NEIGHBOR_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no router field", NULL);
			return (TCL_ERROR);
		}
		if (strcmp(argv[1], "on") == 0) {
			*(icpkt.icmp6_data32) |= htonl(0x80000000);
		} else if (strcmp(argv[1], "off") == 0) {
			*(icpkt.icmp6_data32) &= htonl(~0x80000000);
		} else {
			Tcl_AppendResult(interp, "pset ", proto_name, ": ",
			    "Bad value ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "override") == 0) {
		if (icpkt.icmp6_type != ND_NEIGHBOR_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no override field", NULL);
			return (TCL_ERROR);
		}
		if (strcmp(argv[1], "on") == 0) {
			*(icpkt.icmp6_data32) |= htonl(0x20000000);
		} else if (strcmp(argv[1], "off") == 0) {
			*(icpkt.icmp6_data32) &= htonl(~0x20000000);
		} else {
			Tcl_AppendResult(interp, "pset ", proto_name, ": ",
			    "Bad value ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "word1") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "Bad value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		*(icpkt.icmp6_data32) = htonl(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &icpkt, sizeof (icpkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "target") == 0) {
		if (!ICMP6_HAS_TARGET_FIELD(icpkt.icmp6_type)) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no target field", NULL);
			return (TCL_ERROR);
		}
		if (psh_ascii2addr(AF_INET6, argv[1], &addr) < 0) {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "Badly formed ipv6 addr: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp6_hdr), &addr, sizeof (addr));
		return (TCL_OK);
	} else if (strcmp(argv[0], "dest") == 0) {
		if (icpkt.icmp6_type != ND_REDIRECT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no dest field", NULL);
			return (TCL_ERROR);
		}
		if (psh_ascii2addr(AF_INET6, argv[1], &addr) < 0) {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "Badly formed ip6 addr: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    sizeof (struct icmp6_hdr) + sizeof (addr), &addr,
		    sizeof (addr));
		return (TCL_OK);
	} else if (strcmp(argv[0], "hoplim") == 0) {
		if (icpkt.icmp6_type != ND_ROUTER_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no hop limit field", NULL);
			return (TCL_ERROR);
		}
		if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK) ||
		    (val > 255) || (val < 0)) {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&rapkt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (rapkt));
		rapkt.nd_ra_curhoplimit = (uchar_t)val;
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &rapkt, sizeof (rapkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "managed") == 0) {
		if (icpkt.icmp6_type != ND_ROUTER_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no managed flag", NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&rapkt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (rapkt));
		if (strcmp(argv[1], "on") == 0) {
			rapkt.nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED;
		} else if (strcmp(argv[1], "off") == 0) {
			rapkt.nd_ra_flags_reserved &= ~ND_RA_FLAG_MANAGED;
		} else {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &rapkt, sizeof (rapkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "other") == 0) {
		if (icpkt.icmp6_type != ND_ROUTER_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no 'other' flag", NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&rapkt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (rapkt));
		if (strcmp(argv[1], "on") == 0) {
			rapkt.nd_ra_flags_reserved |= ND_RA_FLAG_OTHER;
		} else if (strcmp(argv[1], "off") == 0) {
			rapkt.nd_ra_flags_reserved &= ~ND_RA_FLAG_OTHER;
		} else {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &rapkt, sizeof (rapkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "lifetime") == 0) {
		if (icpkt.icmp6_type != ND_ROUTER_ADVERT) {
			(void) sprintf(buf, "%d", icpkt.icmp6_type);
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type ", buf, " has no lifetime field",
			    NULL);
			return (TCL_ERROR);
		}
		if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK) ||
		    (val > 0xffff)) {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&rapkt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (rapkt));
		rapkt.nd_ra_router_lifetime = htons(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &rapkt, sizeof (rapkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "reachable") == 0) {
		if (icpkt.icmp6_type != ND_ROUTER_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no reachable field", NULL);
			return (TCL_ERROR);
		}
		if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK)) {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "Bad value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&rapkt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (rapkt));
		rapkt.nd_ra_reachable = htonl(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &rapkt, sizeof (rapkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "retrans") == 0) {
		if (icpkt.icmp6_type != ND_ROUTER_ADVERT) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": icmp type has no retrans field", NULL);
			return (TCL_ERROR);
		}
		if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK)) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ":Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&rapkt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    sizeof (rapkt));
		rapkt.nd_ra_retransmit = htonl(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &rapkt, sizeof (rapkt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "target-ether") == 0) {
		if ((eap = ether_aton(argv[1])) == NULL) {
			eap = &eaddr;
			if (ether_hostton(argv[1], &eaddr) != 0) {
				Tcl_AppendResult(interp,
				    "Badly formed ether addr: ", argv[1],
				    NULL);
				return (TCL_ERROR);
			}
		}
		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset,
		    protolen, ND_OPT_TARGET_LINKADDR, 1);
		if (poffset == -1) {
			/* Target Link-Layer option not found */
			poffset = protolen;
			if (ptbl_fix_length(pckt, element, protolen +
			    sizeof (struct nd_opt_hdr) +
			    sizeof (struct ether_addr)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset", proto_name,
				    ": Packet too small for target LLA",
				    NULL);
				return (TCL_ERROR);
			}
		}
		p = pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset;
		*p++ = ND_OPT_TARGET_LINKADDR;
		*p++ = 1;
		(void) memcpy(p, eap, sizeof (struct ether_addr));
		return (TCL_OK);
	} else if (strcmp(argv[0], "source-ether") == 0) {
		if ((eap = ether_aton(argv[1])) == NULL) {
			eap = &eaddr;
			if (ether_hostton(argv[1], &eaddr) != 0) {
				Tcl_AppendResult(interp,
				    "Badly formed ether addr: ", argv[1],
				    NULL);
				return (TCL_ERROR);
			}
		}
		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset, protolen,
		    ND_OPT_SOURCE_LINKADDR, 1);
		if (poffset == -1) {
			/* Target Link-Layer option not found */
			poffset = protolen;
			if (ptbl_fix_length(pckt, element, protolen +
			    sizeof (struct nd_opt_hdr) +
			    sizeof (struct ether_addr)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset", proto_name,
				    ": Packet too small for target LLA",
				    NULL);
				return (TCL_ERROR);
			}
		}
		p = pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset;
		*p++ = ND_OPT_SOURCE_LINKADDR;
		/* The length of the option, in units of 8 octets */
		*p++ = 1;
		(void) memcpy(p, eap, sizeof (struct ether_addr));
		return (TCL_OK);
	} else if (strcmp(argv[0], "mtu") == 0) {
		if (icpkt.icmp6_type == ICMP6_PACKET_TOO_BIG) {
			if ((Tcl_GetInt(interp, argv[1], &val) != TCL_OK)) {
				Tcl_ResetResult(interp);
				Tcl_AppendResult(interp, "Bad value: ",
				    argv[1], NULL);
				return (TCL_ERROR);
			}
			bcopy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
			    &icpkt, sizeof (icpkt));
			icpkt.icmp6_mtu = htonl(val);
			bcopy(&icpkt,
			    pckt->p_buf + pckt->p_tbl[element].pt_offset,
			    sizeof (icpkt));
			return (TCL_OK);
		}

		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset, protolen,
		    ND_OPT_MTU, 1);
		if (poffset == -1) {
			poffset = protolen;
			if (ptbl_fix_length(pckt, element, protolen +
			    sizeof (struct nd_opt_mtu)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": Packet too small for MTU option",
				    NULL);
				return (TCL_ERROR);
			}
		}
		p = pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset;
		*p++ = ND_OPT_MTU;
		/* The length of the option, in units of 8 octets. */
		*p++ = 1;
		/* The next two bytes are reserved. */
		*p++ = 0x00;
		*p++ = 0x00;
		(void) memcpy(p, &val, 4);
		return (TCL_OK);

	/*
	 * The redirected header option is a strange case.  The actual IPv6
	 * packet which follows it is in another element of the packet array.
	 * As a result, the redirected header option must be the last option.
	 * We can't really enforce this.
	 * XXX The IPv6 packet must be copied _after_ the redirected header
	 * is added.
	 */

	} else if (strcmp(argv[0], "redirect") == 0) {
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Badly formed length: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (val > 255) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Invalid length: ", argv[1], NULL);
			return (TCL_ERROR);
		}

		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset, protolen,
		    ND_OPT_REDIRECTED_HEADER, 1);
		if (poffset == -1) {
			poffset = protolen;
			/* Make sure there is enough room in the buffer */
			if (ptbl_fix_length(pckt, element, protolen +
			    sizeof (struct nd_opt_hdr) +
			    sizeof (struct ether_addr)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": Packet too small for redirect option",
				    NULL);
				return (TCL_ERROR);
			}
		}
		p = pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset;
		*p++ = ND_OPT_REDIRECTED_HEADER;
		*p++ = val;
		bzero(p, sizeof (struct ether_addr));
		return (TCL_OK);
	} else {
		p = strchr(argv[0], ':');
		if (p == NULL) {
			p = "0";
		} else {
			*p = '\0';
			p++;
		}
	}
	if (strcmp(argv[0], "prefix") == 0) {
		if (psh_ascii2addr(AF_INET6, argv[1], &addr) < 0) {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Badly formed ip6 addr: ", argv[2], NULL);
			return (TCL_ERROR);
		}
		if (strcmp(p, "end") == 0) {
			poffset = protolen;
			if (ptbl_fix_length(pckt, element, poffset +
			    sizeof (struct nd_opt_prefix_info)) != TCL_OK) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": Packet too small to add prefix option",
				    NULL);
				return (TCL_ERROR);
			}
		} else if (Tcl_GetInt(interp, p, &val) == TCL_OK) {
			poffset = nd_option_offset(pckt->p_buf +
			    pckt->p_tbl[element].pt_offset,
			    protolen, ND_OPT_PREFIX_INFORMATION, val);
			if (poffset == -1) {
				Tcl_AppendResult(interp, "pset ", proto_name,
				    ": Prefix option number ", p,
				    " not found", NULL);
				return (TCL_ERROR);
			}
		} else {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad prefix index: ", p, NULL);
		}
		(void) memcpy(&prefix_opt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset,
		    sizeof (prefix_opt));
		prefix_opt.nd_opt_pi_prefix = addr;
		prefix_opt.nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
		prefix_opt.nd_opt_pi_len = 4;
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    poffset, &prefix_opt, sizeof (prefix_opt));
		return (TCL_OK);

		/*
		 * The following are for setting elements of the 'N'th Prefix
		 * information option.	The option must have already been
		 * created with set prefix.
		 */

	} else if (strcmp(argv[0], "prefix-len") == 0) {
		/* check for valid index and value */
		if (Tcl_GetInt(interp, p, &index) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad index: ", p, NULL);
			return (TCL_ERROR);
		}
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad length: ", argv[1], NULL);
			return (TCL_ERROR);
		} else if (val > 255) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad length: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset, protolen,
		    ND_OPT_PREFIX_INFORMATION, index);
		if (poffset == -1) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Prefix option index ", p,
			    " not found", NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&prefix_opt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset,
		    sizeof (prefix_opt));
		prefix_opt.nd_opt_pi_prefix_len = (uchar_t)val;
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    poffset, &prefix_opt, sizeof (prefix_opt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "prefix-valid") == 0) {
		if (Tcl_GetInt(interp, p, &index) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad index: ", p, NULL);
			return (TCL_ERROR);
		}
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad length: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset, protolen,
		    ND_OPT_PREFIX_INFORMATION, index);
		if (poffset == -1) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Prefix option index ", p, " not found", NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&prefix_opt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset,
		    sizeof (prefix_opt));
		prefix_opt.nd_opt_pi_valid_time = htonl(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    poffset, &prefix_opt, sizeof (prefix_opt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "prefix-preferred") == 0) {
		if (Tcl_GetInt(interp, p, &index) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad index: ", p, NULL);
			return (TCL_ERROR);
		}
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad length: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset, protolen,
		    ND_OPT_PREFIX_INFORMATION, index);
		if (poffset == -1) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Prefix option index ", p, " not found", NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&prefix_opt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset,
		    sizeof (prefix_opt));
		prefix_opt.nd_opt_pi_preferred_time = htonl(val);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    poffset, &prefix_opt, sizeof (prefix_opt));
		return (TCL_OK);
	} else if (strcmp(argv[0], "prefix-flags") == 0) {
		/* check for valid index and value */
		if (Tcl_GetInt(interp, p, &index) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad index: ", p, NULL);
			return (TCL_ERROR);
		}
		if (Tcl_GetInt(interp, argv[1], &val) != TCL_OK) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (val > 255) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Bad value: ", argv[1], NULL);
			return (TCL_ERROR);
		}

		poffset = nd_option_offset(pckt->p_buf +
		    pckt->p_tbl[element].pt_offset, protolen,
		    ND_OPT_PREFIX_INFORMATION, index);
		if (poffset == -1) {
			Tcl_AppendResult(interp, "pset ", proto_name,
			    ": Prefix option index ", p, " not found", NULL);
			return (TCL_ERROR);
		}
		(void) memcpy(&prefix_opt,
		    pckt->p_buf + pckt->p_tbl[element].pt_offset + poffset,
		    sizeof (prefix_opt));
		prefix_opt.nd_opt_pi_flags_reserved =
		    (val & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO));
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset +
		    poffset, &prefix_opt, sizeof (prefix_opt));
		return (TCL_OK);
		/*
		 * End of settable fields in the prefix information option
		 */
	} else {
		Tcl_AppendResult(interp, "Unknown ", proto_name, " field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}
}

int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct icmp6_hdr	    icpkt;
	struct nd_neighbor_advert   ndpkt;
	struct nd_router_advert	    rtrpkt;
	struct nd_redirect	    rdpkt;
	struct nd_opt_prefix_info   pfix;
	char			    tmp[128];
	unsigned char		    *p;
	int			    pktlen;
	uchar_t			    *start_addr, *end_addr;
	uchar_t			    *curr_addr;

	start_addr = (uchar_t *)(pckt->p_buf + pckt->p_tbl[element].pt_offset);
	pktlen = pckt->p_tbl[element+1].pt_offset -
	    pckt->p_tbl[element].pt_offset;
	end_addr = start_addr + pktlen;
	(void) memcpy(&icpkt, start_addr, sizeof (icpkt));

	p = (unsigned char *)icmp_type_name(icpkt.icmp6_type);
	if (strcmp((char *)p, "<invalid-ICMP6>") == 0) {
		(void) sprintf(tmp, "0x%x", icpkt.icmp6_type);
		p = (unsigned char *)tmp;
	}
	Tcl_AppendResult(interp, "type ", p, NULL);
	p = (unsigned char *)icmp_code_name(icpkt.icmp6_type,
	    icpkt.icmp6_code);
	Tcl_AppendResult(interp, " code ", p, NULL);
	(void) sprintf(tmp, "0x%x (%s)", icpkt.icmp6_cksum,
	    verify_cksum(pckt, element) ?  "correct" : "invalid");
	Tcl_AppendResult(interp, " cksum ", tmp, NULL);
	/*
	 * Echo and Echo reply don't carry options, so we can bail now...
	 */
	if (icpkt.icmp6_type == ICMP6_ECHO_REQUEST ||
	    icpkt.icmp6_type == ICMP6_ECHO_REPLY ||
	    icpkt.icmp6_type < 128) {
		return (TCL_OK);
	} else if (icpkt.icmp6_type == ND_NEIGHBOR_SOLICIT) {
		if (pktlen >= sizeof (struct nd_neighbor_advert)) {
			(void) memcpy(&ndpkt, start_addr, sizeof (ndpkt));
			Tcl_AppendResult(interp, " target ", NULL);
			psh_addr2ascii(AF_INET6, (void *)&ndpkt.nd_na_target,
			    tmp);
			Tcl_AppendResult(interp, tmp, NULL);
			curr_addr = start_addr + sizeof (ndpkt);
		} else {
			Tcl_AppendResult(interp, " truncated packet", NULL);
			curr_addr = end_addr;
		}
	} else if (icpkt.icmp6_type == ND_NEIGHBOR_ADVERT) {
		if (pktlen >= sizeof (struct nd_neighbor_advert)) {
			(void) memcpy(&ndpkt, start_addr, sizeof (ndpkt));
			if (ndpkt.nd_na_flags_reserved & ND_NA_FLAG_ROUTER) {
				Tcl_AppendResult(interp, " (router)", NULL);
			}
			if (ndpkt.nd_na_flags_reserved &
			    ND_NA_FLAG_SOLICITED) {
				Tcl_AppendResult(interp, " (solicited)", NULL);
			}
			if (ndpkt.nd_na_flags_reserved & ND_NA_FLAG_OVERRIDE) {
				Tcl_AppendResult(interp, " (overrride)", NULL);
			}

			Tcl_AppendResult(interp, " target ", NULL);
			psh_addr2ascii(AF_INET6, (void *)&ndpkt.nd_na_target,
			    tmp);
			Tcl_AppendResult(interp, tmp, NULL);
			curr_addr = start_addr + sizeof (ndpkt);
		} else {
			Tcl_AppendResult(interp, " truncated packet", NULL);
			curr_addr = end_addr;
		}
	} else if (icpkt.icmp6_type == ND_ROUTER_ADVERT) {
		if (pktlen >= sizeof (struct nd_router_advert)) {
			(void) memcpy(&rtrpkt, start_addr, sizeof (rtrpkt));
			(void) sprintf(tmp, "%d", rtrpkt.nd_ra_curhoplimit);
			Tcl_AppendResult(interp, " Hop Limit ", tmp, NULL);
			if (rtrpkt.nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) {
				Tcl_AppendResult(interp, " (managed)", NULL);
			}
			if (rtrpkt.nd_ra_flags_reserved & ND_RA_FLAG_OTHER) {
				Tcl_AppendResult(interp, " (other)", NULL);
			}
			(void) sprintf(tmp, " lifetime %d seconds",
			    ntohs(rtrpkt.nd_ra_router_lifetime));
			Tcl_AppendResult(interp, tmp, NULL);
			(void) sprintf(tmp, " reachable %d ms",
			    ntohl(rtrpkt.nd_ra_reachable));
			Tcl_AppendResult(interp, tmp, NULL);
			(void) sprintf(tmp, " retrans %d ms",
			    ntohl(rtrpkt.nd_ra_retransmit));
			Tcl_AppendResult(interp, tmp, NULL);
			curr_addr = start_addr + sizeof (rtrpkt);
		} else {
			Tcl_AppendResult(interp, " truncated packet", NULL);
			curr_addr = end_addr;
		}
	} else if (icpkt.icmp6_type == ND_REDIRECT) {
		if (pktlen >= sizeof (struct nd_redirect)) {
			(void) memcpy(&rdpkt, start_addr, sizeof (rdpkt));
			Tcl_AppendResult(interp, " target ", NULL);
			psh_addr2ascii(AF_INET6, (void *)&rdpkt.nd_rd_target,
			    tmp);
			Tcl_AppendResult(interp, tmp, NULL);
			Tcl_AppendResult(interp, " dest ", NULL);
			psh_addr2ascii(AF_INET6, (void *)&rdpkt.nd_rd_dst,
			    tmp);
			Tcl_AppendResult(interp, tmp, NULL);
			curr_addr = start_addr + sizeof (rdpkt);
		} else {
			Tcl_AppendResult(interp, " truncated packet", NULL);
			curr_addr = end_addr;
		}
	} else {
		return (TCL_OK);
	}

	if (curr_addr <= (end_addr-8)) {
		/*
		 * Some options must be present
		 */
		Tcl_AppendResult(interp, " options", NULL);
		while (curr_addr < end_addr) {
			switch (*curr_addr) {
			case ND_OPT_SOURCE_LINKADDR:
				/* XXX generalize--allowed to be any length */
				if (curr_addr[1] == 1) {
					(void) sprintf(tmp,
					    " source-ether %x:%x:%x:%x:%x:%x",
					    curr_addr[2], curr_addr[3],
					    curr_addr[4], curr_addr[5],
					    curr_addr[6], curr_addr[7]);
					Tcl_AppendResult(interp, tmp, NULL);
					curr_addr += curr_addr[1] * 8;
				}
				break;

			case ND_OPT_TARGET_LINKADDR:
				/* XXX generalize--allowed to be any length */
				if (curr_addr[1] == 1) {
					(void) sprintf(tmp,
					    " target-ether %x:%x:%x:%x:%x:%x",
					    curr_addr[2], curr_addr[3],
					    curr_addr[4], curr_addr[5],
					    curr_addr[6], curr_addr[7]);
					Tcl_AppendResult(interp, tmp, NULL);
					curr_addr += curr_addr[1] * 8;
				}
				break;

			case ND_OPT_PREFIX_INFORMATION:
				if (curr_addr +
				    sizeof (struct nd_opt_prefix_info) >
				    end_addr) {
					Tcl_AppendResult(interp, " Truncated "
					    "prefix info", NULL);
					curr_addr = end_addr;
					break;
				}
				(void) memcpy(&pfix, curr_addr, sizeof (pfix));
				Tcl_AppendResult(interp, " prefix ", NULL);
				psh_addr2ascii(AF_INET6,
				    (void *)&pfix.nd_opt_pi_prefix, tmp);
				Tcl_AppendResult(interp, tmp, NULL);
				(void) sprintf(tmp, "/%d",
				    pfix.nd_opt_pi_prefix_len);
				Tcl_AppendResult(interp, tmp, NULL);
				if (pfix.nd_opt_pi_flags_reserved &
				    ND_OPT_PI_FLAG_ONLINK) {
					Tcl_AppendResult(interp, " (onlink)",
					    NULL);
				}
				if (pfix.nd_opt_pi_flags_reserved &
				    ND_OPT_PI_FLAG_AUTO) {
					Tcl_AppendResult(interp, " (auto)",
					    NULL);
				}
				Tcl_AppendResult(interp, " valid", NULL);
				if (pfix.nd_opt_pi_valid_time == 0xffffffff) {
					Tcl_AppendResult(interp, " Infinity",
					    NULL);
				} else {
					(void) sprintf(tmp, " %d s",
					    pfix.nd_opt_pi_valid_time);
					Tcl_AppendResult(interp, tmp, NULL);
				}
				Tcl_AppendResult(interp, " preferred", NULL);
				if (pfix.nd_opt_pi_preferred_time ==
				    0xffffffff) {
					Tcl_AppendResult(interp, " Infinity",
					    NULL);
				} else {
					(void) sprintf(tmp, " %d s",
					    pfix.nd_opt_pi_preferred_time);
					Tcl_AppendResult(interp, tmp, NULL);
				}
				curr_addr += curr_addr[1] * 8;
				break;

			case ND_OPT_REDIRECTED_HEADER:
				(void) sprintf(tmp, " redirected header, "
				    "len %d ", curr_addr[1]);
				Tcl_AppendResult(interp, tmp, NULL);
				curr_addr = end_addr;
				break;

			case ND_OPT_MTU:
				(void) sprintf(tmp, "mtu %d ",
				    (curr_addr[4] << 24) |
				    (curr_addr[5] << 16) |
				    (curr_addr[6] << 8) |
				    curr_addr[7]);
				Tcl_AppendResult(interp, tmp, NULL);
				curr_addr += curr_addr[1] * 8;
				break;

			default:
				(void) sprintf(tmp, " unknown 0x%x\n ",
				    *curr_addr);
				Tcl_AppendResult(interp, tmp, NULL);
				curr_addr += curr_addr[1] * 8;
			}
		}
	}
	/*
	 * XXX lots more to decode, all pretty special case
	 */
	return (TCL_OK);
}

/*
 * nd_option_offset must be given a buffer which starts with an
 * icmp6_hdr.
 */

static int nd_option_offset(char *start, int pktlen,
    int type, int index)
{
	int count = 0;
	int curpos = 0;
	int curtype = 0;

	if (pktlen <= sizeof (struct icmp6_hdr)) {
		return (-1);
	}

	curtype = *(uchar_t *)start;
	switch (curtype) {
	case ND_ROUTER_ADVERT:
		curpos = sizeof (struct nd_router_advert);
		break;
	case ND_NEIGHBOR_SOLICIT:
	case ND_NEIGHBOR_ADVERT:
		curpos = sizeof (struct nd_neighbor_solicit);
		break;
	case ND_REDIRECT:
		curpos = sizeof (struct nd_redirect);
		break;
	case ICMP6_MEMBERSHIP_QUERY:
	case ICMP6_MEMBERSHIP_REPORT:
	case ICMP6_MEMBERSHIP_REDUCTION:
		curpos = sizeof (struct icmp6_mld);
		break;
	case ND_ROUTER_SOLICIT:
		curpos = sizeof (struct nd_router_solicit);
		break;
	default:
		return (-1);
	}

	while (curpos < pktlen) {
		curtype = *(uchar_t *)(start + curpos);
		if (curtype == type) {
			if (index == count) {
				return (curpos);
			} else {
				count++;
			}
		}
		curpos += (*(uchar_t *)(start + curpos + 1) * 8);
	}
	return (-1);
}

static char *bytes_to_colon_hex(char *dest, unsigned char *start, int len)
{
	int i = 1;
	char *return_val;

	return_val = dest;
	if (len > 0) {
		*(uchar_t *)dest++ = *start++ + 0x30;
	}
	for (i = 1; i < len; i++) {
		*(uchar_t *)dest++ = ':';
		*(uchar_t *)dest++ = *start++ + 0x30;
	}
	return (return_val);
}
