/*
 * ifstat - InterFace STATistics
 * Copyright (c) 2001, Gaël Roualland <gael.roualland@dial.oleane.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: drivers.c,v 1.45 2003/11/22 01:27:51 gael Exp $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#if STDC_HEADERS
#include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
# ifndef HAVE_MEMCPY
#  define memcpy(d, s, n) bcopy ((s), (d), (n))
#  define memmove(d, s, n) bcopy ((s), (d), (n))
# endif
#endif
#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_NET_SOIOCTL_H
#include <net/soioctl.h>
#endif
#ifdef HAVE_SYS_MBUF_H
#include <sys/mbuf.h>
#endif
#ifdef HAVE_NET_ROUTE_H
#include <net/route.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif
#ifdef HAVE_NET_IF_MIB_H
#include <net/if_mib.h>
#endif
#ifdef HAVE_NET_IF_VAR_H
#include <net/if_var.h>
#endif
#ifdef HAVE_NET_IF_TYPES_H
#include <net/if_types.h>
#endif
#ifdef HAVE_NET_IF_DL_H
#include <net/if_dl.h>
#endif
#ifdef HAVE_SYS_DLPI_H
#include <sys/dlpi.h>
#endif
#ifdef HAVE_SYS_DLPI_EXT_H
#include <sys/dlpi_ext.h>
#endif
#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif
#ifdef HAVE_SYS_MIB_H
#include <sys/mib.h>
#endif
#ifdef HAVE_KSTAT_H
#include <kstat.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_KVM_H
#include <kvm.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#ifdef HAVE_NLIST_H
#include <nlist.h>
#endif
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include "ifstat.h"
#ifdef USE_SNMP  
#include "snmp.h"
#endif

#ifndef IFNAMSIZ
#define IFNAMSIZ 16
#endif

#ifdef USE_WIN32
#include <windows.h>
#include <iphlpapi.h>
#ifndef IFF_UP
#define IFF_UP       1
#endif
#ifndef IFF_LOOPBACK
#define IFF_LOOPBACK 2
#endif
#endif

static void examine_interface(struct ifstat_list *ifs, char *name,
			      int ifflags, int iftype) {
#ifdef IFF_LOOPBACK
  if ((ifflags & IFF_LOOPBACK) && !(ifs->flags & IFSTAT_LOOPBACK))
    return;
#endif
#ifdef IFF_UP
  if (!(ifflags & IFF_UP) && !(ifs->flags & IFSTAT_DOWN))
    return;
#endif  
#ifdef IFT_PFLOG
  /* assume PFLOG interfaces are loopbacks (on OpenBSD) */
  if (iftype == IFT_PFLOG && !(ifs->flags & IFSTAT_LOOPBACK))
    return;
#endif
  ifstat_add_interface(ifs, name, 0);
}

#ifdef USE_IOCTL

#ifdef USE_IFNAMEINDEX
static int ioctl_map_ifs(int sd,
			 int (*mapf)(int sd, struct ifreq *ifr, void *data),
			 void *mdata) {
  struct if_nameindex *iflist, *cur;
  struct ifreq ifr;
  
  if ((iflist = if_nameindex()) == NULL) {
    ifstat_perror("if_nameindex");
    return 0;
  }

  for(cur = iflist; cur->if_index != 0 && cur->if_name != NULL; cur++) {
    memcpy(ifr.ifr_name, cur->if_name, sizeof(ifr.ifr_name));
    ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0';
    if (!mapf(sd, &ifr, mdata))
      return 0;
  }
  if_freenameindex(iflist);
  return 1;
}
#else
static int ioctl_map_ifs(int sd,
			 int (*mapf)(int sd, struct ifreq *ifr, void *data),
			 void *mdata) {
  struct ifconf ifc;
  struct ifreq *ifr;
  int len, n, res = 0;
  char *buf;

#ifdef SIOCGIFNUM
  if (ioctl(sd, SIOCGIFNUM, &n) < 0) {
    ifstat_perror("ioctl(SIOCGIFNUM):");
    goto end;
  }
  n += 2;
#else
  n = 256; /* bad bad bad... */
#endif

  len = n * sizeof(struct ifreq);
  if ((buf = malloc(len)) == NULL) {
    ifstat_perror("malloc");
    return 0;
  }

  ifc.ifc_buf = buf;
  ifc.ifc_len = len;
  if (ioctl(sd, SIOCGIFCONF, (char *)&ifc) < 0) {
    ifstat_perror("ioctl(SIOCGIFCONF):");
    goto end;
  }

  n = 0;
  while (n < ifc.ifc_len) {
    ifr = (struct ifreq *) (buf + n);
#ifdef HAVE_SOCKADDR_SA_LEN    
    n += sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len;
#else
    n += sizeof(struct ifreq);
#endif
    if (!mapf(sd, ifr, mdata))
      goto end;
  }
  res = 1;
  
 end:
  free(buf);
  return res;
}
#endif

static int ioctl_map_scan(int sd, struct ifreq *ifr, void *data) {
  
  if (ioctl(sd, SIOCGIFFLAGS, (char *)ifr) != 0)
    return 1;
  examine_interface((struct ifstat_list *) data, ifr->ifr_name,
		    ifr->ifr_flags, 0);
  return 1;
}

static int ioctl_scan_interfaces(struct ifstat_driver *driver,
				 struct ifstat_list *ifs) {
  int sd;

  if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    ifstat_perror("socket");
    return 0;
  }

  ioctl_map_ifs(sd, &ioctl_map_scan, (void *) ifs);
  close(sd);
  
  return 1;
} 
#endif

