#include "ipxtund.h"
#include <linux/etherdevice.h>

/*
  Functions to open / read / write / close an Ethernet interface by using
  sockets (especially the way Linux want).
  Each of the four supported Ethernet-IPX protocols use its own socket for
  reading, but writing happens using one socket (equals the first reading one)
  for all types.
*/

int open_ether(struct Ether *ether)
{
  int k;
  u_short p_anz;
  u_short protocol[4];
  size_t len;
  struct ifreq ifr;

  /* the four supported Ethernet-IPX protocolls */
  protocol[0] = htons(ETH_P_IPX);
  protocol[1] = htons(ETH_P_802_2);
  protocol[2] = htons(ETH_P_802_3);
  protocol[3] = htons(ETH_P_SNAP);
  /* make a UNIX-Domain socket for each Ethernet-IPX type */
  p_anz = 4;
  len = sizeof(ether->w_saddr);
  memset((void *) &ether->w_saddr, '\0', len);
  ether->w_saddr.sa_family = AF_UNIX;
  strncpy(ether->w_saddr.sa_data, ether->name, 14);
  for (k = 0; k < p_anz; k++) {
    ether->fd[k] = socket(AF_INET, SOCK_PACKET, protocol[k]);
    if (ether->fd[k] < 0) {
      errormsg(E_ALL, "open_ether","socket (%d): %m", k);
      return -1;
    }
    if (bind(ether->fd[k], &ether->w_saddr, len) < 0) {
      errormsg(E_ALL,"open_ether","bind(%s): %m", ether->name);
      return -1;
    }
  }

  if (debuglevel > 0) {
    fprintf(log_fp,"using %s, file descriptors %d, %d, %d, %d\n", ether->name,
            ether->fd[0], ether->fd[1], ether->fd[2], ether->fd[3]);
  }

  ether->fd_anz = 4;
  /* use first socket for writing */
  ether->w_fd = ether->fd[0];

  /* switch Etherdevice to promiscous mode */
  strcpy(ifr.ifr_name, ether->name);
  if (ioctl(ether->fd[0], SIOCGIFFLAGS, &ifr) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(SIOCGIFFLAGS, %s): %m", ether->name);
    return -1;
  }
  ifr.ifr_flags |= IFF_PROMISC;
  if (ioctl(ether->fd[0], SIOCSIFFLAGS, &ifr) < 0) {
    errormsg(E_ALL, "open_ether","ioctl(SIOCSIFFLAGS, %s): %m", ether->name);
    return -1;
  }

  /* define buffer size, this is independed from sockets and Etherdecives */
  ether->bufsize = BUFSIZE;

  if (debuglevel > 0) {
    fprintf(log_fp,"buffer size for %s is %d\n\n", ether->name,
            ether->bufsize);
  }

  /* reset slatistics */
  clear_stat(&ether->stat);

  return 0;
}


int close_ether(struct Ether *ether)
{
  struct ifreq ifr;
  
  /* switch Etherdevices back to non-promiscous mode */
  strcpy(ifr.ifr_name, ether->name);
  if (ioctl(ether->fd[0], SIOCGIFFLAGS, &ifr) < 0) {
    errormsg(E_ALL, "close_ether","ioctl(SIOCGIFFLAGS, %s): %m", ether->name);
    return -1;
  }
  ifr.ifr_flags &= ~IFF_PROMISC;
  if (ioctl(ether->fd[0], SIOCSIFFLAGS, &ifr) < 0) {
    errormsg(E_ALL, "close_ether","ioctl(SIOCSIFFLAGS, %s): %m", ether->name);
    return -1;
  }
  return 0;
}


