#include "ipxtund.h"
#include <net/bpf.h>
#include <net/ethernet.h>
#include <netipx/ipx.h>
#include <netipx/ipx_if.h>
#include <netinet/if_ether.h>

/*
  Functions to open / read / write / close an Ethernet device by using Berkely
  Packet Filter (BPF), e.g. for FreeBSD.
*/

int open_ether(struct Ether *ether)
{
  int pid, in_fd, pipe_fd[2];
  u_int n, blen;
  char in_dev[16];
  struct bpf_version bpf_v;
  struct ifreq ifr;
#ifdef NO_ETHERNET_II_HOPS
  /* accept only Ethernet II frames with an IPX hop-count of zero */
  char *bpf_name_II = "Ethernet II (no hops)";
  struct bpf_insn ipxframe_II[] = {
    BPF_STMT(BPF_LD+BPF_B+BPF_ABS, 18),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 3),
    BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IPX, 0, 1),
    BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
    BPF_STMT(BPF_RET+BPF_K, 0)
  };
#else
  /* accept only Ethernet II frames with any hop-count */
  char *bpf_name_II = "Ethernet II (hops allowed)";
  struct bpf_insn ipxframe_II[] = {
    BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IPX, 0, 1),
    BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
    BPF_STMT(BPF_RET+BPF_K, 0)
  };
#endif
  /* accept any Ethernet frametype but IP, ARP, REVARP */
  char *bpf_name_any = "any Ethernet (II, 802.2, 802.3, SNAP)";
  struct bpf_insn ipxframe_any[] = {
    BPF_STMT(BPF_LD+BPF_H+BPF_ABS, 12),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_IP, 3, 0),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_ARP, 2, 0),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ETHERTYPE_REVARP, 1, 0),
    BPF_STMT(BPF_RET+BPF_K, (u_int)-1),
    BPF_STMT(BPF_RET+BPF_K, 0)
  };
  struct bpf_program ipxfilter;

  /* open a BPF */
  in_fd = -1;
  errno = 0;
  n = 0;
  while (in_fd < 0 && errno != ENOENT) {
    sprintf(in_dev, "/dev/bpf%d", n);
    in_fd = open(in_dev, O_RDWR);
    if (in_fd < 0 && errno != EBUSY) {
      errormsg(E_ALL, "open_ether","open %s: %m", in_dev);
    }
    n++;
  }
  if (in_fd < 0) {
    errormsg(E_ALL,"open_ether","cant open a bpf");
    return -1;
  }
  if (debuglevel > 0) {
    fprintf(log_fp,"%s: using bpf %s, file descriptor %d\n", ether->name,
            in_dev, in_fd);
  }
  
  /* set BPF buffersize */
  blen = BUFSIZE;
  if (ioctl(in_fd, BIOCSBLEN, &blen) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCSBLEN, %s): %m",
             in_dev);
    return -1;
  }

  /* don't accumulate frames, return if one is received */
  n = 1;
  if (ioctl(in_fd, BIOCIMMEDIATE, &n) < 0) {
    errormsg(E_ALL,"open_ether","ioctl(%s, BIOIMMEDIATE): %m",
             in_dev);
    return -1;
  }

  /* glue BPF with Ethernet interface */
  memset(&ifr, '\0', sizeof(ifr));
  strcpy(ifr.ifr_name, ether->name);
  if (ioctl(in_fd, BIOCSETIF, &ifr) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCSETIF, %s, %s): %m",
             in_dev, ether->name);
    return -1;
  }

  /* check type of interface (have to be Ethernet) */
  if (ioctl(in_fd, BIOCGDLT, &n) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCGDLT, %s): %m",
             in_dev);
    return -1;
  }
  if (n != DLT_EN10MB) {
    errormsg(E_ALL, "open_ether",
             "%s: wrong data link layer (%d), must be Ethernet",
	     in_dev, n);
    return -1;
  }

  /* get size of BPF buffer (may be deviate from earlier setting) */
  if (ioctl(in_fd, BIOCGBLEN, &blen) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCGBLEN, %s): %m",
             in_dev);
    return -1;
  }
  if (debuglevel > 0) {
    fprintf(log_fp,"buffer size for %s is %d\n", in_dev, blen);
  }
  
  /* check for possible overflow */
  if (blen > BUFSIZE) {
    errormsg(E_ALL, "open_ether","BUFSIZE too small");
    return -1;
  }
  /* store buffersize */
  ether->bufsize = (size_t)blen;

  /* check BPF version of filter-languge */
  if (ioctl(in_fd, BIOCVERSION, &bpf_v) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCSETF, %s): %m", in_dev);
    return -1;
  }
  if (bpf_v.bv_major != BPF_MAJOR_VERSION ||
      bpf_v.bv_minor < BPF_MINOR_VERSION) {
    errormsg(E_ALL,"open_ether","incomptible BPF version");
    return -1;
  }

  /* assign BPF filter */
  if (ether->r_frametypes == ETHER_II) {
    ipxfilter.bf_len = sizeof(ipxframe_II) / sizeof(struct bpf_insn);
    ipxfilter.bf_insns = ipxframe_II;
    if (debuglevel > 0) {
      fprintf(log_fp,"bpf-filter is %s\n", bpf_name_II);
    }
  }
  else {
    ipxfilter.bf_len = sizeof(ipxframe_any) / sizeof(struct bpf_insn);
    ipxfilter.bf_insns = ipxframe_any;
    if (debuglevel > 0) {
      fprintf(log_fp,"bpf-filter is %s\n", bpf_name_any);
    }
  }
  if (ioctl(in_fd, BIOCSETF, &ipxfilter) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCSETF, %s): %m", in_dev);
    return -1;
  }

  /* switch Etherdevice to promiscous mode */
  if (ioctl(in_fd, BIOCPROMISC, 0) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCPROMISC, %s): %m",
             in_dev);
    return -1;
  }

  /* clear old data from it and reset statistics */
  if (ioctl(in_fd, BIOCFLUSH, 0) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(BIOCFLUSH, %s): %m",
             in_dev);
    return -1;
  }

