/*
 *	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	"@(#)ether.c	1.45	99/05/28 SMI"

#include <tcl.h>
#include "psh.h"
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#ifdef SOLARIS
#include <stropts.h>
#endif /* SOLARIS */
#include <setjmp.h>
#include <string.h>

#include <pcap.h>

static char *proto_name;

#define	DLT_MAX	15		/* Should be in libpcap! */

static char *dlt_names[DLT_MAX] = {
	"no link-layer encapsulation",	/* DLT_NULL */
	"ethernet",			/* DLT_EN10MB */
	"3Mb ethernet",			/* DLT_EN3MB */
	"amateur radio AX.25",		/* DLT_AX25 */
	"proteon token ring",		/* DLT_PRONET */
	"chaos",			/* DLT_CHAOS */
	"ieee802",			/* DLT_IEEE802 */
	"arcnet",			/* DLT_ARCNET */
	"slip",				/* DLT_SLIP */
	"ppp",				/* DLT_PPP */
	"fddi",				/* DLT_FDDI */
	"llc/snap atm",			/* DLT_ATM_RFC1483 */
	"raw ip",			/* DLT_RAW */
	"bsd/os slip",			/* DLT_SLIP_BSDOS */
	"bsd/os ppp"			/* DLT_PPP_BSDOS */
};

/*
 * Defaults for creating new packets
 * def_src, and def_dst are set through the etheraddr() routine, while
 */
static struct ether_addr def_src, def_dst;

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

static int
etheraddr(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ether_addr	address;
	struct ether_addr	*addrp, *ap;

	addrp = (struct ether_addr *)clientData;
	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: ", argv[0], " <etheraddr>",
		    NULL);
		return (TCL_ERROR);
	}
	/*
	 * First we try a numeric form...
	 */
	if ((ap = ether_aton(argv[1])) != NULL) {
		*addrp = *ap;
		return (TCL_OK);
	}
	/*
	 * Look up by host name
	 */
	if (ether_hostton(argv[1], &address) == 0) {
		*addrp = address;
		return (TCL_OK);
	}
	/*
	 * Can't work out what this string might be.  Call it off!
	 */
	Tcl_AppendResult(interp, "Bad address: ", argv[1], NULL);
	return (TCL_ERROR);
}

/*
 * End points for ethernet communication.  We retain here enough to be able
 * to do whatever communication operations we might need.  E.g., the fd
 * for the interface.
 */
struct ether_ep {
	ep_t		ee_ep;	/* psh end point */
	pcap_t		*ee_pd;
	int		ee_pdfd;
};

struct protoent {
	char	*name;
	int	value;
};

#define	NPROTOS	3
static struct protoent ether_proto_list[NPROTOS] = {
	{ "arp",	0x806 },
	{ "ip", 	0x800 },
	{ "ipv6", 	0x86dd }
};

static char *
proto_name_by_number(int n)
{
	int i;

	for (i = 0; i < NPROTOS; i++) {
		if (ether_proto_list[i].value == n) {
			return (ether_proto_list[i].name);
		}
	}
	/*
	 * No name...
	 */
	return ("unknown");
}

/*
 * No one uses this today, but it might be useful later.
 */
static int
proto_number_by_name(char *s)
{
	int i;

	for (i = 0; i < NPROTOS; i++) {
		if (strcmp(ether_proto_list[i].name, s) == 0) {
			return (ether_proto_list[i].value);
		}
	}
	return (0);
}

int
init(proto_tbl_t *prototbl, Tcl_Interp *interp)
{
	prototbl->proto_epsize = sizeof (struct ether_ep);

	/*
	 * Register protocol specific name-address translation commands.
	 */
	(void) Tcl_CreateCommand(interp, "ether.src", etheraddr,
	    (ClientData)&def_src, NULL);
	(void) Tcl_CreateCommand(interp, "ether.dst", etheraddr,
	    (ClientData)&def_dst, NULL);
	if ((proto_name = strdup(prototbl->proto_name)) == NULL) {
		Tcl_AppendResult(interp, "Internal Error: malloc: no space",
		    NULL);
		return (0);
	}
	return (1);
}

