/* LINTLIBRARY */
/*
 *	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.c	1.45	99/10/22 SMI"

#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netdb.h>
#include "ipv6.h"
#include "snoop.h"
#include <sys/dlpi.h>
#include "dlprims.h"
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#ifdef SOLARIS
#include <stropts.h>
#endif /* SOLARIS */
#include <strings.h>
#include <sys/bufmod.h>
#include <sys/time.h>
#include <values.h>
#include <signal.h>


/*
 * The chunksize for bufmod.  This large value makes bufmod alter the high
 * and low water marks at the stream head.  Since each packet received is
 * not necessarily read immediately, we need this larger read-side queue.
 */

#define	CHUNKSIZE	16000

#define	TIMEVAL_IS_YOUNGER(t1, t2) (((t1).tv_sec < (t2).tv_sec) || \
	(((t1).tv_sec == (t2).tv_sec) && ((t1).tv_usec < (t2).tv_usec)))

/* The file which contains IPv6 addresses and names. */
#define	IPNODES_FILENAME	"/etc/inet/ipnodes"

typedef enum { RQ_EMPTY, RQ_PACKET, RQ_TIMEOUT, RQ_ERROR } ReadQueueStatus;

static char *proto_name;

extern int seconds_bias;


/*
 * Defaults for creating new packets
 * def_src, and def_dst are set through the ipaddr() routine.
 */
static struct ip6_addr def_src, def_dst;

static int mtu_table[DL_OTHER + 1] = {
	1550, -1, 17800, -1, 1550, -1, -1, -1, 4500, -1
};

static char *mac_names[DL_OTHER + 1] = {
	"DL_CSMACD", "DL_TPB", "DL_TPR", "DL_METRO", "DL_ETHER",
	"DL_HDLC", "DL_CHAR", "DL_CTCA", "DL_FDDI", "DL_OTHER"
};

/*
 * ipv6_nametable is a hash table which serves as a cache for
 * IPv6 address to name lookups.
 * ipv6_addrtable is a hash table which serves as a cache for
 * IPv6 name to address lookups.
 */
static Tcl_HashTable ipv6_nametable;
static Tcl_HashTable ipv6_addrtable;

/*
 * End points for IPv6 communication.  We retain here enough to be able
 * to do whatever communication operations we might need.  E.g., the fd
 * for the interface.
 */
struct ipv6_ep {
	ep_t			ie_ep;	/* psh end point */
	int			ie_fd;
	struct ether_header	ie_etherhdr;
	struct iovec		ie_iov[2];
};

static struct sigaction alarm_action, old_alarm_action;
static struct sigaction poll_action, old_poll_action;
static sigset_t my_mask;
static sigset_t original_mask;
static sigset_t wait_mask;

/*
 * The wait_time_left boolean variable is a boolean which is used during the
 * receive function to decide whether it should keep waiting for a packet, or
 * return with a timeout.
 */
static bool_t wait_time_left = 0;

/*
 * ipv6_loadnames()
 * Manually parse the ipnodes file if present (the location of which is defined
 * as IPNODES_FILENAME), and store the contents in the name and address
 * translation caches.
 */
static void
ipv6_loadnames()
{
	FILE *ipn_fptr;
	char addrstr[INET6_ADDRSTRLEN];
	struct ip6_addr testaddr;
	char name[MAXHOSTNAMELEN];
	char line[BUFSIZ];
	int words;
	Tcl_HashEntry *addrentryPtr;
	Tcl_HashEntry *nameentryPtr;
	int entryexists;

	ipn_fptr = fopen(IPNODES_FILENAME, "r");
	/*
	 * If the fopen failed, it probably means there's no ipnodes file.
	 * Quietly return, this just means that all name lookups will
	 * fail.
	 */
	if (ipn_fptr == NULL) {
		return;
	}

	/* read the file line by line */
	while (fgets(line, sizeof (line), ipn_fptr) != NULL) {
		/*
		 * read the first "word" on the line.
		 */
		words = sscanf(line, "%s%s", addrstr, name);
		if (words != 2) {
			/* this was an incomplete line.  Read the next one */
			continue;
		}

		/* Make sure the address string is an address. */
		if (psh_ascii2addr(AF_INET6, addrstr, &testaddr) != 0) {
			/* If not, go on to the next line. */
			continue;
		}

		/* Make sure it's not an IPv4 address */
		if (strchr(addrstr, '.') != NULL) {
			/* It was an IPv4 address.  Skip it. */
			continue;
		}

		/* We have a pair.  Store the results. */

		nameentryPtr = Tcl_CreateHashEntry(&ipv6_nametable, addrstr,
		    &entryexists);
		/* only add the entry if it doesn't already exist. */
		if (entryexists != TCL_OK) {
			Tcl_SetHashValue(nameentryPtr, strdup(name));
		}

		addrentryPtr = Tcl_CreateHashEntry(&ipv6_addrtable, name,
		    &entryexists);
		/* only add the entry if it doesn't already exist. */
		if (entryexists != TCL_OK) {
			Tcl_SetHashValue(addrentryPtr, strdup(addrstr));
		}
	}
}

/*
 * ipv6_h2a() resolves a nodename to an IPv6 address by looking it up
 * in the cache.
 */
static int
ipv6_h2a(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	Tcl_HashEntry *entryPtr;
	char *addrstr;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ipv6.h2a <hostname>", NULL);
		return (TCL_ERROR);
	}

	entryPtr = Tcl_FindHashEntry(&ipv6_addrtable, argv[1]);
	if (entryPtr != NULL) {
		addrstr = (char *)Tcl_GetHashValue(entryPtr);
		Tcl_AppendResult(interp, addrstr, NULL);
		return (TCL_OK);
	} else {
		Tcl_AppendResult(interp, "Unknown host: ", argv[1], NULL);
		return (TCL_ERROR);
	}
}