#ifdef USE_BPF_BUFFERING
  /* Spawn an additional process which read out the Etherdevice as fast as
     possible. While the main process may block longer time at writing to
     the slow Internet, this process write into a pipe.
     The problem is, the packet accumulation at BPF is a bit broken.
     Writing is no problem.
  */
  if (pipe(pipe_fd) < 0) {
    errormsg(E_ALL, "open_ether","pipe: %m");
    return -1;
  }
  if ((pid = fork()) < 0) {
    errormsg(E_ALL, "open_ether","fork: %m");
    return -1;
  }
  else if (pid == 0) {
    /* child */
    ssize_t bytes;
    char *packetbuf;
    struct sigaction act;
    
    /* reset signal handler (was set by parent) */
    sigemptyset(&act.sa_mask);
    act.sa_handler = SIG_DFL;
    act.sa_flags = 0;
    sigaction(SIGINT, &act, NULL);
    sigaction(SIGHUP, &act, NULL);
    sigaction(SIGQUIT, &act, NULL);

    if ((packetbuf = malloc(blen)) < 0) {
      errormsg(E_ALL, "open_ether (child)","malloc: %m");
      exit(-1);
    }
    close(pipe_fd[0]);
    if (debuglevel > 0) {
      fprintf(log_fp,"using bpf buffering (pid %d)\n", getpid());
    }
    for (;;) {
      /* read Etherdevice and write to pipe */
      bytes = read(in_fd, packetbuf, blen);
      if (bytes < 0) {
	errormsg(E_ALL, "open_ether (child)","read: %m");
	exit(-1);
      }
      else if (bytes == 0) {
	errormsg(E_ALL, "open_ether (child)","EOF");
	exit(0);
      }
      if (bytes > 0) {
        /* format is |no. of data|data| */
	write(pipe_fd[1], (char *)&bytes, sizeof(bytes));
	bytes = writen(pipe_fd[1], packetbuf, bytes);
	if (bytes < 0) {
	  errormsg(E_ALL, "open_ether (child)","writen: %m");
	  exit(-1);
	}
      }
    }
  }
  else {
    /* parent */
    /* parent (i.e. the main program) reads from pipe but writes directly */
    close(pipe_fd[1]);
    ether->fd[0] = pipe_fd[0];
    ether->w_fd = in_fd;
    ether->fd_anz = 1;
  }
#else
  /* if no additional process is used */
  ether->fd[0] = in_fd;
  ether->w_fd = in_fd;
  ether->fd_anz = 1;
#endif

  clear_stat(&ether->stat);

  if (debuglevel > 0) {
    fprintf(log_fp,"\n");
  }
  return in_fd;
}


int close_ether(struct Ether *ether)
{
  struct bpf_stat bpf_s;
  
  /* retrieve BPF statistics */
  if (ioctl(ether->w_fd, BIOCGSTATS, &bpf_s) < 0) {
    errormsg(E_ALL, "close_ether","ioctl(BIOCGSTATS, %s): %m",
             ether->name);
    return -1;
  }
  if (debuglevel > 0) {
    fprintf(log_fp,"\ninterface %s received %u packets, %u dropped\n",
            ether->name, bpf_s.bs_recv, bpf_s.bs_drop);
  }
  return 0;
}