#ifdef USE_KSTAT
static int get_kstat_long(kstat_t *ksp, char *name, unsigned long *value) {
  kstat_named_t *data;

  if ((data = kstat_data_lookup(ksp, name)) == NULL)
    return 0;
  switch (data->data_type) {
#ifdef KSTAT_DATA_INT32
    /* solaris 2.6 and over */    
  case KSTAT_DATA_INT32:
    *value = data->value.i32;
    break;
  case KSTAT_DATA_INT64:
    *value = data->value.i64;
    break;
  case KSTAT_DATA_UINT32:
    *value = data->value.ui32;
    break;
  case KSTAT_DATA_UINT64:
    *value = data->value.ui64;
    break;
#else
  case KSTAT_DATA_LONGLONG:
    *value = data->value.ll;
    break;
  case KSTAT_DATA_ULONGLONG:
    *value = data->value.ull;
    break;
  case KSTAT_DATA_LONG:
    *value = data->value.l;
    break;
  case KSTAT_DATA_ULONG:
    *value = data->value.ul;
    break;
#endif    
  default:
    return 0;
  }
  return 1;
}

static int kstat_open_driver(struct ifstat_driver *driver,
			     char *options) {
  kstat_ctl_t *kc;
  
  if ((kc = kstat_open()) == NULL) {
    ifstat_perror("kstat_open");
    return 0;
  }

  driver->data = (void *) kc;
  return 1;
}

static int kstat_get_stats(struct ifstat_driver *driver,
			   struct ifstat_list *ifs) {
  unsigned long bytesin, bytesout;
  struct ifstat_data *cur;
  kstat_ctl_t *kc = driver->data;
  kstat_t *ksp;

  for (cur = ifs->first; cur != NULL; cur = cur->next) {
    if (cur->flags & IFSTAT_TOTAL)
      continue;
    if ((ksp = kstat_lookup(kc, NULL, -1, cur->name)) == NULL ||
	ksp->ks_type != KSTAT_TYPE_NAMED)
      continue;
    if (kstat_read(kc, ksp, 0) >= 0 &&
	get_kstat_long(ksp, "obytes", &bytesout) &&
	get_kstat_long(ksp, "rbytes", &bytesin))
      ifstat_set_interface_stats(cur, bytesin, bytesout);
  }
  return 1;
}

static void kstat_close_driver(struct ifstat_driver *driver) {
  kstat_close(((kstat_ctl_t *) driver->data));
}
#endif

#ifdef USE_KVM
#ifndef HAVE_KVM
/* use internal emulation using open/read/nlist */
#ifndef _POSIX2_LINE_MAX
#define _POSIX2_LINE_MAX 2048
#endif

#ifndef _PATH_KMEM
#define _PATH_KMEM "/dev/kmem"
#endif

#ifndef _PATH_UNIX
#define _PATH_UNIX "/vmunix"
#endif

typedef struct _kvm_t {
  int fd;
  char *errbuf;
  char *execfile;
} kvm_t;

static void _kvm_error(char *errbuf, char *message) {
  strncpy(errbuf, message ? message : strerror(errno),
	  _POSIX2_LINE_MAX - 1);
  errbuf[_POSIX2_LINE_MAX - 1] = '\0';
}

static kvm_t *kvm_openfiles(const char *execfile, const char *corefile,
			    const char *swapfile, int flags, char *errbuf) {
  kvm_t *kd;

  if (swapfile != NULL) {
    _kvm_error(errbuf, "swap file option not supported");
    return NULL;
  }
  
  if ((kd = malloc(sizeof(kvm_t))) == NULL) {
    _kvm_error(errbuf, NULL);
    return NULL;
  }

  if ((kd->fd = open(corefile ? corefile : _PATH_KMEM, flags)) < 0) {
    _kvm_error(errbuf, NULL);
    free(kd);
    return NULL;
  }
  kd->execfile = execfile ? strdup(execfile) : NULL;
  kd->errbuf = errbuf;
  return kd;
}

static int kvm_nlist (kvm_t *kd, struct nlist *nl) {
  int count;
  
#ifdef HAVE_KNLIST
  if (kd->execfile == NULL) {
#ifdef HAVE_KNLIST_ARGS3 
    for(count = 0; nl[count].n_name != NULL; count++);
    count = knlist(nl, count, sizeof(struct nlist));
#else
    count = knlist(nl);
#endif
    if (count < 0)
      _kvm_error(kd->errbuf, "error looking up symbol in live kernel");
    return count;
  }
#endif  
  if ((count = nlist(kd->execfile ? kd->execfile : _PATH_UNIX, nl)) < 0)
    _kvm_error(kd->errbuf, "error looking up symbol in kernel file");
  return count;
}

#ifdef HAVE_READX
#define KOFFSET(x) ((x) & 0x7FFFFFFF)
#define KREAD(fd, buf, size, addr) readx((fd), (buf), (size), ((off_t) (addr)) < 0 ? 1 : 0)
#else
#define KOFFSET(x) (x)
#define KREAD(fd, buf, size, addr) read((fd), (buf), (size))
#endif

static ssize_t kvm_read(kvm_t *kd, unsigned long addr, void *buf, size_t nbytes) {
  ssize_t len;

  if (lseek(kd->fd, KOFFSET(addr), SEEK_SET) == -1) {
    _kvm_error(kd->errbuf, NULL);
    return -1;
  }
  if ((len = KREAD(kd->fd, buf, nbytes, addr)) < 0) {
    _kvm_error(kd->errbuf, NULL);
    return -1;
  }
  return len;
}

