/* pcap.c - does roughly the job of bpf.c from the NetBSD version sources 
 * 
 * This code was written in about 1996 by 
 * Peter Maydell <pmaydell@chiark.greenend.org.uk>, and revised
 * in 1998 to clean it up a little...
 * 
 * On the positive side, this code is rather less nasty than bpf.c,
 * primarily because a lot of lower-level stuff has vanished into libpcap.
 *
 * Provides :
 * PcapGetIntfName(char* errmsg)  get first ethernet interface name,
 *                                or put an error message into errmsg buffer
 * 
 * PcapOpen()                     open the packet filter, etc
 * PcapDispatch(handlerfcn)       dispatch a packet if one present
 * PcapHandler()                  internal function - passes packets to 
 *                                user's handler.
 * PcapClose()                    close filter, deinitialise, etc
 *
 * The legal bumph from the NetBSD sources is huge. Consider it said.
 */

#include <sys/types.h>
#include <sys/socket.h>				/* socket(), sendto() */
#include <netinet/in.h>				/* htons(), ntohs() */
#include <sys/ioctl.h>				/* ioctl() */
#include <unistd.h>				/* close() */
#include <syslog.h>				/* syslog() */
#include <net/if.h>				/* struct ifreq */
#include <linux/if_ether.h>			/* ETH_P_ALL */
#include <string.h>                          	/* strncpy(), bcopy() */
#include "defs.h"

static pcap_t *pdesc = NULL;                 /* packet capture descriptor */
static char errbuf[PCAP_ERRBUF_SIZE];  /* buffer we use to get error msgs */

/* PcapOpen() - open & init packet filter. No params/return code.
 * If we encounter an error, we stop here (since that's what BpfOpen does)
 */
void PcapOpen() 
{
   int n;
   /* First we open the device. We use promiscuous mode as I have no 
    * idea whether or not pcap supports multicast. Besides, there's not
    * going to be much net traffic for *my* application :->
    * We set capture length to twice the size of the RMP packet
    * as a cheap way to detect oversize packets. This is a nasty hack...
    */
   pdesc = pcap_open_live(IntfName, 3 * sizeof(struct rmp_packet), 
                          1, 1000, errbuf);
   if (pdesc == NULL)
   {
      syslog(LOG_ERR, "pcap: couldn't open filter: %s",errbuf);
      Exit(0);
   }

   /* check we're being used on an Ethernet device */
   n = pcap_datalink(pdesc);
   if (n != DLT_EN10MB) 
   {
      syslog(LOG_ERR, "pcap: %s: datalink type %d unsupported",IntfName, n);
      Exit(0);
   }

   /* Now set the filter program; this is straight from bpf.c */
   {
#define	RMP	((struct rmp_packet *)0)
      static struct bpf_insn bpf_insn[] = {
         { BPF_LD|BPF_B|BPF_ABS,  0, 0, (long)&RMP->hp_llc.dsap },
         { BPF_JMP|BPF_JEQ|BPF_K, 0, 5, IEEE_DSAP_HP },
         { BPF_LD|BPF_H|BPF_ABS,  0, 0, (long)&RMP->hp_llc.cntrl },
         { BPF_JMP|BPF_JEQ|BPF_K, 0, 3, IEEE_CNTL_HP },
         { BPF_LD|BPF_H|BPF_ABS,  0, 0, (long)&RMP->hp_llc.dxsap },
         { BPF_JMP|BPF_JEQ|BPF_K, 0, 1, HPEXT_DXSAP },
         { BPF_RET|BPF_K,         0, 0, RMP_MAX_PACKET },
         { BPF_RET|BPF_K,         0, 0, 0x0 }
      };
#undef	RMP
      static struct bpf_program bpf_pgm = {
         sizeof(bpf_insn)/sizeof(bpf_insn[0]), bpf_insn
      };
   
      if (pcap_setfilter(pdesc, &bpf_pgm) != 0)
      {
         syslog(LOG_ERR, "pcap: failed to set filter (bug?)");
         Exit(0);
      }
   }
}

/* PcapGetIntfName - get name of the first ethernet device in the system.
 * We ignore loopback. We return 0 if none found, and then *errmsg 
 * explains why. Why do we pass the errmsg buffer as char**? We only 
 * dereference it here, and we use address-of to pass it in rbootd.c... 
 * Ah well, presumably there was a reason :->
 */
char* PcapGetIntfName(char **errmsg)
{
   /* We'll assume this function is suitable... */
   char* netdev = pcap_lookupdev(errbuf); /* find a device */
   if ((int)netdev == -1)
   {
      strncpy(*errmsg,pcap_lookupdev(errbuf),strlen(*errmsg));
      return NULL; /* flag error to caller */
   }
   return netdev; /* device found - return ptr to its name */
}


/* var used to pass the user's handler from PcapDispatch to PcapHandler */
static void (*uhandler)(RMPCONN rconn);

