/* LINTLIBRARY */
/*
 *	Copyright (c) 1995, 1996, 2000 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	"@(#)ip.c	1.36	00/06/29 SMI"

#include <tcl.h>
#include "psh.h"
#include "proto.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.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 <fcntl.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>
#ifdef SOLARIS
#include <stropts.h>
#endif /* SOLARIS */
#include <strings.h>
#include <unistd.h>

static char *proto_name;

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

/*
 * Default for creating new endpoints and new packets
 * def_protocol is set through the set_def_proto() routine.
 */
static unsigned int def_protocol = IPPROTO_TCP;

/*
 * End points for IP communication.  We retain here enough to be able
 * to do whatever communication operations we might need.  E.g., the sndsock
 * and rcvsock for the interface.  Packets are sent and received on at
 * least a protocol basis--i.e., if you ask for "ggp", you should be talking
 * about (via /etc/protocols or the IANA protocol mappings) protocol 3.
 * It would be nice to use only one socket, but SunOS 5.6 and earlier
 * can't cope.
 * XXX if protocol not specified can receive any protocol?
 * XXX should be able to send any protocol even if recving bound to 1 protocol?
 */
struct ip_ep {
	ep_t			ie_ep;
	int			ie_sndsock;
	int			ie_rcvsock;
	struct ip		ie_iph;
	int			ie_srcport;
	int			ie_dstport;
	int			ie_proto;
};

static int ip_send(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[]);
static int ip_recv(ClientData clientData, Tcl_Interp *interp, int argc,
    char *argv[]);

int ip_send(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]);
int ip_recv(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]);

