/* arp.c - manipulate the system ARP cache
 *
 * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
 * Copyright 2014 Kyungwan Han <asura321@gamil.com>
 * No Standard 

USE_ARP(NEWTOY(arp, "vi:nDsdap:A:H:[+Ap][!sd]", TOYFLAG_USR|TOYFLAG_BIN))

config ARP
  bool "arp"
  default n
  help
    Usage: arp 
    [-vn] [-H HWTYPE] [-i IF] -a [HOSTNAME]
    [-v]              [-i IF] -d HOSTNAME [pub]
    [-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [temp]
    [-v]  [-H HWTYPE] [-i IF] -s HOSTNAME HWADDR [netmask MASK] pub
    [-v]  [-H HWTYPE] [-i IF] -Ds HOSTNAME IFACE [netmask MASK] pub

    Manipulate ARP cache

    -a    Display (all) hosts
    -s    Set new ARP entry
    -d    Delete a specified entry
    -v    Verbose
    -n    Don't resolve names
    -i IF Network interface
    -D    Read <hwaddr> from given device
    -A,-p AF  Protocol family
    -H    HWTYPE Hardware address type

*/

#define FOR_arp
#include "toys.h"
#include <net/if_arp.h>

GLOBALS(
    char *hw_type;
    char *af_type_A;
    char *af_type_p;
    char *interface;
    
    int sockfd;
    char *device;
)

struct arpreq req; //Global request structure 

struct type {
  char *name;
  int val;
};

struct type hwtype[] = {
  {"ether", ARPHRD_ETHER }, 
  {"loop" ,ARPHRD_LOOPBACK},
  {"ppp" ,ARPHRD_PPP},
  {"infiniband" ,ARPHRD_INFINIBAND},
  {NULL, -1},
};

struct type aftype[] = {
  {"inet", AF_INET }, 
  {"inet6" ,AF_INET6},
  {"unspec" ,AF_UNSPEC},
  {NULL, -1},
};

struct type flag_type[] = {
  {"PERM", ATF_PERM }, 
  {"PUB" ,ATF_PUBL},
  {"DONTPUB" ,ATF_DONTPUB},
  {"TRAIL" ,ATF_USETRAILERS},
  {NULL, -1},
};

static int get_index(struct type arr[], char *name)
{
  int i;
  
  for (i = 0; arr[i].name; i++) 
    if (!strcmp(arr[i].name, name)) break;
  return arr[i].val;
}


void get_hw_add(char *hw_addr, char *ptr) 
{
  char *p = ptr, *hw = hw_addr;

  while (*hw_addr && (p-ptr) < 6) {
    int val, len = 0;

    if (*hw_addr == ':') hw_addr++;
    sscanf(hw_addr, "%2x%n", &val, &len);
    if (!len || len > 2) break;
    hw_addr += len;
    *p++ = val;
  }

  if ((p-ptr) != 6 || *hw_addr)
    error_exit("bad hw addr '%s'", hw);
}

static void resolve_host(char *host, struct sockaddr *sa)
{
  struct addrinfo hints, *res = NULL;
  int ret;

  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  if ((ret = getaddrinfo(host, NULL, &hints, &res))) 
    perror_exit("%s", gai_strerror(ret));

  memcpy(sa, res->ai_addr, res->ai_addrlen);
  freeaddrinfo(res);
}

static void check_flags(int *i, char** argv)
{
  struct sockaddr sa;
  int flag = *i, j;
  struct flags {
    char *name;
    int or, flag;
  } f[] = {
    {"pub",  1 ,ATF_PUBL},
    {"priv", 0 ,~ATF_PUBL},
    {"trail", 1, ATF_USETRAILERS},
    {"temp", 0, ~ATF_PERM},
    {"dontpub",1, ATF_DONTPUB},
  };
  
  for (;*argv; argv++) {
    for (j = 0;  j < ARRAY_LEN(f); j++) { 
      if (!strcmp(*argv, f[j].name)) {
        (f[j].or) ?(flag |= f[j].flag):(flag &= f[j].flag);
        break;
      }
    }
    if (j > 4 && !strcmp(*argv, "netmask")) {
      if (!*++argv) error_exit("NULL netmask");
      if (strcmp(*argv, "255.255.255.255")) {
        resolve_host(toys.optargs[0], &sa);
        memcpy(&req.arp_netmask, &sa, sizeof(sa));
        flag |= ATF_NETMASK;
      } else argv++; 
    } else if (j > 4 && !strcmp(*argv, "dev")) {
      if (!*++argv) error_exit("NULL dev");
      TT.device = *argv;
    } else if (j > 4) error_exit("invalid arg");
  }
  *i = flag;
}

static int set_entry(void) 
{
  int flags = 0;
  
  if (!toys.optargs[1]) error_exit("bad syntax");

  if (!(toys.optflags & FLAG_D)) get_hw_add(toys.optargs[1], (char*)&req.arp_ha.sa_data);
  else {
    struct ifreq ifre;

    xstrncpy(ifre.ifr_name, toys.optargs[1], IFNAMSIZ);
    xioctl(TT.sockfd, SIOCGIFHWADDR, &ifre);
    if ((toys.optflags & FLAG_H) && (ifre.ifr_hwaddr.sa_family != ARPHRD_ETHER)) 
      error_exit("protocol type mismatch");
    memcpy(&req.arp_ha, &(ifre.ifr_hwaddr), sizeof(req.arp_ha));
  }

  flags = ATF_PERM | ATF_COM;
  if (toys.optargs[2]) check_flags(&flags, (toys.optargs+2));
  req.arp_flags = flags;
  xstrncpy(req.arp_dev, TT.device, sizeof(req.arp_dev));
  xioctl(TT.sockfd, SIOCSARP, &req);
  
  if (toys.optflags & FLAG_v) xprintf("Entry set for %s\n", toys.optargs[0]);
  return 0;
}

