/*     
 * $RCSfile: nit.c,v $
 *
 * x-kernel v3.3
 *
 * $Revision: 1.2 $
 * $Date: 1996/01/31 16:27:21 $
 */


/*---------------------------------------------------------------------------+
|    Module : nit_ether.c
+----------------------------------------------------------------------------+
|
|    Author  :        Werner H.P. Vogels 	(werner@inesc.pt)
|
+---------------------------------------------------------------------------*/
/***************************************************************************
 *
 * Copyright (c) 1993 Distributed Systems and Industrial Automation Group
 * Copyright (c) 1993 INESC
 *
 * This file is part of INESC xAMp protocol.
 *
 * This software is copyrighted by INESC and the Distributed Systems
 * and Industrial Automation Group of INESC and SHOULD ONLY BE USED 
 * for research and evaluation purposes, as long as this notice remains.
 *
 * For more information, contact:
 * INESC -- Distributed Systems and Industrial Automation Group
 * Rua Alves Redol, 9  1000 LISBOA  --  PORTUGAL
 * or mail: xamp@inesc.pt
 *
 ***************************************************************************/

/* 
 * This driver uses the Network Interface Tap to get access to raw
 * ethernet packets.  You generally have to run as root to use this. 
 */


/*---------------------------------------------------------------------+
|   Includes
+----------------------------------------------------------------------*/

#include <stdio.h>
#include <stropts.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/file.h>

#include <net/if.h>
#include <net/nit.h>
#include <net/nit_if.h>
#include <net/nit_pf.h>
#include <net/packetfilt.h>

#include <netinet/in.h>
#include <netinet/if_ether.h>

#include "xkernel.h"
#include "eth.h"
#include "eth_i.h"
#include "machine.h"
#include "inputProcess.h"
#include "romopt.h"

/*---------------------------------------------------------------------+
|   Defines
+----------------------------------------------------------------------*/

#define NIT_DEV         "/dev/nit"
#define NIT_SHEPHERD_THREADS	16
#define RCV_BUF_SIZE	(MAX_ETH_DATA_SZ + 100)

/*---------------------------------------------------------------------
|   Private Variables
+--------------------------------------------------------------------*/


typedef struct {
    ETHhost	myHost;
    int		if_fd;
    Map		typeBlockMap;
} NitState, PState;


static char	*if_tab[] = { "ie0", "le0", NULL};
/* 
 * Single buffer pool for all NIT instances.
 */
static BufferPool *	bufPool;
static int		numShepherdThreads = NIT_SHEPHERD_THREADS;
static char		dummyInputBuf[RCV_BUF_SIZE];

int	tracenitp;


int	getmsg( int, struct strbuf *, struct strbuf *, int * );
int	ioctl( int, int, void * );
int	putmsg( int, struct strbuf *, struct strbuf *, int );

void	nit_init(Protl);

static void	internalDemux( InputBuffer * );
static void	nit_SetPromiscuous( Protl, int );
static int	nit_ih( VOID * );
static int	nitControl( Protl, int, char *, int );
static XkReturn	nitOpenEnable( Protl, Protl, Protl, Part * );
static XkHandle	nitPush( Sessn, Msg * );
static XkReturn	readBlockType( Protl, char **, int, int, VOID * );


#define	TYPE_BLOCK_MAP_SZ	5

/*
 * Defining TYPE_FILTER will enable code which filters packets on
 * type before creating processes.  The types in the filter can only
 * be configured once at boot time.
 */
#define TYPE_FILTER

static ProtlRomOpt protlOpts[] = {
    { "block", 3, readBlockType }
};


/* msg2buf is no longer supplied in the message tool, so it is included here. */
/*
 * Copy fragments of the message into a contiguous buffer.  Buffer must be
 * long enough.
 */