static int kvm_close(kvm_t *kd) {
  close(kd->fd);
  free(kd->execfile);
  free(kd);
  return 0;
}
#endif

struct kvm_driver_data {
  kvm_t *kvmfd;
  unsigned long ifnetaddr;
  char errbuf[_POSIX2_LINE_MAX + 1];
};

static int kvm_open_driver(struct ifstat_driver *driver,
			   char *options) {
  struct kvm_driver_data *data;
  struct nlist kvm_syms[2];
  unsigned long ifnetaddr;
  char *files[3] = { NULL /* execfile */,
		     NULL /* corefile */,
		     NULL /* swapfile */ };
  int i;
  
  if ((data = malloc(sizeof(struct kvm_driver_data))) == NULL) {
    ifstat_perror("malloc");
    return 0;
  }
  data->errbuf[0] = '\0';

  /* cut options : [execfile][,[corefile][,[swapfile]]] */
  i = 0;
  while (options != NULL && i < 3) {
    char *v = strchr(options, ',');

    if (v != NULL)
      *v++ = '\0';
    if (*options != '\0')
      files[i] = options;
    i++;
    options = v;
  }

  if ((data->kvmfd = kvm_openfiles(files[0], files[1], files[2],
				   O_RDONLY, data->errbuf)) == NULL) {
    ifstat_error("kvm_openfiles: %s", data->errbuf);
    return 0;
  }

  memset(&kvm_syms, 0, sizeof(kvm_syms));
  kvm_syms[0].n_name = "_ifnet";
  if (kvm_nlist(data->kvmfd, kvm_syms) < 0 ||
      kvm_syms[0].n_value == 0) {
    kvm_syms[0].n_name = "ifnet";
    if (kvm_nlist(data->kvmfd, kvm_syms) < 0) {
      ifstat_error("kvm_nlist: %s", data->errbuf);
      return 0;
    }
  }
  
  if (kvm_syms[0].n_value == 0) {
    ifstat_error("kvm: no _ifnet or ifnet symbol found");
    return 0;
  }
  
  if (kvm_read(data->kvmfd, (unsigned long) kvm_syms[0].n_value,
	       &ifnetaddr, sizeof(ifnetaddr)) < 0) {
    ifstat_error("kvm_read(ifnetaddr): %s", data->errbuf);
    return 0;
  }
  
  if (ifnetaddr == 0) {
    ifstat_error("kvm: ifnetaddr has null address.");
    return 0;
  }
  
  data->ifnetaddr = ifnetaddr;

  driver->data = (void *) data;
  return 1;
}

#ifdef HAVE_IFNET_IF_NEXT
#define IFNET_NEXT(ifnet) (ifnet).if_next
#else
#ifdef HAVE_IFNET_IF_LINK
#define if_list if_link
#endif
#ifndef TAILQ_NEXT
#define TAILQ_NEXT(elm, field)    ((elm)->field.tqe_next)
#endif
#define IFNET_NEXT(ifnet) TAILQ_NEXT((&ifnet), if_list)
#endif

static int kvm_map_ifs(struct ifstat_driver *driver,
		       int (*mapf)(char *name, struct ifnet *ifnet, void *data),
		       void *mdata) {
  struct kvm_driver_data *data = driver->data;
  unsigned long ifaddr;
  struct ifnet ifnet;
#ifndef HAVE_IFNET_IF_XNAME
  char ifname[IFNAMSIZ + 1];
#endif
  char interface[IFNAMSIZ + 10];

  for (ifaddr = data->ifnetaddr; ifaddr != 0;
       ifaddr = (unsigned long) IFNET_NEXT(ifnet)) {
    if (kvm_read(data->kvmfd, ifaddr, &ifnet, sizeof(ifnet)) < 0) {
      ifstat_error("kvm_read: %s", data->errbuf);
      return 0;
    }
#ifdef HAVE_IFNET_IF_XNAME
    memcpy(interface, ifnet.if_xname, sizeof(interface));
    interface[sizeof(interface) - 1] = '\0';
#else   
    if (kvm_read(data->kvmfd, (unsigned long) ifnet.if_name, &ifname, sizeof(ifname)) < 0) {
      ifstat_error("kvm_read: %s", data->errbuf);
      return 0;
    }
    ifname[sizeof(ifname) - 1] = '\0';
    sprintf(interface, "%s%d", ifname, ifnet.if_unit);
#endif

    if (!mapf(interface, &ifnet, mdata))
      return 0;
  }
  return 1;
}

static int kvm_map_stats(char *name, struct ifnet *ifnet, void *data) {
  struct ifstat_data *cur;
    
  if ((cur = ifstat_get_interface((struct ifstat_list *) data, name)) == NULL)
    return 1;
  ifstat_set_interface_stats(cur, ifnet->if_ibytes, ifnet->if_obytes);
  return 1;
}

static int kvm_get_stats(struct ifstat_driver *driver,
			 struct ifstat_list *ifs) {
  return kvm_map_ifs(driver, &kvm_map_stats, (void *) ifs);
}

static int kvm_map_scan(char *name, struct ifnet *ifnet, void *data) {
  examine_interface((struct ifstat_list *) data, name,
		    ifnet->if_flags, ifnet->if_type);
  return 1;
}

static int kvm_scan_interfaces(struct ifstat_driver *driver,
			       struct ifstat_list *ifs) {
  return kvm_map_ifs(driver, &kvm_map_scan, (void *) ifs);
} 

