/*
 *	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	"@(#)tcp.c	1.36	00/03/23 SMI"

#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include "name2int.h"
/*
 * A subtle but important note:  ipv6.h must come before sys/socket.h.
 * This is because ipv6.h only defines ip6_addr if AF_INET6 _hasn't_ been
 * defined.
 */
#include "ipv6.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netinet/ip.h>

static struct nameint opt_names[] = {
	{ "nop",	TCPOPT_NOP },
	{ "eol",	TCPOPT_EOL },
	{ "mss",	TCPOPT_MAXSEG }
};
#define	NTCP_OPTS	(sizeof (opt_names)/sizeof (struct nameint))

/*
 * XXX port # to service name routines
 */

#ifdef SOLARIS
static int tcp_send(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[]);
static int tcp_recv(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[]);
#endif /* SOLARIS */

static void
fmt_flags(char *str, int flags)
{
	*str = '\0';
	if (flags & TH_FIN) {
		(void) strcat(str, "FIN");
	}
	if (flags & TH_SYN) {
		if (*str) {
			(void) strcat(str, "|");
		}
		(void) strcat(str, "SYN");
	}
	if (flags & TH_RST) {
		if (*str) {
			(void) strcat(str, "|");
		}
		(void) strcat(str, "RST");
	}
	if (flags & TH_PUSH) {
		if (*str) {
			(void) strcat(str, "|");
		}
		(void) strcat(str, "PUSH");
	}
	if (flags & TH_ACK) {
		if (*str) {
			(void) strcat(str, "|");
		}
		(void) strcat(str, "ACK");
	}
	if (flags & TH_URG) {
		if (*str) {
			(void) strcat(str, "|");
		}
		(void) strcat(str, "URG");
	}
}

static int
scan_flags(Tcl_Interp *interp, char *str, uchar_t *newflags)
{
	char	flags = 0;
	int	value;

	if (Tcl_GetInt(interp, str, &value) == TCL_OK) {
		/*
		 * Specified the hard way--by integer value.  Just make
		 * sure the value fits.
		 */
		if (value < 0 || value > 255) {
			return (0);
		}
		*newflags = (uchar_t)value;
		return (1);
	}
	Tcl_ResetResult(interp);
	while (*str) {
		if (strncmp(str, "|", 1) == 0) {
			str++;
			continue;
		}
		if (strncasecmp(str, "FIN", 3) == 0) {
			str += 3;
			flags |= TH_FIN;
			continue;
		}
		if (strncasecmp(str, "syn", 3) == 0) {
			str += 3;
			flags |= TH_SYN;
			continue;
		}
		if (strncasecmp(str, "rst", 3) == 0) {
			str += 3;
			flags |= TH_RST;
			continue;
		}
		if (strncasecmp(str, "push", 4) == 0) {
			str += 4;
			flags |= TH_PUSH;
			continue;
		}
		if (strncasecmp(str, "ack", 3) == 0) {
			str += 3;
			flags |= TH_ACK;
			continue;
		}
		if (strncasecmp(str, "urg", 3) == 0) {
			str += 3;
			flags |= TH_URG;
			continue;
		}
		/*
		 * String doesn't match anything we expect.
		 */
		return (0);
	}
	*newflags = flags;
	return (1);
}

static int
get_delta(struct timeval *out, struct timeval *in)
{
	if ((out->tv_usec -= in->tv_usec) < 0) {
		out->tv_sec--;
		out->tv_usec += 1000000;
	}
	out->tv_sec -= in->tv_sec;
	return (out->tv_sec*1000 + (out->tv_usec/1000));
}

/*
 * End points for IP communication.  We retain here enough to be able
 * to do whatever communication operations we might need.  E.g., the fd
 * for the interface.
 */

typedef union {
	struct ip	ip4h;
	struct ip6	ip6h;
} ipu;

struct tcp_ep {
	ep_t		te_ep;
	int		te_fd;
	ipu		te_iph;
	int		te_dstport;
	int		te_af;
};

static char *proto_name;

/*
 * Initialization
 */
int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	/* set the size of a TCP end-point */
	prototbl->proto_epsize = sizeof (struct tcp_ep);

	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "malloc: no space", 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);
}

/*
 * Create a "default" packet.
 */