/*
 * ipv6_a2h() translates an IPv6 address to a nodename.  If the nodename
 * is not in the cache, the function returns the IPv6 address.
 */
static int
ipv6_a2h(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	Tcl_HashEntry *entryPtr;
	char *name;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ipv6.a2h <ipv6-addr>", NULL);
		return (TCL_ERROR);
	}

	entryPtr = Tcl_FindHashEntry(&ipv6_nametable, argv[1]);
	if (entryPtr != NULL) {
		name = (char *)Tcl_GetHashValue(entryPtr);
		Tcl_AppendResult(interp, name, NULL);
		return (TCL_OK);
	} else {
		Tcl_AppendResult(interp, argv[1], NULL);
		return (TCL_ERROR);
	}
}

/*
 * The ipv6_addr function converts the given IPv6 address or hostname
 * (argv[1]) to a binary IPv6 address, and puts the result in clientData,
 * which must be a pointer to a struct ip6_addr.  This function is used to
 * set the default IPv6 source or destination address via the ipv6.{src|dst}
 * psh command.
 */
static int
ipv6_addr(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ip6_addr	address;
	struct ip6_addr *addrp;

	addrp = (struct ip6_addr *)clientData;
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ipv6.{src|dst} <ipv6-addr>",
		    NULL);
		return (TCL_ERROR);
	}
	if (psh_ascii2addr(AF_INET6, argv[1], &address.s6_addr) == 0) {
		*addrp = address;
		return (TCL_OK);
	}
	/*
	 * Can't work out what this string might be.  Call it off!
	 */
	Tcl_AppendResult(interp, "Bad address: ", argv[1], NULL);
	return (TCL_ERROR);
}

static int
ipv6_addrequal(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct ip6_addr addr1, addr2;

	if (argc != 3) {
		Tcl_AppendResult(interp, "Usage: ipv6.addrequal <ipv6-addr> "
		    "<ipv6-addr>", NULL);
		return (TCL_ERROR);
	}

	/* first, convert the two addresses to their binary forms */
	if (psh_ascii2addr(AF_INET6, argv[1], &addr1) != 0) {
		Tcl_AppendResult(interp, "Invalid IPv6 address: ", argv[1],
		    NULL);
		return (TCL_ERROR);
	}
	if (psh_ascii2addr(AF_INET6, argv[2], &addr2) != 0) {
		Tcl_AppendResult(interp, "Invalid IPv6 address: ", argv[2],
		    NULL);
		return (TCL_ERROR);
	}

	/* Now, compare them */
	if (IN6_ARE_ADDR_EQUAL(&addr1, &addr2))
		Tcl_AppendResult(interp, "1", NULL);
	else
		Tcl_AppendResult(interp, "0", NULL);

	return (TCL_OK);
}

static int
ipv6addr2ndm(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ip6_addr		addr;
	struct ip6_addr		maddr;
	char			buf[64];

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ipv6.a2ndm <ipv6-addr>",
		    NULL);
		return (TCL_ERROR);
	}
	if (psh_ascii2addr(AF_INET6, argv[1], &addr) < 0) {
bad_addr:
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Badly formed ipv6 addr ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
	/*
	 * Now get some words with the prefix for the neighbor discovery
	 * multicast address.
	 */
	(void) psh_ascii2addr(AF_INET6, "ff02::1:ff00:0", &maddr);
	maddr.s6_addr[3] = maddr.s6_addr[3] | addr.s6_addr[3];

	if (psh_addr2ascii(AF_INET6, (void *)&maddr, buf) == NULL) {
		goto bad_addr;
	}
	Tcl_AppendResult(interp, buf, NULL);
	return (TCL_OK);
}

/*
 * Function ipv6_ipv62ether: Given an IPv6 address, generate the
 * corresponding ethernet address which was presumably used to build
 * the token.  If the IPv6 address is multicast, return the corresponding
 * multicast ethernet address.
 *
 * Args     IPv6 Address (Formatted Text), not changed
 * Returns  48 bit Ethernet Address (Formatted Text)
 */
int
ipv6_ipv62ether(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct ip6_addr	addr;
	char		buf[20];
	uchar_t		*addrptr;

	/*
	 * Check for the correct number of Arguments
	 */
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ipv6.ipv62ether <ip6-addr>",
		    NULL);
		return (TCL_ERROR);
	}

	/*
	 * Convert the Ipv6 Address passed as an argument to a struct
	 */
	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);
	}

	/*
	 * What kind of IPv6 address is this?
	 */
	if (IN6_IS_ADDR_MULTICAST(&addr)) {
		/*
		 * It's a multicast address.  Let the IPv6 address be
		 * of the form:
		 * ffxx:xxxx:xxxx:xxxx:xxxx:xxxx:aabb:ccdd
		 * then the corresponding ethernet address is of the form:
		 * 33:33:aa:bb:cc:dd
		 */
		addrptr = (uchar_t *)&addr.s6_addr[3];
		sprintf(buf, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
		    0x33,
		    0x33,
		    addrptr[0],
		    addrptr[1],
		    addrptr[2],
		    addrptr[3]);
	} else {
		/*
		 * We assume this is an address build with a EUI64
		 * based interface identifier.
		 */
		addrptr = (uchar_t *)&addr.s6_addr[2];
		sprintf(buf, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
		    addrptr[0] & 0xfd,
		    addrptr[1],
		    addrptr[2],
		    addrptr[5],
		    addrptr[6],
		    addrptr[7]);
	}

	Tcl_AppendResult(interp, buf, NULL);
	return (TCL_OK);
}

