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

#include <net/bpf.h>
#include <net/if.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>

#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <stdio.h>
#include <fcntl.h>

static int dl_bpf_open_dev(char *dev, const size_t len)
{
  int i=0, fd;

  do
    {
      snprintf(dev, len, "/dev/bpf%d", i);
      if((fd = open(dev, O_RDWR)) == -1)
	{
	  if(errno == EBUSY)
	    {
	      continue;
	    }
	  else
	    {
	      perror("could not open bpf");
	      return -1;
	    }
	}
      else break;
    }
  while(++i < 32768);

  return fd;
}

uint16_t in_cksum(const void *buf, const size_t len)
{
  uint16_t *w = (uint16_t *)buf;
  size_t l = len;
  int sum = 0;

  while(l > 1)
    {
      sum += *w++;
      l   -= 2;
    }

  if(l != 0)
    {
      sum += ((uint8_t *)w)[0];
    }

  sum  = (sum >> 16) + (sum & 0xffff);
  sum += (sum >> 16);

  return ~sum;
}

int main(int argc, char *argv[])
{
  struct ifreq    ifreq;
  char           *ifname, *src, *dst;
  int             ifindex;
  char            dev[16];
  int             fd;
  uint8_t        *pktbuf;
  struct ip      *ip;
  struct icmp    *icmp;
  ssize_t         wb, len = sizeof(int);
  int             af;

  if(argc != 5)
    {
      fprintf(stderr, "usage: bpf_null_tx <if> <src> <dst> <len>\n");
      return -1;
    }
  ifname = argv[1];
  src    = argv[2];
  dst    = argv[3];
  len    = sizeof(int) + atoi(argv[4]);

  printf("ifname: %s, src: %s, dst: %s, len: %d\n", ifname, src, dst, len);

  if((ifindex = if_nametoindex(ifname)) == 0)
    {
      perror("if_nametoindex");
      return -1;
    }

  if((pktbuf = malloc(len)) == NULL)
    {
      perror("malloc failed");
      return -1;
    }
  memset(pktbuf, 0, len);

  printf("ifindex: %d\n", ifindex);

  if((fd = dl_bpf_open_dev(dev, sizeof(dev))) == -1)
    {
      perror("could not open device");
      return -1;
    }

  /* set the interface that will be sniffed */
  memset(&ifreq, 0, sizeof(ifreq));
  strcpy(ifreq.ifr_name, ifname);
  if(ioctl(fd, BIOCSETIF, &ifreq) == -1)
    {
      perror("BIOCSETIF failed");
      close(fd);
      return -1;
    }

  af = AF_INET;

  memcpy(pktbuf, &af, sizeof(int));

  ip = (struct ip *)(pktbuf + sizeof(int));
  ip->ip_v   = 4;
  ip->ip_hl  = 5;
  ip->ip_tos = 0;
  ip->ip_len = htons(len - sizeof(int));
  ip->ip_id  = 0;
  ip->ip_off = htons(IP_DF);
  ip->ip_ttl = 255;
  ip->ip_p   = IPPROTO_ICMP;
  ip->ip_sum = 0;
  inet_pton(AF_INET, src, &ip->ip_src);
  inet_pton(AF_INET, dst, &ip->ip_dst);
  ip->ip_sum = in_cksum(ip, sizeof(struct ip));

  icmp = (struct icmp *)(pktbuf + sizeof(int) + sizeof(struct ip));
  icmp->icmp_type  = ICMP_ECHO;
  icmp->icmp_code  = 0;
  icmp->icmp_cksum = 0;
  icmp->icmp_id    = getpid();
  icmp->icmp_seq   = 0;
  icmp->icmp_cksum = in_cksum(icmp, len - sizeof(int) - sizeof(struct ip));

  if((wb = write(fd, pktbuf, len)) < len)
    {
      if(wb == -1)
	{
	  perror("could not send packet");
	}
      else
	{
	  fprintf(stderr, "%d bytes sent of %d total\n", wb, len);
	}

      return -1;
    }

  printf("sent %d bytes\n", wb);

  return 0;
}