void
msg2buf(Msg *msg, char *dst_buf)
{
    MsgWalk cxt;
    void    *buf;
    int     len;

    xTrace2(msg, TR_FUNCTIONAL_TRACE, "msg2Buf(msg=%#lx,dst_buf=%#lx)",
	    (u_long)msg, (u_long)dst_buf);

    msgWalkInit(&cxt, msg);
    while ((buf = msgWalkNext(&cxt, &len)) != 0) {
	bcopy(buf, dst_buf, len);
        dst_buf += len;
    } /* while */
    msgWalkDone(&cxt);

    xTrace0(msg, TR_FUNCTIONAL_TRACE, "msg2Buf returns");
} /* msg2buf */


/*---------------------------------------------------------------------
|   Function: ethCtlrInit, init the nit stream
+--------------------------------------------------------------------*/

void
nit_init( self )
    Protl	self;
{
	struct strioctl si;
	struct ifreq    ifr;
	char            **dev;
	PState		*ps;


	xTraceP0(self, TR_GROSS_EVENTS, "init");
	ps = X_NEW(PState);
	self->state = ps;
	ps->typeBlockMap = mapCreate(TYPE_BLOCK_MAP_SZ, sizeof(ETHtype));
	findProtlRomOpts(self, protlOpts, sizeof(protlOpts)/sizeof(ProtlRomOpt), 0);
	/* Open the network interface tap device */
	if ((ps->if_fd = open(NIT_DEV, O_RDWR)) < 0)
	{
		xError("Cannot open nit device");	
		exit(1);
    	}

	/* set the stream to message-discard mode */
	if (ioctl (ps->if_fd, I_SRDOPT, (char *)RMSGD) < 0)
	{	
		xError("nit_init: cannot set stream to msgd mode");
		exit(1);
	}	

	/* set the stream to signal SIGPOLL on input */
	if (ioctl (ps->if_fd, I_SETSIG, (void *)S_INPUT) < 0)
	{
		xError("init_init: cannot set signal mode");
		exit(1);
	}

	/* bind the stream to the correct device, just search through 
           a table with intel, lance and ... */
 
	for (dev = if_tab; *dev != NULL; dev++)
	{
		strcpy(ifr.ifr_name, *dev); 
		si.ic_len = sizeof ifr;
		si.ic_dp = (char *)&ifr;
		si.ic_cmd = NIOCBIND;
		if (ioctl (ps->if_fd, I_STR, (char *)&si) < 0)
			continue;
		else
			break;
	}

	/* be safe, unknown interface */
	if (*dev == NULL) 
	{
		xError("nit_init: cannot bind nit to device");
		exit(1);
	}	

	xTraceP1(self, TR_GROSS_EVENTS, "bound to %s", *dev);

	if (ioctl (ps->if_fd, SIOCGIFADDR, &ifr) < 0)
	{
		xError("nit_init: cannot find my ethernet address");
		exit(1);
	}

	bcopy(ifr.ifr_addr.sa_data, (char *)&ps->myHost, sizeof(ethAd_t));

	xTraceP1(self, TR_MAJOR_EVENTS, "srcAd = %x", ethHostStr(&ps->myHost));

	if ( ! bufPool ) {
	    bufPool = xkBufferPoolInit(numShepherdThreads, RCV_BUF_SIZE,
				       internalDemux);
	}
	self->push = nitPush;
	self->controlprotl = nitControl;
	self->openenable = nitOpenEnable;
	self->up = 0;
	/* send the handler to the signal dispatcher */
	installSignalHandler(ps->if_fd, nit_ih, self);
}


/*---------------------------------------------------------------------
|   Function: nitPush, transmit a packet
+--------------------------------------------------------------------*/