/*
 * Function ipv6_ether2token: Given a unicast ethernet address, this function
 * generates the EUI64 based token an IPv6 implementation would use to
 * build it's IPv6 address.
 *
 * Args     48 bit Ethernet Address (Formatted Text)
 * Returns  IPv6 token (Formatted Text) of the form xxxx:xxxx:xxxx:xxxx
 */
int
ipv6_ether2token(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct ether_addr	*etheraddr;
	char		buf[20];

	/*
	 * Check for the correct number of Arguments
	 */
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ipv6.ether2token <ether-addr>",
		    NULL);
		return (TCL_ERROR);
	}

	/*
	 * Convert the ethernet address passed as an argument to a struct
	 */
	if ((etheraddr = ether_aton(argv[1])) == NULL) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "Badly formed ethernet address ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}

	/*
	 * Let the ethernet address be of the form:
	 * o1:o2:o3:o4:o5:o6
	 * then the corresponding IPv6 token will be:
	 * o1o2:o3ff:feo4:o5o6
	 * Where o1 has the universal/local bit set to 1.
	 */
	sprintf(buf, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x",
	    etheraddr->ether_addr_octet[0] | 0x02,
	    etheraddr->ether_addr_octet[1],
	    etheraddr->ether_addr_octet[2],
	    0xff,
	    0xfe,
	    etheraddr->ether_addr_octet[3],
	    etheraddr->ether_addr_octet[4],
	    etheraddr->ether_addr_octet[5]);
	Tcl_AppendResult(interp, buf, NULL);
	free(etheraddr);
	return (TCL_OK);
}

/*
 * Add SIGALRM and SIGPOLL to the signal mask.  Store the old mask in
 * the original_mask variable, so that we can restore it later.
 */
static void
ipv6_block_signals(void)
{
	/*
	 * Store the original signal mask.
	 */
	if (sigprocmask(SIG_BLOCK, NULL, &original_mask) < 0) {
		perror("ipv6_block_signals: sigprocmask");
		abort();
	}

	/*
	 * We need to add SIGALRM and SIGPOLL to the signal mask. in order
	 * to make sure we don't get interrupted by an incoming packe.
	 */
	my_mask = original_mask;
	(void) sigaddset(&my_mask, SIGALRM);
	(void) sigaddset(&my_mask, SIGPOLL);

	/*
	 * Set the new mask, which is the old mask plus SIGALRM and SIGPOLL.
	 */
	if (sigprocmask(SIG_SETMASK, &my_mask, NULL) < 0) {
		perror("ipv6_block_signals: sigprocmask");
		abort();
	}
}

/*
 * Restore the signal mask, which was saved in original_mask.
 */
static void
ipv6_restore_signals(void)
{
	if (sigprocmask(SIG_SETMASK, &original_mask, NULL) < 0) {
		perror("ipv6_restore_signals: sigprocmask");
		abort();
	}
}

/*
 * The function of ipv6action_alrm_handler is to break out of sigsuspend()
 * when we receive a SIGALRM.  We also need to set wait_time_left to 0
 * since a timeout occured, and we no longer want to wait for a packet
 * to be received.
 */
/*ARGSUSED0*/
static void
ipv6action_alrm_handler(int signo)
{
	wait_time_left = 0;
}

/*
 * The function of ipv6adction_poll_handler is to break out of sigsuspend()
 * when we receive a SIGPOLL.  Without this hanndler, sigsuspend() would not
 * return.
 */
/*ARGSUSED0*/
static void
ipv6action_poll_handler(int signo)
{
	/* this handler intentionally left empty */
}

/*
 * ipv6_install_handlers installs two signal handlers.  When a SIGALRM is
 * received, we want the wait_time_left to be set to 0 and we want to break
 * out of sigsuspend(), which is the purpose of the ipv6action_wait-handler
 * function.  Secondly, when a SIGPOLL is received, we simply want to return
 * from sigsuspend(), which is the purpose of the ipv6action_poll_handler.
 */
static void
ipv6_install_handlers(void)
{
	alarm_action.sa_handler = ipv6action_alrm_handler;
	(void) sigfillset(&alarm_action.sa_mask);
	alarm_action.sa_flags = 0;
	if (sigaction(SIGALRM, &alarm_action, &old_alarm_action) != 0) {
		perror("ipv6_install_handlers: sigprocmask");
		abort();
	}
	/*
	 * We need the following handler so that sigsuspend() will return
	 * upon receipt of a SIGPOLL.
	 */
	poll_action.sa_handler = ipv6action_poll_handler;
	(void) sigfillset(&poll_action.sa_mask);
	poll_action.sa_flags = 0;
	if (sigaction(SIGPOLL, &poll_action, &old_poll_action) != 0) {
		perror("ipv6_install_handlers: sigprocmask");
		abort();
	}
}

/*
 * Reset the state of the signal handlers for this process.
 */
static void
ipv6_restore_handlers(void)
{
	if (sigaction(SIGALRM, &old_alarm_action, NULL) != 0) {
		perror("ipv6_restore_handlers: sigprocmask");
		abort();
	}
	if (sigaction(SIGPOLL, &old_poll_action, NULL) != 0) {
		perror("ipv6_restore_handlers: sigprocmask");
		abort();
	}
}

/*
 * Return Values:
 *	<0: An Error Occured
 *	 0: There was nothing in the queue.
 *	 1: There was a packet in the queue, but it was received after the
 *	    timeout event occured.
 *	 2: We read a packet which was within the valid time range.
 */
static ReadQueueStatus
process_read_queue(struct ipv6_ep *epsp, struct packet *pckt,
    struct timeval *end_timep, Tcl_Interp *interp)
{
	struct strpeek peekbuf;
	struct sb_hdr  bufhdr;
	struct strbuf data;
	char *databuf;
	int flags;
	int trunclen;
	int peek_return;