static int
ip_dosum(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct packet *pckt;
	int offset, length;
	int sum = 0;
	unsigned short ussum;
	char buf[BUFSIZ];

	if (argc < 4 || argc > 5) {
		Tcl_AppendResult(interp, "Usage: ip.cksum <pkt> <offset>",
		    " <len> [<initial>]", NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, "ip.cksum: invalid packet name: ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
	if (Tcl_GetInt(interp, argv[2], (int *)&offset) != TCL_OK) {
		Tcl_AppendResult(interp, ": ip.cksum: Bad offset value: ",
		    argv[2], NULL);
		return (TCL_ERROR);
	}
	if (Tcl_GetInt(interp, argv[3], (int *)&length) != TCL_OK) {
		Tcl_AppendResult(interp, ": ip.cksum: Bad length value: ",
		    argv[3], NULL);
		return (TCL_ERROR);
	}
	if (offset + length > pckt->p_maxlen) {
		Tcl_AppendResult(interp, ": ip.cksum: Bad range: ",
		    argv[2], "for ", argv[3], NULL);
		return (TCL_ERROR);
	}
	if (argc == 5 && Tcl_GetInt(interp, argv[4], (int *)&sum) != TCL_OK) {
		Tcl_AppendResult(interp, ": ip.cksum: Bad initial value: ",
		    argv[4], NULL);
		return (TCL_ERROR);
	}

	ussum = (unsigned short)sum; /* Yes, it can overflow */
	ip_cksum(&ussum, (unsigned char *)pckt->p_buf + offset, length);
	sum = ussum;
	(void) sprintf(buf, "0x%x", sum);
	Tcl_AppendResult(interp, buf, NULL);
	return (TCL_OK);
}

/*ARGSUSED0*/
static int
iph2a(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct hostent *hp;
	char		buf[128];

	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: ip.h2a <hostname>", NULL);
		return (TCL_ERROR);
	}
	if ((hp = gethostbyname(argv[1])) == NULL) {
		if (inet_addr(argv[1]) != -1) {
			(void) sprintf(buf, "%x", (int)inet_addr(argv[1]));
			Tcl_AppendResult(interp, buf, NULL);
			return (TCL_OK);
		}
		Tcl_AppendResult(interp, "Unknown host: ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
	(void) sprintf(buf, "0x%x", ntohl(*((int *)hp->h_addr_list[0])));
	Tcl_AppendResult(interp, buf, NULL);
	return (TCL_OK);
}

/*ARGSUSED0*/
static int
ipa2h(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct in_addr	address;
	struct hostent *hp;

	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: ip.a2h <addr>", NULL);
		return (TCL_ERROR);
	}
	if (Tcl_GetInt(interp, argv[1], (int *)&address.s_addr) != TCL_OK) {
		Tcl_AppendResult(interp, ": Bad address: ", argv[1], NULL);
		return (TCL_ERROR);
	}
	address.s_addr = htonl(address.s_addr);
	if ((hp = gethostbyaddr((char *)&address.s_addr, sizeof (address),
	    AF_INET)) == NULL) {
		Tcl_AppendResult(interp, inet_ntoa(address), NULL);
		return (TCL_OK);
	}

	Tcl_AppendResult(interp, hp->h_name, NULL);
	return (TCL_OK);
}

static int
ipaddr(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct in_addr	address;
	struct hostent *hp;
	unsigned long *addrp;

	addrp = (unsigned long *)clientData;
	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: ip.{src,dst} <addr>", NULL);
		return (TCL_ERROR);
	}
	/*
	 * First we try an integer address
	 */
	if (Tcl_GetInt(interp, argv[1], (int *)&address.s_addr) == TCL_OK) {
		*addrp = htonl(address.s_addr);
		return (TCL_OK);
	}
	Tcl_ResetResult(interp);
	/*
	 * Nope, how about dotted quad?
	 */
	address.s_addr = inet_addr(argv[1]);
	if ((int)address.s_addr != -1) {
		*addrp = address.s_addr;
		return (TCL_OK);
	}
	/*
	 * Finally, it could be a hostname
	 */
	if ((hp = gethostbyname(argv[1])) != NULL) {
		*addrp = *(unsigned long *)hp->h_addr_list[0];
		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
set_def_proto(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	unsigned	*protop;
	char		*name;

	protop = (unsigned *)clientData;
	if (argc < 2) {
		Tcl_AppendResult(interp, "Usage: ip.proto proto", NULL);
		return (TCL_ERROR);
	}
	if ((*protop = ipproto_number_by_name(argv[1])) == -1) {
		if (Tcl_GetInt(interp, argv[1], (int *)protop) != TCL_OK) {
			Tcl_AppendResult(interp, proto_name,
			    ": unknown protocol: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		name = ipproto_name_by_number(def_protocol);
		if (strcmp(name, "unknown") == 0) {
			Tcl_AppendResult(interp, proto_name,
			    ": unknown protocol: ", argv[1], NULL);
			return (TCL_ERROR);
		}
	}
	return (TCL_OK);
}

int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	/* set the size of an IP end-point */
	prototbl->proto_epsize = sizeof (struct ip_ep);

	(void) Tcl_CreateCommand(interp, "ip.a2h", ipa2h, NULL, NULL);
	(void) Tcl_CreateCommand(interp, "ip.h2a", iph2a, NULL, NULL);
	(void) Tcl_CreateCommand(interp, "ip.cksum", ip_dosum, NULL, NULL);
	(void) Tcl_CreateCommand(interp, "ip.src", ipaddr,
	    (ClientData)&def_src, NULL);
	(void) Tcl_CreateCommand(interp, "ip.dst", ipaddr,
	    (ClientData)&def_dst, NULL);
	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: malloc: no space",
			NULL);
		return (TCL_ERROR);
	}
	/*
	 * We pre-load a table of all protocol name<->number entries so we
	 * we don't consult a name service (NIS, NIS+?) which will generate
	 * network traffic in the middle of something interesting.  If those
	 * packets end up getting consumed by the script you get into a sort
	 * of recursive loop, which can be nasty.
	 *
	 * XXX: Be nice to do the same with host/address translations too.
	 */
	(void) Tcl_CreateCommand(interp, "ip.proto", set_def_proto,
		(ClientData)&def_protocol, NULL);
	return (1);
}

/*ARGSUSED0*/
int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ip_ep *ep;
	char tmp[128];
	char *name, *p;
	int value, save_errno;
	struct sockaddr_in from;
	struct hostent *hp;
	struct servent *sp;

	if (argc < 4 || argc > 7) {
		Tcl_AppendResult(interp, "Usage: popen ", proto_name,
		    " <id> <interface> <ethersrc> <etherdst>\n"
		    "\t[ <proto> [ <ip-src> [ '/' <src-port> ] "
		    " [ <ip-dst> [ '/' <dst-port> ] ] ] ]",
		    NULL);
		return (TCL_ERROR);
	}

	ep = (struct ip_ep *)clientData;

	ep->ie_iph.ip_src.s_addr = def_src;
	ep->ie_iph.ip_dst.s_addr = def_dst;
	ep->ie_proto = def_protocol;

	argc -= 4;
	/*
	 * Check for a <proto> parameter
	 */
	if (argc) {
		name = argv[4];
		if ((ep->ie_proto = ipproto_number_by_name(argv[4])) == -1) {
			if (Tcl_GetInt(interp, argv[4], &ep->ie_proto) !=
			    TCL_OK) {
				Tcl_AppendResult(interp, proto_name,
				    ": unknown protocol: ", argv[4], NULL);
				return (TCL_ERROR);
			}
			name = ipproto_name_by_number(ep->ie_proto);
			if (strcmp(name, "unknown") == 0) {
				Tcl_AppendResult(interp, proto_name,
					": unknown protocol: ", argv[4], NULL);
				return (TCL_ERROR);
			}
		}
		argc--;
	}
	if ((ep->ie_sndsock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) {
		save_errno = errno;
		Tcl_AppendResult(interp, proto_name, " socket(IPPROTO_RAW) ",
		    "failed:", strerror(save_errno), NULL);
		return (TCL_ERROR);
	}
	if ((ep->ie_rcvsock = socket(AF_INET, SOCK_RAW, ep->ie_proto)) == -1) {
		save_errno = errno;
		Tcl_AppendResult(interp, proto_name, " socket(", argv[4],
		    ") failed:", strerror(save_errno), NULL);
		(void) close(ep->ie_sndsock);
		return (TCL_ERROR);
	}
	if (argc == 0) {
		goto out;
	}
	/*
	 * Parse <ip-src> [ '/' <src-port> ] (as a single argument string)
	 */
	if ((p = strchr(argv[5], '/')) != NULL) {
		*p = '\0';
	}
	if (inet_addr(argv[5]) != -1) {
		ep->ie_iph.ip_src.s_addr = inet_addr(argv[5]);
	} else if ((hp = gethostbyname(argv[5])) != NULL) {
		ep->ie_iph.ip_src.s_addr = *(ulong_t *)(hp->h_addr_list[0]);
	} else {
		Tcl_AppendResult(interp, proto_name, ": bad src host: ",
		    argv[1], NULL);
		goto err;
	}
	/*
	 * Check for '/port' syntax
	 */
	if (p != NULL) {
		p++;
		if (Tcl_GetInt(interp, p, &value) == TCL_OK) {
			if (value < 0 || value > 65535) {
				Tcl_AppendResult(interp, proto_name,
				    ": bad src port: ", p, NULL);
				goto err;
			}
			ep->ie_srcport = htons(value);
		} else if ((sp = getservbyname(p, name)) != NULL) {
			ep->ie_srcport = sp->s_port;
			Tcl_ResetResult(interp);
		} else {
			Tcl_AppendResult(interp, proto_name, ": bad src port: ",
			    p, NULL);
			goto err;
		}
	}
	argc--;
	if (argc == 0) {
		goto out;
	}

	/*
	 * Parse <ip-dest> [ '/' <dst-port> ] (as a single argument string)
	 */
	if ((p = strchr(argv[6], '/')) != NULL) {
		*p = '\0';
	}
	if (inet_addr(argv[6]) != -1) {
		ep->ie_iph.ip_dst.s_addr = inet_addr(argv[6]);
	} else if ((hp = gethostbyname(argv[6])) != NULL) {
		ep->ie_iph.ip_dst.s_addr = *(ulong_t *)(hp->h_addr_list[0]);
	} else {
		Tcl_AppendResult(interp, proto_name, ": bad dst host: ",
		    argv[6], NULL);
		goto err;
	}

	if (p == NULL) {
		goto out;
	}
	p++;
	if (Tcl_GetInt(interp, p, &value) == TCL_OK) {
		if (value < 0 || value > 65535) {
			Tcl_AppendResult(interp, proto_name, ": bad dst port: ",
			    p, NULL);
			goto err;
		}
		ep->ie_dstport = htons(value);
	} else if ((sp = getservbyname(p, name)) != NULL) {
		ep->ie_dstport = sp->s_port;
		Tcl_ResetResult(interp);
	} else {
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, proto_name, ": bad dst port: ",
		    p, NULL);
		goto err;
	}

out:
	from.sin_family = AF_INET;
	from.sin_port = ep->ie_srcport;
	from.sin_addr.s_addr = ep->ie_iph.ip_src.s_addr;
	if (bind(ep->ie_rcvsock, (struct sockaddr *)&from,
	    sizeof (struct sockaddr_in)) == -1) {
		save_errno = errno;
		Tcl_AppendResult(interp, proto_name,
		    " bind failed: ", strerror(save_errno), NULL);
		goto err;
	}

	/*
	 * Got it all, we're open for business.
	 */
	Tcl_AppendResult(interp, "popen ", proto_name, ": ", argv[1], ": ",
		    "successfully opened.  ", NULL);

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

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

	return (TCL_OK);

err:
	(void) close(ep->ie_sndsock);
	(void) close(ep->ie_rcvsock);
	return (TCL_ERROR);
}

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

	ep = (struct ip_ep *)clientData;

	(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->ie_sndsock);
	(void) close(ep->ie_rcvsock);
	return (TCL_OK);
}

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

	if (pckt->p_len == 0)
		pckt->p_len = sizeof (iphdr);
	if (ptbl_update(pckt, 1, NULL, pckt->p_len) == 0) {
		return (TCL_ERROR);
	}
	iphdr.ip_hl = 5;
	iphdr.ip_v = 4;
	iphdr.ip_tos = 0;
	iphdr.ip_off = htons(IP_DF);
	iphdr.ip_ttl = 40;
	iphdr.ip_p = def_protocol;
	iphdr.ip_src.s_addr = def_src;
	iphdr.ip_dst.s_addr = def_dst;
	/*
	 * The next IP header fields will be overwritten by IP.  Just
	 * zero them for now. This is needed for the accurate
	 * computation of header checksum.  The word where the
	 * checksum goes is part of the header, so is included in the
	 * checksum computation.  The fields involved are:
	 * iphdr.ip_id		= ...
	 * iphdr.ip_sum		= ...
	 */
	iphdr.ip_sum = 0;
	iphdr.ip_id = 0;

	bcopy(&iphdr, pckt->p_buf, min(pckt->p_len, sizeof (iphdr)));
	return (TCL_OK);
}



