/*
 *	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	"@(#)socket.c	1.46	00/06/19 SMI"

#include <sys/types.h>			/* keep this first */
#include <inet/common.h>

#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include "name2int.h"
#include "mib2.h"			/* for compilation from Solaris 2.4 */

#include <unistd.h>			/* close(), etc */
#include <ctype.h>			/* isX() */
#include <netinet/in.h>			/* ntohX(), etc */
#include <sys/socket.h>			/* getsockopt(), etc */
#include <netdb.h>			/* getXbyY() */
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <sys/uio.h>			/* readv()/writev() */
#include <sys/un.h>
#include <sys/sockio.h>			/* SIOCATMARK, etc */
#include <arpa/inet.h>			/* inet_addr() */
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/tcp.h>
#include <strings.h>
#ifdef SOLARIS
#include <stropts.h>
#include <sys/tihdr.h>
#include <sys/tiuser.h>
#endif

#ifndef	T_CURRENT
#define	T_CURRENT	MI_T_CURRENT
#endif

#ifndef _SA_FAMILY_T
#define	_SA_FAMILY_T
typedef	unsigned short	sa_family_t;
#endif

#ifdef AF_INET6
#define	ADDRSZ(ep) (ep->se_sai.sa.sa_family == PF_INET6) ? \
	sizeof (struct sockaddr_in6) : (ep->se_sai.sa.sa_family == PF_INET) ? \
	sizeof (struct sockaddr_in) : sizeof (struct sockaddr_un)
#else
#define	ADDRSZ(ep) (ep->se_sai.sa.sa_family == PF_INET) ? \
	sizeof (struct sockaddr_in) : sizeof (struct sockaddr_un)
#endif

#define	N_ITEMS(x)	(sizeof (x) / sizeof (x[0]))

extern proto_tbl_t *proto_lookup(char *);

typedef	sock_func(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[]);

static int sock_saved_errno;

static sock_func	sock_listen,	sock_connect, 	sock_send,
			sock_recv,	sock_atmark,	sock_accept,
			sock_bind,	sock_shutdown,	sock_setsockopt,
			sock_read,	sock_write,	sock_getsockopt,
			sock_recvfrom,	sock_sendto,	sock_getpeername,
			sock_readv,	sock_writev,	sock_getsockname,
			sock_poll,	sock_setroute,	sock_mib,
			sock_sendmsg;

static struct nameint pfnames[] = {
	{ "unspec",		PF_UNSPEC	},
	{ "unix",		PF_UNIX		},
	{ "inet",		PF_INET 	},
#ifdef PF_INET6
	{ "inet6",		PF_INET6 	},
#endif
	{ "implink",		PF_IMPLINK 	},
	{ "pup",		PF_PUP 		},
	{ "chaos",		PF_CHAOS 	},
	{ "ns",			PF_NS 		},
#ifdef	PF_NBS
	{ "nbs",		PF_NBS 		},
#endif
	{ "ecma",		PF_ECMA 	},
	{ "datakit",		PF_DATAKIT 	},
	{ "ccitt",		PF_CCITT 	},
	{ "sna",		PF_SNA 		},
	{ "decnet",		PF_DECnet 	},
	{ "dli",		PF_DLI 		},
	{ "lat",		PF_LAT 		},
	{ "hylink",		PF_HYLINK 	},
	{ "appletalk",		PF_APPLETALK 	},
#ifdef	PF_NIT
	{ "nit",		PF_NIT 		},
#endif
#ifdef	PF_802
	{ "802",		PF_802 		},
#endif
#ifdef	PF_OSI
	{ "osi",		PF_OSI 		},
#endif
#ifdef	PF_X25
	{ "x25",		PF_X25 		},
#endif
#ifdef	PF_OSINET
	{ "osinet",		PF_OSINET 	},
#endif
#ifdef	PF_GOSIP
	{ "gosip",		PF_GOSIP 	},
#endif
	{ "ipx",		PF_IPX 		},
};

static struct nameint typenames[] = {
	{ "stream",	SOCK_STREAM 	},
	{ "dgram",	SOCK_DGRAM 	},
	{ "raw",	SOCK_RAW 	},
	{ "seqpacket",	SOCK_SEQPACKET 	},
	{ "rdm",	SOCK_RDM 	}
};

static struct nameint protonames[] = {
	{ "ip",		IPPROTO_IP 	},
	{ "udp",	IPPROTO_UDP 	},
	{ "tcp",	IPPROTO_TCP 	},
	{ "icmp",	IPPROTO_ICMP 	},
#ifdef	IPPROTO_ICMPV6
	{ "icmp6",	IPPROTO_ICMPV6 	},
#endif
	{ "igmp",	IPPROTO_IGMP 	},
	{ "raw",	IPPROTO_RAW 	}
};

static struct nameint sockopts[] = {

	/* SOL_SOCKET */
	{ "broadcast",		SO_BROADCAST 	},
	{ "debug",		SO_DEBUG 	},
	{ "dontroute",		SO_DONTROUTE 	},
	{ "error",		SO_ERROR 	},
	{ "keepalive",		SO_KEEPALIVE 	},
	{ "linger",		SO_LINGER 	},
	{ "oobinline",		SO_OOBINLINE 	},
	{ "rcvbuf",		SO_RCVBUF 	},
	{ "sndbuf",		SO_SNDBUF 	},
	{ "rcvlowat",		SO_RCVLOWAT 	},
	{ "sndlowat",		SO_SNDLOWAT 	},
	{ "rcvtimeo",		SO_RCVTIMEO 	},
	{ "sndtimeo",		SO_SNDTIMEO 	},
	{ "reuseaddr",		SO_REUSEADDR 	},
	{ "type",		SO_TYPE 	},
	{ "useloopback",	SO_USELOOPBACK 	},

	/* IPPROTO_TCP */
	{ "tcp_nodelay",		TCP_NODELAY			},
	{ "tcp_maxseg",			TCP_MAXSEG			},
	{ "tcp_notify_threshold",	TCP_NOTIFY_THRESHOLD		},
	{ "tcp_abort_threshold",	TCP_ABORT_THRESHOLD		},
	{ "tcp_conn_notify_threshold",	TCP_CONN_NOTIFY_THRESHOLD	},
	{ "tcp_conn_abort_threshold",	TCP_CONN_ABORT_THRESHOLD	},

	/* IPPROTO_IP */
	{ "ip_add_membership",		IP_ADD_MEMBERSHIP	},
	{ "ip_drop_membership",		IP_DROP_MEMBERSHIP	},
#ifdef AF_INET6
	/* IPPROTO_IPV6 */
	{ "ipv6_add_membership",	IPV6_ADD_MEMBERSHIP	},
	{ "ipv6_drop_membership",	IPV6_DROP_MEMBERSHIP	},
#endif
};

static struct nameint levelnames[] = {
	{ "SOL_SOCKET",		SOL_SOCKET	},
	{ "IPPROTO_TCP",	IPPROTO_TCP	},
	{ "tcp",		IPPROTO_TCP	},
	{ "IPPROTO_IP",		IPPROTO_IP	},
	{ "ip",			IPPROTO_IP	},
#ifdef AF_INET6

	{ "IPPROTO_IPV6",	IPPROTO_IPV6	},
	{ "ipv6",		IPPROTO_IPV6	},
#endif
};