	peekbuf.ctlbuf.buf = NULL;
	peekbuf.databuf.buf = (char *)&bufhdr;
	peekbuf.databuf.maxlen = sizeof (struct sb_hdr);
	peekbuf.flags = 0;

	databuf = (char *)malloc(pckt->p_maxlen);

	data.buf = databuf;
	data.len = 0;
	data.maxlen = pckt->p_maxlen;
	flags = 0;

	while ((peek_return = ioctl(epsp->ie_fd, I_PEEK, &peekbuf)) == 1) {
		if (TIMEVAL_IS_YOUNGER(*end_timep,
		    bufhdr.sbh_timestamp)) {
			free(databuf);
			return (RQ_TIMEOUT);
		} else {
			data.maxlen = pckt->p_maxlen;
			if (getmsg(epsp->ie_fd, NULL, &data, &flags) < 0) {
				perror("getmsg");
			}
			trunclen = min(bufhdr.sbh_msglen, pckt->p_maxlen);
			pckt->p_len = trunclen;
			pckt->p_pat = bufhdr.sbh_timestamp;
			(void) memcpy(pckt->p_buf,
			    data.buf + sizeof (struct sb_hdr), trunclen);
			if (ptbl_init(pckt, "ether") == 0) {
				/*
				 * We can't handle non ethernet packets (yet).
				 * Throw it away and keep going.
				 */
				continue;
			}

			free(databuf);
			return (RQ_PACKET);
		}
	}
	free(databuf);
	if (peek_return == 0)
		return (RQ_EMPTY);
	else
		return (RQ_ERROR);
}

/*
 * Cleanup the process signal mask and signal handlers.  Also retrieves
 * any pending SIGPOLL or SIGALRM signals which were not handled.
 */
static void
ipv6_wait_cleanup(struct ipv6_ep *epsp)
{
	sigset_t pending;

	(void) ualarm(0, 0);
	if (ioctl(epsp->ie_fd, I_SETSIG, 0) < 0) {
		perror("pshaction_wait_cleanup: I_SETSIG");
		abort();
	}

	for (; ; ) {
		(void) sigpending(&pending);
		if (sigismember(&pending, SIGPOLL) ||
		    sigismember(&pending, SIGALRM)) {
			(void) sigsuspend(&wait_mask);
		} else {
			break;
		}
	}
	ipv6_restore_handlers();
	ipv6_restore_signals();
	wait_time_left = 0;
}


/*
 * ipv6_waitpkt waits for a packet until a timeout is reached (if specified).
 * Once a packet has been received, it optionally calls the
 * pshaction_apply_actions() function to filter the packet, but this is only
 * done if the boolean variable filterpackets is set to BTRUE.  The function
 * should always return TCL_OK unless an error occurs, in which case TCL_ERROR
 * is returned.  The function sets the interp result to one of these values:
 *	packet:  a packet was received.
 *	timeout: no packets were received
 *	nomatch: if packets are filtered, a packet was received, but did not
 *		 match a terminal filter.
 */
static int
ipv6_waitpkt(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[],
    bool_t filterpackets)
{
	struct ipv6_ep *epsp;
	struct packet *pckt;
	ReadQueueStatus rv;
	int timeout;
	int eval_return;
	struct timeval end_time;
	int queue_empty = 0;
	sigset_t wait_mask;

	if (wait_time_left != 0) {
		/*
		 * nested <ep>.wait_action calls are not allowed for the
		 * simple reason that the effort has not been put into
		 * making it possible.  The benefits of being able to
		 * do this are unclear.
		 */
		Tcl_AppendResult(interp, "ERROR: nested calls not allowed.",
		    NULL);
		return (TCL_ERROR);
	}

	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, "ERROR: packet not found: ", argv[1],
		    NULL);
		return (TCL_ERROR);
	}

	epsp = (struct ipv6_ep *)clientData;

	Tcl_ResetResult(interp);

	(void) sigprocmask(SIG_BLOCK, NULL, &wait_mask);
	(void) sigdelset(&wait_mask, SIGALRM);
	(void) sigdelset(&wait_mask, SIGPOLL);

	ipv6_block_signals();
	ipv6_install_handlers();

	if (argc == 3) {
		timeout = atoi(argv[2]);
		timeout *= 1000;
		(void) gettimeofday(&end_time, NULL);
		end_time.tv_sec += timeout / 1000000;
		end_time.tv_usec += timeout % 1000000;
		(void) ualarm(timeout, 0);
	} else {
		timeout = -1;
		end_time.tv_sec = MAXINT;
	}
	wait_time_left = 1;
	/* set nonblocking I/O on the endpoint */
	if (ioctl(epsp->ie_fd, I_SETSIG, S_RDNORM) < 0) {
		perror("wait_action: I_SETSIG");
		abort();
	}

	/*
	 * Wait for a packet to come in, or the timeout to occur.
	 */
	for (;;) {
		/*
		 * We're here for one of three reasons.
		 * 1. This is the first time through the loop.
		 * 2. We received a packet (SIGPOLL was handled and
		 *    sigsuspend() returned).
		 * 3. The timeout expired (SIGALRM was handled and sigsuspend()
		 *    returned).
		 * For all three cases we want to process the packets in the
		 * queue, if any.
		 */
		queue_empty = 0;
		while (queue_empty != 1) {
			rv = process_read_queue(epsp, pckt, &end_time, interp);
			switch (rv) {
			case RQ_EMPTY:
				queue_empty = 1;
				break;
			case RQ_TIMEOUT:
				ipv6_wait_cleanup(epsp);
				Tcl_AppendResult(interp, "timeout", NULL);
				return (TCL_OK);
			case RQ_PACKET:
				if (filterpackets) {
					if (pshaction_apply_actions(interp,
					    pckt, (ep_t *)epsp,
					    &eval_return) == 0) {
						ipv6_wait_cleanup(epsp);
						return (eval_return);
					}
				} else {
					ipv6_wait_cleanup(epsp);
					Tcl_AppendResult(interp, "packet",
					    NULL);
					return (TCL_OK);
				}
				break;
			case RQ_ERROR:
				(void) fprintf(stderr,
				    "wait_action: read failed\n");
				abort();
				break;
			default:
				(void) fprintf(stderr, "FATAL: "
				    "process_read_queue: invalid return value: "
				    "%d\n", rv);
				abort();
			}
		}

		if (wait_time_left == 0) {
			/*
			 * If wait_time_left is 0, it means the timeout
			 * expired.
			 */
			ipv6_wait_cleanup(epsp);
			Tcl_AppendResult(interp, "timeout", NULL);
			return (TCL_OK);
		}

		(void) sigsuspend(&wait_mask);
	}

	/*
	 * If we got this far, then there's something drastically wrong...
	 * There's no mechanism for breaking out of the above loop, except
	 * by returning from this function.
	 */
	/* NOTREACHED */
}