void PcapHandler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
{ 
   /* Called when we get a packet. Sort it out into the right format for 
    * the user's handler. Then call that.
    * This is probably the only really tricky bit in this file. 
    * user is just the ptr passed to pcap_dispatch() - we don't use it.
    * h is ptr to the header. sp is the pointer to the actual packet.
    * You can get h->caplen bytes of data from sp onwards.
    * Header length is just sizeof(struct pcap_pkthdr).
    */

   RMPCONN rconn;                      /* fill this in with packet details */
   register int datlen, caplen; /* declared register because bpf.c does... */
   datlen = h->len;                 /* length of this packet (untruncated) */
   caplen = h->caplen;                              /* max. captured bytes */

   /* First we ensure we got a complete packet of the right size! */
   if (caplen < datlen)
      syslog(LOG_ERR, "bpf: truncated packet dropped (%d of %d bytes)",
             caplen, datlen);
   else if (datlen > sizeof(struct rmp_packet))
      syslog(LOG_ERR, "bpf: oversized packet dropped (%d bytes)", caplen);
   else 
   {
      /* fill in fields in rconn and pass packet to user handler */
      rconn.rmplen = datlen;
      bcopy((char *)&h->ts, (char *)&rconn.tstamp, sizeof(struct timeval)); 
      bcopy(sp, (char *)&rconn.rmp, datlen); /* and copy packet over */
      uhandler(rconn);
   }
}

/* PcapDispatch(handlerfcn) : just an interface to the real 
 * dispatch / handler code...
 * Takes one parameter - the user's handler, which has prototype:
 * void DealWithPacket(RMPCONN rconn);
 */
int PcapDispatch(void (*usrhandler)(RMPCONN rconn))
{
   int retcode;   
   /* First we save ptr to the user's handler code */
   uhandler = usrhandler;
   /* Then we use pcap_dispatch() to start things off,
      using a ptr to the real PcapHandler() */
   retcode = pcap_dispatch(pdesc, 1, PcapHandler, NULL);
   /* We return the errorcode ( < 0 for errors) 
    * but we syslog it here, since only we know the pdesc 
    */
   if (retcode < 0)
   	syslog(LOG_ERR, "pcap: dispatch failed: %s", pcap_geterr(pdesc));
   return retcode;
}

/* PcapClose() - close filter, deinitialise anything we did, etc. */
void PcapClose()
{
   /* call pcap_close() to shut down interface...
      ...but only if we've already opened it! */
   if (pdesc != NULL)
      pcap_close(pdesc);
   /* now do our own deinit, if any */
}

/* We also need to be able to write packets to the Ethernet...
   This facility isn't provided by libpcap, but the subroutine's
   in this source file anyway. */
int PcapWrite(RMPCONN* rconn)
{ 
  /* write rconn direct to ethernet */ 
  /* What we get in an RMPCONN:
   * rconn->rmp    is a struct rmp_packet
   * rconn->rmplen is the length of said packet
   * rconn->tstamp 
   * rconn->bootfd filedesc of open bootfile
   * rconn->next   ptr to next RMPCONN... 
   * 
   * All we need to do is fill out source addr and send rconn->rmp. 
   * This is all BpfWrite does, although there it's the BPF that does 
   * the adding of source address.
   * NOTE : we do *not* just set the source addr in the rconn we get given!
   * We copy the old source addr and reset it, since code in rmpproto.c
   * depends on the source address being the address of the *destination* 
   * machine. (see the comment in rmp
   *  
   * Bits of this code borrowed from a post by 
   * Jamie Lokier (jamie@rebellion.co.uk) giving source for a program that
   * used SOCK_PACKET. Documentation on this facility is *really* rare...
   */ 
   int s,cs;
   struct sockaddr sa;
   char saveaddr[RMP_ADDRLEN];
   
   sa.sa_family = AF_INET;
   strcpy (sa.sa_data, IntfName);
   
   s=socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL));
   if (s == -1)
   {
      syslog(LOG_ERR, "attempt to open socket for write failed!");
      return 1;
   }
   
   /* Get the source address for the packets. */
   {
      struct ifreq req;
      int status;
        
      strcpy (req.ifr_name, IntfName);
      status = ioctl (s, SIOCGIFHWADDR, &req);
      if (status == -1)
      {
         syslog(LOG_ERR, "SIOCGIFHWADDR: couldn't get source address!");
         return 0;
      }
      /* save old source addr, then write real source addr into packet */
      memcpy (saveaddr, &(rconn->rmp.hp_hdr.saddr), RMP_ADDRLEN);
      memcpy (&(rconn->rmp.hp_hdr.saddr), req.ifr_hwaddr.sa_data, RMP_ADDRLEN);
   }
   /* perform the actual raw packet send */
   cs = sendto(s, &(rconn->rmp), rconn->rmplen, 0, &sa, sizeof(sa));       

   /* restore previous contents of the source address field before return */
   memcpy  (&(rconn->rmp.hp_hdr.saddr),saveaddr, RMP_ADDRLEN);
   close (s);

   if (cs == -1)
   {
      syslog (LOG_ERR, "sendto: failed to send packet!");
      return 0;
   }
   
   return 1; /* success */
}