#define	NSOCK_ERRORS	58
static struct nameint sock_error_codes[NSOCK_ERRORS] = {
	{ "EPERM",		EPERM		},
	{ "EAFNOSUPPORT",	EAFNOSUPPORT	},
	{ "EPFNOSUPPORT",	EPFNOSUPPORT	},
	{ "EMFILE",		EMFILE		},
	{ "ENFILE",		ENFILE		},
	{ "EPROTONOSUPPORT",	EPROTONOSUPPORT	},
	{ "ESOCKTNOSUPPORT",	ESOCKTNOSUPPORT	},
	{ "EPROTOTYPE",		EPROTOTYPE	},
	{ "ENOBUFS",		ENOBUFS		},
	{ "ENOMEM",		ENOMEM		},
	{ "ENOSR",		ENOSR		},
	{ "EBADF",		EBADF		},
	{ "ENOTSOCK",		ENOTSOCK	},
	{ "EOPNOTSUPP",		EOPNOTSUPP	},
	{ "EINVAL",		EINVAL		},
	{ "ENOTCONN",		ENOTCONN	},
	{ "EBADMSG",		EBADMSG		},
	{ "EDEADLK",		EDEADLK		},
	{ "EFAULT",		EFAULT		},
	{ "EINTR",		EINTR		},
	{ "EIO",		EIO		},
	{ "EISDIR",		EISDIR		},
	{ "ENOLCK",		ENOLCK		},
	{ "ENOLINK",		ENOLINK		},
	{ "ENXIO",		ENXIO		},
	{ "EACCES",		EACCES		},
	{ "EOVERFLOW",		EOVERFLOW	},
	{ "ESPIPE",		ESPIPE		},
	{ "EAGAIN",		EAGAIN		},
	{ "EDQUOT",		EDQUOT		},
	{ "EFBIG",		EFBIG		},
	{ "ENOSPC",		ENOSPC		},
	{ "EPIPE",		EPIPE		},
	{ "ERANGE",		ERANGE		},
	{ "EADDRNOTAVAIL",	EADDRNOTAVAIL	},
	{ "EADDRINUSE",		EADDRINUSE	},
	{ "EDESTADDRREQ",	EDESTADDRREQ	},
	{ "ENOTDIR",		ENOTDIR		},
	{ "ENAMETOOLONG",	ENAMETOOLONG	},
	{ "ENOENT",		ENOENT		},
	{ "ELOOP",		ELOOP		},
	{ "EROFS",		EROFS		},
	{ "EISCONN",		EISCONN		},
	{ "ENOPROTOOPT",	ENOPROTOOPT	},
	{ "EALREADY",		EALREADY	},
	{ "ECONNREFUSED",	ECONNREFUSED	},
	{ "EINPROGRESS",	EINPROGRESS	},
	{ "ENETUNREACH",	ENETUNREACH	},
	{ "ENETDOWN",		ENETDOWN	},
	{ "ECONNABORTED",	ECONNABORTED	},
	{ "ENETRESET",		ENETRESET	},
	{ "ECONNRESET",		ECONNRESET	},
	{ "ETIMEDOUT",		ETIMEDOUT	},
	{ "EWOULDBLOCK",	EWOULDBLOCK	},
	{ "ENODEV",		ENODEV		},
	{ "EPROTO",		EPROTO		},
	{ "EMSGSIZE",		EMSGSIZE	},
	{ "ESTALE",		ESTALE		},
};

static char *proto_name;

/*
 * 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 sockaddr		sa;
	struct sockaddr_in	sin;
	struct sockaddr_un	sockun;
#ifdef AF_INET6
	struct sockaddr_in6	sin6;
#endif
} su;

struct socket_ep {
	ep_t			se_ep;
	int			se_sock;
	su			se_sai;
};

static bool_t IPv6_supported;

static int
parse_sockaddr(ClientData clientData, char *s)
{
	int			default_af;
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct hostent 		*hp;
	struct servent 		*sp;
	char			*p = s;
	int			error_num = 0;


	default_af = ep->se_sai.sa.sa_family;
	if ((p = strchr(s, ':')) != NULL) {
		/*
		 * Don't use the default address family
		 */
		*p = '\0';
		if (name2int(s, &default_af, pfnames, N_ITEMS(pfnames)) == 0) {
			(void) printf("\nXXX: %s\n", s);
			/* XXX.sparker error message pass back? */
			return (TCL_ERROR);
		}
		s = s + strlen(s) + 1;
	}
	if (default_af == PF_UNIX) {
		ep->se_sai.sockun.sun_family = (sa_family_t)default_af;
		if (strlen(s) >= sizeof (ep->se_sai.sockun.sun_path)) {
			(void) printf("\nXXX:sun_path too long %s\n", s);
			return (TCL_ERROR);
		}
		(void) strcpy(ep->se_sai.sockun.sun_path, s);
		return (TCL_OK);
	}
#ifdef AF_INET6
	if (default_af == PF_INET6) {
		ep->se_sai.sin6.sin6_family = (sa_family_t)default_af;
	} else {
#endif
		ep->se_sai.sin.sin_family = (sa_family_t)default_af;
#ifdef AF_INET6
	}
#endif
	if ((p = strchr(s, '/')) == NULL) {
		return (TCL_ERROR);
	}
	if (p == s) {
		p++;
		goto parse_port;
	}
	*p++ = '\0';

	if (strchr(s, ':') != NULL || strchr(s, '.') != NULL) {
#ifdef AF_INET6
		if (default_af == PF_INET6) {
			if (inet_pton(AF_INET6, s,
				(void *) &(ep->se_sai.sin6.sin6_addr)) != 1) {
				(void) printf("XXX: (inet_pton) %s\n", s);
				return (TCL_ERROR);
			}
		} else {
#endif
			if ((ep->se_sai.sin.sin_addr.s_addr = inet_addr(s))
				== (in_addr_t)-1) {
				if (strcmp("255.255.255.255", s) != 0) {
					(void) printf("XXX: (inet_addr) %s\n",
						s);
					return (TCL_ERROR);
				}
			}
#ifdef AF_INET6
		}
#endif
	} else if (isalpha(*s) == 0 && default_af != PF_INET6) {
		ep->se_sai.sin.sin_addr.s_addr = (in_addr_t)(atoi(s));
	} else {
#ifdef AF_INET6
		if (default_af == PF_INET6 || IPv6_supported == BTRUE) {
			hp = getipnodebyname(s, default_af, 0, &error_num);
		} else {
#endif
			hp = gethostbyname(s);
#ifdef AF_INET6
		}
#endif
		if (hp == NULL) {
			(void) printf("XXX: (getipnode/hostbyname) %s err %d\n",
				s, error_num);
			return (TCL_ERROR);
		}
#ifdef AF_INET6
		if (default_af == PF_INET6) {
			ep->se_sai.sin6.sin6_addr =
				*(struct in6_addr *)hp->h_addr_list[0];
		} else {
#endif
			ep->se_sai.sin.sin_addr =
				*(struct in_addr *)hp->h_addr_list[0];
#ifdef AF_INET6
		}
#endif
	}

parse_port:
	if (isalpha(*p) == 0)
#ifdef AF_INET6
		if (default_af == PF_INET6) {
			ep->se_sai.sin6.sin6_port = htons(atoi(p));
		} else {
#endif
			ep->se_sai.sin.sin_port = htons(atoi(p));
#ifdef AF_INET6
		}