/*
 * ipv6_send --
 *            Includes a "flush" option, for which packets that arrive
 *            prior to the time the packet is sent will not be accepted.
 *	      The mark_time field of the end point structure is used to
 *	      implement this option.
 */
static int
ipv6_send(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ipv6_ep *ep;
	struct packet *pckt;

	ep = (struct ipv6_ep *)clientData;

	/*
	 * The undocumented "mark" option is for backward compatibility.
	 * It is what "flush" used to be called.
	 */
	if ((argc < 2) || (argc > 3) ||
	    (argc == 3 && 0 != strcmp(argv[2], "flush") &&
	    0 != strcmp(argv[2], "mark"))) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <pckt> [flush]",
		    NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, proto_name, " send operation: ",
		    "packet not found: ", argv[1], NULL);
		return (TCL_ERROR);
	}
	ep->ie_iov[1].iov_base = (char *)pckt->p_buf;
	ep->ie_iov[1].iov_len = pckt->p_len;

	/*
	 * If "flush" was specified, flush the read queues.
	 */
	if (argc == 3) {
		if (ioctl(ep->ie_fd, I_FLUSH, FLUSHR) < 0) {
			Tcl_AppendResult(interp, proto_name, " send operation: "
			    "I_FLUSH: ", strerror(errno), NULL);
			return (TCL_ERROR);
		}
	}

	if (writev(ep->ie_fd, ep->ie_iov, 2) < 0) {
		Tcl_AppendResult(interp, proto_name, " send operation: ",
		    "write failed: ", strerror(errno), NULL);
		return (TCL_ERROR);
	}

	return (TCL_OK);
}

static int
ipv6_recv(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	if (argc > 3 || argc < 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <pckt> [<timeout>] ;# <timeout> in milliseconds", NULL);
		return (TCL_ERROR);
	}

	return (ipv6_waitpkt(clientData, interp, argc, argv, BFALSE));
}

int
pwait_action(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	return (ipv6_waitpkt(clientData, interp, argc, argv, BTRUE));
}

int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	/* First, initialize the name and address translation caches. */
	Tcl_InitHashTable(&ipv6_nametable, TCL_STRING_KEYS);
	Tcl_InitHashTable(&ipv6_addrtable, TCL_STRING_KEYS);

	/*
	 * pre-load IPv6 addresses and associated names from the
	 * /etc/inet/ipnodes file if present.  No other IPv6 name
	 * service is supported since psh must compile and run on
	 * operating systems which don't support IPv6.
	 */
	ipv6_loadnames();

	/* set the size of an IPv6 end-point */
	prototbl->proto_epsize = sizeof (struct ipv6_ep);

	/*
	 * Register protocol specific commands.
	 */
	(void) Tcl_CreateCommand(interp, "ipv6.a2h", ipv6_a2h, NULL, NULL);
	(void) Tcl_CreateCommand(interp, "ipv6.h2a", ipv6_h2a, NULL, NULL);
	(void) Tcl_CreateCommand(interp, "ipv6.src", ipv6_addr,
	    (ClientData)&def_src, NULL);
	(void) Tcl_CreateCommand(interp, "ipv6.dst", ipv6_addr,
	    (ClientData)&def_dst, NULL);
	(void) Tcl_CreateCommand(interp, "ipv6.addrequal", ipv6_addrequal,
	    NULL, NULL);
	(void) Tcl_CreateCommand(interp, "ipv6.a2ndm", ipv6addr2ndm,
	    (ClientData)&def_dst, NULL);
	(void) Tcl_CreateCommand(interp, "ipv6.ipv62ether", ipv6_ipv62ether,
	    NULL, NULL);
	(void) Tcl_CreateCommand(interp, "ipv6.ether2token", ipv6_ether2token,
	    NULL, NULL);
	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: no space left.",
		    NULL);
		return (0);
	}
	(void) Tcl_SetVar(interp, "pset-protocols", proto_name,
	    TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
	(void) Tcl_SetVar(interp, "pget-protocols", proto_name,
	    TCL_LIST_ELEMENT|TCL_APPEND_VALUE);
	return (1);
}