int read_ipx_from_ether(struct Data_packet *packet, struct Ether *ether,
                        const u_short ether_idx, boolean *more_data)
{
  size_t ipx_off, len;
  ssize_t read_len, bytes;
  static ssize_t remaining_len;
  u_int frametype;
  static u_short packet_cnt;
  static u_long frame_cnt = 0;
  struct bpf_hdr *bh;
  static char rbuf[BUFSIZE], *p_ptr;
  char ipx_dest_node[6];

  if (debuglevel > 1) {
    fprintf(log_fp,"\n");
  }

  if (! *more_data) {
    /* A read from a BPF returns all frames accumulated by the Etherdevice,
       but this functions always returns exactly one frame. Therefore, we
       need two distinguish two cases:
       1. There are remaining frames alredy read from Etherdevice but not
          processed and delivered (*more_data == TRUE).
       2. There are no remaining frames (*more_data == FALSE).
   */
#ifdef USE_BPF_BUFFERING
    /* this matches to the corresponding part of "open", i.e. we get a
       packet of format |no. of data | data | */
    if ((read_len = read(ether->fd[0], (char *)&bytes, sizeof(bytes))) < 0) {
      errormsg(E_LOG,"read_ipx_from_ether", "%s: read: %m", ether->name);
      return -1;
    }
    if (read_len != sizeof(bytes)) {
      errormsg(E_LOG,"read_ipx_from_ether",
      "%s: read: %d bytes expected, got %d", ether->name, sizeof(bytes),
      bytes);
      return -1;
    }
    read_len = readn(ether->fd[0], rbuf, bytes);
    if (read_len < 0) {
      errormsg(E_LOG,"read_ipx_from_ether", "%s: read: %m", ether->name);
      ether->stat.errors_r++;
      return -1;
    }
    if (read_len != bytes) {
      errormsg(E_LOG,"read_ipx_from_ether", "%s: read: bytes mismatch (%d %d)",
               ether->name, bytes, read_len);
      ether->stat.errors_r++;
      return -1;
    }
#else
    /* we get only data */
    read_len = read(ether->fd[0], rbuf, ether->bufsize);
    if (read_len < 0) {
      errormsg(E_LOG,"read_ipx_from_ether", "%s: read: %m", ether->name);
      ether->stat.errors_r++;
      return -1;
    }
#endif
    /* because we have a new packet from BPF, reset our internal buffer */
    p_ptr = rbuf;
    remaining_len = read_len;
    bh = (struct bpf_hdr *)p_ptr;
    packet_cnt = 1;
  }
  else {
    /* we further exploy already read data, move pointer p_ptr to next
       portion */
    bh = (struct bpf_hdr *)p_ptr;
    p_ptr += BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
    packet_cnt++;
  }
  frame_cnt++;
  
  /* if there are remaining data, set *more_data to TRUE */
  remaining_len -= BPF_WORDALIGN(bh->bh_hdrlen + bh->bh_caplen);
  if (remaining_len > 0) {
    *more_data = TRUE;
  }
  else {
    *more_data = FALSE;
  }

  /* check if packet is complete */  
  if (bh->bh_datalen != bh->bh_caplen) {
    errormsg(E_LOG,"read_ipx_from_ether", "%s: fragmented Ethernet packet",
             ether->name);
    ether->stat.errors_r++;
    return -1;
  }
  
  /* check size of Ethernet frame */
  if (bh->bh_datalen < 60 || bh->bh_datalen > 1514) {
    errormsg(E_LOG,"read_ipx_from_ether",
             "%s: wrong Ethernet length (%d bytes)", ether->name,
	     bh->bh_datalen);
    ether->stat.errors_r++;
    return -1;
  }

  /* copy frame into a buffer which will be returned to caller */  
  memcpy(packet->buf, p_ptr + bh->bh_hdrlen, bh->bh_caplen);
/*
fprintf(log_fp,"--------------------\n");
fprint_as_hex_nl(log_fp, packet->buf, bh->bh_caplen);
fprintf(log_fp,"-----------\n\n");
*/
  /* determine and check Ethernet-IPX frame type */
  if ( ! get_ipx_to_ether_offset(packet->buf, &frametype, &ipx_off)) {
    errormsg(E_LOG,"read_ipx_from_ether","unknown ether frame");
    ether->stat.errors_r++;
    return -1;
  }
  if (! (frametype & ether->r_frametypes)) {
    errormsg(E_LOG,"read_ipx_from_ether","unconfigured ether frame");
    log_frametypes(frametype);
    ether->stat.errors_r++;
    return -1;
  }
  packet->from_frametype = frametype;
  
  /* save Ether addresses */
  save_ether_addr(packet->buf, ipx_off);
  
  /* cut off Ethernet header */
  packet->data = packet->buf + ipx_off;
  packet->len = get_ipx_length(packet->data);
  /* check size of IPX packet */
  len = bh->bh_datalen - ipx_off;
  if (len != packet->len && len != 2*((packet->len + 1) / 2)) {
    if (bh->bh_datalen == 60 && packet->len >= 30 && packet->len < len) {
      /* ok, due to minimum ethernet size padding */
    }
    else if (packet->len < len) {
      /* this may happen due to broken BPF packet accumulation */
      errormsg(E_LOG,"read_ipx_from_ether",
	 "ipx_length %d differ from packet_len %d (try transfer)", packet->len,
         len);
    }
    else {
      /* this may happen due to broken BPF packet accumulation */
      errormsg(E_LOG,"read_ipx_from_ether",
	  "ipx_length %d differ from packet_len %d (discard)", packet->len,
	  len);
      ether->stat.errors_r++;
      return -1;
    }
  }

  if (debuglevel > 1) {
    get_ipx_dest_node(ipx_dest_node, packet->data);
    if (memcmp(ipx_dest_node, IPX_BROADCAST_NODE, 6) != 0) {
      fprintf(log_fp,"Packet received from Ethernet %s for peer ",
              ether->name);
      fprint_as_hex(log_fp, ipx_dest_node, 6);
    }
    else {
      fprintf(log_fp,"Packet (Broadcast) received from Ethernet %s ",
              ether->name);
    }
    fprintf(log_fp," [");
    log_frametypes(frametype);
    fprintf(log_fp,"]:\n");
    fprintf(log_fp,"Frame No: %ld [%d]\n", frame_cnt, packet_cnt);
    if (debuglevel > 2) {
      log_ether_header(packet->buf);
      log_ipx_header(packet->data);
      if (debuglevel > 3) {
	log_ipx_packet(packet);
      }
    }
  }

  /* check if frame was not sent from myself */
  if (memcmp(packet->buf + 6, ether->mac, 6) == 0) {
    if (debuglevel > 2) {
      fprintf(log_fp,"packet dropped: packet was sent from myself\n");
    }
    return 0;
  }
  /* type is Ethernet, receiver the only one fd[0] */
  packet->from_com = com_ether;
  packet->from_fd = ether->fd[0];
  
  len = bh->bh_caplen;
  ether->stat.packets_r++;
  ether->stat.bytes_r += len;
  if (len > ether->stat.max_bytes_r) {
    ether->stat.max_bytes_r = len;
  }
  
  return packet->len;
}


