/*
 * $Id: dlpi.c,v 1.8 2007/03/12 18:32:11 c4chris Exp $
 */
#include "os.h"

#ifdef USE_DLPI
/*
 * dpli.c - routines for messing with the Data Link Provider Interface.
 *
 * The code in this module is based in large part (especially the dl*
 * routines) on the example code provided with the document "How to Use
 * DLPI", by Neal Nuckolls of Sun Internet Engineering.  Gotta give credit
 * where credit is due.  If it weren't for Neal's excellent document,
 * this module just plain wouldn't exist.
 *
 * David A. Curry
 * Purdue University
 * Engineering Computer Network
 * 1285 Electrical Engineering Building
 * West Lafayette, IN 47907-1285
 * davy@ecn.purdue.edu
 *
 */
#include <sys/param.h>
#include <unistd.h>
#include <stropts.h>
#include <sys/dlpi.h>
#ifdef SUNOS5
#include <sys/bufmod.h>
#endif
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/time.h>
#include <net/if.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include "nfswatch.h"
#include "externs.h"

static void	dlbindreq();
static void	dlinforeq();
static void	dlattachreq();
static void	dlpromisconreq();
static void	sigalrm();
static int	dlokack();
static int	dlinfoack();
static int	dlbindack();
static int	expecting();
static int	strgetmsg();

/*
 * setup_dlpi_dev - set up the data link provider interface.
 */
int
setup_dlpi_dev(device)
char **device;
{
	char *p;
	u_int chunksz;
	char cbuf[BUFSIZ];
	struct ifconf ifc;
	struct ifreq *ifrp;
	struct strioctl si;
	char devname[BUFSIZ];
	int n, s, fd, devppa;
	struct timeval timeout;
	long buf[DLPI_MAXDLBUF];

	/*
	 * If the interface device was not specified,
	 * get the default one.
	 */
	if (*device == NULL) {
		/*
		 * Grab a socket.
		 */
		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
			error("socket");
			finish(-1);
		}

		ifc.ifc_buf = cbuf;
		ifc.ifc_len = sizeof(cbuf);

		/*
		 * See what devices we've got.
		 */
		if (ioctl(s, SIOCGIFCONF, (char *) &ifc) < 0) {
			error("ioctl: SIOCGIFCONF");
			finish(-1);
		}

		/*
		 * Take the first device we encounter.
		 */
		ifrp = ifc.ifc_req;
		for (n = ifc.ifc_len/sizeof(struct ifreq); n > 0; n--,ifrp++) {
			/*
			 * Skip the loopback interface.
			 */
			if (strcmp(ifrp->ifr_name, "lo0") == 0)
				continue;

			*device = strdup(ifrp->ifr_name);
			break;
		}

		(void) close(s);
	}

	/*
	 * Split the device name into type and unit number.
	 */
	p = *device + strlen(*device);
	while (p > *device && isdigit(*(p - 1)))
		p -= 1;
	if (p == *device)
		return(-1);

	strcpy(devname, DLPI_DEVDIR);
	strncat(devname, *device, p - *device);
	devppa = atoi(p);

	/*
	 * Open the device.
	 */
	if ((fd = open(devname, O_RDWR)) < 0) {
		if (errno == ENOENT || errno == ENXIO)
			return(-1);

		error(devname);
		finish(-1);
	}

	/*
	 * Attach to the device.  If this fails, the device
	 * does not exist.
	 */
	dlattachreq(fd, devppa);

	if (dlokack(fd, buf) < 0) {
		close(fd);
		return(-1);
	}

	/*
	 * We want the ethernet in promiscuous mode if we're looking
	 * at nodes other than ourselves.
	 */
	if (allflag || dstflag) {
#ifdef DL_PROMISC_PHYS
		dlpromisconreq(fd, DL_PROMISC_PHYS);

		if (dlokack(fd, buf) < 0) {
			fprintf(stderr, "%s: DL_PROMISC_PHYS failed.\n", pname);
			finish(-1);
		}
#else
		fprintf(stderr, "%s: DLPI 1.3 does not support promiscuous ",
			pname);
		fprintf(stderr, "mode operation.\n");
		fprintf(stderr, "%s: cannot implement -all or -dst options.\n",
			pname);
		finish(-1);
#endif
	}

	/*
	 * Bind to the specific unit.
	 */
	dlbindreq(fd, DLPI_DEFAULTSAP, 0, DL_CLDLS, 0, 0);

	if (dlbindack(fd, buf) < 0) {
		fprintf(stderr, "%s: dlbindack failed.\n", pname);
		finish(-1);
	}

