/*
 *	BIRD -- Unix Interface Scanning and Syncing
 *
 *	(c) 1998 Martin Mares <mj@ucw.cz>
 *
 *	Can be freely distributed and used under the terms of the GNU GPL.
 */

#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <errno.h>

#define LOCAL_DEBUG

#include "nest/bird.h"
#include "nest/iface.h"
#include "lib/timer.h"

#include "unix.h"

int if_scan_sock;
int if_scan_period = 60;

static timer *if_scan_timer;

static void
scan_ifs(struct ifreq *r, int cnt)
{
  struct iface i;
  char *err;
  unsigned fl;
  ip_addr netmask;
  int l;

  for (cnt /= sizeof(struct ifreq); cnt; cnt--, r++)
    {
      bzero(&i, sizeof(i));
      debug("%s\n", r->ifr_ifrn.ifrn_name);
      strncpy(i.name, r->ifr_ifrn.ifrn_name, sizeof(i.name) - 1);
      i.name[sizeof(i.name) - 1] = 0;
      get_sockaddr((struct sockaddr_in *) &r->ifr_addr, &i.ip, NULL);
      l = ipa_classify(i.ip);
      if (l < 0 || !(l & IADDR_HOST))
	{
	  log(L_ERR "%s: Invalid interface address", i.name);
	  goto bad;
	}
      if ((l & IADDR_SCOPE_MASK) == SCOPE_HOST)
	i.flags |= IF_LOOPBACK | IF_IGNORE;

      if (ioctl(if_scan_sock, SIOCGIFFLAGS, r) < 0)
	{
	  err = "SIOCGIFFLAGS";
	faulty:
	  log(L_ERR "%s(%s): %m", err, i.name);
	bad:
	  i.flags = (i.flags & ~IF_UP) | IF_ADMIN_DOWN;
	  continue;
	}
      fl = r->ifr_flags;
      if (fl & IFF_UP)
	i.flags |= IF_UP;

      if (ioctl(if_scan_sock, SIOCGIFNETMASK, r) < 0)
	{ err = "SIOCGIFNETMASK"; goto faulty; }
      get_sockaddr((struct sockaddr_in *) &r->ifr_addr, &netmask, NULL);
      l = ipa_mklen(netmask);
      if (l < 0 || l == 31)
	{
	  log(L_ERR "%s: Invalid netmask", i.name);
	  goto bad;
	}
      i.pxlen = l;

      if (fl & IFF_POINTOPOINT)
	{
	  i.flags |= IF_UNNUMBERED;
	  i.pxlen = BITS_PER_IP_ADDRESS;
	  if (ioctl(if_scan_sock, SIOCGIFDSTADDR, r) < 0)
	    { err = "SIOCGIFDSTADDR"; goto faulty; }
	  get_sockaddr((struct sockaddr_in *) &r->ifr_addr, &i.opposite, NULL);
	}
      if (fl & IFF_LOOPBACK)
	i.flags |= IF_LOOPBACK | IF_IGNORE;
#ifndef CONFIG_ALL_MULTICAST
      if (fl & IFF_MULTICAST)
#endif
	i.flags |= IF_MULTICAST;

      i.prefix = ipa_and(i.ip, ipa_mkmask(i.pxlen));
      if (i.pxlen < 32)
	{
	  i.brd = ipa_or(i.prefix, ipa_not(ipa_mkmask(i.pxlen)));
	  if (ipa_equal(i.ip, i.prefix) || ipa_equal(i.ip, i.brd))
	    {
	      log(L_ERR "%s: Using network or broadcast address for interface", i.name);
	      goto bad;
	    }
	  if (fl & IFF_BROADCAST)
	    i.flags |= IF_BROADCAST;
	  if (i.pxlen < 30)
	    i.flags |= IF_MULTIACCESS;
	  else
	    i.opposite = ipa_opposite(i.ip);
	}
      else
	i.brd = i.opposite;

      if (ioctl(if_scan_sock, SIOCGIFMTU, r) < 0)
	{ err = "SIOCGIFMTU"; goto faulty; }
      i.mtu = r->ifr_mtu;

#ifdef SIOCGIFINDEX
      if (ioctl(if_scan_sock, SIOCGIFINDEX, r) < 0)
	DBG("SIOCGIFINDEX failed: %m\n");
      else
	i.index = r->ifr_ifindex;
#endif

      if_update(&i);
    }
  if_end_update();
}

static void
scan_if(timer *t)
{
  struct ifconf ic;
  static int last_ifbuf_size = 4*sizeof(struct ifreq);
  int res;

  DBG("Scanning interfaces...\n");
  for(;;)
    {
      if (last_ifbuf_size)
	{
	  struct ifreq *r = alloca(last_ifbuf_size);
	  ic.ifc_ifcu.ifcu_req = r;
	  ic.ifc_len = last_ifbuf_size;
	  res = ioctl(if_scan_sock, SIOCGIFCONF, &ic);
	  if (res < 0 && errno != EFAULT)
	    die("SIOCCGIFCONF: %m");
	  if (res < last_ifbuf_size)
	    {
	      scan_ifs(r, ic.ifc_len);
	      break;
	    }
	}
#ifdef CLEAN_WAY_WORKING_ONLY_ON_LINUX_2_1	/* FIXME */
      ic.ifc_req = NULL;
      ic.ifc_len = 999999999;
      if (ioctl(if_scan_sock, SIOCGIFCONF, &ic) < 0)
	die("SIOCIFCONF: %m");
      ic.ifc_len += sizeof(struct ifreq);
      if (last_ifbuf_size < ic.ifc_len)
	{
	  last_ifbuf_size = ic.ifc_len;
	  DBG("Increased ifconf buffer size to %d\n", last_ifbuf_size);
	}
#else
      last_ifbuf_size *= 2;
      DBG("Increased ifconf buffer size to %d\n", last_ifbuf_size);
#endif
    }
}

void
scan_if_init(void)
{
  if_scan_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
  DBG("Using socket %d for interface and route scanning\n", if_scan_sock);
  if (if_scan_sock < 0)
    die("Cannot create scanning socket: %m");
  scan_if(NULL);
  if_scan_timer = tm_new(&root_pool);
  if_scan_timer->hook = scan_if;
  if_scan_timer->recurrent = if_scan_period;
  tm_start(if_scan_timer, if_scan_period);
}