static void kvm_close_driver(struct ifstat_driver *driver) {
  kvm_close(((struct kvm_driver_data *) driver->data)->kvmfd);
}
#endif

#ifdef USE_IFMIB
static int get_ifcount() {
  int ifcount[] = {
    CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_SYSTEM, IFMIB_IFCOUNT
  };
  int count, size;
  
  size = sizeof(count);
  if (sysctl(ifcount, sizeof(ifcount) / sizeof(int), &count, &size, NULL, 0) < 0) {
    ifstat_perror("sysctl(net.link.generic.ifmib.ifcount)");
    return -1;
  }
  return count;
}

static int get_ifdata(int index, struct ifmibdata * ifmd) {
  int ifinfo[] = {
    CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_IFDATA, index, IFDATA_GENERAL
  };
  int size = sizeof(*ifmd);

  if (sysctl(ifinfo, sizeof(ifinfo) / sizeof(int), ifmd, &size, NULL, 0) < 0)
    return 0;

  return 1;
}

static int ifmib_scan_interfaces(struct ifstat_driver *driver,
				 struct ifstat_list *ifs) {
  int count, i;
  struct ifmibdata ifmd;
  
  if ((count = get_ifcount()) <= 0)
    return 0;

  for (i = 1; i <= count; i++) {
    if (!get_ifdata(i, &ifmd))
      continue;
    examine_interface(ifs, ifmd.ifmd_name, ifmd.ifmd_flags,
		      ifmd.ifmd_data.ifi_type);
  }
  return 1;
}

static int ifmib_get_stats(struct ifstat_driver *driver,
			   struct ifstat_list *ifs) {
  int count, i;
  struct ifmibdata ifmd;
  struct ifstat_data *cur;

  if (ifs->flags & IFSTAT_HASINDEX) { /* poll by index */
    for (cur = ifs->first; cur != NULL; cur = cur->next) {
      i = ifstat_get_interface_index(cur);
      if (i < 0 || !get_ifdata(i, &ifmd))
	continue;
      if (strcmp(ifstat_get_interface_name(cur), ifmd.ifmd_name))
	continue;
      ifstat_set_interface_stats(cur,
				 ifmd.ifmd_data.ifi_ibytes,
				 ifmd.ifmd_data.ifi_obytes);
      ifstat_set_interface_index(cur, i);
    }
    return 1;
  } 

  if ((count = get_ifcount()) <= 0)
    return 0;
  
  for (i = 1; i <= count; i++) {
    if (!get_ifdata(i, &ifmd))
      continue;
    if ((cur = ifstat_get_interface(ifs, ifmd.ifmd_name)) == NULL)
      continue;
    ifstat_set_interface_stats(cur,
			       ifmd.ifmd_data.ifi_ibytes,
			       ifmd.ifmd_data.ifi_obytes);
    ifstat_set_interface_index(cur, i);
  }
  return 1;
}
#endif

#ifdef USE_IFDATA
struct ifdata_driver_data {
  int sd;
#ifdef HAVE_IFREQ_IFR_DATA
  struct if_data ifd;
#else
  struct ifdatareq ifd;
#endif  
};