int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	int fd, save_errno;
	dl_info_ack_t dl;
	struct ipv6_ep *ep;
	char *err;
	char tmp[128];
	struct ether_header etherhdr;
	struct ether_addr *addrp, addr;
	struct timeval tv;
	ulong_t bufmodtmp;
	char filterbuf[BUFSIZ];
	int ppa;

	if (argc < 5 || argc > 6) {
		Tcl_AppendResult(interp, "Usage: popen ", proto_name,
		    " <id> <path-to-interface> <ppa> <ethersrc> <etherdst>",
		    " [<filter string>]", NULL);
		return (TCL_ERROR);
	}

	/*
	 * The end-point was already allocated by psh_popen() in psh.c,
	 * and passed in to this function as clientData.  Our job is to
	 * fill in the IPv6 specific fields, and to return an error if
	 * we're unable to do this.
	 */
	ep = (struct ipv6_ep *)clientData;

	if ((fd = open(argv[1], O_RDWR)) < 0) {
		save_errno = errno;
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "popen ipv6: open: ", argv[1], ": ",
		    strerror(save_errno), NULL);
		return (TCL_ERROR);
	}
	if (Tcl_GetInt(interp, argv[2], &ppa) != TCL_OK) {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "popen ipv6: ", argv[2],
		    ": not integer", NULL);
		return (TCL_ERROR);
	}

	addrp = ether_aton(argv[3]);
	if (addrp == NULL) {
		addrp = &addr;
		if (ether_hostton(argv[3], &addr) != 0) {
			Tcl_AppendResult(interp,
			    "Badly formed ether addr ", argv[3], NULL);
			return (TCL_ERROR);
		}
	}
	(void) memcpy((void *)&etherhdr.ether_shost, (void *)addrp,
	    sizeof (struct ether_addr));

	free(addrp);
	addrp = ether_aton(argv[4]);
	if (addrp == NULL) {
		addrp = &addr;
		if (ether_hostton(argv[4], &addr) != 0) {
			Tcl_AppendResult(interp,
			    "Badly formed ether addr ", argv[4], NULL);
			return (TCL_ERROR);
		}
	}

	(void) memcpy((void *)&etherhdr.ether_dhost, (void *)addrp,
	    sizeof (struct ether_addr));
	free(addrp);
	etherhdr.ether_type = htons(0x86dd);

	/*
	 * Do a DL_INFO_REQ to find out if we understand how to cope with
	 * this interface.
	 */
	if (dlinforeq(fd, &dl, &err) < 0) {
		Tcl_AppendResult(interp, "ifopen: dlinforeq: ", argv[1], ": ",
		    err, NULL);
		return (TCL_ERROR);
	}
	if (dl.dl_version != DL_VERSION_2) {
		(void) sprintf(tmp, "%ld", dl.dl_version);
		Tcl_AppendResult(interp, "ifopen: dlinforeq: ", argv[1], ": ",
		    "Incompatible version:", tmp, NULL);
		return (TCL_ERROR);
	}
	if (dl.dl_mac_type > DL_OTHER ||
	    mtu_table[dl.dl_mac_type] < 0) {
		/*
		 * Unknown MAC type
		 */
		(void) sprintf(tmp, "mac type %ld", dl.dl_mac_type);
		Tcl_AppendResult(interp, "ifopen: dlinforeq: ", argv[1], ": ",
		    "unknown mac type: ", tmp, NULL);

	}
	dlattachreq(fd, ppa);

	if (dlbindreq(fd, /* XXX 8 for PPP if */ 0x86dd, 0, DL_CLDLS, 0) < 0) {
		Tcl_AppendResult(interp, "ifopen: ", argv[1], ": ",
		    "DL_BIND_REQ failed", NULL);
		return (TCL_ERROR);
	}

	/*
	 * Set the interface in multicast and phys promiscuous mode.  This
	 * is necessary because "this" end of the connection does not
	 * necessarily have the same ethernet address as the host machine.
	 */
	if (dlpromiscon(fd, DL_PROMISC_PHYS) < 0) {
		Tcl_AppendResult(interp, "ifopen: ", argv[1], " ",
		    "DL_PROMISCON_REQ physical failed", NULL);
		return (TCL_ERROR);
	}
	if (dlpromiscon(fd, DL_PROMISC_MULTI) < 0) {
		Tcl_AppendResult(interp, "ifopen: ", argv[1], " ",
		    "DL_PROMISCON_REQ multicast failed", NULL);
		return (TCL_ERROR);
	}

	if (ioctl(fd, DLIOCRAW, 0) < 0) {
		save_errno = errno;
		(void) close(fd);
		Tcl_AppendResult(interp, "ifopen: ", argv[1], ": ",
		    "DLIOCRAW ioctl: ", strerror(save_errno), NULL);
		return (TCL_ERROR);
	}

	/*
	 * Accept only IPv6 packets, and only from the ether address
	 * supplied as the "destination" of the open.
	 */
	(void) sprintf(filterbuf, "(ethertype 0x86dd and ether src %s)",
	    argv[4]);

	/* Concatinate the user-supplied filter with the "system" filter */
	if (argc == 6) {
		(void) strcat(filterbuf, " and (");
		(void) strcat(filterbuf, argv[5]);
		(void) strcat(filterbuf, ")");
	}
	if (pf_install(filterbuf, fd) == TCL_ERROR) {
		Tcl_AppendResult(interp, "Bad filter expression:",
		    argv[4], NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}
	if (ioctl(fd, I_PUSH, "bufmod") < 0) {
		Tcl_AppendResult(interp, "push bufmod: ", strerror(errno),
		    NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}

	/*
	 * Set the chunksize and the timeout.  In order for the chunksize to
	 * be nonzero, the timeout must also be nonzero (see bufmod(7M)).
	 * Until another way is found to avoid the loss of packets at the
	 * stream head, we are stuck with up to a 50ms delay on the receipt
	 * of packets.
	 */

	tv.tv_sec = 0;
	tv.tv_usec = 10000;
	if (ioctl(fd, SBIOCSTIME, &tv) < 0) {
		Tcl_AppendResult(interp, "SBIOCSTIME: ",
		    strerror(errno), NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}
	bufmodtmp = CHUNKSIZE;
	if (ioctl(fd, SBIOCSCHUNK, &bufmodtmp) < 0) {
		Tcl_AppendResult(interp, "SBIOCSCHUNK: ",
		    strerror(errno), NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}
	if (ioctl(fd, SBIOCGFLAGS, &bufmodtmp) < 0) {
		Tcl_AppendResult(interp, "SBIOCGFLAGS: ",
		    strerror(errno), NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}
	bufmodtmp |= SB_NO_DROPS;
	if (ioctl(fd, SBIOCSFLAGS, &bufmodtmp) < 0) {
		Tcl_AppendResult(interp, "SBIOCSFLAGS: ",
		    strerror(errno), NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}

	bufmodtmp = 0;
	if (ioctl(fd, SBIOCSSNAP, &bufmodtmp) < 0) {
		Tcl_AppendResult(interp, "SBIOCSSAP: ", strerror(errno), NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}

	/*
	 * Flush the read queue, to get rid of anything that
	 * accumulated before the device reached its final configuration.
	 */

	if (ioctl(fd, I_FLUSH, FLUSHR) < 0) {
		Tcl_AppendResult(interp, "I_FLUSH: ", strerror(errno), NULL);
		(void) close(fd);
		return (TCL_ERROR);
	}

	/* set all other end-point attributes */
	ep->ie_fd = fd;
	(void) memcpy((void *)&ep->ie_etherhdr, (void *)&etherhdr,
	    sizeof (struct ether_header));
	ep->ie_iov[0].iov_base = (char *)&ep->ie_etherhdr;
	ep->ie_iov[0].iov_len = sizeof (struct ether_header);

	(void) sprintf(tmp, "%s.send", argv[0]);
	(void) Tcl_CreateCommand(interp, tmp, ipv6_send, ep, NULL);

	(void) sprintf(tmp, "%s.recv", argv[0]);
	(void) Tcl_CreateCommand(interp, tmp, ipv6_recv, ep, NULL);

	/*
	 * Got it all, we're open for business.
	 */
	Tcl_AppendResult(interp, "popen ", proto_name, ": ", argv[0], ": ",
	    "successfully opened.  ", NULL);
	Tcl_AppendResult(interp, "mac type ",
	    mac_names[dl.dl_mac_type], NULL);
	(void) sprintf(tmp, "%ld", dl.dl_addr_length);
	Tcl_AppendResult(interp, " addr_len ", tmp, NULL);

	return (TCL_OK);
}

int
prclose(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ipv6_ep *ep;
	char tmp[128];

	ep = (struct ipv6_ep *)clientData;

	(void) sprintf(tmp, "%s.send", ep->ie_ep.ep_name);
	(void) Tcl_DeleteCommand(interp, tmp);
	(void) sprintf(tmp, "%s.recv", ep->ie_ep.ep_name);
	(void) Tcl_DeleteCommand(interp, tmp);

	(void) close(ep->ie_fd);
	return (TCL_OK);
}

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

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

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

	ip6hdr.ip6_vflow = htonl(0x60000000);
	ip6hdr.ip6_src = def_src;
	ip6hdr.ip6_dst = def_dst;
	bcopy(&ip6hdr, pckt->p_buf, sizeof (ip6hdr));
	return (TCL_OK);
}

int
decode(struct packet *pckt, int element, char **name, int *next_offset)
{
	struct ip6 ip6hdr;

	if ((pckt->p_tbl[element].pt_offset + sizeof (struct ip6)) >=
	    pckt->p_len) {
		*name = "end";
		*next_offset = pckt->p_len;
		return (0);
	}

	bcopy(pckt->p_buf + pckt->p_tbl[element].pt_offset, &ip6hdr,
	    sizeof (ip6hdr));
	*name = ipproto_name_by_number(ip6hdr.ip6_nextheader);
	*next_offset = pckt->p_tbl[element].pt_offset + sizeof (ip6hdr);
	return (1);
}

/*
 * Get protocol fields.
 */
int
get(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct ip6 ip6hdr;
	char tmp[128];

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget <pkt> ipv6 <item>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&ip6hdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (ip6hdr));
	/*
	 * Print one field
	 */
	if (strcmp(argv[0], "dst") == 0) {
		(void) psh_addr2ascii(AF_INET6, (void *)&ip6hdr.ip6_dst, tmp);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "src") == 0) {
		(void) psh_addr2ascii(AF_INET6, (void *)&ip6hdr.ip6_src, tmp);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if ((strcmp(argv[0], "proto") == 0) ||
	    (strcmp(argv[0], "nexthdr") == 0)) {
		(void) strcpy(tmp,
		    ipproto_name_by_number(ip6hdr.ip6_nextheader));
		if (strcmp(tmp, "unknown") != 0) {
			Tcl_AppendResult(interp, tmp, NULL);
		} else {
			(void) sprintf(tmp, "%u", ip6hdr.ip6_nextheader);
			Tcl_AppendResult(interp, tmp, NULL);
		}
	} else if (strcmp(argv[0], "len") == 0) {
		(void) sprintf(tmp, "0x%.4x", htons(ip6hdr.ip6_len));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "v") == 0) {
		(void) sprintf(tmp, "0x%.1x", htonl(ip6hdr.ip6_vflow) >> 28);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "flow") == 0) {
		(void) sprintf(tmp, "0x%x",
		    htonl(ip6hdr.ip6_vflow) & 0xfffffff);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "hoplimit") == 0 ||
	    strcmp(argv[0], "ttl") == 0) {
		(void) sprintf(tmp, "0x%.2x", ip6hdr.ip6_hoplimit);
		Tcl_AppendResult(interp, tmp, NULL);
	} else {
		Tcl_AppendResult(interp, "Unknown ipv6 field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}

	return (TCL_OK);
}

/*
 * Set a protocol element
 *
 * Supported protocol fields:
 *	src
 *	dst
 *	proto
 *	flow
 *	v
 *	hoplimit
 *	len
 */
int
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct ip6		ip6hdr;
	int			value;
	struct ip6_addr		addr;
	struct protoent		*pp;

	(void) memcpy(&ip6hdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (ip6hdr));

	if ((argc == 1) && (strcmp(argv[0], "len") == 0)) {
		value = pckt->p_len - pckt->p_tbl[element].pt_offset -
		    sizeof (ip6hdr);
		ip6hdr.ip6_len = htons(value);
		Tcl_ResetResult(interp);
		(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset,
		    &ip6hdr, sizeof (ip6hdr));
		return (TCL_OK);
	}

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: pset <pkt>", proto_name,
		    " <field> <value>", NULL);
		return (TCL_ERROR);
	}
	/*
	 * Set one field
	 */
	if (strcmp(argv[0], "dst") == 0) {
		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);
		}
		ip6hdr.ip6_dst = addr; /* XXX */
	} else if (strcmp(argv[0], "src") == 0) {
		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);
		}
		ip6hdr.ip6_src = addr;
	} else if ((strcmp(argv[0], "proto") == 0) ||
	    (strcmp(argv[0], "nexthdr") == 0)) {
		if ((pp = getprotobyname(argv[1])) != NULL) {
			ip6hdr.ip6_nextheader = pp->p_proto;
		} else if ((value = ipproto_number_by_name(argv[1])) > -1) {
			ip6hdr.ip6_nextheader = (uchar_t)value;
		} else if (Tcl_GetInt(interp, argv[1], &value) ==
		    TCL_OK) {
			if (value > 255) {
				Tcl_AppendResult(interp, "IP proto value ",
				    "out of range: ", argv[1], NULL);
			}
			ip6hdr.ip6_nextheader = (unsigned char)value;
		} else {
			Tcl_AppendResult(interp, "Bad ipv6 proto: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
	} else if (strcmp(argv[0], "len") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp,
			    "Badly formed ipv6 len: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 65535) {
			Tcl_AppendResult(interp, "bad ipv6 len value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		/*
		 * Set the packet structure length appropriately
		 */
		if (value > pckt->p_len) {
			/* XXX */
		}
		/* XXX don't assume IP is first in packet */
		/* pckt->p_len = value + pckt->p_tbl[element].pt_offset; */
		ip6hdr.ip6_len = htons(value);
	} else if (strcmp(argv[0], "v") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp,
			    "Badly formed hl/v: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 16) {
			Tcl_AppendResult(interp, "bad hl/v value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		ip6hdr.ip6_vflow = htonl(ip6hdr.ip6_vflow);
		ip6hdr.ip6_vflow &= IP6_FLOW_MASK;
		ip6hdr.ip6_vflow |= (value << 28);
		ip6hdr.ip6_vflow = htonl(ip6hdr.ip6_vflow);
	} else if (strcmp(argv[0], "hoplimit") == 0 ||
	    strcmp(argv[0], "ttl") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp,
			    "Badly formed hoplimit: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 255) {
			Tcl_AppendResult(interp, "bad hoplimit value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		ip6hdr.ip6_hoplimit = (unsigned char)value;
	} else if (strcmp(argv[0], "flow") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp,
			    "Badly formed flow: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > IP6_FLOW_MASK || value < 0) {
			Tcl_AppendResult(interp, "bad flow value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		ip6hdr.ip6_vflow = ntohl(ip6hdr.ip6_vflow);
		ip6hdr.ip6_vflow &= IP6_VERSION_MASK;
		ip6hdr.ip6_vflow |= value;
		ip6hdr.ip6_vflow = htonl(ip6hdr.ip6_vflow);
	} else {
		Tcl_AppendResult(interp, "Unknown ipv6 field: ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
	Tcl_ResetResult(interp);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset, &ip6hdr,
	    sizeof (ip6hdr));
	return (TCL_OK);
}

int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct ip6 ip6hdr;
	char tmp[128];
	unsigned short *usp;
	int count;

	/*
	 * Print it all
	 */
	(void) memcpy(&ip6hdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (ip6hdr));
	(void) strcpy(tmp, ipproto_name_by_number(ip6hdr.ip6_nextheader));
	if (strcmp(tmp, "unknown") == 0) {
		(void) sprintf(tmp, "%u", ip6hdr.ip6_nextheader);
	}
	Tcl_AppendResult(interp, "proto ", tmp, NULL);
	Tcl_AppendResult(interp, " src ", NULL);
	/* XXX byte ordering */
	for (usp = (unsigned short *)&ip6hdr.ip6_src, count = 0; count < 8;
	    count++, usp++) {
		if (count != 7) {
			(void) sprintf(tmp, "%x:", htons(*usp));
		} else {
			(void) sprintf(tmp, "%x ", htons(*usp));
		}
		Tcl_AppendResult(interp, tmp, NULL);
	}
	Tcl_AppendResult(interp, " dst ", NULL);
	for (usp = (unsigned short *)&ip6hdr.ip6_dst, count = 0; count < 8;
	    count++, usp++) {
		if (count != 7) {
			(void) sprintf(tmp, "%x:", htons(*usp));
		} else {
			(void) sprintf(tmp, "%x ", htons(*usp));
		}
		Tcl_AppendResult(interp, tmp, NULL);
	}
	(void) sprintf(tmp, "0x%x", htons(ip6hdr.ip6_len));
	Tcl_AppendResult(interp, " len ", tmp, NULL);
	(void) sprintf(tmp, "0x%.2x", ip6hdr.ip6_hoplimit);
	Tcl_AppendResult(interp, " hoplimit ", tmp, NULL);
	(void) sprintf(tmp, "0x%x", htonl(ip6hdr.ip6_vflow) & 0xfffffff);
	Tcl_AppendResult(interp, " flow ", tmp, NULL);
	return (TCL_OK);
}