#ifdef SUNOS5
	/*
	 * We really want all types of packets.  However, the SVR4 DLPI does
	 * not let you have the packet frame header, so we won't be able to
	 * distinguish protocol types.  But SunOS5 gives you the DLIOCRAW
	 * ioctl to get the frame headers, so we can do this on SunOS5.
	 */
	dlpromisconreq(fd, DL_PROMISC_SAP);

	if (dlokack(fd, buf) < 0) {
		fprintf(stderr, "%s: DL_PROMISC_SAP failed.\n", pname);
		finish(-1);
	}

	/*
	 * We want raw packets with the packet frame header.  But we can
	 * only get this in SunOS5 with the DLIOCRAW ioctl; it's not in
	 * standard SVR4.
	 */
	si.ic_cmd = DLIOCRAW;
	si.ic_timout = -1;
	si.ic_len = 0;
	si.ic_dp = 0;

	if (ioctl(fd, I_STR, &si) < 0) {
		error("ioctl: I_STR DLIOCRAW");
		finish(-1);
	}
#endif /* SUNOS5 */

	/*
	 * Arrange to get discrete messages.
	 */
	if (ioctl(fd, I_SRDOPT, (char *) RMSGD) < 0) {
		error("ioctl: I_SRDOPT RMSGD");
		finish(-1);
	}

#ifdef SUNOS5
	/*
	 * Push and configure the streams buffering module.  This is once
	 * again SunOS-specific.
	 */
	if (ioctl(fd, I_PUSH, DLPI_BUFMOD) < 0) {
		error("ioctl: I_PUSH BUFMOD");
		finish(-1);
	}

	/*
	 * Set the read timeout.
	 */
	timeout.tv_sec = 1;
	timeout.tv_usec = 0;

	si.ic_cmd = SBIOCSTIME;
	si.ic_timout = INFTIM;
	si.ic_len = sizeof(timeout);
	si.ic_dp = (char *) &timeout;

	if (ioctl(fd, I_STR, (char *) &si) < 0) {
		error("ioctl: I_STR SBIOCSTIME");
		finish(-1);
	}

	/*
	 * Set the chunk size.
	 */
	chunksz = DLPI_CHUNKSIZE;

	si.ic_cmd = SBIOCSCHUNK;
	si.ic_len = sizeof(chunksz);
	si.ic_dp = (char *) &chunksz;

	if (ioctl(fd, I_STR, (char *) &si) < 0) {
		error("ioctl: I_STR SBIOCSCHUNK");
		finish(-1);
	}

	/*
	 * Set snapshot mode.
	 */
	si.ic_cmd = SBIOCSSNAP;
	si.ic_len = sizeof(truncation);
	si.ic_dp = (char *) &truncation;

	if (ioctl(fd, I_STR, (char *) &si) < 0) {
		error("ioctl: I_STR SBIOCSSNAP");
		finish(-1);
	}
#endif /* SUNOS5 */

	return(fd);
}

/*
 * flush_dlpi - flush data from the dlpi.
 */
void
flush_dlpi(fd)
int fd;
{
	if (ioctl(fd, I_FLUSH, (char *) FLUSHR) < 0) {
		error("ioctl: I_FLUSH");
		finish(-1);
	}
}