static int ifdata_open_driver(struct ifstat_driver *driver,
			      char *options) {
  struct ifdata_driver_data *data;

  if ((data = malloc(sizeof(struct ifdata_driver_data))) == NULL) {
    ifstat_perror("malloc");
    return 0;
  }

  if ((data->sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    ifstat_perror("socket");
    free(data);
    return 0;
  }
  
  driver->data = (void *) data;
  return 1;
}

static struct if_data *ifdata_get_data(struct ifdata_driver_data *data,
				       char *name) {
#ifdef HAVE_IFREQ_IFR_DATA  
  struct ifreq ifr;
  
  strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
  ifr.ifr_data = (caddr_t) &(data->ifd);
  if (ioctl(data->sd, SIOCGIFDATA, (char *) &ifr) < 0)
    return NULL;
  return &(data->ifd);
#else
  strncpy(data->ifd.ifd_name, name, sizeof(data->ifd.ifd_name));
  if (ioctl(data->sd, SIOCGIFDATA, (char *) &(data->ifd)) < 0)
    return NULL;
  return &(data->ifd.ifd_ifd);
#endif
}

struct ifdata_scan_data {
  struct ifstat_list *ifs;
  struct ifdata_driver_data *data;
};

static int ifdata_map_scan(int sd, struct ifreq *ifr, void *pdata) {
  struct ifdata_scan_data *sdata = pdata;
  struct if_data *ifd;
  
  if (ioctl(sd, SIOCGIFFLAGS, (char *)ifr) != 0)
    return 1;
  if ((ifd = ifdata_get_data(sdata->data, ifr->ifr_name)) == NULL)
    return 1;
  examine_interface(sdata->ifs, ifr->ifr_name,
		    ifr->ifr_flags, ifd->ifi_type);
  return 1;
}

static int ifdata_scan_interfaces(struct ifstat_driver *driver,
				  struct ifstat_list *ifs) {
  struct ifdata_driver_data *data = driver->data;
  struct ifdata_scan_data sdata = { ifs, data };

  return ioctl_map_ifs(data->sd, &ifdata_map_scan, &sdata);
} 

static int ifdata_get_stats(struct ifstat_driver *driver,
			    struct ifstat_list *ifs) {
  struct ifdata_driver_data *data = driver->data;
  struct if_data *ifd;
  struct ifstat_data *cur;
  
  for (cur = ifs->first; cur != NULL; cur = cur->next) {
    if (cur->flags & IFSTAT_TOTAL)
      continue;
    if ((ifd = ifdata_get_data(data, cur->name)) != NULL)
      ifstat_set_interface_stats(cur, ifd->ifi_ibytes, ifd->ifi_obytes);
  }
  return 1;
}

static void ifdata_close_driver(struct ifstat_driver *driver) {
  struct ifdata_driver_data *data = driver->data;

  if (data->sd >= 0)
    close(data->sd);
  free(data);
}
#endif

#ifdef USE_PROC
struct proc_driver_data {
  char *file;
  int checked;
};

static int proc_open_driver(struct ifstat_driver *driver,
			    char *options) {
  struct proc_driver_data *data;

  if ((data = malloc(sizeof(struct proc_driver_data))) == NULL) {
    ifstat_perror("malloc");
    return 0;
  }

  data->file = (options != NULL) ? strdup(options) : NULL;
  data->checked = 0;
  driver->data = (void *) data;

  return 1;
}

static void proc_close_driver(struct ifstat_driver *driver) {
  struct proc_driver_data *data = driver->data;

  if (data->file != NULL)
    free(data->file);
  free(data);
}

static int proc_get_stats(struct ifstat_driver *driver,
			  struct ifstat_list *ifs) {
  char buf[1024];
  FILE *f;
  char *iface, *stats;
  unsigned long bytesin, bytesout;
  struct ifstat_data *cur;
  struct proc_driver_data *data = driver->data;
  char *file;

  if (data->file != NULL)
    file = data->file;
  else
    file = PROC_FILE;

  if ((f = fopen(file, "r")) == NULL) {
    ifstat_error("can't open %s: %s", file, strerror(errno));
    return 0;
  }
  
  /* check first lines */
  if (fgets(buf, sizeof(buf), f) == NULL)
    goto badproc;
  if (!data->checked && strncmp(buf, "Inter-|", 7))
    goto badproc;
  if (fgets(buf, sizeof(buf), f) == NULL)
    goto badproc;
  if (!data->checked) {
    if (strncmp(buf, " face |by", 9))
      goto badproc;
    data->checked = 1;
  }

  while (fgets(buf, sizeof(buf), f) != NULL) {
    if ((stats = strchr(buf, ':')) == NULL)
      continue;
    *stats++ = '\0';
    iface = buf;
    while (*iface == ' ')
      iface++;
    if (*iface == '\0')
      continue;
    if (sscanf(stats, "%lu %*u %*u %*u %*u %*u %*u %*u %lu %*u", &bytesin, &bytesout) != 2)
      continue;
    
    if ((cur = ifstat_get_interface(ifs, iface)) != NULL)
      ifstat_set_interface_stats(cur, bytesin, bytesout);
  }
  fclose(f);
  return 1;

 badproc:
  fclose(f);
  ifstat_error("%s: unsupported format.", file);
  return 0;
}
#endif

#ifdef USE_ROUTE
struct route_driver_data {
  char *buf;
  size_t size;
};

#define DEFAULT_SIZE 16384

static int route_open_driver(struct ifstat_driver *driver,
			      char *options) {
  struct route_driver_data *data;

  if ((data = malloc(sizeof(struct route_driver_data))) == NULL) {
    ifstat_perror("malloc");
    return 0;
  }

  if ((data->buf = malloc(DEFAULT_SIZE)) < 0) {
    ifstat_perror("malloc");
    free(data);
    return 0;
  }
  data->size = DEFAULT_SIZE;

  driver->data = (void *) data;
  return 1;
}

static int route_map_ifs(struct ifstat_driver *driver,
		       int (*mapf)(char *name, struct if_msghdr *ifmsg, void *data),
		       void *mdata) {
  struct route_driver_data *data = driver->data;
  int iflist[] = {
    CTL_NET, PF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, 0 
  };
  struct if_msghdr *ifm;
  struct sockaddr_dl *dl;
  char *ptr;
  char ifname[IFNAMSIZ + 1];
  size_t len;

  if (data->size != 0) { /* try with current buf size */
    len = data->size;
    if (sysctl(iflist, sizeof(iflist) / sizeof(int),
	       data->buf, &len, NULL, 0) < 0) {
      if (errno != ENOMEM) {
	ifstat_perror("sysctl");
	return 0;
      }
      /* buffer too small */
      free (data->buf);
      data->size = 0;
    }
  } 
  if (data->size == 0) {
    /* ask for size */
    if (sysctl(iflist, sizeof(iflist) / sizeof(int),
	       NULL, &len, NULL, 0) < 0) {
      ifstat_perror("sysctl");
      return 0;
    }
    if ((data->buf = malloc(len)) < 0) {
      ifstat_perror("malloc");
      return 0;
    }
    if (sysctl(iflist, sizeof(iflist) / sizeof(int),
	       data->buf, &len, NULL, 0) < 0) {
      ifstat_perror("sysctl");
      return 0;
    }
  }

  /* browse interfaes */
  for (ptr = data->buf; ptr < data->buf + len; ptr += ifm->ifm_msglen) {
    ifm = (struct if_msghdr *) ptr;
    if (ifm->ifm_type != RTM_IFINFO)
      continue;
    if (ifm->ifm_msglen <= sizeof(struct if_msghdr)) /* no address */
      continue;
    dl = (struct sockaddr_dl *) (ptr + sizeof(struct if_msghdr));
    if (dl->sdl_family != AF_LINK)
      continue;
    if (dl->sdl_nlen > (sizeof(ifname) - 1))
      dl->sdl_nlen = sizeof(ifname) - 1;
    memcpy(ifname, dl->sdl_data, dl->sdl_nlen);
    ifname[dl->sdl_nlen] = '\0';

    if (!mapf(ifname, ifm, mdata))
      return 0;
  }
  return 1;
}

static int route_map_stats(char *name, struct if_msghdr *ifmsg, void *data) {
  struct ifstat_data *cur;
  
  if ((cur = ifstat_get_interface((struct ifstat_list *) data, name)) == NULL)
    return 1;
  ifstat_set_interface_stats(cur, ifmsg->ifm_data.ifi_ibytes,
			     ifmsg->ifm_data.ifi_obytes);
  return 1;
}

static int route_get_stats(struct ifstat_driver *driver,
			   struct ifstat_list *ifs) {
  return route_map_ifs(driver, &route_map_stats, ifs);
}

static int route_map_scan(char *name, struct if_msghdr *ifmsg, void *data) {
  examine_interface((struct ifstat_list *) data, name,
		    ifmsg->ifm_flags, ifmsg->ifm_data.ifi_type);
  return 1;
}

static int route_scan_interfaces(struct ifstat_driver *driver,
				 struct ifstat_list *ifs) {
  return route_map_ifs(driver, &route_map_scan, (void *) ifs);
} 

static void route_close_driver(struct ifstat_driver *driver) {
  struct route_driver_data *data = driver->data;

  if (data->buf != NULL)
    free(data->buf);
  free(data);
}
#endif

#ifdef USE_DLPI

#define DLPI_DEFBUF_LEN 1024
#define DLPI_NO_PPA -1
#define DLPI_DEVICE "/dev/dlpi"

struct dlpi_driver_data {
  int fd;
  unsigned int *buf;
  int maxlen;
  int ppa;
};

static int dlpi_open_driver(struct ifstat_driver *driver, char *options) {
  struct dlpi_driver_data *dlpi;

  if ((dlpi = malloc(sizeof(struct dlpi_driver_data))) == NULL) {
    ifstat_perror("malloc");
    return 0;
  }

  if ((dlpi->fd = open(options != NULL ? options : DLPI_DEVICE, O_RDWR)) < 0) {
    ifstat_perror("open");
    free(dlpi);
    return 0;
  }

  dlpi->maxlen = DLPI_DEFBUF_LEN;
  if ((dlpi->buf = malloc(dlpi->maxlen)) == NULL) {
    ifstat_perror("malloc");
    free(dlpi);
    return 0;

  }
  dlpi->ppa = DLPI_NO_PPA;

  driver->data = (void *) dlpi;
  return 1;
}

static int dlpi_req (struct dlpi_driver_data *dlpi, void *req, int reqlen,
		     int ackprim, void **ack, int *acklen) {
  struct strbuf ctlptr;
  int len, ret, flags;
  dl_error_ack_t *err_ack;
  
  ctlptr.maxlen = 0;
  ctlptr.len = reqlen;
  ctlptr.buf = req;
  
  if (putmsg(dlpi->fd, &ctlptr, NULL, 0) < 0) {
    ifstat_perror("putmsg");
    return 0;
  }
  
  ctlptr.maxlen = dlpi->maxlen;
  ctlptr.buf = (char *) dlpi->buf;
  ctlptr.len = 0;
  
  len = 0;
  flags = 0;
  while ((ret = getmsg(dlpi->fd, &ctlptr, NULL, &flags)) == MORECTL) {
    /* duplicate size of buf */
    dlpi->maxlen *= 2;
    if ((dlpi->buf = realloc(dlpi->buf, dlpi->maxlen)) == NULL) {
      ifstat_perror("malloc");
      return 0;
    }
    len += ctlptr.len;
    ctlptr.buf = (char *) dlpi->buf + len;
    ctlptr.maxlen = dlpi->maxlen - len;
    ctlptr.len = 0;
  }
  if (ret < 0) {
    ifstat_perror("getmsg");
    return 0;
  }
  len += ctlptr.len;
  
  err_ack = (dl_error_ack_t *) dlpi->buf;
  if (err_ack->dl_primitive != ackprim) {
    if (err_ack->dl_primitive == DL_ERROR_ACK) {
      errno = err_ack->dl_errno;
      ifstat_perror("dlpi");
    } else {
      ifstat_error("dlpi: unexpected ack type returned");
    }
    return 0;
  }

  if (ack != NULL)
    *ack = dlpi->buf;
  if (acklen != NULL)
    *acklen = len;
	
  return 1;
}

static int dlpi_attach(struct dlpi_driver_data *dlpi, int ppa) {
  dl_attach_req_t attach_req;
  dl_detach_req_t dettach_req;
  
  /* check if already attached */
  if (dlpi->ppa == ppa)
    return 1;
  
  /* else detach */
  if (dlpi->ppa != DLPI_NO_PPA) {
    dettach_req.dl_primitive = DL_DETACH_REQ;
    if (!dlpi_req(dlpi, &dettach_req, sizeof(dl_detach_req_t),
		  DL_OK_ACK, NULL, NULL))
      return 0;
    dlpi->ppa = DLPI_NO_PPA;
    if (ppa == DLPI_NO_PPA)
      return 1; /* we're done */
  }

  /* attach */
  attach_req.dl_primitive = DL_ATTACH_REQ;
  attach_req.dl_ppa = ppa;
  if (!dlpi_req(dlpi, &attach_req, sizeof(dl_attach_req_t),
		DL_OK_ACK, NULL, NULL))
    return 0;
  dlpi->ppa = ppa;
  
  return 1;
}

static int dlpi_get_ifmib(struct dlpi_driver_data *dlpi, int ppa, mib_ifEntry *mib) {
  dl_get_statistics_req_t stats_req;
  dl_get_statistics_ack_t *stats_ack;
  int len;

  /* first attach to PPA */
  if (!dlpi_attach(dlpi, ppa))
    return 0;

  /* grab stats */
  stats_req.dl_primitive = DL_GET_STATISTICS_REQ;
  if (!dlpi_req(dlpi, &stats_req, sizeof(dl_get_statistics_req_t), 
		DL_GET_STATISTICS_ACK, (void **) &stats_ack, &len))
    return 0;

  if (len < sizeof(dl_get_statistics_ack_t) ||
      stats_ack->dl_stat_offset < 0 || 
      stats_ack->dl_stat_offset + sizeof(mib_ifEntry) > len) {
    ifstat_error("dlpi: invalid data returned by stats ack");
  }

  memcpy(mib, (char *) stats_ack + stats_ack->dl_stat_offset,
	 sizeof(mib_ifEntry));

  return 1;
}

static int dlpi_map_ifs(struct dlpi_driver_data *dlpi,
			int (*mapf)(mib_ifEntry *mib, int ppa, char *name,
				    void *mdata),
			void *mdata) {
  dl_hp_ppa_req_t ppa_req;
  dl_hp_ppa_ack_t *ppa_ack;
  dl_hp_ppa_info_t *ppa_info;
  mib_ifEntry mib;
  void *buf;
  int len, i, ofs;
  char ifname[sizeof(ppa_info->dl_module_id_1) + 12];

  if (!dlpi_attach(dlpi, DLPI_NO_PPA))
    return 0;

  ppa_req.dl_primitive = DL_HP_PPA_REQ;
  if (!dlpi_req(dlpi, &ppa_req, sizeof(ppa_req),
		DL_HP_PPA_ACK, (void **) &ppa_ack, &len))
    return 0;

  if (len < sizeof(dl_hp_ppa_ack_t)) {
    ifstat_error("dlpi: short read for ppa ack");
    return 0;
  }

  /* copy buffer since used by later calls */
  if ((buf = malloc(len)) == NULL) {
    perror("malloc");
    return 0;
  }
  memcpy(buf, (void *) ppa_ack, len);
  ppa_ack = buf;
  
  /* browse interface list */
  ofs = ppa_ack->dl_offset;
  for(i = 0; i < ppa_ack->dl_count; i++) {
    if (ofs < 0 || ofs + sizeof(dl_hp_ppa_info_t) > len) {
      ifstat_error("dlpi: data returned by ppa ack exceeds data buffer");
      free(buf);
      return 0;
    }

    ppa_info = (dl_hp_ppa_info_t *) ((char *) ppa_ack + ofs);

    if (dlpi_get_ifmib(dlpi, ppa_info->dl_ppa, &mib)) {
      sprintf(ifname, "%s%d", ppa_info->dl_module_id_1, ppa_info->dl_instance_num);
      if (!mapf(&mib, ppa_info->dl_ppa, ifname, mdata)) {
	free(buf);
	return 0;
      }
    }
    
    ofs = ppa_ack->dl_offset + ppa_info->dl_next_offset;
  }

  free(buf);
  return 1;
}

static int dlpi_map_scan(mib_ifEntry *mib, int ppa, char *name,
			void *mdata) {
  examine_interface((struct ifstat_list *) mdata, name,
		    (mib->ifOper == 1 ? IFF_UP : 0) |
		    (mib->ifType == 24 ? IFF_LOOPBACK : 0), 0);
  return 1;    
}

static int dlpi_scan_interfaces(struct ifstat_driver *driver,
				struct ifstat_list *ifs) {
  return dlpi_map_ifs(driver->data, &dlpi_map_scan, (void *) ifs);
}

static int dlpi_map_stats(mib_ifEntry *mib, int ppa, char *name,
			  void *mdata) {
  struct ifstat_data *cur;

  if ((cur = ifstat_get_interface((struct ifstat_list *) mdata, name)) == NULL)
    return 1;
  ifstat_set_interface_stats(cur, mib->ifInOctets, mib->ifOutOctets);
  ifstat_set_interface_index(cur, ppa);
  return 1;
}

static int dlpi_get_stats(struct ifstat_driver *driver,
			  struct ifstat_list *ifs) {
  int i;
  struct ifstat_data *cur;
  mib_ifEntry mib;

  if (ifs->flags & IFSTAT_HASINDEX) { /* poll by index (ppa) */
    for (cur = ifs->first; cur != NULL; cur = cur->next) {
      i = ifstat_get_interface_index(cur);
      if (!dlpi_get_ifmib(driver->data, i, &mib))
	continue;
      ifstat_set_interface_stats(cur, mib.ifInOctets, mib.ifOutOctets);
      ifstat_set_interface_index(cur, i);
    }
    return 1;
  }

  return dlpi_map_ifs(driver->data, &dlpi_map_stats, (void *) ifs);
}    

void dlpi_close_driver(struct ifstat_driver *driver) {
  struct dlpi_driver_data *dlpi = driver->data;

  free(dlpi->buf);
  close(dlpi->fd);
  free(dlpi);
}
#endif

#ifdef USE_WIN32
struct win32_driver_data {
  void *buf;
  int len;
};

static int win32_open_driver(struct ifstat_driver *driver, char *options) {
  struct win32_driver_data *data;

  if ((data = malloc(sizeof(struct win32_driver_data))) == NULL) {
    ifstat_perror("malloc");
    return 0;
  }

  data->buf = NULL;
  data->len = 0;
  driver->data = (void *) data;
  return 1;
}

static int win32_getiftable(struct ifstat_driver *driver,
			    PMIB_IFTABLE *iftable) {
  struct win32_driver_data *data = driver->data;
  ULONG size;
  DWORD ret;

  size = data->len;
  while ((ret = GetIfTable((PMIB_IFTABLE) data->buf,
			   &size, 1)) == ERROR_INSUFFICIENT_BUFFER) {
    data->len = size * 2;
    if ((data->buf = realloc(data->buf, data->len)) == NULL) {
      perror("realloc");
      return 0;
    }
  }
  
  if (ret == NO_ERROR) {
    *iftable = (PMIB_IFTABLE) data->buf;
    return 1;
  }
  
  perror("GetIfTable");
  return 0;
}

static int win32_scan_interfaces(struct ifstat_driver *driver,
				 struct ifstat_list *ifs) {
  PMIB_IFTABLE iftable;
  DWORD i;
  
  if (!win32_getiftable(driver, &iftable))
    return 0;

  for (i = 0; i < iftable->dwNumEntries; i++) 
    examine_interface(ifs,
		      iftable->table[i].bDescr,
		      ((iftable->table[i].dwOperStatus ==
		       MIB_IF_OPER_STATUS_OPERATIONAL) ? IFF_UP : 0) |
		      ((iftable->table[i].dwType ==
		       MIB_IF_TYPE_LOOPBACK) ? IFF_LOOPBACK : 0), 0);

  return 1;
}

static int win32_get_stats(struct ifstat_driver *driver,
			   struct ifstat_list *ifs) {
  PMIB_IFTABLE iftable;
  DWORD i;
  struct ifstat_data *cur;

  if (!win32_getiftable(driver, &iftable))
    return 0;

  for (i = 0; i < iftable->dwNumEntries; i++) {
    if ((cur = ifstat_get_interface(ifs, iftable->table[i].bDescr)) != NULL)
      ifstat_set_interface_stats(cur,
				 (unsigned long)
				 iftable->table[i].dwInOctets,
				 (unsigned long)
				 iftable->table[i].dwOutOctets);
  }
  return 1;
}

void win32_close_driver(struct ifstat_driver *driver) {
  struct win32_driver_data *data = driver->data;
  
  if (data->buf != NULL)
    free(data->buf);
  free(data);
}
#endif 

static struct ifstat_driver drivers[] = {
#ifdef USE_KSTAT  
  { "kstat", &kstat_open_driver, &ioctl_scan_interfaces, &kstat_get_stats,
    &kstat_close_driver },
#endif
#ifdef USE_IFMIB  
  { "ifmib", NULL, &ifmib_scan_interfaces, &ifmib_get_stats, NULL },
#endif
#ifdef USE_IFDATA
  { "ifdata", &ifdata_open_driver, &ifdata_scan_interfaces,
    &ifdata_get_stats, &ifdata_close_driver },
#endif
#ifdef USE_ROUTE
  { "route", &route_open_driver, &route_scan_interfaces,
    &route_get_stats, &route_close_driver },
#endif  
#ifdef USE_KVM  
  { "kvm",  &kvm_open_driver, &kvm_scan_interfaces, &kvm_get_stats,
    &kvm_close_driver },
#endif
#ifdef USE_PROC  
  { "proc", &proc_open_driver, &ioctl_scan_interfaces, &proc_get_stats,
    &proc_close_driver },
#endif
#ifdef USE_DLPI
  { "dlpi", &dlpi_open_driver, &dlpi_scan_interfaces, &dlpi_get_stats,
    &dlpi_close_driver },
#endif
#ifdef USE_WIN32
  { "win32", &win32_open_driver, &win32_scan_interfaces,
    &win32_get_stats, &win32_close_driver },
#endif  
#ifdef USE_SNMP  
  { "snmp", &snmp_open_driver, &snmp_scan_interfaces, &snmp_get_stats,
    &snmp_close_driver },
#endif  
  { NULL } };
  
int ifstat_get_driver(char *name, struct ifstat_driver *driver) {
  int num = 0;
  
  if (name != NULL) 
    for (num = 0; drivers[num].name != NULL; num++)
      if (!strcasecmp(drivers[num].name, name))
	break;

  if (drivers[num].name == NULL)
    return 0;

  memcpy(driver, &(drivers[num]), sizeof(struct ifstat_driver));
  driver->data = NULL;
  return 1;
}

char* ifstat_list_drivers() {
  int num;
  int len = 0, pos = 0;
  char *res;
  
  for(num = 0; drivers[num].name != NULL; num++)
    len += strlen(drivers[num].name) + 2;

  if ((res = malloc(len + 1)) == NULL) {
    ifstat_perror("malloc");
    return NULL;
  }

  for(num = 0; drivers[num].name != NULL; num++) {
    if (num != 0) {
      memcpy(res + pos, ", ", 2);
      pos += 2;
    }
    len = strlen(drivers[num].name);
    memcpy(res + pos, drivers[num].name, len);
    pos += len;
  }
  res[pos] = '\0';
  return res;
}