/* The clientData argument is never meaningful */
int
propen(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	pcap_t *pd;
	char ebuf[PCAP_ERRBUF_SIZE];
	struct bpf_program bpf_pgm;
	int save_errno;
	struct ether_ep *ep;
	char tmp[128];
	int promisc = 0;

	if (argc < 2 || argc > 4) {
		Tcl_AppendResult(interp, "Usage: popen ", proto_name,
		    " <id> <interface> [<promisc> [<pkt-filter>] ]", NULL);
		return (TCL_ERROR);
	}

	ep = (struct ether_ep *)clientData;

	if (argc >= 3 && Tcl_GetInt(interp, argv[2], &promisc) != TCL_OK) {
		Tcl_AppendResult(interp, "Bad promiscuous flag: ",
		    argv[2], NULL);
		return (TCL_ERROR);
	}

	ebuf[0] = '\0';
	if ((pd = pcap_open_live(argv[1], 65536, promisc, 200, ebuf)) == NULL) {
		save_errno = errno;
		Tcl_ResetResult(interp);
		Tcl_AppendResult(interp, "popen ether: open: ", argv[1], ": ",
		    NULL);
		if (ebuf[0] != '\0') {
			Tcl_AppendResult(interp, ebuf, NULL);
		} else {
			Tcl_AppendResult(interp, strerror(save_errno), NULL);
		}
		return (TCL_ERROR);
	}

	/*
	 * Accept and parse BPF filter, if present.
	 *
	 * Small potential problem:  Old versions of ether protocol library
	 * exposed the SAP in argv[2].  Now argv[2] is a promiscuous flag.
	 */
	if (argc == 4) {
		unsigned netmask = 0;
		unsigned localnet = 0;

		(void) pcap_lookupnet(argv[1], &localnet, &netmask, ebuf);
		if (pcap_compile(pd, &bpf_pgm, argv[3], BTRUE, netmask) < 0) {
			Tcl_AppendResult(interp, "Bad filter expression: ",
			    argv[3], NULL);
			return (TCL_ERROR);
		}
		if (pcap_setfilter(pd, &bpf_pgm) < 0) {
			Tcl_AppendResult(interp, "Filter installation failed "
			    "on ", argv[3], NULL);
			return (TCL_ERROR);
		}
	}

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

	ep->ee_pd = pd;
	ep->ee_pdfd = pcap_fileno(pd);

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

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

	return (TCL_OK);
}

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

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: prclose <end-point>", NULL);
		return (TCL_ERROR);
	}

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

	ep = (struct ether_ep *)clientData;

	pcap_close(ep->ee_pd);
	return (TCL_OK);
}

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

	Tcl_ResetResult(interp);
	if (pckt->p_len == 0)
		pckt->p_len = sizeof (etherhdr);
	if (ptbl_update(pckt, 1, NULL, pckt->p_len) == 0) {
		return (TCL_ERROR);
	}
	bcopy(&def_dst, &etherhdr.ether_dhost, 6);
	bcopy(&def_src, &etherhdr.ether_shost, 6);
	etherhdr.ether_type = 0;
	bcopy(&etherhdr, pckt->p_buf, min(pckt->p_len, sizeof (etherhdr)));
	return (TCL_OK);
}


int
decode(struct packet *pkt, int element, char **name, int *next_offset)
{
	struct ether_header etherhdr;

	(void) memcpy(&etherhdr, pkt->p_buf + pkt->p_tbl[element].pt_offset,
	    sizeof (etherhdr));
	*name = proto_name_by_number(ntohs(etherhdr.ether_type));
	*next_offset = pkt->p_tbl[element].pt_offset + sizeof (etherhdr);
	return (1);
}

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

	if (argc != 1) {
		Tcl_AppendResult(interp, "Usage: pget ether <pckt> <item>",
		    NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&etherhdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (etherhdr));
	/*
	 * Print one field
	 */
	if (strcmp(argv[0], "dst") == 0) {
		(void) sprintf(tmp, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
#ifdef SOLARIS
		    etherhdr.ether_dhost.ether_addr_octet[0],
		    etherhdr.ether_dhost.ether_addr_octet[1],
		    etherhdr.ether_dhost.ether_addr_octet[2],
		    etherhdr.ether_dhost.ether_addr_octet[3],
		    etherhdr.ether_dhost.ether_addr_octet[4],
		    etherhdr.ether_dhost.ether_addr_octet[5]);
#else /* SOLARIS */
		    etherhdr.ether_dhost[0],
		    etherhdr.ether_dhost[1],
		    etherhdr.ether_dhost[2],
		    etherhdr.ether_dhost[3],
		    etherhdr.ether_dhost[4],
		    etherhdr.ether_dhost[5]);
#endif /* SOLARIS */
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "src") == 0) {
		(void) sprintf(tmp, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
#ifdef SOLARIS
		    etherhdr.ether_shost.ether_addr_octet[0],
		    etherhdr.ether_shost.ether_addr_octet[1],
		    etherhdr.ether_shost.ether_addr_octet[2],
		    etherhdr.ether_shost.ether_addr_octet[3],
		    etherhdr.ether_shost.ether_addr_octet[4],
		    etherhdr.ether_shost.ether_addr_octet[5]);
#else /* SOLARIS */
		    etherhdr.ether_shost[0],
		    etherhdr.ether_shost[1],
		    etherhdr.ether_shost[2],
		    etherhdr.ether_shost[3],
		    etherhdr.ether_shost[4],
		    etherhdr.ether_shost[5]);