#endif
	else {
		if ((sp = getservbyname(p, NULL)) == NULL) {
			(void) printf("XXX: (getservbyname) %s\n", p);
			return (TCL_ERROR);
		}
#ifdef AF_INET6
		if (default_af == PF_INET6) {
			ep->se_sai.sin6.sin6_port = sp->s_port;
		} else {
#endif
			ep->se_sai.sin.sin_port = sp->s_port;
#ifdef AF_INET6
		}
#endif
	}
	return (TCL_OK);
}

static int
scan_flags(char *str)
{
	char	*cur_flag = strtok(str, "|");
	int 	set_flags = 0;
	int	bit_lit;

	static struct nameint flags[] = {
		{ "OOB",	MSG_OOB		 },
		{ "EOR",	MSG_EOR		 },
		{ "DONTROUTE",	MSG_DONTROUTE	 },
		{ "PEEK",	MSG_PEEK	 }
	};

	/* scan through all the flags.  if flag is unknown, ignore */
	for (; cur_flag; cur_flag = strtok(NULL, "|"))
		if (name2int(cur_flag, &bit_lit, flags, N_ITEMS(flags)) != 0)
			set_flags |= bit_lit;

	return (set_flags);
}

/*
 * Arrange to poll for I/O for up to 'timeout' milliseconds.
 *
 * Note that we use select because it's the most portable choice.
 */
static int
poll_timeout(int fd, int timeout)
{
	fd_set ins;
	struct timeval timo;

	(void) FD_ZERO(&ins);
	FD_SET(fd, &ins);
	timo.tv_usec = (timeout % 1000) * 1000;
	timo.tv_sec = timeout / 1000;

	return (select(fd+1, &ins, NULL, &ins, &timo));
}

/*ARGSUSED0*/
int
sock_err(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	char			*p;

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage:  socket.errno", NULL);
		return (TCL_ERROR);
	}

	p = int2name(sock_saved_errno, sock_error_codes, NSOCK_ERRORS);
	if (p == NULL) {
		(void) printf("\nXXX: unknown errno = %d\n", sock_saved_errno);
		return (TCL_ERROR);
	}
	Tcl_AppendResult(interp, p, NULL);
	return (TCL_OK);
}

/*
 * Initialization
 */
int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	int foo_IPv6_sock;

	/* set the size of a socket end-point */
	prototbl->proto_epsize = sizeof (struct socket_ep);

	IPv6_supported = BFALSE;
#ifdef AF_INET6
	if ((foo_IPv6_sock = socket(AF_INET6, SOCK_STREAM, 0)) > 0) {
		IPv6_supported = BTRUE;
		(void) close(foo_IPv6_sock);
	}
#endif
	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: malloc: no space",
		    NULL);
		return (0);
	}

	/*
	 * Register protocol specific commands.
	 */
	(void) Tcl_CreateCommand(interp, "socket.errno", sock_err, NULL, NULL);
	return (1);
}

enum ep_op { EP_OP_ADD, EP_OP_REMOVE };

static void
ep_cmd_ctl(Tcl_Interp *interp, enum ep_op op, const char *ep_name,
    struct socket_ep *ep)
{
	char		ep_cmd[128]; /* this could be dynamically allocated */
	size_t		i;

	static struct	name_to_cmd {
		const char	 *name;
		sock_func	 *cmd;
	} cmds[] = {
		{ "send", 		sock_send	},
		{ "connect",		sock_connect	},
		{ "listen",		sock_listen 	},
		{ "bind",		sock_bind	},
		{ "setsockopt",		sock_setsockopt },
		{ "getsockopt", 	sock_getsockopt },
		{ "accept",		sock_accept	},
		{ "recv",		sock_recv	},
		{ "atmark",		sock_atmark	},
		{ "shutdown",		sock_shutdown	},
		{ "read",		sock_read	},
		{ "write",		sock_write	},
		{ "recvfrom",		sock_recvfrom	},
		{ "sendto",		sock_sendto	},
		{ "getpeername",	sock_getpeername },
		{ "getsockname",	sock_getsockname },
		{ "readv",		sock_readv	},
		{ "writev",		sock_writev	},
		{ "sendmsg",		sock_sendmsg	},
		{ "poll",		sock_poll	},
		{ "setipsr",		sock_setroute	},
		{ "mib",		sock_mib	},
		{  NULL,		NULL		}
	};

	for (i = 0; cmds[i].name; i++) {
		(void) sprintf(ep_cmd, "%s.%s", ep_name, cmds[i].name);
		if (op == EP_OP_ADD) {
			(void) Tcl_CreateCommand(interp, ep_cmd, cmds[i].cmd,
			    ep, 0);
		} else if (op == EP_OP_REMOVE) {
			(void) Tcl_DeleteCommand(interp, ep_cmd);
		}
	}
}