/*
 * dlpi_devtype - determine the type of device we're looking at.
 */
int
dlpi_devtype(fd)
int fd;
{
	long buf[DLPI_MAXDLBUF];
	union DL_primitives *dlp;

	dlp = (union DL_primitives *) buf;

	dlinforeq(fd);

	if (dlinfoack(fd, buf) < 0)
		return(DLT_EN10MB);

	switch (dlp->info_ack.dl_mac_type) {
	case DL_CSMACD:
	case DL_ETHER:
		return(DLT_EN10MB);
#ifdef DL_FDDI
	case DL_FDDI:
		return(DLT_FDDI);
#endif
	default:
		fprintf(stderr,
#ifdef _LP64
			"%s: DLPI MACtype %d unknown, ",
#else
			"%s: DLPI MACtype %ld unknown, ",
#endif
			pname, dlp->info_ack.dl_mac_type);
		fprintf(stderr, "assuming ethernet.\n");
		return(DLT_EN10MB);
	}
}

/*
 * dlinforeq - request information about the data link provider.
 */
static void
dlinforeq(fd)
int fd;
{
	dl_info_req_t info_req;
	struct strbuf ctl;
	int flags;

	info_req.dl_primitive = DL_INFO_REQ;

	ctl.maxlen = 0;
	ctl.len = sizeof (info_req);
	ctl.buf = (char *) &info_req;

	flags = RS_HIPRI;

	if (putmsg(fd, &ctl, (struct strbuf *) NULL, flags) < 0) {
		error("putmsg");
		finish(-1);
	}
}

/*
 * dlattachreq - send a request to attach.
 */
static void
dlattachreq(fd, ppa)
u_long ppa;
int fd;
{
	dl_attach_req_t	attach_req;
	struct strbuf ctl;
	int flags;

	attach_req.dl_primitive = DL_ATTACH_REQ;
	attach_req.dl_ppa = ppa;

	ctl.maxlen = 0;
	ctl.len = sizeof (attach_req);
	ctl.buf = (char *) &attach_req;

	flags = 0;

	if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) {
		error("putmsg");
		finish(-1);
	}
}

#ifdef DL_PROMISCON_REQ
/*
 * dlpromisconreq - send a request to turn promiscuous mode on.
 */
static void
dlpromisconreq(fd, level)
u_long level;
int fd;
{
	dl_promiscon_req_t promiscon_req;
	struct strbuf ctl;
	int flags;

	promiscon_req.dl_primitive = DL_PROMISCON_REQ;
	promiscon_req.dl_level = level;

	ctl.maxlen = 0;
	ctl.len = sizeof (promiscon_req);
	ctl.buf = (char *) &promiscon_req;

	flags = 0;

	if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) {
		error("putmsg");
		finish(-1);
	}
}
#endif /* DL_PROMISCON_REQ */

/*
 * dlbindreq - send a request to bind.
 */
static void
dlbindreq(fd, sap, max_conind, service_mode, conn_mgmt, xidtest)
u_long sap, max_conind, service_mode, conn_mgmt, xidtest;
int fd;
{
	dl_bind_req_t bind_req;
	struct strbuf ctl;
	int flags;

	bind_req.dl_primitive = DL_BIND_REQ;
	bind_req.dl_sap = sap;
	bind_req.dl_max_conind = max_conind;
	bind_req.dl_service_mode = service_mode;
	bind_req.dl_conn_mgmt = conn_mgmt;
#ifdef DL_PROMISC_PHYS
	/*
	 * DLPI 2.0 only?
	 */
	bind_req.dl_xidtest_flg = xidtest;
#endif

	ctl.maxlen = 0;
	ctl.len = sizeof (bind_req);
	ctl.buf = (char *) &bind_req;

	flags = 0;

	if (putmsg(fd, &ctl, (struct strbuf*) NULL, flags) < 0) {
		error("putmsg");
		finish(-1);
	}
}