/*ARGSUSED0*/
int
pinit(ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct tcphdr tcph;
	int tcph_sz = sizeof (tcph);

	if (pckt->p_len == 0)
		pckt->p_len = sizeof (tcph);
	bzero(&tcph, tcph_sz);
	tcph.th_off = 5;
	bcopy(&tcph, pckt->p_buf, min(pckt->p_len, sizeof (tcph)));
	if (ptbl_update(pckt, 1, NULL, pckt->p_len) == 0) {
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

int
decode(struct packet *pkt, int element, char **name, int *next_offset)
{
	struct tcphdr tcp;

	(void) memcpy(&tcp, pkt->p_buf + pkt->p_tbl[element].pt_offset,
	    sizeof (tcp));
	*name = "data"; /* XXX should decode service port */
	*next_offset = pkt->p_tbl[element].pt_offset + tcp.th_off * 4;
	if (*next_offset == pkt->p_len) {
		*name = "end";
		return (0);
	}
	return (1);
}

/*ARGSUSED0*/
int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct tcp_ep *ep = (struct tcp_ep *)clientData;
	int value, save_errno;
	struct sockaddr_in from;
	struct hostent *hp;
	struct servent *sp;
	int fd, rc;
	int af = AF_INET;
	int srcport;
	int error_num = 0;
#ifdef AF_INET6
	struct	sockaddr_in6	from6;
#endif
	char tmp[BUFSIZ];

	if (argc > 6 || argc < 5) {
		Tcl_AppendResult(interp, "Usage: popen ", proto_name,
		    " <id> <src-addr> <src-port> <dst-addr> <dst-port>"
		    " [<ipversion>]", NULL);
		return (TCL_ERROR);
	}

	if (argc == 6 && (strcmp(argv[5], "ipv6") == 0)) {
#ifdef AF_INET6
		if ((fd = socket(AF_INET6, SOCK_RAW, IPPROTO_TCP)) == -1) {
			save_errno = errno;
			Tcl_AppendResult(interp, proto_name, " socket(AF_INET6)"
			    " failed:", strerror(save_errno), NULL);
			return (TCL_ERROR);
		}
		af = AF_INET6;
#else
		Tcl_AppendResult(interp, proto_name, " The library was compiled"
			" over IPv4", NULL);
		return (TCL_ERROR);
#endif
	} else {
		if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) == -1) {
			save_errno = errno;
			Tcl_AppendResult(interp, proto_name, " socket(AF_INET)"
			    " failed:", strerror(save_errno), NULL);
			return (TCL_ERROR);
		}
	}

	ep->te_fd = fd;
	ep->te_af = af;

	if (Tcl_GetInt(interp, argv[4], &value) == TCL_OK) {
		if (value < 0 || value > 65535) {
			Tcl_AppendResult(interp, proto_name,
			    ": bad dst port: ", argv[4], NULL);
			return (TCL_ERROR);
		}
		ep->te_dstport = htons(value);
	} else if ((sp = getservbyname(argv[4], "tcp")) != NULL) {
		ep->te_dstport = sp->s_port;
		Tcl_ResetResult(interp);
	} else {
		Tcl_AppendResult(interp, proto_name, ": bad dst port: ",
		    argv[4], NULL);
		return (TCL_ERROR);
	}

	if (Tcl_GetInt(interp, argv[2], &value) == TCL_OK) {
		if (value < 0 || value > 65535) {
		Tcl_AppendResult(interp, proto_name, ": bad src port: ",
		    argv[2], NULL);
		return (TCL_ERROR);
		}
		srcport = htons(value);
	} else if ((sp = getservbyname(argv[2], "tcp")) != NULL) {
		srcport = sp->s_port;
		Tcl_ResetResult(interp);
	} else {
		Tcl_AppendResult(interp, proto_name, ": bad src port: ",
			    argv[2], NULL);
	return (TCL_ERROR);
	}

	if (af == AF_INET) {
		if (((int)inet_addr(argv[1])) != -1) {
			ep->te_iph.ip4h.ip_src.s_addr = inet_addr(argv[1]);
		} else if ((hp = gethostbyname(argv[1])) != NULL) {
			ep->te_iph.ip4h.ip_src.s_addr =
				*(ulong_t *)(hp->h_addr_list[0]);
		} else {
			Tcl_AppendResult(interp, proto_name, ": bad src host: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}

		if (inet_addr(argv[3]) != -1) {
			ep->te_iph.ip4h.ip_dst.s_addr = inet_addr(argv[3]);
		} else if ((hp = gethostbyname(argv[3])) != NULL) {
			ep->te_iph.ip4h.ip_dst.s_addr =
				*(ulong_t *)(hp->h_addr_list[0]);
		} else {
			Tcl_AppendResult(interp, proto_name, ": bad dst host: ",
			    argv[3], NULL);
			return (TCL_ERROR);
		}

		from.sin_port = srcport;
		from.sin_family = AF_INET;
		from.sin_addr.s_addr = ep->te_iph.ip4h.ip_src.s_addr;
		rc = bind(ep->te_fd, (struct sockaddr *)&from, sizeof (from));
#ifdef AF_INET6
	} else {
		if (inet_pton(af, argv[1], &(ep->te_iph.ip6h.ip6_src)) != 1) {
			hp = getipnodebyname(argv[1], af, 0, &error_num);
			if (hp != NULL) {
				ep->te_iph.ip6h.ip6_src =
				    *(struct ip6_addr *)hp->h_addr_list[0];
			} else {
				Tcl_AppendResult(interp, proto_name,
				    ": bad src host: ", argv[1], NULL);
				return (TCL_ERROR);
			}
		}

		if (inet_pton(af, argv[3], &(ep->te_iph.ip6h.ip6_dst)) != 1) {
			hp = getipnodebyname(argv[3], af, 0, &error_num);
			if (hp != NULL) {
				ep->te_iph.ip6h.ip6_dst =
				    *(struct ip6_addr *)hp->h_addr_list[0];
			} else {
				Tcl_AppendResult(interp, proto_name,
				    ": bad dst host: ", argv[3], NULL);
				return (TCL_ERROR);
			}
		}

		from6.sin6_family = (sa_family_t)AF_INET6;
		from6.sin6_flowinfo = 0;
		from6.sin6_scope_id = 0;
		from6.__sin6_src_id = 0;
		from6.sin6_addr =
		    *((struct in6_addr *)&(ep->te_iph.ip6h.ip6_src));
		from6.sin6_port = srcport;
		rc = bind(ep->te_fd, (struct sockaddr *)&from6,
						sizeof (struct sockaddr_in6));

	}
#else
	}
#endif
	if (rc == -1) {
		save_errno = errno;
		Tcl_AppendResult(interp, proto_name,
		    " bind failed: ", strerror(save_errno), NULL);
		return (TCL_ERROR);
	}

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

	(void) sprintf(tmp, "%s.recv", argv[0]);
	(void) Tcl_CreateCommand(interp, tmp, tcp_recv, ep, NULL);
#endif /* SOLARIS */

	return (TCL_OK);
}