#endif /* SOLARIS */
		Tcl_AppendResult(interp, tmp, NULL);
	} else if (strcmp(argv[0], "type") == 0) {
		Tcl_AppendResult(interp,
		    proto_name_by_number(ntohs(etherhdr.ether_type)), NULL);
	} else {
		Tcl_AppendResult(interp, "Unknown ether field: ",
		    argv[0], NULL);
		return (TCL_ERROR);
	}

	return (TCL_OK);
}

/*
 * Set a protocol element
 *
 * Supported protocol fields:
 *	src
 *	dst
 *	type
 */
int
set(int element, ClientData clientData, Tcl_Interp *interp, struct packet *pckt,
    int argc, char *argv[])
{
	struct ether_header	etherhdr;
	int			value;
	struct ether_addr		*addrp, addr;

	if (argc != 2) {
		Tcl_AppendResult(interp, "Usage: pset <pckt> ", proto_name,
		    " <field> <value>", NULL);
		return (TCL_ERROR);
	}
	(void) memcpy(&etherhdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (etherhdr));
	/*
	 * Set one field
	 */
	if (strcmp(argv[0], "dst") == 0) {
		if ((addrp = ether_aton(argv[1])) == NULL) {
			addrp = &addr;
			if (ether_hostton(argv[1], &addr) != 0) {
				Tcl_AppendResult(interp,
				    "Badly formed ether addr ", argv[1], NULL);
				return (TCL_ERROR);
			}
		}
		bcopy(addrp, &etherhdr.ether_dhost, 6);
	} else if (strcmp(argv[0], "src") == 0) {
		if ((addrp = ether_aton(argv[1])) == NULL) {
			addrp = &addr;
			if (ether_hostton(argv[1], &addr) != 0) {
				Tcl_AppendResult(interp,
				    "Badly formed ether addr ", argv[1], NULL);
				return (TCL_ERROR);
			}
		}
		bcopy(addrp, &etherhdr.ether_shost, 6);
	} else if (strcmp(argv[0], "type") == 0) {
		if (Tcl_GetInt(interp, argv[1], &value) == TCL_OK) {
			if (value > 65535 || value < 0) {
				Tcl_AppendResult(interp, "arp proto value ",
				    "out of range: ", argv[1], NULL);
			}
			etherhdr.ether_type = htons(value);
		} else if ((value = proto_number_by_name(argv[1])) != 0) {
			etherhdr.ether_type = htons(value);
		} else {
			Tcl_AppendResult(interp, "Bad ether proto number: ",
			    argv[1], NULL);
			return (TCL_ERROR);
		}
	} else {
		Tcl_AppendResult(interp, "Unknown ether field: ",
		    argv[1], NULL);
		return (TCL_ERROR);
	}
	Tcl_ResetResult(interp);
	(void) memcpy(pckt->p_buf + pckt->p_tbl[element].pt_offset, &etherhdr,
	    sizeof (etherhdr));
	return (TCL_OK);
}

int
list(int element, ClientData clientData, Tcl_Interp *interp,
    struct packet *pckt, int argc, char *argv[])
{
	struct ether_header etherhdr;
	char tmp[128];