static int ip_to_host(struct sockaddr *sa, int flag) 
{
  int status = 0;
  char hbuf[NI_MAXHOST] = {0,}, sbuf[NI_MAXSERV] = {0,}; 
  socklen_t len = sizeof(struct sockaddr_in6);
  
  *toybuf = 0;
  if (!(status = getnameinfo(sa, len, hbuf, sizeof(hbuf), sbuf, 
          sizeof(sbuf), flag))) {
    strcpy(toybuf, hbuf);
    return 0;
  }
  return 1;
}

static int delete_entry(void)
{
  int flags;
  
  flags = ATF_PERM;
  if (toys.optargs[1]) check_flags(&flags, (toys.optargs+1));
  req.arp_flags = flags;
  xstrncpy(req.arp_dev, TT.device, sizeof(req.arp_dev));
  xioctl(TT.sockfd, SIOCDARP, &req);
  
  if (toys.optflags & FLAG_v) xprintf("Delete entry for  %s\n", toys.optargs[0]);
  return 0;
}

void arp_main(void)
{
  struct sockaddr sa;
  char ip[128], hw_addr[128], mask[12], dev[128], *host_ip = NULL, *buf;
  int h_type, type, flag, i, fd, entries = 0, disp = 0;

  TT.device = "";
  memset(&sa, 0, sizeof(sa));
  memset(&req, 0, sizeof(req));
  TT.sockfd = xsocket(AF_INET, SOCK_STREAM, 0);

  if ((toys.optflags & FLAG_A) || (toys.optflags & FLAG_p)) {
    if ((type = get_index(aftype, 
            (TT.af_type_A)?TT.af_type_A:TT.af_type_p)) != AF_INET) 
      error_exit((type != -1)?"only inet supported by kernel":"unknown family");
  } 

  req.arp_ha.sa_family = ARPHRD_ETHER;
  if (toys.optflags & FLAG_H) {
    if ((type = get_index(hwtype, TT.hw_type)) != ARPHRD_ETHER) 
      error_exit((type != -1)?"h/w type not supported":"unknown h/w type");
    req.arp_ha.sa_family = type;
  }

  if (((toys.optflags & FLAG_s) || toys.optflags & FLAG_d)) { 
    if (!toys.optargs[0]) error_exit("host name req");
    resolve_host(toys.optargs[0], &sa);
    memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr));
  }

  if ((toys.optflags & FLAG_s) && !set_entry()) return;
  if ((toys.optflags & FLAG_d) && !delete_entry()) return; 

  //show arp chache
  fd = xopenro("/proc/net/arp");
  buf = get_line(fd);
  free(buf); //skip first line

  if (toys.optargs[0]) {
    resolve_host(toys.optargs[0], &sa);
    ip_to_host(&sa, NI_NUMERICHOST);
    host_ip = xstrdup(toybuf);
  }

  while ((buf = get_line(fd))) {
    char *host_name = "?";
    
    if ((sscanf(buf, "%s 0x%x 0x%x %s %s %s\n", ip,
        &h_type, &flag, hw_addr, mask, dev )) != 6) break;
    entries++;
    if (((toys.optflags & FLAG_H) && (get_index(hwtype, TT.hw_type) != h_type))
     || ((toys.optflags & FLAG_i) && strcmp(TT.interface, dev))
     || (toys.optargs[0] && strcmp(host_ip, ip))) {
      free(buf);
      continue;
    }

    resolve_host(buf, &sa);
    if (!(toys.optflags & FLAG_n)) { 
      if (!ip_to_host(&sa, NI_NAMEREQD)) host_name = toybuf;
    } else ip_to_host(&sa, NI_NUMERICHOST);
    
    disp++;
    printf("%s (%s) at" , host_name, ip);

    for (i = 0; hwtype[i].name; i++) 
      if (hwtype[i].val & h_type) break;
    if (!hwtype[i].name) error_exit("unknown h/w type");

    if (!(flag & ATF_COM)) {
      if ((flag & ATF_PUBL)) printf(" *");
      else printf(" <incomplete>");
    } else printf(" %s [%s]", hw_addr, hwtype[i].name);

    if (flag & ATF_NETMASK) printf("netmask %s ", mask);

    for (i = 0; flag_type[i].name; i++) 
      if (flag_type[i].val & flag) printf(" %s", flag_type[i].name);

    printf(" on %s\n", dev);
    free(buf);
  }
  
  if (toys.optflags & FLAG_v) 
    xprintf("Entries: %d\tSkipped: %d\tFound: %d\n",
        entries, entries - disp, disp);
  if (!disp) xprintf("No Match found in %d entries\n", entries);
  
  if (CFG_TOYBOX_FREE) {
    free(host_ip);
    free(buf);
    xclose(fd);
  }
}