int write_ipx_to_ether(const struct Data_packet *packet,
                       struct Ether *ether)
{
  u_int frametype;
  size_t ether_len, ipx_off;
  ssize_t len;
  char ether_packet[BUFSIZE];

  /* determine Ethernet-IPX frame type to sent */
  if (is_realframetype(ether->s_frametype)) {
    frametype = ether->s_frametype;
  }
  else if (is_realframetype(packet->from_frametype)) {
    frametype = packet->from_frametype;
  }
  else {
    frametype = DEFAULT_BUILD_ETHER_FRAMETYPE;
    fprintf(log_fp,"unknown Ether type, trying default");
    log_frametypes(frametype);
    fprintf(log_fp,"\n");
  }

  /* build Ethernet-IPX frame */
  if (build_ether_packet(ether_packet, &ether_len, &ipx_off, frametype,
                         packet->data, packet->len) < 0) {
    errormsg(E_LOG,"write_ipx_to_ether","unknown frametype");
    ether->stat.errors_s++;
    return -1;
  }
  /* and write it to BPF */
  len = writen(ether->w_fd, ether_packet, ether_len);
  if (len < 0) {
    errormsg(E_LOG,"write_ipx_to_ether","write to %s: %m", ether->name);
    ether->stat.errors_s++;
    return -1;
  }
  if (len != ether_len) {
    errormsg(E_LOG,"write_ipx_to_ether",
             "Warning: could only write to %s %d bytes of %d bytes\n",
	     ether->name, len, ether_len);
  }

  if (debuglevel > 1) {
    fprintf(log_fp,"Packet sent to Ethernet %s [", ether->name);
    log_frametypes(frametype);
    fprintf(log_fp,"]:\n");    
    if (debuglevel > 2) {
      log_ether_header(ether_packet);
      log_ipx_header(ether_packet + ipx_off);
    }
  }
  
  ether->stat.packets_s++;
  ether->stat.bytes_s += len;
  if (len > ether->stat.max_bytes_s) {
    ether->stat.max_bytes_s = len;
  }

  return 0;
}