/*ARGSUSED0*/
int
prclose(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct tcp_ep *ep = (struct tcp_ep *)clientData;
	char tmp[BUFSIZ];

	(void) sprintf(tmp, "%s.send", argv[0]);
	(void) Tcl_DeleteCommand(interp, tmp);

	(void) sprintf(tmp, "%s.recv", argv[0]);
	(void) Tcl_DeleteCommand(interp, tmp);
	(void) close(ep->te_fd);
	return (TCL_OK);
}

/*
 * Get protocol fields.
 * There are three fields we process:
 *	dport
 *	sport
 *	seq
 *	ack
 *	flags
 *	win
 *	sum
 *	urp
 *	off
 */

/*ARGSUSED0*/
int
get(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	int			offset;
	struct tcphdr		tcph;
	char			tmp[BUFSIZ];

	offset = pckt->p_tbl[element].pt_offset;
	(void) memcpy(&tcph, pckt->p_buf + offset, sizeof (tcph));
	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget ", proto_name,
		    " <field-name>", NULL);
		return (TCL_ERROR);
	}
	/*
	 * Print one field
	 */
	if (strcmp(argv[0], "sport") == 0) {
		(void) sprintf(tmp, "%d", ntohs(tcph.th_sport));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "dport") == 0) {
		(void) sprintf(tmp, "%d", ntohs(tcph.th_dport));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "seq") == 0) {
		(void) sprintf(tmp, "%u", ntohl(tcph.th_seq));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "ack") == 0) {
		(void) sprintf(tmp, "%u", ntohl(tcph.th_ack));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "flags") == 0) {
		fmt_flags(tmp, tcph.th_flags);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "win") == 0) {
		(void) sprintf(tmp, "0x%x", ntohs(tcph.th_win));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "sum") == 0) {
		(void) sprintf(tmp, "0x%x", ntohs(tcph.th_sum));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "urp") == 0) {
		(void) sprintf(tmp, "%d", ntohs(tcph.th_urp));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "off") == 0) {
		(void) sprintf(tmp, "%d", tcph.th_off);
		Tcl_AppendResult(interp, tmp, NULL);
	} else {
		Tcl_AppendResult(interp, "Unknown ", proto_name, " field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

/*ARGSUSED0*/
int
pinfo(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	int flags;
	char *s;
	char *varname;

	flags = TCL_LIST_ELEMENT|TCL_APPEND_VALUE;
	if (argv[1] == "pset") {
		varname = "pset-tcp";
	} else if (argv[1] == "pget") {
		varname = "pget-tcp";
	}
	s = Tcl_GetVar(interp, varname, 0);
	if (s == NULL) {
		(void) Tcl_SetVar(interp, varname, "dport", flags);
		(void) Tcl_SetVar(interp, varname, "sport", flags);
		(void) Tcl_SetVar(interp, varname, "seq", flags);
		(void) Tcl_SetVar(interp, varname, "ack", flags);
		(void) Tcl_SetVar(interp, varname, "flags", flags);
		(void) Tcl_SetVar(interp, varname, "win", flags);
		(void) Tcl_SetVar(interp, varname, "sum", flags);
		(void) Tcl_SetVar(interp, varname, "urp", flags);
		(void) Tcl_SetVar(interp, varname, "off", flags);
		s = Tcl_GetVar(interp, varname, 0);
	}
	Tcl_AppendResult(interp, s, NULL);
	return (TCL_OK);
}

/*
 * Set a protocol element
 */

/*ARGSUSED0*/
int
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct tcphdr tcph;
	struct servent *sp;
	int value;

	(void) memcpy(&tcph, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (tcph));
	if (argc == 1 && strcmp(argv[0], "cksum") == 0) {
		/*
		 * Special case for 'cksum' command.  No argument, since
		 * the whole point is to prevent the user from having
		 * to compute it.
		 *
		 * For TCP this is tricky since we must work out the
		 * checksum for "psuedo header" as well.  We take advantage
		 * the checksum properties, though, and first checksum
		 * the base TCP packet.  Then we adjust it based on a
		 * prior ipv4 or ipv6 header if the previous entry in this
		 * packet is ip or ipv6.
		 */
		unsigned short sum;
		int len;
		int ip_index = -1;
		int ipv6_index = -1;
		int i;

		sum = 0;
		len = pckt->p_tbl[pckt->p_lptbl].pt_offset -
		    pckt->p_tbl[element].pt_offset;
		ip_cksum(&sum, (uchar_t *)((ulong_t)pckt->p_buf +
		    pckt->p_tbl[element].pt_offset), len);
		if (element == 0) {
			tcph.th_sum = htons(sum);
			goto out;
		}

		/*
		 * Look for an IP or IPv6 header.
		 */
		for (i = element - 1; i >= 0; i--) {
			if (strcmp(pckt->p_tbl[i].pt_name, "ipv6") == 0) {
				ipv6_index = i;
				break;
			}
			if (strcmp(pckt->p_tbl[i].pt_name, "ip") == 0) {
				ip_index = i;
				break;
			}
		}

		if (ip_index != -1) {
			/*
			 * IPv4 packet, so do a checksum for it.
			 */
			struct ip iphdr;

			(void) memcpy(&iphdr, (void *)((ulong_t)pckt->p_buf +
			    pckt->p_tbl[element-1].pt_offset),
			    sizeof (struct ip));
			iphdr.ip_ttl = 0;
			iphdr.ip_p = IPPROTO_TCP; /* TCP */
			iphdr.ip_sum = htons((ushort_t)len);
			sum = ~sum;
			ip_cksum(&sum, (uchar_t *)(((int *)&iphdr) + 2), 12);
			tcph.th_sum = htons(sum);
		} else if (ipv6_index != -1) {
			/*
			 * IPv6 packet
			 */
			struct ip6 *ip6hdr;
			struct routeopt6 *rh;
			struct pseudo_ip6 pseudo_hdr;
			int rthdr_index = -1;

			ip6hdr = (struct ip6 *)((ulong_t)pckt->p_buf +
			    pckt->p_tbl[ipv6_index].pt_offset);

			bzero(&pseudo_hdr, sizeof (struct pseudo_ip6));
			(void) memcpy(&pseudo_hdr.pseudo_ip6_src,
			    &ip6hdr->ip6_src, sizeof (struct ip6_addr));
			(void) memcpy(&pseudo_hdr.pseudo_ip6_dst,
			    &ip6hdr->ip6_dst, sizeof (struct ip6_addr));
			pseudo_hdr.pseudo_ip6_len = htons((ushort_t)len);
			pseudo_hdr.pseudo_ip6_p = IPPROTO_TCP; /* TCP */

			/*
			 * 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 (i = element - 1; i > ipv6_index; i--) {
				if (strcmp(pckt->p_tbl[i].pt_name,
				    "ipv6-route") == 0) {
					rh = (struct routeopt6 *)
					    ((ulong_t)pckt->p_buf +
					    pckt->p_tbl[i].pt_offset);
					if ((rh->routeopt6_routingtype == 0) &&
					    (rh->routeopt6_segmentsleft > 0)) {
						rthdr_index = i;
					}
					break;
				}
			}

			if (rthdr_index != -1) {
				/*
				 * We've found a type 0 routing header.  We
				 * must swap the destination address into the
				 * pseudo header.
				 */
				(void) memcpy(&pseudo_hdr.pseudo_ip6_dst,
				    (void *)((ulong_t)rh +
				    rh->routeopt6_hdrextlen * 8 - 8),
				    sizeof (struct ip6_addr));
			}

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

			tcph.th_sum = htons(sum);
		} else {
			(void) printf("XXX: cannot do pseudo header cksum!\n");
			abort();
		}
		goto out;
	}
	if (strcmp(argv[0], "addopt") == 0) {
		char option_bytes[256];
		char *option_end = option_bytes;
		int len;

		if (argc != 3) {
			Tcl_AppendResult(interp, proto_name, " set addopt ",
			    "<option> <value>", NULL);
			return (TCL_ERROR);
		}
		if (strcmp(argv[1], "mss") == 0) {
			if (Tcl_GetInt(interp, argv[2], (int *)&value) !=
			    TCL_OK) {
bad_mss_value:
				Tcl_AppendResult(interp, proto_name,
				    ": mss option, bad mss value: ", argv[2],
				    NULL);
				return (TCL_ERROR);
			}
			if (value > 65535 || value < 0) {
				goto bad_mss_value;
			}
			*option_end++ = TCPOPT_MAXSEG;
			*option_end++ = 4;
			*option_end++ = (value & 0xff00) >> 8;
			*option_end++ = (value & 0xff);
		} else {
			Tcl_AppendResult(interp, proto_name, " set addopt ",
			    "- unrecognized option: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		/*
		 * Common code to insert the new option
		 */
		len = option_end - option_bytes;
		if ((int)(((len+3)/4) + tcph.th_off) >= 16) {
			Tcl_AppendResult(interp, proto_name, " set addopt: ",
			    "no room for option", NULL);
			return (TCL_ERROR);
		}
		if (pckt->p_len + len > pckt->p_maxlen) {
			Tcl_AppendResult(interp, proto_name, " set addopt: ",
			    "XXX packet resizing unimplemented", NULL);
			return (TCL_ERROR);
		}
		/*
		 * Now copy up what follows the TCP header + options.
		 */
		bcopy(pckt->p_buf + pckt->p_tbl[element+1].pt_offset,
		    pckt->p_buf + pckt->p_tbl[element+1].pt_offset + len,
		    (size_t)(pckt->p_len - pckt->p_tbl[element+1].pt_offset));
		pckt->p_len += len;
		value = element;
		for (element++; element <= pckt->p_lptbl; element++) {
			if (ptbl_update(pckt, element, NULL,
			    pckt->p_tbl[element].pt_offset + len) == 0)
				return (TCL_ERROR);
		}
		element = value;
		bcopy(option_bytes, pckt->p_buf +
		    pckt->p_tbl[element].pt_offset + tcph.th_off * 4,
		    (size_t)len);
		tcph.th_off += ((len + 3)/4);
		goto out;
	}

	if (strcmp(argv[0], "delopt") == 0) {
		int len = 0;
		int option;
		unsigned char *del_begin;

		if (argc != 2) {
			Tcl_AppendResult(interp, proto_name, " set delopt ",
			    "<option>", NULL);
			return (TCL_ERROR);
		}
		if (!name2int(argv[1], &option, opt_names, NTCP_OPTS)) {
			Tcl_AppendResult(interp, proto_name,
			    ": bad option name: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		del_begin = (unsigned char *)pckt->p_buf +
		    pckt->p_tbl[element].pt_offset;
		del_begin += sizeof (tcph);
		/*
		 * Scan options looking for the indicated option
		 */
		while ((char *)del_begin < pckt->p_buf +
		    pckt->p_tbl[element+1].pt_offset) {
			if (*del_begin == option) {
				goto found;
			}
			if (*del_begin == TCPOPT_NOP) {
				del_begin++;
			} else if (*del_begin == TCPOPT_EOL) {
				break;
			}
			len += *del_begin;
			del_begin += len;
		}
found:
		if (*del_begin == TCPOPT_NOP || *del_begin == TCPOPT_EOL) {
			Tcl_AppendResult(interp, proto_name,
			    ": unimplemented: single byte option delete",
			    NULL);
			return (TCL_ERROR);
		} else {
			len = del_begin[1];
		}

		/*
		 * length of segment to move:
		 * length of remaining TCP options + length of rest of packet
		 */
		bcopy(del_begin + len, del_begin,
		    ((pckt->p_buf + pckt->p_tbl[element+1].pt_offset) -
		    (char *)del_begin) +
		    (pckt->p_len - pckt->p_tbl[element+1].pt_offset));
		for (value = element + 1; value <= pckt->p_lptbl; value++) {
			if (ptbl_update(pckt, value, NULL,
			    pckt->p_tbl[value].pt_offset - len) == 0)
				return (TCL_ERROR);
		}
		pckt->p_len = pckt->p_tbl[pckt->p_lptbl].pt_offset;
		tcph.th_off -= (len/4);
		goto out;
	}
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: pset ", proto_name,
		    " <field> <value>", NULL);
		return (TCL_ERROR);
	}
	if (strcmp(argv[0], "sport") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
			if ((sp = getservbyname(argv[1], "tcp")) == NULL) {
				goto bad_port;
			}
			value = sp->s_port;
			Tcl_ResetResult(interp);
		} else {
			value = htons(value);
		}
		if (value > 65535 || value < 0) {
bad_port:
			Tcl_AppendResult(interp, "Badly formed port ",
			    "number: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		tcph.th_sport = (ushort_t)value;
	} else if (strcmp(argv[0], "dport") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
			if ((sp = getservbyname(argv[1], "tcp")) == NULL) {
				goto bad_port;
			}
			value = sp->s_port;
			Tcl_ResetResult(interp);
		} else {
			value = htons(value);
		}
		if (value > 65535 || value < 0) {
			goto bad_port;
		}
		tcph.th_dport = (ushort_t)value;
	} else if (strcmp(argv[0], "seq") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
			Tcl_AppendResult(interp, "Bad sequence no.: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		tcph.th_seq = htonl(value);
	} else if (strcmp(argv[0], "ack") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
			Tcl_AppendResult(interp, "Bad ack: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		tcph.th_ack = htonl(value);
	} else if (strcmp(argv[0], "flags") == 0) {
		if (!scan_flags(interp, argv[1], &tcph.th_flags)) {
			Tcl_AppendResult(interp, "Bad flags: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
	} else if (strcmp(argv[0], "win") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
bad_window:
			Tcl_AppendResult(interp, "Bad window: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 65535 || value < 0) {
			goto bad_window;
		}
		tcph.th_win = htons(value);
	} else if (strcmp(argv[0], "sum") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
bad_cksum:
			Tcl_AppendResult(interp, "Bad checksum: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 65535 || value < 0) {
			goto bad_cksum;
		}
		tcph.th_sum = htons(value);
	} else if (strcmp(argv[0], "urp") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
bad_urp:
			Tcl_AppendResult(interp, "Bad urgent ptr: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 65535 || value < 0) {
			goto bad_urp;
		}
		tcph.th_urp = htons(value);
	} else if (strcmp(argv[0], "off") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) != TCL_OK) {
bad_off:
			Tcl_AppendResult(interp, "Bad offset value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 16 || value < 0) {
			goto bad_off;
		}
		/* XXX should adjust length of this entry! */
		tcph.th_off = value;
	} else {
		Tcl_AppendResult(interp, "Error: bad field: ",
		    argv[0]);
		return (TCL_ERROR);
	}
out:
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset, &tcph,
	    sizeof (tcph));
	return (TCL_OK);
}

/*
 * To do a smart snappy listing we need to keep state.  This structure
 * keeps all that state, for each connection.
 *
 * This is inspired from tcpdump.  However, unlike tcpdump, we don't bother
 * to track the ip or ipv6 address underneath with it.  This would provide
 * for less ambiguity, and would be an excellent enhancement.
 *
 * Another excellent improvement would be to use the service names instead
 * of the port numbers when printing this out.  Remember that requires
 * consulting the name service at init time, and using the cached data
 * to translate so that you don't disturb the wire with name service
 * lookup packets during a test.
 */
struct tcp_seq_hash {
	struct tcp_seq_hash *tsh_next;
	uint32_t	tsh_sseq;	/* Source's "base" sequence number */
	uint32_t	tsh_dseq;	/* Destination's "base" seq. number */
	uint16_t	tsh_sport;	/* Source's port number */
	uint16_t	tsh_dport;	/* Destination's port number */
};

#define	NHASH	919
static struct tcp_seq_hash *tcp_seqh[NHASH];
#define	TCP_SEQ_HASH(lport, hport) tcp_seqh[(int)((lport) + (hport)) % NHASH]

/*
 * Print it all
 */
/*ARGSUSED0*/
int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct tcphdr tcph;
	struct tcp_seq_hash *tchp;
	char tmp[BUFSIZ];
	unsigned int data_size;
	uint32_t seq, ack;

	(void) memcpy(&tcph, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (tcph));
	data_size = pckt->p_len - pckt->p_tbl[element+1].pt_offset;
	(void) sprintf(tmp, "%d -> %d: ", ntohs(tcph.th_sport),
	    ntohs(tcph.th_dport));
	Tcl_AppendResult(interp, tmp, NULL);
	if ((tcph.th_flags & (TH_SYN|TH_FIN|TH_RST|TH_PUSH)) != 0) {
		if ((tcph.th_flags & TH_SYN) != 0)
			Tcl_AppendResult(interp, "S", NULL);
		if ((tcph.th_flags & TH_FIN) != 0)
			Tcl_AppendResult(interp, "F", NULL);
		if ((tcph.th_flags & TH_RST) != 0)
			Tcl_AppendResult(interp, "R", NULL);
		if ((tcph.th_flags & TH_PUSH) != 0)
			Tcl_AppendResult(interp, "P", NULL);
	} else {
		Tcl_AppendResult(interp, ".", NULL);
	}
	(void) sprintf(tmp, "0x%x", ntohs(tcph.th_win));
	Tcl_AppendResult(interp, " win ", tmp, NULL);
	if (tcph.th_flags & TH_URG) {
		(void) sprintf(tmp, " urg %d ", ntohs(tcph.th_urp));
		Tcl_AppendResult(interp, tmp, NULL);
	}

	seq = htonl(tcph.th_seq);
	ack = htonl(tcph.th_ack);
	if (tcph.th_flags & TH_ACK) {

		tchp = TCP_SEQ_HASH(tcph.th_sport, tcph.th_dport);
		while (tchp != NULL && tchp->tsh_sport != tcph.th_sport &&
		    tchp->tsh_dport != tcph.th_dport) {
			tchp = tchp->tsh_next;
		}
		if (tchp == NULL) {
			/* Found a new connection.  Make a new record for it. */
			tchp = malloc(sizeof (struct tcp_seq_hash));
			if (tchp == NULL) {
				Tcl_AppendResult(interp, proto_name,
				    ": malloc: no space.", NULL);
				return (TCL_ERROR);
			}
			tchp->tsh_next =
			    TCP_SEQ_HASH(tcph.th_sport, tcph.th_dport);
			TCP_SEQ_HASH(tcph.th_sport, tcph.th_dport) = tchp;
			tchp->tsh_sport = tcph.th_sport;
			tchp->tsh_dport = tcph.th_dport;
			tchp->tsh_sseq = seq;
			tchp->tsh_dseq = ack;
		}
		seq -= tchp->tsh_sseq;
		ack -= tchp->tsh_dseq;
	}

	if (data_size != 0 || (tcph.th_flags & (TH_SYN|TH_FIN|TH_RST)) != 0) {
		(void) sprintf(tmp, " %u:%u(%d)", seq, seq + data_size,
		    data_size);
		Tcl_AppendResult(interp, tmp, NULL);
	}
	(void) sprintf(tmp, "%u", ack);
	Tcl_AppendResult(interp, " ack ", tmp, NULL);
	(void) sprintf(tmp, "0x%x", ntohs(tcph.th_sum));
	Tcl_AppendResult(interp, " sum ", tmp, " ", NULL);
	if (tcph.th_off > 5) {
		/*
		 * TCP options are present.
		 */
		unsigned char *op;
		int count;

		op = (unsigned char *)pckt->p_buf +
		    pckt->p_tbl[element].pt_offset + sizeof (tcph);
		count = tcph.th_off * sizeof (int) - sizeof (tcph);
		for (; count > 0; count--, op++) {
			if (*op == TCPOPT_EOL) {
				break;
			}
			if (*op == TCPOPT_NOP) {
				continue;
			}
			if (*op == TCPOPT_MAXSEG) {
				(void) sprintf(tmp, "%d",
				    ntohs(*((unsigned short *)(&op[2]))));
				Tcl_AppendResult(interp, "mss ", tmp, NULL);
				count -= op[1] - 1;
				op += op[1] - 1;
				continue;
			}
			/*
			 * Don't know this one...
			 */
			(void) sprintf(tmp, "option %d ", *op);
			Tcl_AppendResult(interp, tmp, NULL);
			count -= op[1] - 1;
			op += op[1] - 1;
		}
	}
	return (TCL_OK);
}

#ifdef SOLARIS
#include <sys/stropts.h>
#include <sys/tihdr.h>

static int
tcp_send(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct tcp_ep		*ep;
	struct packet		*pckt;
	int			size, rv, save_errno;
	ushort_t		sum = 0;
	int			zero_cksum = 0;
	struct tcphdr		tcph;
	struct sockaddr_in	destaddr;
#ifdef AF_INET6
	struct sockaddr_in6	destaddr6;
#endif

	ep = (struct tcp_ep *)clientData;
	if (argc != 2) {
		Tcl_AppendResult(interp, proto_name, " send operation: ",
		    "Usage: ", argv[0], " <pckt>", 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);
	}
	size = pckt->p_len;

	bcopy(pckt->p_buf, &tcph, sizeof (tcph));
	/*
	 * Our chosen semantic is that when the checksum value is zero,
	 * we automatically provide the correct checksum.  It would probably
	 * be better to also provide a per end-point open-time flag which
	 * allowed this to be suppressed.  This choice precludes deliberately
	 * sending the checksum equal to zero.
	 */
	if (tcph.th_sum == 0) {
		/*
		 * Since we don't actually use the IP header in the end-point
		 * as part of the packet we send, we use it to do the pseudo
		 * header part of the checksum calculation.
		 */
		zero_cksum = BTRUE;
		sum = 0;
		if (ep->te_af == AF_INET) {
			/*
			 * tcp packet for IPv4 endpoint
			 */
			ep->te_iph.ip4h.ip_ttl = 0;
			ep->te_iph.ip4h.ip_p = IPPROTO_TCP;
			ep->te_iph.ip4h.ip_sum = htons(pckt->p_len);
			ip_cksum(&sum, &ep->te_iph.ip4h.ip_ttl, 12);
		} else {
			/*
			 * tcp packet for IPv6 endpoint
			 */
			struct pseudo_ip6 pseudo_hdr;

			bzero(&pseudo_hdr, sizeof (struct pseudo_ip6));
			(void) memcpy(&pseudo_hdr.pseudo_ip6_src,
			    &(ep->te_iph.ip6h.ip6_src),
			    sizeof (struct ip6_addr));
			(void) memcpy(&pseudo_hdr.pseudo_ip6_dst,
			    &(ep->te_iph.ip6h.ip6_dst),
			    sizeof (struct ip6_addr));
			pseudo_hdr.pseudo_ip6_len = htons(pckt->p_len);
			pseudo_hdr.pseudo_ip6_p = IPPROTO_TCP;
			ip_cksum(&sum, (uchar_t *)&pseudo_hdr,
			    sizeof (struct pseudo_ip6));
		}
		sum = ~sum;
		ip_cksum(&sum, (uchar_t *)pckt->p_buf, pckt->p_len);
		tcph.th_sum = htons(sum);
	}
	bcopy(&tcph, pckt->p_buf, sizeof (tcph));

	/*
	 * Although binding is supported, if we bind then we can't receive
	 * in Solaris 2.6!   See bugid 4077711.
	 */
	if (ep->te_af == AF_INET) {
		destaddr.sin_family = AF_INET;
		destaddr.sin_addr.s_addr = ep->te_iph.ip4h.ip_dst.s_addr;
		destaddr.sin_port = ep->te_dstport;
		rv = sendto(ep->te_fd, pckt->p_buf, size, 0,
		    (struct sockaddr *)&destaddr, sizeof (struct sockaddr));
#ifdef AF_INET6
	} else {
		destaddr6.sin6_family = AF_INET6;
		(void) memcpy(&destaddr6.sin6_addr,
		    &(ep->te_iph.ip6h.ip6_dst), sizeof (struct ip6_addr));
		destaddr6.sin6_port = ep->te_dstport;
		destaddr6.sin6_flowinfo = 0;
		destaddr6.sin6_scope_id = 0;
		destaddr6.__sin6_src_id = 0;

		rv = sendto(ep->te_fd, pckt->p_buf, size, 0,
		    (struct sockaddr *)&destaddr6,
		    sizeof (struct sockaddr_in6));
	}
#else
	}
#endif
	if (zero_cksum == BTRUE) {
		tcph.th_sum = 0;
		bcopy(&tcph, pckt->p_buf, sizeof (tcph));
	}
	if (rv < 0) {
		save_errno = errno;
		Tcl_AppendResult(interp, "send ", proto_name, ": sendto: ",
		    strerror(save_errno), NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

static int
tcp_recv(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct tcp_ep *ep;
	struct packet *pckt;
	fd_set fds;
	struct timeval timo;
	struct timeval	tvb;
	struct timeval	tve;
	ssize_t size;
	int timeout, save_errno;
	int offset = 0;
	int peerport;
	struct tcphdr	*tcph;

	ep = (struct tcp_ep *)clientData;
	peerport = ep->te_dstport;

	if (argc > 3 || argc < 2) {
		Tcl_AppendResult(interp, proto_name, " recv: ",
		    "Usage: ", argv[0], " <pckt> [<timeout>] ;# <timeout> "
		    "in milliseconds", NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, proto_name, " recv operation: ",
		    "packet not found: ", argv[1], NULL);
		return (TCL_ERROR);
	}
	if (argc == 3) {
		timeout = atoi(argv[2]);
	}

	for (;;) {
		if (argc == 3) {
			/*
			 * Parse timeout value, and poll rather than just doing
			 * blocking read.
			 */
			(void) FD_ZERO(&fds);
			FD_SET(ep->te_fd, &fds);
			timo.tv_usec = (timeout % 1000) * 1000;
			timo.tv_sec = timeout / 1000;

			(void) gettimeofday(&tvb, (void *)NULL);
			if (select(ep->te_fd+1, &fds, NULL, &fds, &timo) <= 0) {
				Tcl_AppendResult(interp, "timeout", NULL);
				return (TCL_OK);
			}
		}
		size = read(ep->te_fd, pckt->p_buf, (size_t)pckt->p_maxlen);
		if (size <= 0) {
			save_errno = errno;
			Tcl_AppendResult(interp, proto_name, " read : ",
			    strerror(save_errno));
			return (TCL_ERROR);
		}

		/*
		 * Behavior of IPv6 endpoints (RAW sockets) differs from
		 * behavior of IPv4 endpoints:
		 *	IPv4 endpoints receive tcp packets with ip header,
		 *	IPv6 endpoints receive tcp packets without ip header.
		 */
		if (ep->te_af == AF_INET) {
			if (size <= sizeof (struct ip)) {
				Tcl_AppendResult(interp, proto_name,
				    ": short packet", NULL);
				return (TCL_ERROR);
			}
			size -= sizeof (struct ip);
			offset = sizeof (struct ip);
		}
		pckt->p_len = size;
		/*
		 * It is possible for RAW socket to receive tcp packet
		 * from port other than peer port. So, we should
		 * perform additional filtering to sift foreign packets.
		 */
		tcph = (struct tcphdr *)(pckt->p_buf + offset);
		if (peerport != tcph->th_sport) {
			(void) printf("\n XXX: Foreign packet srcport = %d \n",
			    ntohs(tcph->th_sport));
			if (argc == 3) {
				/*
				 * Correction of timeout value
				 */
				(void) gettimeofday(&tve, (void *)NULL);
				timeout -= get_delta(&tve, &tvb);
				if (timeout <= 0) {
					Tcl_AppendResult(interp, "timeout",
					    NULL);
					return (TCL_OK);
				}
			}
			continue;
		}
		bcopy(pckt->p_buf + offset, pckt->p_buf, (size_t)size);
		if (ptbl_init(pckt, proto_name) == 0)
			return (TCL_ERROR);
		Tcl_AppendResult(interp, "packet", NULL);
		return (TCL_OK);
	}
}
#endif /* SOLARIS */