/*
 * dlokack - general acknowledgement reception.
 */
static int
dlokack(fd, bufp)
char *bufp;
int fd;
{
	union DL_primitives *dlp;
	struct strbuf ctl;
	int flags;

	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = bufp;

	if (strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags) < 0)
		return(-1);

	dlp = (union DL_primitives *) ctl.buf;

	if (expecting(DL_OK_ACK, dlp) < 0)
		return(-1);

	if ((unsigned int) ctl.len < sizeof (dl_ok_ack_t))
		return(-1);

	if (flags != RS_HIPRI)
		return(-1);

	if ((unsigned int) ctl.len < sizeof (dl_ok_ack_t))
		return(-1);

	return(0);
}

/*
 * dlinfoack - receive an ack to a dlinforeq.
 */
static int
dlinfoack(fd, bufp)
char *bufp;
int fd;
{
	union DL_primitives *dlp;
	struct strbuf ctl;
	int flags;

	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = bufp;

	if (strgetmsg(fd, &ctl, (struct strbuf *)NULL, &flags) < 0)
		return(-1);

	dlp = (union DL_primitives *) ctl.buf;

	if (expecting(DL_INFO_ACK, dlp) < 0)
		return(-1);

	if ((unsigned int) ctl.len < sizeof (dl_info_ack_t))
		return(-1);

	if (flags != RS_HIPRI)
		return(-1);

	if ((unsigned int) ctl.len < sizeof (dl_info_ack_t))
		return(-1);

	return(0);
}

/*
 * dlbindack - receive an ack to a dlbindreq.
 */
static int
dlbindack(fd, bufp)
char *bufp;
int fd;
{
	union DL_primitives *dlp;
	struct strbuf ctl;
	int flags;

	ctl.maxlen = DLPI_MAXDLBUF;
	ctl.len = 0;
	ctl.buf = bufp;

	if (strgetmsg(fd, &ctl, (struct strbuf*)NULL, &flags) < 0)
		return(-1);

	dlp = (union DL_primitives *) ctl.buf;

	if (expecting(DL_BIND_ACK, dlp) < 0)
		return(-1);

	if (flags != RS_HIPRI)
		return(-1);

	if ((unsigned int) ctl.len < sizeof (dl_bind_ack_t))
		return(-1);

	return(0);
}

/*
 * expecting - see if we got what we wanted.
 */
static int
expecting(prim, dlp)
union DL_primitives *dlp;
int prim;
{
	if (dlp->dl_primitive != (u_long)prim)
		return(-1);

	return(0);
}

/*
 * strgetmsg - get a message from a stream, with timeout.
 */
static int
strgetmsg(fd, ctlp, datap, flagsp)
struct strbuf *ctlp, *datap;
int *flagsp;
int fd;
{
	int rc;

	/*
	 * Start timer.
	 */
	(void) sigset(SIGALRM, sigalrm);

	alarm(DLPI_MAXWAIT);

	/*
	 * Set flags argument and issue getmsg().
	 */
	*flagsp = 0;
	if ((rc = getmsg(fd, ctlp, datap, flagsp)) < 0) {
		error("getmsg");
		finish(-1);
	}

	/*
	 * Stop timer.
	 */
	alarm(0);

	/*
	 * Check for MOREDATA and/or MORECTL.
	 */
	if ((rc & (MORECTL | MOREDATA)) == (MORECTL | MOREDATA))
		return(-1);
	if (rc & MORECTL)
		return(-1);
	if (rc & MOREDATA)
		return(-1);

	/*
	 * Check for at least sizeof (long) control data portion.
	 */
	if ((unsigned int) (ctlp->len) < sizeof (long))
		return(-1);

	return(0);
}

/*
 * sigalrm - handle alarms.
 */
static void
sigalrm()
{
	(void) fprintf(stderr, "dlpi: timeout\n");
}
#endif /* USE_DLPI */