	if (argc > 0) {
		Tcl_AppendResult(interp, "Usage: plist ", proto_name,
		    " <pkt>", NULL);
		return (TCL_ERROR);
	}
	/*
	 * Print it all
	 */
	(void) memcpy(&etherhdr, pckt->p_buf + pckt->p_tbl[element].pt_offset,
	    sizeof (etherhdr));
	(void) sprintf(tmp, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
#ifdef SOLARIS
	    etherhdr.ether_dhost.ether_addr_octet[0],
	    etherhdr.ether_dhost.ether_addr_octet[1],
	    etherhdr.ether_dhost.ether_addr_octet[2],
	    etherhdr.ether_dhost.ether_addr_octet[3],
	    etherhdr.ether_dhost.ether_addr_octet[4],
	    etherhdr.ether_dhost.ether_addr_octet[5]);
#else /* SOLARIS */
	    etherhdr.ether_dhost[0],
	    etherhdr.ether_dhost[1],
	    etherhdr.ether_dhost[2],
	    etherhdr.ether_dhost[3],
	    etherhdr.ether_dhost[4],
	    etherhdr.ether_dhost[5]);
#endif /* SOLARIS */
	Tcl_AppendResult(interp, "dst ", tmp, NULL);
	(void) sprintf(tmp, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
#ifdef SOLARIS
	    etherhdr.ether_shost.ether_addr_octet[0],
	    etherhdr.ether_shost.ether_addr_octet[1],
	    etherhdr.ether_shost.ether_addr_octet[2],
	    etherhdr.ether_shost.ether_addr_octet[3],
	    etherhdr.ether_shost.ether_addr_octet[4],
	    etherhdr.ether_shost.ether_addr_octet[5]);
#else /* SOLARIS */
	    etherhdr.ether_shost[0],
	    etherhdr.ether_shost[1],
	    etherhdr.ether_shost[2],
	    etherhdr.ether_shost[3],
	    etherhdr.ether_shost[4],
	    etherhdr.ether_shost[5]);
#endif /* SOLARIS */
	Tcl_AppendResult(interp, " src ", tmp, NULL);
	Tcl_AppendResult(interp, " type ",
	    proto_name_by_number(ntohs(etherhdr.ether_type)), NULL);
	return (TCL_OK);
}

static int
ether_send(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct ether_ep *ep;
	struct packet *pckt;

	ep = (struct ether_ep *)clientData;
	if (argc != 2) {
		Tcl_AppendResult(interp, "ether send operation: ",
		    "Usage: ", argv[0], " <pckt>", NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, "ether send operation: ",
		    "packet not found: ", argv[1], NULL);
		return (TCL_ERROR);
	}

	(void) write(ep->ee_pdfd, pckt->p_buf, pckt->p_len);
	return (TCL_OK);
}

/*
 * This function is passed to libpcap's pcap_loop function to pull out
 * packets received.
 */
static void
ethergetpkt(unsigned char *uppkt, const struct pcap_pkthdr *h, const uchar_t *p)
{
	struct packet *pckt = (struct packet *)uppkt;

	pckt->p_len = h->caplen;
	pckt->p_pat = h->ts;
	bcopy(p, pckt->p_buf, h->caplen);
}

static int
ether_recv(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
	struct packet *pckt;
	struct ether_ep *ep;
	int timeout;
	fd_set fds;
	struct timeval timo;

	ep = (struct ether_ep *)clientData;
	if (argc < 2 || argc > 3) {
		Tcl_AppendResult(interp, "ether recv operation: ",
		    "Usage: ", argv[0], " <pckt> [<timeout>] ;# <timeout> in"
		    " milliseconds", NULL);
		return (TCL_ERROR);
	}
	if ((pckt = pckt_lookup(argv[1])) == NULL) {
		Tcl_AppendResult(interp, "ether 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.
		 */
		timeout = atoi(argv[2]);
		(void) FD_ZERO(&fds);
		FD_SET(ep->ee_pdfd, &fds);
		timo.tv_usec = (timeout % 1000) * 1000;
		timo.tv_sec = timeout / 1000;

		if (select(ep->ee_pdfd + 1, &fds, NULL, &fds, &timo) <= 0) {
			Tcl_AppendResult(interp, "timeout", NULL);
			return (TCL_OK);
		}
	}
	if (pcap_loop(ep->ee_pd, 1, ethergetpkt, (uchar_t *)pckt) < 0) {
		Tcl_AppendResult(interp, "recv ether: pcap_loop: ",
		    pcap_geterr(ep->ee_pd), NULL);
		return (TCL_ERROR);
	}

	/*
	 * Now we need to evaluate the entire protocol table for this
	 * packet, since everything in it has changed.
	 */
	if (ptbl_init(pckt, "ether") == 0)
		return (TCL_ERROR);

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