int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int			pfamily		= AF_INET;
	int			type		= SOCK_STREAM;
	int			proto		= 0;
	int			nonbl		= 0;
	int			fd;

	if (argc > 5 || argc < 1) {
		Tcl_AppendResult(interp, "Usage: popen ", proto_name,
		    " <id> [<protocol-family> [<type> [<protocol>]]]",
		    "[nonblock]", NULL);
		return (TCL_ERROR);
	}

	if (argc > 1 && strcasecmp(argv[argc - 1], "nonblock") == 0) {
		nonbl = 1;
		argc--;
	}

	/*
	 * First look at the protocol family, and see if it looks cool.
	 */

	if (argc > 1) {
		if (name2int(argv[1], &pfamily, pfnames,
		    N_ITEMS(pfnames)) == 0) {
			Tcl_AppendResult(interp, proto_name,
			    ": bad proto family: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (strcmp(argv[1], "inet6") == 0 && IPv6_supported == BFALSE) {
			Tcl_AppendResult(interp, proto_name, ": proto family: ",
			    argv[1], " not supported", NULL);
			return (TCL_ERROR);
		}
	}

	if (argc > 2 &&
	    name2int(argv[2], &type, typenames, N_ITEMS(typenames)) == 0) {
		Tcl_AppendResult(interp, proto_name, ": bad socket type: ",
		    argv[2], NULL);
		return (TCL_ERROR);
	}

	if (argc > 3 && name2int(argv[3], &proto, protonames,
	    N_ITEMS(protonames)) == 0) {
		Tcl_AppendResult(interp, proto_name, ": bad socket protocol: ",
		    argv[3], NULL);
		return (TCL_ERROR);
	}

	fd = socket(pfamily, type, proto);
	if (fd < 0) {
		sock_saved_errno = errno;
		/* XXX for consistency should return "EBADF", etc. */
		Tcl_AppendResult(interp, proto_name, ": socket open failed :",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	if (nonbl) {
		(void) fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK);
	}

	ep->se_sock = fd;
	ep->se_sai.sa.sa_family = (sa_family_t)pfamily;
	sock_saved_errno = 0;

	/* add the endpoint commands */
	ep_cmd_ctl(interp, EP_OP_ADD, ep->se_ep.ep_name, ep);

	return (TCL_OK);
}

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

	/* delete the endpoint commands */
	ep_cmd_ctl(interp, EP_OP_REMOVE, ep->se_ep.ep_name, ep);

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

static int
sock_bootstrap(struct socket_ep *ep, struct packet *pckt, size_t data_size)
{
	struct servent		*sp;
	int			 sock_type;
	int			 len = sizeof (sock_type);
	struct sockaddr_in	 sai;
	int			 sai_size = sizeof (sai);
	char			*name = "ip";

	pckt->p_len		= data_size;
	if (ptbl_update(pckt, 0, "data", 0) == 0 ||
	    ptbl_update(pckt, 1, "end", data_size) == 0)
		return (0);

	if (getsockopt(ep->se_sock, SOL_SOCKET, SO_TYPE, (char *)&sock_type,
	    &len) == -1) {
		(void) fprintf(stderr, "sock_bootstrap: getsockopt() failed\n");
		return (0);
	}

	switch (sock_type) {
	case SOCK_RAW:
		break;
	case SOCK_DGRAM:	/* fallthru */
	case SOCK_STREAM:
		(void) getsockname(ep->se_sock, (struct sockaddr *)&sai,
		    &sai_size);
		sp = getservbyport(sai.sin_port, (sock_type == SOCK_DGRAM) ?
		    "udp" : "tcp");
		/*
		 * Some services won't be found, which just means we have
		 * to leave the default of treating it as the "protocol" data.
		 * This isn't an error.
		 */
		if (sp == NULL)
			return (1);
		name = sp->s_name;
		break;

	default:
		(void) fprintf(stderr, "sock_bootstrap: unknown socket type\n");
		return (0);
	}

	if (ptbl_update(pckt, 0, name, PTBL_KEEP_OFFSET) == 0)
		return (0);

	return (bootstrap_packet(pckt, 0));
}

/*ARGSUSED0*/
static int
sock_getname(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[], int (*namefunc)(int, struct sockaddr *, void *))
{
	su			sai;
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int 			addrlen	= ADDRSZ(ep);
	char 			buf[128];

	(void) bzero((void *)&sai, ADDRSZ(ep));

	if (namefunc(ep->se_sock, (struct sockaddr *)&sai, &addrlen) == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, ": ", argv[0], " failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
#ifdef AF_INET6
	if (ep->se_sai.sa.sa_family == PF_INET6) {
		inet_ntop(AF_INET6, (void *) &(sai.sin6.sin6_addr),
			buf, ADDRSZ(ep));
		(void) sprintf(buf + strlen(buf), "/%d",
		    ntohs(sai.sin6.sin6_port));
	} else {
#endif
		if (ep->se_sai.sa.sa_family == PF_INET) {
			(void) sprintf(buf, "%s/%d",
			    inet_ntoa(sai.sin.sin_addr),
			    ntohs(sai.sin.sin_port));
		} else {
			(void) sprintf(buf, "%s", sai.sockun.sun_path);
		}
#ifdef AF_INET6
	}
#endif
	Tcl_AppendResult(interp, proto_name, " ", buf, NULL);
	return (TCL_OK);
}

static int
sock_getpeername(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	return (sock_getname(clientData, interp, argc, argv, getpeername));
}

static int
sock_getsockname(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	return (sock_getname(clientData, interp, argc, argv, getsockname));
}

static int
sock_readv(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct iovec		*iov;
	struct packet		*pckt;
	size_t			i;
	ssize_t			result;
	char			tmp[128];

	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <pckt> [<pckt>]...", NULL);
		return (TCL_ERROR);
	}

	iov = calloc(argc - 1, sizeof (struct iovec));
	if (iov == NULL) {
		Tcl_AppendResult(interp, proto_name, "readv operation:",
		    " out of memory", NULL);
		return (TCL_ERROR);
	}

	/*
	 * initialize the iovec we pass into readv()
	 */
	for (i = 1; i < argc; i++) {
		if ((pckt = pckt_lookup(argv[i])) == NULL) {
			Tcl_AppendResult(interp, proto_name, " readv ",
			    "operation: packet not found: ", argv[i], NULL);
			free(iov);
			return (TCL_ERROR);
		}
		iov[i - 1].iov_len  = pckt->p_len;
		iov[i - 1].iov_base = pckt->p_buf;
	}

	result = readv(ep->se_sock, iov, argc);
	free(iov);
	if (result == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " readv failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	/*
	 * we know readv() will fill the buffers in order.  go back through
	 * them and mark the parts that are used
	 */
	(void) sprintf(tmp, "%d", result);
	Tcl_AppendResult(interp, tmp, NULL);
	for (i = 1; result > 0; i++) {
		pckt = pckt_lookup(argv[i]);	/* we could cache this */

		result -= pckt->p_len;
		if (ptbl_update(pckt, 0, "data", 0) == 0 ||
		    ptbl_update(pckt, 1, "end", (result > 0) ?
			pckt->p_len : result) == 0)
			return (TCL_ERROR);
	}

	/*
	 * should packet be printed for each packet that readv()
	 * put data into?  or just once?
	 */

	Tcl_AppendResult(interp, "packet", NULL);

	/*
	 * it's not clear here how sock_readv() should bootstrap this
	 * packet.  since readv() is a gather-read mechanism, it is
	 * quite likely that a big packet got gathered up into multiple
	 * little packets here in readv().  it wouldn't make sense for
	 * us to bootstrap each fragment.  on the other hand, if the
	 * packet isn't fragmented, bootstrapping would be the right
	 * thing to do.  but then why would the user be using readv()
	 * and not just plain read()/recvmsg()?
	 */

	/*
	 * return (sock_bootstrap(ep, pckt, size) ? TCL_OK : TCL_ERROR);
	 */

	return (TCL_OK);
}

static int
sock_writev(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct iovec		*iov;
	struct packet		*pckt;
	size_t			 i;
	ssize_t			 result;
	char			 tmp[128];

	if (argc < 2) {
		Tcl_AppendResult(interp, proto_name, " writev operation: ",
		    "Usage: ", argv[0], " <pckt> [<pckt>]...", NULL);
		return (TCL_ERROR);
	}

	iov = calloc(argc - 1, sizeof (struct iovec));
	if (iov == NULL) {
		Tcl_AppendResult(interp, proto_name, "writev operation:",
		    " out of memory", NULL);
		return (TCL_ERROR);
	}

	for (i = 1; i < argc; i++) {
		if ((pckt = pckt_lookup(argv[i])) == NULL) {
			Tcl_AppendResult(interp, proto_name,
			    " writev operation: ", "packet not found: ",
			    argv[i], NULL);
			free(iov);
			return (TCL_ERROR);
		}
		iov[i - 1].iov_base = pckt->p_buf;
		iov[i - 1].iov_len  = pckt->p_len;
	}

	result = writev(ep->se_sock, iov, i - 1);
	free(iov);
	if (result == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " write failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
	(void) sprintf(tmp, "%d", result);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
}

static int
sock_sendmsg(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct iovec		*iov = NULL;
	struct packet		*pckt;
	size_t			i;
	int			res;
	int			flags = 0;
	ssize_t			result;
	struct	msghdr		message;
	char			tmp[128];
	int			n = 1;
	int			optlen = sizeof (int);


	(void) bzero(&message, sizeof (struct msghdr));

	if (argc < 2) {
		Tcl_AppendResult(interp, proto_name, " sendmsg operation: ",
		    "Usage: ", argv[0],
		    " [<address>] <pckt> [<pckt>]...[<flags>]", NULL);
		return (TCL_ERROR);
	}

	if ((parse_sockaddr(ep, argv[1]) != TCL_ERROR)) {
		n++;
		message.msg_name = (void *)&ep->se_sai;
		message.msg_namelen = (socklen_t)ADDRSZ(ep);
	}

	iov = calloc(argc - 1, sizeof (struct iovec));
	if (iov == NULL) {
		Tcl_AppendResult(interp, proto_name, "sendmsg operation:",
		    " out of memory", NULL);
		return (TCL_ERROR);
	}
	message.msg_iov = iov;
	for (i = n; i < argc; i++) {
		if ((pckt = pckt_lookup(argv[i])) == NULL) {
			if (i == n) {
				Tcl_AppendResult(interp, proto_name,
				    " sendmsg : ",
				    "It need at least one packet: ", argv[i],
				    NULL);
				res = TCL_ERROR;
				goto done;
			}
			if (i < argc - 1) {
				Tcl_AppendResult(interp, proto_name,
				    " sendmsg : ", "packet not found: ",
				    argv[i], NULL);
				res = TCL_ERROR;
				goto done;
			} else {
				/*
				 * parse flags
				 */
				flags = scan_flags(argv[i]);
				break;
			}
		}
		iov[i - n].iov_base = pckt->p_buf;
		iov[i - n].iov_len  = pckt->p_len;
		message.msg_iovlen = message.msg_iovlen + 1;
	}

	result = sendmsg(ep->se_sock, &message, flags);
	if (result == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " sendmsg failed: ",
		    strerror(errno), NULL);
		res = TCL_ERROR;
		goto done;
	}
	(void) sprintf(tmp, "%d", result);
	Tcl_AppendResult(interp, tmp, NULL);
	res = TCL_OK;

done:
	if (iov)
		free(iov);
	return (res);
}


static int
sock_shutdown(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <how>", NULL);
		return (TCL_ERROR);
	}

	if (shutdown(ep->se_sock, atoi(argv[1])) == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, ": shutdown failed :",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

static int
sock_listen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <backlog>",
		    NULL);
		return (TCL_ERROR);
	}

	if (listen(ep->se_sock, atoi(argv[1])) == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, ": listen failed :",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

static int
sock_bind(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <sockaddr>",
		    NULL);
		return (TCL_ERROR);
	}

	if ((parse_sockaddr(ep, argv[1]) == TCL_ERROR)) {
		Tcl_AppendResult(interp, proto_name, ": bad <sockaddr> : ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}

	if (bind(ep->se_sock, (struct sockaddr *)&ep->se_sai, ADDRSZ(ep)) < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " bind failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

static int
sock_getsockopt(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int			level;
	struct linger		 ling;
	int			 intval;
	char			*value;
	int			 result;
	int			 optlen;
	int			 option;
	char			 tmp[256];

	if (argc != 3) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <level> <option>", NULL);
		return (TCL_ERROR);
	}

	if (name2int(argv[1], &level, levelnames, N_ITEMS(levelnames)) == 0) {
		Tcl_AppendResult(interp, proto_name, ": bad level: ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}

	if (name2int(argv[2], &option, sockopts, N_ITEMS(sockopts)) == 0) {
		Tcl_AppendResult(interp, proto_name, ": bad option: ",
		    argv[2], NULL);
		return (TCL_ERROR);
	}

	value  = (option == SO_LINGER) ? (char *)&ling : (char *)&intval;
	optlen = (option == SO_LINGER) ? sizeof (ling) : sizeof (intval);

	result = getsockopt(ep->se_sock, level, option, value, &optlen);
	if (result < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " getsockopt failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	if (option == SO_LINGER)
		(void) sprintf(tmp, "%d %d", ling.l_onoff, ling.l_linger);
	else
		(void) sprintf(tmp, "%d", intval);

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

static int
sock_setsockopt(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int			level;
	int			intval;
	char			*value;
	int			result;
	int			opt_storage[64];
	struct linger		*ling;
	struct ip_mreq		*mreq;
	int			option;
	int			optlen;

	(void) memset((char *)opt_storage, 0, sizeof (opt_storage));
	if (argc < 4 || argc > 5) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <level> <option> <value1> [<value2>]", NULL);
		return (TCL_ERROR);
	}

	if (name2int(argv[1], &level, levelnames, N_ITEMS(levelnames)) == 0) {
		Tcl_AppendResult(interp, proto_name, ": bad level: ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}

	if (name2int(argv[2], &option, sockopts, N_ITEMS(sockopts)) == 0) {
		Tcl_AppendResult(interp, proto_name, ": bad option: ",
		    argv[2], NULL);
		return (TCL_ERROR);
	}

	switch (option) {
	case SO_LINGER:
		ling = (struct linger *)opt_storage;
		if (argc != 5) {
			Tcl_AppendResult(interp, proto_name,
			    " Option (linger) requires <value2>", NULL);
			return (TCL_ERROR);
		}
		ling->l_onoff  = atoi(argv[3]);
		ling->l_linger = atoi(argv[4]);
		value  = (char *)ling;
		optlen = sizeof (struct linger);
		break;
	case IP_ADD_MEMBERSHIP:
	case IP_DROP_MEMBERSHIP:
		mreq = (struct ip_mreq *)opt_storage;
		mreq->imr_multiaddr.s_addr = inet_addr(argv[3]);
		if (argc == 5) {
			mreq->imr_interface.s_addr = inet_addr(argv[4]);
		}
		value  = (char *)mreq;
		optlen = sizeof (struct ip_mreq);
		break;
	default:
		if (argc != 4) {
			Tcl_AppendResult(interp, proto_name,
			    " Option accepts one argument", NULL);
			return (TCL_ERROR);
		}
		intval = atoi(argv[3]);
		optlen = sizeof (int);
		value  = (char *)&intval;
		break;
	}

	result = setsockopt(ep->se_sock, level, option, value, optlen);
	if (result < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " setsockopt failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	Tcl_AppendResult(interp, proto_name, " setsockopt succeeded", NULL);
	return (TCL_OK);
}

static int
sock_connect(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <sockaddr>",
		    NULL);
		return (TCL_ERROR);
	}

	/*
	 * Now the fun part, need to parse < sockaddr > and init structure vars
	 * It should look something like this inet:129.146.86.183/8000
	 */

	if (parse_sockaddr(ep, argv[1]) == TCL_ERROR) {
		Tcl_AppendResult(interp, proto_name, ": bad <sockaddr> : ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
	if (connect(ep->se_sock, (struct sockaddr *)&ep->se_sai,
	    ADDRSZ(ep)) < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " connect failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

int
sock_accept(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct socket_ep	*accept_ep;
	int			namelen = ADDRSZ(ep);
	int			fd;

	/*
	 * First, validate some syntax--caller specified name for new socket,
	 * and the name is available.
	 */
	if (argc < 2 || argc > 3) {
		Tcl_AppendResult(interp, proto_name, " accept: Usage: "
		    "<socket.accept> new-socket [timeout]", NULL);
		return (TCL_ERROR);
	}
	if (psh_find_ep(argv[1]) != NULL) {
		Tcl_AppendResult(interp, proto_name, " accept operation: ",
		    "End-point in use: ", argv[1], NULL);
		return (TCL_ERROR);
	}

	if (argc == 3)
		if (poll_timeout(ep->se_sock, atoi(argv[2])) <= 0) {
			Tcl_AppendResult(interp, "timeout", NULL);
			return (TCL_OK);
		}

	fd = accept(ep->se_sock, (struct sockaddr *)&ep->se_sai, &namelen);
	if (fd < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " accept failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
	/*
	 * Accept succeeded.  Create a new end-point with the given name
	 */
	accept_ep = (struct socket_ep *)psh_new_ep(argv[1],
	    ep->se_ep.ep_prototbl);
	if (accept_ep == NULL) {
		Tcl_AppendResult(interp, proto_name, ": endpoint allocation ",
		    "failed!", NULL);
		return (TCL_ERROR);
	}
	accept_ep->se_sock = fd;
	(void) memcpy(&accept_ep->se_sai, &ep->se_sai, namelen);

	/* add the new end-point */
	psh_add_ep((ep_t *)accept_ep);

	/* add the endpoint commands for new socket */
	ep_cmd_ctl(interp, EP_OP_ADD, accept_ep->se_ep.ep_name, accept_ep);

	Tcl_AppendResult(interp, "accepted", NULL);
	return (TCL_OK);
}

static int
sock_atmark(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int			 atmark;
	const char		*vals[] = { "ANYMARK", "LASTMARK" };
	size_t			 i;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <arg>", NULL);
		return (TCL_ERROR);
	}

	for (i = 0; i < N_ITEMS(vals); i++)
		if (strcasecmp(argv[1], vals[i]) == 0) {
			if (ioctl(ep->se_sock, SIOCATMARK, &atmark, i) == -1) {
				Tcl_AppendResult(interp, "ioctl failed", NULL);
				return (TCL_ERROR);
			}
			Tcl_AppendResult(interp, atmark ? "" : "!", "mark",
			    NULL);
			return (TCL_OK);
		}

	Tcl_AppendResult(interp, proto_name, " atmark operation: invalid "
	    "argument", NULL);
	return (TCL_ERROR);
}

static int
sock_write(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <pckt>", NULL);
		return (TCL_ERROR);
	}

	/* call sock_writev() to do the real work */
	return (sock_writev(clientData, interp, argc, argv));
}

static int
sock_send(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct packet 		*pckt;
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int			 flags = (argc == 3) ? scan_flags(argv[2]) : 0;
	int			 result;
	char			 tmp[128];

	if (argc > 3 || argc < 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <pckt> [<flags>]", 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);
	}

	result = send(ep->se_sock, pckt->p_buf, pckt->p_len, flags);
	(void) sprintf(tmp, "%d", result);
	Tcl_AppendResult(interp, tmp, NULL);
	if (result < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " send failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}
	return (TCL_OK);
}

static int
sock_read(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct packet		*pckt;
	ssize_t			 size;

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

	size = read(ep->se_sock, pckt->p_buf, pckt->p_maxlen);
	if (size < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " read() failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	Tcl_AppendResult(interp, "packet", NULL);
	return (sock_bootstrap(ep, pckt, size) ? TCL_OK : TCL_ERROR);
}

static int
sock_recvfrom(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct packet		*pckt;
	int			 size;

	if (argc > 4 || argc < 3) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <pckt> <flags> [<timeout>]  ;# <timeout> in milliseconds",
		    NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, proto_name, " recvfrom operation: ",
		    "packet not found: ", argv[1], NULL);
		return (TCL_ERROR);
	}
	if (argc == 4)
		if (poll_timeout(ep->se_sock, atoi(argv[3])) <= 0) {
			Tcl_AppendResult(interp, "timeout", NULL);
			return (TCL_OK);
		}

	size = recvfrom(ep->se_sock, pckt->p_buf, pckt->p_maxlen,
	    scan_flags(argv[2]), NULL, NULL);
	if (size == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " recvfrom(): ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	Tcl_AppendResult(interp, "packet", NULL);
	return (sock_bootstrap(ep, pckt, size) ? TCL_OK : TCL_ERROR);
}

static int
sock_sendto(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct packet 		*pckt;
	int			 size;
	char			 tmp[128];

	if (argc != 4) {
		Tcl_AppendResult(interp, "Usage: ", argv[0],
		    " <pckt> <flags> <sockaddr>", NULL);
		return (TCL_ERROR);
	}

	/*
	 * Now the fun part, need to parse <sockaddr> and init structure vars
	 * It should look something like this inet:129.146.86.183/8000
	 */

	if (parse_sockaddr(ep, argv[3]) == TCL_ERROR) {
		Tcl_AppendResult(interp, proto_name, ": bad <sockaddr> : ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, proto_name, " sendto operation: ",
		    "packet not found: ", argv[2], NULL);
		return (TCL_ERROR);
	}
	size = sendto(ep->se_sock, pckt->p_buf, pckt->p_len,
	    scan_flags(argv[2]), (struct sockaddr *)&ep->se_sai, ADDRSZ(ep));
	(void) sprintf(tmp, "%d", size);
	Tcl_AppendResult(interp, tmp, NULL);
	if (size == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " sendto failed: ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	return (TCL_OK);
}

static int
sock_recv(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	struct packet		*pckt;
	int			size;

	if (argc > 4 || argc < 3) {
		Tcl_AppendResult(interp, proto_name, " recv operation: ",
		    "Usage: ", argv[0], " <pckt> <flags> [<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 == 4) {
		if (poll_timeout(ep->se_sock, atoi(argv[3])) <= 0) {
			Tcl_AppendResult(interp, "timeout", NULL);
			return (TCL_OK);
		}

	}
	size = recv(ep->se_sock, pckt->p_buf, pckt->p_maxlen,
	    scan_flags(argv[2]));
	if (size < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, " recv(): ",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	Tcl_AppendResult(interp, "packet", NULL);
	return (sock_bootstrap(ep, pckt, size) ? TCL_OK : TCL_ERROR);
}

static int
sock_poll(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep *ep = (struct socket_ep *)clientData;
	struct pollfd	pfd;
	int		timeout;
	char		tmp[128];

	if (argc != 2) {
		Tcl_AppendResult(interp, proto_name, " poll operation: ",
		    "Usage: ", argv[0], " <timeout>",
		    NULL);
		return (TCL_ERROR);
	}

	pfd.revents	= 0;
	pfd.events	= POLLIN|POLLRDBAND|POLLRDNORM|POLLPRI;
	pfd.fd		= ep->se_sock;

	timeout = atoi(argv[1]);
	if (poll(&pfd, 1, timeout) <= 0) {
		Tcl_AppendResult(interp, "timeout", NULL);
		return (TCL_OK);
	}

	if (pfd.revents & (POLLERR|POLLHUP|POLLNVAL)) {
		Tcl_AppendResult(interp, "poll error occured", NULL);
		return (TCL_ERROR);
	}

	tmp[0] = 0;
	if (pfd.revents & POLLIN) {
		(void) strcat(tmp, "POLLIN|");
	}
	if (pfd.revents & POLLRDBAND) {
		(void) strcat(tmp, "POLLRDBAND|");
	}
	if (pfd.revents & POLLRDNORM) {
		(void) strcat(tmp, "POLLRDNORM|");
	}
	if (pfd.revents & POLLPRI) {
		(void) strcat(tmp, "POLLPRI|");
	}
	tmp[strlen(tmp) -1] = 0;	/* nuke the last "|" character */

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

static int
sock_setroute(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int			route_type = IPOPT_LSRR;
	int			hl_beg = 1;
	int			hl_len = argc - 1;
	char			ip_opt[128];
	int			opt_len;
	int			i;
	struct hostent		*hp;

	if (argc < 3) {
		Tcl_AppendResult(interp, proto_name,
		    " setipsr operation: ",
		    "Usage: ", argv[0],
		    "  [!] hop1 hop2 ... dst",
		    NULL);
		return (TCL_ERROR);
	}
	if (strcmp(argv[1], "!") == 0) {
		route_type = IPOPT_SSRR;
		hl_beg++;
		hl_len--;
	}
	/*
	 * code	len	ptr	IP#1	IP#2............IP#N	IPOPT_NOP
	 *	|	|	|	|	|	|	|
	 * --------------------------------------------------------------
	 *
	 */

	opt_len = 3 + (hl_len * sizeof (struct in_addr));

	ip_opt[IPOPT_OPTVAL] = route_type;
	ip_opt[IPOPT_OLEN] = opt_len;
	ip_opt[IPOPT_OFFSET] = IPOPT_MINOFF;
	for (i = 0; i < hl_len; i++) {
		hp = (struct hostent *)gethostbyname(argv[i + hl_beg]);
		if (hp == (struct hostent *)NULL) {
			Tcl_AppendResult(interp, proto_name, " invalid host: ",
			    argv[i + hl_beg], NULL);
			return (TCL_ERROR);
		}
		bcopy((char *)(hp->h_addr_list[0]),
		    ip_opt + (IPOPT_OFFSET + 1) + sizeof (struct in_addr) * i,
		    sizeof (struct in_addr));
	}
	ip_opt[opt_len] = IPOPT_NOP;
	if (setsockopt(ep->se_sock, IPPROTO_IP, IP_OPTIONS, ip_opt,
			opt_len + 1) < 0) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name, ": setsockopt failed :",
		    strerror(errno), NULL);
		return (TCL_ERROR);
	}

	Tcl_AppendResult(interp, proto_name, " setipsr succeeded", NULL);
	return (TCL_OK);
}

#ifdef SOLARIS
static int
sock_mib(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct socket_ep	*ep = (struct socket_ep *)clientData;
	int			mib_ep;
	int			sock_type;
	int			optlen = sizeof (sock_type);
	int			needed_level = MIB2_UDP;
	bool_t			tcp = BFALSE;
	int			r, sz;
	int			rcv_sz = 0;
	int			flags, value;
	struct strbuf		ctl;
	struct strbuf		dat;
	char			proto_buf[512];
	char			*mib_space;
	struct T_optmgmt_req 	*tor;
	struct T_optmgmt_ack	*toa;
	struct T_error_ack	*tea = (struct T_error_ack *)proto_buf;
	struct opthdr		*req;
	mib2_tcp_t		*mib;
	mib2_udp_t		*mibu;
	char			tmp[256];

	if (argc != 2) {
		Tcl_AppendResult(interp, proto_name, " mib operation: ",
		    "Usage: ", argv[0], " <field_name>", NULL);
		return (TCL_ERROR);
	}
	if (getsockopt(ep->se_sock, SOL_SOCKET, SO_TYPE, (char *)&sock_type,
	    &optlen) == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name,
		    " getsockopt failed: ", strerror(errno), NULL);
		return (TCL_ERROR);
	}
	if (sock_type == SOCK_STREAM) {
		tcp = BTRUE;
		needed_level = MIB2_TCP;
		mib_ep = open("/dev/tcp", O_RDWR);
	} else if (sock_type != SOCK_DGRAM) {
		mib_ep = open("/dev/udp", O_RDWR);
	} else {
		Tcl_AppendResult(interp, proto_name, " mib operation: ",
		    " Unsupported socket type ", NULL);
	}

	/* Getting TCP/UDP MIB for this endpoint */

	tor = (struct T_optmgmt_req *)proto_buf;
	tor->PRIM_type = T_OPTMGMT_REQ;
	tor->OPT_length = sizeof (struct opthdr);
	tor->OPT_offset = sizeof (struct T_optmgmt_req);
	tor->MGMT_flags = T_CURRENT;
	req = (struct opthdr *)(proto_buf + tor->OPT_offset);
	req->level = needed_level;
	req->name  = 0;
	req->len   = 0;
	ctl.buf = proto_buf;
	ctl.len = tor->OPT_length + tor->OPT_offset;
	if (putmsg(mib_ep, &ctl, (struct strbuf *)NULL, 0) == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name,
		    " putmsg failed: ", strerror(errno), NULL);
		(void) close(mib_ep);
		return (TCL_ERROR);
	}

	toa = (struct T_optmgmt_ack *)proto_buf;
	req = (struct opthdr *)(proto_buf + sizeof (struct T_optmgmt_ack));
	ctl.maxlen = sizeof (proto_buf);
	flags = 0;
	r = getmsg(mib_ep, &ctl, (struct strbuf *)NULL, &flags);
	if (r == -1) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name,
		    " getmsg_1 failed: ", strerror(errno), NULL);
		(void) close(mib_ep);
		return (TCL_ERROR);
	}
	if (tea->PRIM_type == T_ERROR_ACK) {
		sock_saved_errno = errno;
		Tcl_AppendResult(interp, proto_name,
		    " getmsg_1 T_ERROR_ACK: ", strerror(errno), NULL);
		return (TCL_ERROR);
	}
	if (r != MOREDATA || toa->PRIM_type != T_OPTMGMT_ACK ||
	    toa->MGMT_flags != T_SUCCESS || req->level != needed_level) {
		(void) printf("\n\tUnexpected return of 1st getmsg():");
		(void) printf("\nEexpected: rv=%d PRIM_type=%d MGMT_flags=%d"
		    " level=%d", MOREDATA, T_OPTMGMT_ACK, T_SUCCESS,
		    needed_level);
		(void) printf("\nReturned : rv=%d PRIM_type=%ld MGMT_flags=%ld"
		    " level=%ld",
		    r, toa->PRIM_type, toa->MGMT_flags, req->level);
		Tcl_AppendResult(interp, proto_name,
		    " Unexpected result getmsg_1: ", strerror(errno), NULL);
		(void) close(mib_ep);
		return (TCL_ERROR);
	}
	sz = (int)req->len;

	mib_space = (char *)malloc(sz);
	if (mib_space == NULL) {
		Tcl_AppendResult(interp, proto_name,
		    " malloc failed: ", strerror(errno), NULL);
		(void) close(mib_ep);
		return (TCL_ERROR);
	}
	for (;;) {
		dat.maxlen	= sz - rcv_sz;
		dat.buf		= mib_space + rcv_sz;
		dat.len		= 0;
		flags		= 0;
		r = getmsg(mib_ep, (struct strbuf *)NULL, &dat, &flags);
		if (r == -1) {
			sock_saved_errno = errno;
			Tcl_AppendResult(interp, proto_name,
			    " getmsg_2 failed: ", strerror(errno), NULL);
			goto err;
		}
		rcv_sz += dat.len;
		if (r == 0) {
			break;
		} else if (r != MOREDATA) {
			(void) printf("XXX: MIB r=%d, dat.maxlen = %d, "
			    "dat.len = %d\n", r, dat.maxlen, dat.len);
			Tcl_AppendResult(interp, proto_name,
			    " getmsg_2 failed: wrong return value", NULL);
			goto err;
		}
		if (rcv_sz == sz) {
			/*
			 * The data buffer is full, but return value == MOREDATA
			 * It is possible when a message of higher priority
			 * has come in on the stream head.
			 */
			Tcl_AppendResult(interp, proto_name,
			    " mib operation is corrupted", NULL);
			goto err;
		}
	}
	if (rcv_sz != sz) {
		Tcl_AppendResult(interp, proto_name,
		    " mib operation - unexpected message", NULL);
		goto err;
	}
	(void) close(mib_ep);
	if (tcp == BTRUE) {
		mib = (struct mib2_tcp *)mib_space;

		if (strcmp(argv[1], "tcpRtoAlgorithm") == 0) {
			value = mib->tcpRtoAlgorithm;
		} else if (strcmp(argv[1], "tcpRtoMin") == 0) {
			value = mib->tcpRtoMin;
		} else if (strcmp(argv[1], "tcpRtoMax") == 0) {
			value = mib->tcpRtoMax;
		} else if (strcmp(argv[1], "tcpMaxConn") == 0) {
			value = mib->tcpMaxConn;
		} else if (strcmp(argv[1], "tcpActiveOpens") == 0) {
			value = mib->tcpActiveOpens;
		} else if (strcmp(argv[1], "tcpPassiveOpens") == 0) {
			value = mib->tcpPassiveOpens;
		} else if (strcmp(argv[1], "tcpAttemptFails") == 0) {
			value = mib->tcpAttemptFails;
		} else if (strcmp(argv[1], "tcpEstabResets") == 0) {
			value = mib->tcpEstabResets;
		} else if (strcmp(argv[1], "tcpCurrEstab") == 0) {
			value = mib->tcpCurrEstab;
		} else if (strcmp(argv[1], "tcpInSegs") == 0) {
			value = mib->tcpInSegs;
		} else if (strcmp(argv[1], "tcpOutSegs") == 0) {
			value = mib->tcpOutSegs;
		} else if (strcmp(argv[1], "tcpRetransSegs") == 0) {
			value = mib->tcpRetransSegs;
		} else if (strcmp(argv[1], "tcpConnTableSize") == 0) {
			value = mib->tcpConnTableSize;
		} else if (strcmp(argv[1], "tcpOutRsts") == 0) {
			value = mib->tcpOutRsts;
		} else if (strcmp(argv[1], "tcpOutDataSegs") == 0) {
			value = mib->tcpOutDataSegs;
		} else if (strcmp(argv[1], "tcpOutDataBytes") == 0) {
			value = mib->tcpOutDataBytes;
		} else if (strcmp(argv[1], "tcpRetransBytes") == 0) {
			value = mib->tcpRetransBytes;
		} else if (strcmp(argv[1], "tcpOutAck") == 0) {
			value = mib->tcpOutAck;
		} else if (strcmp(argv[1], "tcpOutAckDelayed") == 0) {
			value = mib->tcpOutAckDelayed;
		} else if (strcmp(argv[1], "tcpOutUrg") == 0) {
			value = mib->tcpOutUrg;
		} else if (strcmp(argv[1], "tcpOutWinUpdate") == 0) {
			value = mib->tcpOutWinUpdate;
		} else if (strcmp(argv[1], "tcpOutWinProbe") == 0) {
			value = mib->tcpOutWinProbe;
		} else if (strcmp(argv[1], "tcpOutControl") == 0) {
			value = mib->tcpOutControl;
		} else if (strcmp(argv[1], "tcpOutFastRetrans") == 0) {
			value = mib->tcpOutFastRetrans;
		} else if (strcmp(argv[1], "tcpInAckSegs") == 0) {
			value = mib->tcpInAckSegs;
		} else if (strcmp(argv[1], "tcpInAckBytes") == 0) {
			value = mib->tcpInAckBytes;
		} else if (strcmp(argv[1], "tcpInDupAck") == 0) {
			value = mib->tcpInDupAck;
		} else if (strcmp(argv[1], "tcpInAckUnsent") == 0) {
			value = mib->tcpInAckUnsent;
		} else if (strcmp(argv[1], "tcpInDataInorderSegs") == 0) {
			value = mib->tcpInDataInorderSegs;
		} else if (strcmp(argv[1], "tcpInDataInorderBytes") == 0) {
			value = mib->tcpInDataInorderBytes;
		} else if (strcmp(argv[1], "tcpInDataUnorderSegs") == 0) {
			value = mib->tcpInDataUnorderSegs;
		} else if (strcmp(argv[1], "tcpInDataUnorderBytes") == 0) {
			value = mib->tcpInDataUnorderBytes;
		} else if (strcmp(argv[1], "tcpInDataDupSegs") == 0) {
			value = mib->tcpInDataDupSegs;
		} else if (strcmp(argv[1], "tcpInDataDupBytes") == 0) {
			value = mib->tcpInDataDupBytes;
		} else if (strcmp(argv[1], "tcpInDataPartDupSegs") == 0) {
			value = mib->tcpInDataPartDupSegs;
		} else if (strcmp(argv[1], "tcpInDataPartDupBytes") == 0) {
			value = mib->tcpInDataPartDupBytes;
		} else if (strcmp(argv[1], "tcpInDataPastWinSegs") == 0) {
			value = mib->tcpInDataPastWinSegs;
		} else if (strcmp(argv[1], "tcpInDataPastWinBytes") == 0) {
			value = mib->tcpInDataPastWinBytes;
		} else if (strcmp(argv[1], "tcpInWinProbe") == 0) {
			value = mib->tcpInWinProbe;
		} else if (strcmp(argv[1], "tcpInWinUpdate") == 0) {
			value = mib->tcpInWinUpdate;
		} else if (strcmp(argv[1], "tcpInClosed") == 0) {
			value = mib->tcpInClosed;
		} else if (strcmp(argv[1], "tcpRttNoUpdate") == 0) {
			value = mib->tcpRttNoUpdate;
		} else if (strcmp(argv[1], "tcpRttUpdate") == 0) {
			value = mib->tcpRttUpdate;
		} else if (strcmp(argv[1], "tcpTimRetrans") == 0) {
			value = mib->tcpTimRetrans;
		} else if (strcmp(argv[1], "tcpTimRetransDrop") == 0) {
			value = mib->tcpTimRetransDrop;
		} else if (strcmp(argv[1], "tcpTimKeepalive") == 0) {
			value = mib->tcpTimKeepalive;
		} else if (strcmp(argv[1], "tcpTimKeepaliveProbe") == 0) {
			value = mib->tcpTimKeepaliveProbe;
		} else if (strcmp(argv[1], "tcpTimKeepaliveDrop") == 0) {
			value = mib->tcpTimKeepaliveDrop;
		} else if (sz < sizeof (struct mib2_tcp)) {
			/*
			 * Solaris 2.4
			 */
			Tcl_AppendResult(interp, proto_name,
			    ": bad field_name: ", argv[1], NULL);
			goto err1;
		} else if (strcmp(argv[1], "tcpListenDrop") == 0) {
			value = mib->tcpListenDrop;
		} else if (strcmp(argv[1], "tcpListenDropQ0") == 0) {
			value = mib->tcpListenDropQ0;
		} else if (strcmp(argv[1], "tcpHalfOpenDrop") == 0) {
			value = mib->tcpHalfOpenDrop;
		} else {
			Tcl_AppendResult(interp, proto_name,
			    ": bad field_name: ", argv[1], NULL);
			goto err1;
		}
	} else {
		mibu = (struct mib2_udp *)mib_space;

		if (strcmp(argv[1], "udpInDatagrams") == 0) {
			value = mibu->udpInDatagrams;
		} else if (strcmp(argv[1], "udpInErrors") == 0) {
			value = mibu->udpInErrors;
		} else if (strcmp(argv[1], "udpOutDatagrams") == 0) {
			value = mibu->udpOutDatagrams;
		} else if (strcmp(argv[1], "udpEntrySize") == 0) {
			value = mibu->udpEntrySize;
		} else {
			Tcl_AppendResult(interp, proto_name,
			    ": bad field_name: ", argv[1], NULL);
			goto err1;
		}
	}
	free(mib_space);
	(void) sprintf(tmp, "%i", value);
	Tcl_AppendResult(interp, tmp, NULL);
	return (TCL_OK);
err:
	(void) close(mib_ep);
err1:
	free(mib_space);
	return (TCL_ERROR);
}
#endif