static XkHandle
nitPush( self, packet )
    Sessn	self;
    Msg 	*packet;
{
 	PState		*ps = (PState *)self->state;
	struct strbuf	ctlptr;
	struct strbuf	dataptr;

	ETHhdr		*eth_head;
	struct sockaddr	dest_head;

	int 		xmit_len;
	char		xmit_buff[MAX_ETH_DATA_SZ];


	eth_head = (ETHhdr *)msgGetAttr(packet, 0);
	if ( ! eth_head ) {
	    xTraceS0(self, TR_ERRORS, "push -- no header attached");
	    return XMSG_ERR_HANDLE;
	}
	
	dest_head.sa_family = AF_UNSPEC;
	bcopy((char *)eth_head, dest_head.sa_data, sizeof(ETHhdr));

	xmit_len = msgLength(packet);
	if ( xmit_len > MAX_ETH_DATA_SZ ) {
	    xTraceS1(self, TR_ERRORS, "push -- msgLength(%d) too big",
		     xmit_len);
	    return XMSG_ERR_HANDLE;
	}

	msg2buf(packet, xmit_buff);

	ctlptr.buf	= (char *)&dest_head;
	ctlptr.len	= sizeof(struct sockaddr);

	dataptr.buf	= xmit_buff;
	dataptr.len	= xmit_len;

	xTraceS3(self, TR_EVENTS, "nit Xmit dst : %s type: %x len %d",
		 ethHostStr(&eth_head->dst), eth_head->type, xmit_len );

	if (putmsg (ps->if_fd, &ctlptr, &dataptr, 0) == -1)
	{
		xTraceS0(self, TR_ERRORS, "push: putmsg failed");
		return XMSG_ERR_HANDLE;
	}

	xTraceS0(self, TR_MORE_EVENTS, "msg sent");

	return XMSG_NULL_HANDLE;
}

/*---------------------------------------------------------------------
|   Function: nit_ih, the signal handler, called at SIGIO/SIGPOLL time 
+--------------------------------------------------------------------*/


static int
nit_ih( arg )
    VOID	*arg;
{
    	Sessn	self = (Sessn)arg;
	PState	*ps = (PState *)self->state;

        struct strbuf   ctlptr;
	struct strbuf   dataptr;

	InputBuffer	*bp;
	int             flags;
	static char     ctl_buff[MAX_ETH_DATA_SZ];
	flags 		= 0;


	xTraceS0(self, TR_FUNCTIONAL_TRACE, "interrupt handler");
	ctlptr.maxlen	= MAX_ETH_DATA_SZ;
	ctlptr.buf	= ctl_buff;
	dataptr.maxlen	= RCV_BUF_SIZE;

	bp = xkBufferPoolNextBlock(bufPool);
	if ( ! bp ) {
	    xError("nit ERROR: Can't get buffer, dropping incoming packet");
	    xIfTraceS(self, TR_SOFT_ERRORS) {
		xkBufferPoolDump(bufPool);
	    }
	    /* 
	     * Drop this packet
	     */
	    dataptr.buf = dummyInputBuf;
	    getmsg (ps->if_fd, &ctlptr, &dataptr, &flags);
	    return -1;
	}
	dataptr.buf 	= bp->data;
	/* get the data off the stream */
	if (getmsg (ps->if_fd, &ctlptr, &dataptr, &flags) == -1) {
	    xkBufferPoolReleaseBlock(bp);
	    return 0;
	}
	xAssert(dataptr.len <= RCV_BUF_SIZE);
#ifdef TYPE_FILTER
	/*
	 * Make sure the type is one that we accept
	 */
	{
	    ETHtype		type;
	    XkReturn	xkr;

	    bcopy((char *)&((ETHhdr *)bp->data)->type, (char *)&type,
		  sizeof(ETHtype));
	    xkr = mapResolve(ps->typeBlockMap, &type, 0);
	    if ( xkr == XK_SUCCESS ) {
		xTraceS1(self, TR_EVENTS,
			"type %x blocked by type filter", type);
		xkBufferPoolReleaseBlock(bp);
		return 0;
	    }
	}
#endif /* TYPE_FILTER */
	msgTruncate(&bp->msg, dataptr.len);
	bp->self = self;
	xTraceS1(self, TR_FULL_TRACE, "sem count (pre signal) == %d",
		 bp->sem.count);
	semSignal(&bp->sem);
	return 0;
}


static long
ethMsgLoad( void *hdr, char *netHdr, long len, void *arg )
{
    xAssert(len == sizeof(ETHhdr));
    bcopy(netHdr, (char *)hdr, sizeof(ETHhdr));
    return sizeof(ETHhdr);
}


