/* 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...
 * 
 * Revised to use PF_PACKET instead of SOCK_PACKET (thanks to patch from
 * Kars de Jong <jongk@linux-m68k.org>)
 *
 * 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 <net/ethernet.h>                       /* ETH_P_802_3 */
#include <netpacket/packet.h>                   /* PF_PACKET stuff */
#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, 3, 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)
{
   static char netdev[31];
   pcap_if_t *alldevsp;
   int ret;

   /* We'll assume this function is suitable... */
   ret = pcap_findalldevs(&alldevsp, *errmsg); /* find a device */
   if (ret != 0)
   {
      return NULL; /* flag error to caller */
   }
   strcpy(netdev, alldevsp->name);
   pcap_freealldevs(alldevsp);
   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... 
   * 
   * We use a PF_PACKET socket in IEEE 802.3 mode.
   * All we need to do is fill out the destination addres and send rconn->rmp,
   * skipping the Ethernet header (it will be filled in by the kernel).
   */ 
   int s,cs;
   struct sockaddr_ll sa;

   memset (&sa, 0, sizeof(sa));
   s=socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_802_3));
   if (s == -1)
   {
      syslog(LOG_ERR, "attempt to open socket for write failed!");
      return 1;
   }
   
   /* Get the interface index to be used. */
   {
      struct ifreq req;
      int status;
        
      strcpy (req.ifr_name, IntfName);
      status = ioctl (s, SIOCGIFINDEX, &req);
      if (status == -1)
      {
         syslog(LOG_ERR, "SIOCGIFINDEX: couldn't get interface index!");
         return 0;
      }
      sa.sll_ifindex = req.ifr_ifindex;
   }

   /* Fill in the socket address */
   sa.sll_protocol = htons(ETH_P_802_3);
   sa.sll_family = AF_PACKET;
   memcpy (&sa.sll_addr, &(rconn->rmp.hp_hdr.daddr), RMP_ADDRLEN);
   sa.sll_halen = RMP_ADDRLEN;

   /* perform the actual packet send */
   cs = sendto(s, &(rconn->rmp.hp_llc), rconn->rmplen - sizeof(struct hp_hdr),
              0, (struct sockaddr *)&sa, sizeof(sa));

   close (s);

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