int
decode(struct packet *pckt, int element, char **name, int *next_offset)
{
	struct ip iphdr;
	char *p;

	(void) memcpy(&iphdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (iphdr));
	*name = ipproto_name_by_number(iphdr.ip_p);
	if (iphdr.ip_v != 4) {

		(void) printf(
		    "XXX: Warning: IPv4 packet with bad version? len %d.\n",
		    pckt->p_len);
		p = pckt->p_buf;
		(void) printf("XXX: %.8x %.8x %.8x %.8x\n",
		    htonl(p[0]), htonl(p[1]), htonl(p[2]), htonl(p[3]));
		(void) printf("XXX: %.8x %.8x %.8x %.8x\n",
		    htonl(p[4]), htonl(p[5]), htonl(p[6]), htonl(p[7]));
		*name = "end";
		*next_offset = pckt->p_len;
		return (0);
	}
	*next_offset = pckt->p_tbl[element].pt_offset + (iphdr.ip_hl * 4);
	return (1);
}

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

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget ip <pckt> <item>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&iphdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (iphdr));
	/*
	 * Print one field
	 */
	if (strcmp(argv[0], "dsth") == 0) {
		(void) sprintf(tmp, "0x%.8x", htonl(iphdr.ip_dst.s_addr));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "dst") == 0) {
		(void) sprintf(tmp, "%s", inet_ntoa(iphdr.ip_dst));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "srch") == 0) {
		(void) sprintf(tmp, "0x%.8x", htonl(iphdr.ip_src.s_addr));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "src") == 0) {
		(void) sprintf(tmp, "%s", inet_ntoa(iphdr.ip_src));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "proto") == 0) {
		Tcl_AppendResult(interp, ipproto_name_by_number(iphdr.ip_p),
		    NULL);
	} else if (strcmp(argv[0], "len") == 0) {
		(void) sprintf(tmp, "0x%.4x", htons(iphdr.ip_len));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "sum") == 0) {
		(void) sprintf(tmp, "0x%.4x", htons(iphdr.ip_sum));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "hlv") == 0) {
		(void) sprintf(tmp, "0x%.2x", iphdr.ip_hl | (iphdr.ip_v << 4));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "tos") == 0) {
		(void) sprintf(tmp, "0x%.2x", iphdr.ip_tos);
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "id") == 0) {
		(void) sprintf(tmp, "0x%.4x", htons(iphdr.ip_id));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "off") == 0) {
		(void) sprintf(tmp, "0x%.4x", htons(iphdr.ip_off));
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "ttl") == 0) {
		(void) sprintf(tmp, "0x%.2x", iphdr.ip_ttl);
		Tcl_AppendResult(interp, tmp, NULL);
	} else {
		Tcl_AppendResult(interp, "Unknown ip field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}

	return (TCL_OK);
}

/*
 * Set a protocol element
 *
 * Supported protocol fields:
 *	src
 *	dst
 *	proto
 *	sum
 *	len
 */
/*ARGSUSED0*/
int
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct ip		iphdr;
	int			value;
	ulong_t			addr;
	ushort_t		tmp_cksum;
	struct protoent		*pp;

	(void) memcpy(&iphdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (iphdr));
	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.
		 */
		tmp_cksum = 0;
		ip_cksum(&tmp_cksum, (uchar_t *)&iphdr, sizeof (iphdr));
		/* (void) printf("XXX: DEBUG: checksum is %x\n", tmp_cksum); */
		iphdr.ip_sum = htons(tmp_cksum);
		goto out;
	}
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: pset <pckt> ", proto_name,
		    " <field> <value>", NULL);
		return (TCL_ERROR);
	}
	/*
	 * Set one field
	 */
	if (strcmp(argv[0], "dst") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&addr) != TCL_OK) {
			if ((addr = inet_addr(argv[1])) == -1) {
				Tcl_ResetResult(interp);
				Tcl_AppendResult(interp,
				    "Badly formed ip addr ", argv[1], NULL);
				return (TCL_ERROR);
			}
		} else {
			addr = htonl(addr);
		}
		iphdr.ip_dst.s_addr = addr;
	} else if (strcmp(argv[0], "src") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&addr) != TCL_OK) {
			if ((addr = inet_addr(argv[1])) == -1) {
				Tcl_ResetResult(interp);
				Tcl_AppendResult(interp,
				    "Badly formed ip addr ", argv[1], NULL);
				return (TCL_ERROR);
			}
		} else {
			addr = htonl(addr);
		}
		iphdr.ip_src.s_addr = addr;
	} else if (strcmp(argv[0], "proto") == 0) {
		if ((pp = getprotobyname(argv[1])) != NULL) {
			iphdr.ip_p = pp->p_proto;
		} else if (Tcl_GetInt(interp, argv[1], &value) ==
		    TCL_OK) {
			if (value > 255) {
				Tcl_AppendResult(interp, proto_name,
				    " proto value out of range: ",
				    argv[1], NULL);
				return (TCL_ERROR);
			}
			iphdr.ip_p = (unsigned char)value;
		} else {
			Tcl_ResetResult(interp);
			Tcl_AppendResult(interp, "Bad ip proto: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
	} else if (strcmp(argv[0], "len") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&addr) !=
		    TCL_OK) {
			Tcl_AppendResult(interp, ": Badly formed ip len: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (addr > 65535) {
			Tcl_AppendResult(interp, "bad ip len value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		/*
		 * Set the packet structure length appropriately
		 */
		if (addr > pckt->p_len) {
			/* XXX */
		}
		/* XXX don't assume IP is first in packet */
		pckt->p_len = addr;
		iphdr.ip_len = htons(addr);
	} else if (strcmp(argv[0], "sum") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&addr) !=
		    TCL_OK) {
			Tcl_AppendResult(interp,
			    "Badly formed ip cksum: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (addr > 65535) {
			Tcl_AppendResult(interp, "bad ip cksum value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		iphdr.ip_sum = htons(addr);
	} else if (strcmp(argv[0], "hlv") == 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 > 255) {
			Tcl_AppendResult(interp, "bad hl/v value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		iphdr.ip_hl = value & 0xf;
		iphdr.ip_v = ((value & 0xf0) >> 4);
	} else if (strcmp(argv[0], "tos") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp,
			    "Badly formed tos: ", argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 255) {
			Tcl_AppendResult(interp, "bad tos value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		iphdr.ip_tos = (unsigned char)value;
	} else if (strcmp(argv[0], "id") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp, ": Badly formed id: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 65535) {
			Tcl_AppendResult(interp, "bad id value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		iphdr.ip_id = htons(value);
	} else if (strcmp(argv[0], "off") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp, ": Badly formed off: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 65535) {
			Tcl_AppendResult(interp, "bad off value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		iphdr.ip_off = htons(value);
	} else if (strcmp(argv[0], "ttl") == 0) {
		if (Tcl_GetInt(interp, argv[1], (int *)&value) !=
		    TCL_OK) {
			Tcl_AppendResult(interp, ": Badly formed ttl: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		if (value > 255) {
			Tcl_AppendResult(interp, "bad ttl value: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
		iphdr.ip_ttl = (unsigned short)value;
	} else {
		Tcl_AppendResult(interp, "Unknown ip field: ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
out:
	Tcl_ResetResult(interp);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset, &iphdr,
	    sizeof (iphdr));
	return (TCL_OK);
}

/*ARGSUSED0*/
int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct ip iphdr;
	char tmp[128];

	/*
	 * Print it all
	 */
	(void) memcpy(&iphdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (iphdr));
	Tcl_AppendResult(interp, "proto ",
	    ipproto_name_by_number(iphdr.ip_p), NULL);
	(void) sprintf(tmp, "0x%.8x", htonl(iphdr.ip_src.s_addr));
	Tcl_AppendResult(interp, " src ", tmp, NULL);
	(void) sprintf(tmp, "0x%.8x", htonl(iphdr.ip_dst.s_addr));
	Tcl_AppendResult(interp, " dst ", tmp, NULL);
	(void) sprintf(tmp, "0x%.4x", htons(iphdr.ip_len));
	Tcl_AppendResult(interp, " len ", tmp, NULL);
	(void) sprintf(tmp, "0x%.4x", htons(iphdr.ip_sum));
	Tcl_AppendResult(interp, " sum ", tmp, NULL);
	return (TCL_OK);
}

static int
ip_send(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ip_ep *ep;
	struct packet *pckt;
	struct ip iphdr;
	int rv, save_errno;
	struct sockaddr_in	destaddr;

	ep = (struct ip_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);
	}
	(void) memcpy(&iphdr, pckt->p_buf, sizeof (iphdr));
	bzero(&destaddr, sizeof (struct sockaddr_in));
	destaddr.sin_family = AF_INET;
	if (iphdr.ip_dst.s_addr != 0) { /* ip_dst of packet is preferable */
		destaddr.sin_addr.s_addr = iphdr.ip_dst.s_addr;
	} else {
		destaddr.sin_addr.s_addr = ep->ie_iph.ip_dst.s_addr;
	}
	destaddr.sin_port = ep->ie_dstport;
	rv = sendto(ep->ie_sndsock, pckt->p_buf, pckt->p_len,
	    0, (struct sockaddr *)&destaddr, sizeof (struct sockaddr));

	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
ip_recv(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ip_ep *ep;
	int rv, save_errno;
	struct packet *pckt;
	fd_set fds;
	struct timeval timo;
	int timeout;
	int fromlen = 0;

	ep = (struct ip_ep *)clientData;
	if (argc > 3 || argc < 2) {
		Tcl_AppendResult(interp, "IP recv operation: ",
		    "Usage: ", argv[0], " <pckt> [<timeout>] ;# <timeout> in "
		    "milliseconds", NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, "IP recv operation: ",
		    "packet not found: ", argv[1], NULL);
		return (TCL_ERROR);
	}
	if (argc == 3) {
		/*
		 * Parse timeout value, and poll rather than just doing
		 * blocking read.  Caller specifies timeout value in
		 * milliseconds.
		 */
		timeout = atoi(argv[2]);

		(void) FD_ZERO(&fds);
		FD_SET(ep->ie_rcvsock, &fds);
		timo.tv_usec = (timeout % 1000) * 1000;
		timo.tv_sec = timeout / 1000;

		if (select(ep->ie_rcvsock + 1, &fds, NULL, &fds, &timo) <= 0) {
			Tcl_AppendResult(interp, "timeout", NULL);
			return (TCL_OK);
		}
	}

	rv = recvfrom(ep->ie_rcvsock, pckt->p_buf, pckt->p_maxlen, 0,
	    (struct sockaddr *)NULL, &fromlen);
	if (rv < 0) {
		save_errno = errno;
		Tcl_AppendResult(interp, "recv ", proto_name, ": recvfrom: ",
		    strerror(save_errno), NULL);
		return (TCL_ERROR);
	}
	pckt->p_len = rv;
	/*
	 * Now we need to evaluate the entire protocol table for this
	 * packet, since everything in it has changed.
	 */
	if (ptbl_init(pckt, proto_name) == 0)
		return (TCL_ERROR);
	Tcl_AppendResult(interp, "packet", NULL);
	return (TCL_OK);
}