static void
internalDemux( InputBuffer *blockp )
{
    ETHhdr	hdr;
    Msg		*msg = &blockp->msg;
    Sessn	self = blockp->self;
    void        *buf;
    
    xTraceS0(self, TR_EVENTS, "internal demux");
    
    buf = msgPop(msg, sizeof(ETHhdr));
    if (! buf) {
	xTraceS0(self, TR_SOFT_ERRORS,
		"demux: incoming message too small!");
	msgDestroy(msg);
	return;
    }
    ethMsgLoad(&hdr, buf, sizeof(ETHhdr), 0);
    if ( ! self->up ) {
	xTraceS0(self, TR_ERRORS, "demux: no upper protocol!");
	msgDestroy(msg);
	return;
    }
    msgSetAttr(msg, 0, &hdr, sizeof(ETHhdr));
    xDemux(xGetUp(self), self, msg);
}


/*---------------------------------------------------------------------
|   Function: nit_SetPromiscuous, toggle the interface prom mode 
+--------------------------------------------------------------------*/

static void
nit_SetPromiscuous(self, on)
    Protl	self;
    int		on;
{
	PState			*ps = (NitState *)self->state;	
	struct strioctl 	si;
	unsigned long 		if_flags;

	si.ic_cmd = NIOCGFLAGS;
	si.ic_len = sizeof(if_flags);
	si.ic_dp  = (char*)&if_flags;

	if (ioctl (ps->if_fd, I_STR, &si) < 0)
	{
		xTraceP0(self, TR_ERRORS, "SetPromiscuous getnitflags failed");
		return;
	}

	if (on)
		if_flags |= NI_PROMISC;
	else
		if_flags &= ~NI_PROMISC;

	si.ic_cmd = NIOCSFLAGS;

	if (ioctl (ps->if_fd, I_STR, &si) < 0)
	{
		xTraceP0(self, TR_ERRORS, "SetPromiscuous setnitflags failed");
		return;
	}
}	



static int
nitControl( s, op, buf, len )
    Protl	s;
    int 	op, len;
    char 	*buf;
{
    PState	*ps = (PState *)s->state;

    switch (op) {

      case GETMYHOST:
	checkLen(len, sizeof(ETHhost));
	bcopy((char *) &ps->myHost, buf, sizeof(ETHhost));
	return (sizeof(ETHhost));

      case ETH_SETPROMISCUOUS:
	nit_SetPromiscuous(s, 1);
	return 0;

      default:
	return -1;
    }
}


static XkReturn
nitOpenEnable( self, hlp, hlpType, p )
    Protl	self, hlp, hlpType;
    Part	*p;
{
    if ( self->up ) {
	xError("nitOpenEnable called multiple times!");
	return XK_FAILURE;
    }
    self->up = hlp;
    return XK_SUCCESS;
}


#ifdef TYPE_FILTER

/*
 * readHex -- read a number from 's' which may be either hex (prefixed
 * with an x as in x34b2) or decimal.
 *
 * non-zero on success, zero on failure
 */
static int
readHex( char *s, int *n )
{
    if ( *s == 'x' ) {
	return ( sscanf(s + 1, "%x", n) == 1 );
    } else {
	return ( sscanf(s, "%d", n) == 1 );
    }
}

#endif


static XkReturn
readBlockType( self, str, nFields, line, arg )
    Protl	self;
    int		line, nFields;
    char	**str;
    VOID	*arg;
{
#ifdef TYPE_FILTER

    PState	*ps = (PState *)self->state;
    int		n;
    ETHtype	type;

    if ( ! readHex(str[2], &n) ) {
	return XK_FAILURE;
    }
    type = htons((u_short)n);
    mapBind(ps->typeBlockMap, &type, 0);
    xTraceP1(self, TR_GROSS_EVENTS, "driver is blocking type %x", type);

#else
    xTraceP1(self, TR_GROSS_EVENTS,
	     "driver ignoring type filter on line %d", line);

#endif /* TYPE_FILTER */

    return XK_SUCCESS;
}