int read_ipx_from_ether(struct Data_packet *packet, struct Ether *ether,
                        const u_short ether_idx, boolean *more_data)
{
  int from_len;
  size_t ipx_off;
  ssize_t len, rec;
  u_int frametype;
  char ipx_dest_node[6];
  struct sockaddr from;

  if (debuglevel > 1) {
    fprintf(log_fp,"\n");
  }
  
  /* we get one Ethernet frame for each read exactly */
  *more_data = FALSE;
  from_len = sizeof(from);
  rec = read(ether->fd[ether_idx], packet->buf, ether->bufsize);
  if (rec < 0) {
    errormsg(E_LOG,"read_ipx_from_ether", "%s: read: %m", ether->name);
    ether->stat.errors_r++;
    return -1;
  }
#ifdef TRACE_PACKET_TIME
  if (debuglevel > 1) {
    fprintf(log_fp,"ether read time: %ld msec\n", msec_from_start());
  }
#endif

  /* check size of Ethernet frame */
  if (rec < 60 || rec > 1514) {
    errormsg(E_LOG,"read_ipx_from_ether",
             "%s: wrong Ethernet length (%d bytes)", ether->name, rec);
    ether->stat.errors_r++;
    return -1;
  }

  /* distinguish and check type of frame */
  if ( ! get_ipx_to_ether_offset(packet->buf, &frametype, &ipx_off)) {
    errormsg(E_LOG,"read_ipx_from_ether","unknown ether frame");
    return -1;
  }
  if (! (frametype & ether->r_frametypes)) {
    errormsg(E_LOG,"read_ipx_from_ether","unconfigured ether frame");
    return -1;
  }
  packet->from_frametype = frametype;
  
  /* save Ether addresses */
  save_ether_addr(packet->buf, ipx_off);
  
  /* cut off Ethernet header (and minimum size padding) */
  packet->data = packet->buf + ipx_off;
  packet->len = get_ipx_length(packet->data);

  /* check size of IPX packet */
  len = rec - ipx_off;
  if (len != packet->len && len != 2*((packet->len + 1) / 2)) {
    if (rec == 60 && packet->len >= 30 && packet->len < len) {
      /* ok, due to minimum ethernet size padding */
    }
    else {
#ifndef BROKEN_INTERFACE
      errormsg(E_LOG,"read_ipx_from_ether",
               "ipx_length %d differ from packet_len %d", packet->len, len);
      fprintf(log_fp,"first 60 bytes:\n");
      fprint_as_hex_nl(log_fp, packet->buf, 60);
      fprintf(log_fp,"\n");
      return -1;
#endif
    }
  }

  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," [frametype ");
    log_frametypes(frametype);
    fprintf(log_fp,"]:\n");
    if (debuglevel > 2) {
      log_ether_header(packet->buf);
      log_ipx_header(packet->data);
      if (debuglevel > 3) {
	log_ipx_packet(packet);
      }
    }
  }

  /* check if the packet was not sent from myself */
  if (memcmp(packet->buf + 6, ether->mac, 6) == 0) {
    if (debuglevel > 1) {
      fprintf(log_fp,"packet dropped: packet was sent from myself\n");
    }
    return 0;
  }
  
  /* type is Ethernet and we always claim the first socket as the receiver */
  packet->from_com = com_ether;
  packet->from_fd = ether->fd[0];
  
  ether->stat.packets_r++;
  ether->stat.bytes_r += rec;
  if (rec > ether->stat.max_bytes_r) {
    ether->stat.max_bytes_r = rec;
  }
  
  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 frametype 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 frametype, trying default");
    log_frametypes(frametype);
    fprintf(log_fp,"\n");
  }

  /* build Ethernet 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 send it */
  len = sendto(ether->w_fd, ether_packet, ether_len, 0,
               (struct sockaddr *) &ether->w_saddr, sizeof(ether->w_saddr));
  if (len < 0) {
    errormsg(E_LOG,"write_ipx_to_ether","sendto to %s: %m", ether->name);
    ether->stat.errors_s++;
    return -1;
  }
#ifdef TRACE_PACKET_TIME
  if (debuglevel > 1) {
    fprintf(log_fp,"ether written time: %ld msec\n", msec_from_start());
  }
#endif
  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 [frametype ", 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;
}
