/*
    GSK - a library to write servers
    Copyright (C) 1999-2000 Dave Benson

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

    Contact:
        daveb@ffem.org <Dave Benson>
*/

#include "gsknetworkinterface.h"
#include "config.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>

/* needed under solaris */
#define BSD_COMP

#if HAVE_NET_IF_H
#include <net/if.h>
#endif
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <errno.h>
#include <netdb.h>
#include <unistd.h>



static int
get_IPPROTO_IP ()
{
  static int proto = -1;
  if (proto < 0)
    {
      struct protoent *entry;
      entry = getprotobyname ("ip");
      if (entry == NULL)
	{
	  g_warning ("The ip protocol was not found in /etc/services...");
	  proto = 0;
	}
      else
	{
	  proto = entry->p_proto;
	}
    }
  return proto;
}

/**
 * gsk_network_interface_set_new:
 * @flags: constraints on the interfaces to return.  All the constraints
 * must be satisfied.
 *
 * Create a new list of interfaces, subject to the constraints given.
 *
 * Note that the constraints must all be satified, so
 * using GSK_NETWORK_INTERFACE_NO_LOOKBACK and GSK_NETWORK_INTERFACE_LOOKBACK
 * will always return an empty set.
 *
 * returns: a newly allocated list of interfaces that
 * must be freed with gsk_network_interface_set_destroy().
 */
GskNetworkInterfaceSet *
gsk_network_interface_set_new(GskNetworkInterfaceFlags  flags)
{
  GArray *ifreq_array;
  GArray *rv;
  int tmp_socket;
  guint i;
  tmp_socket = socket (AF_INET, SOCK_DGRAM, get_IPPROTO_IP ());
  if (tmp_socket < 0)
    {
      g_warning ("gsk_network_interface: error creating internal ns socket: %s",
		 g_strerror (errno));
      return NULL;
    }
  ifreq_array = g_array_new (FALSE, FALSE, sizeof (struct ifreq));
  g_array_set_size (ifreq_array, 16);
  for (;;)
    {
      struct ifconf all_interface_config;
      guint num_got;
      all_interface_config.ifc_len = ifreq_array->len * sizeof (struct ifreq);
      all_interface_config.ifc_buf = ifreq_array->data;
      if (ioctl (tmp_socket, SIOCGIFCONF, (char *) &all_interface_config) < 0)
	{ 
	  g_warning ("gsk_network_interface:"
		     "error getting interface configuration: %s",
		     g_strerror (errno));
	  close (tmp_socket);
	  g_array_free (ifreq_array, TRUE);
	  return NULL;
	}
      num_got = all_interface_config.ifc_len / sizeof (struct ifreq);
      if (num_got == ifreq_array->len)
	g_array_set_size (ifreq_array, ifreq_array->len * 2);
      else
	{
	  g_array_set_size (ifreq_array, num_got);
	  break;
	}
    }

  /* now query each of those interfaces. */
  rv = g_array_new (FALSE, FALSE, sizeof (GskNetworkInterface));
  for (i = 0; i < ifreq_array->len; i++)
    {
      struct ifreq *req_array = (struct ifreq *)(ifreq_array->data) + i;
      struct ifreq tmp_req;
      gboolean is_up;
      gboolean is_loopback;
      gboolean has_broadcast;
      gboolean has_multicast;
      gboolean is_p2p;
      guint if_flags;
      GskNetworkInterface interface;

      /* XXX: we don't at all no how to handle a generic interface. */
      /* XXX: is this an IPv6 problem? */
      if (req_array->ifr_addr.sa_family != AF_INET)
	continue;

      memcpy (tmp_req.ifr_name, req_array->ifr_name, sizeof (tmp_req.ifr_name));
      if (ioctl (tmp_socket, SIOCGIFFLAGS, (char *) &tmp_req) < 0) 
	{	
	  g_warning ("error getting information about interface %s",
		     tmp_req.ifr_name);
	  continue;
	}

      if_flags = tmp_req.ifr_flags;
      is_up = (if_flags & IFF_UP) == IFF_UP;
      is_loopback = (if_flags & IFF_LOOPBACK) == IFF_LOOPBACK;
      has_broadcast = (if_flags & IFF_BROADCAST) == IFF_BROADCAST;
      has_multicast = (if_flags & IFF_MULTICAST) == IFF_MULTICAST;
      is_p2p = (if_flags & IFF_POINTOPOINT) == IFF_POINTOPOINT;

      if ((flags & GSK_NETWORK_INTERFACE_UP) != 0 && !is_up)
	continue;
      if ((flags & GSK_NETWORK_INTERFACE_LOOPBACK) != 0 && !is_loopback)
	continue;
      if ((flags & GSK_NETWORK_INTERFACE_NON_LOOPBACK) != 0 && is_loopback)
	continue;
      if ((flags & GSK_NETWORK_INTERFACE_HAS_BROADCAST) != 0 && !has_broadcast)
	continue;
      if ((flags & GSK_NETWORK_INTERFACE_HAS_MULTICAST) != 0 && !has_multicast)
	continue;

      interface.supports_multicast = has_multicast ? 1 : 0;
      interface.is_promiscuous = (if_flags & IFF_PROMISC) ? 1 : 0;

      if (is_up)
	{
	  struct sockaddr *saddr;
	  if (ioctl (tmp_socket, SIOCGIFADDR, (char *) &tmp_req) < 0)
	    {
	      g_warning ("error getting the ip address for interface %s",
			 tmp_req.ifr_name);
	      continue;
	    }
	  saddr = &tmp_req.ifr_addr;
	  interface.address = gsk_socket_address_from_native (saddr, sizeof (*saddr));
	}
      else
	interface.address = NULL;

      interface.is_loopback = is_loopback ? 1 : 0;
#ifdef SIOCGIFHWADDR
      if (!interface.is_loopback)
	interface.hw_address = NULL;
      else
	{
	  if (ioctl (tmp_socket, SIOCGIFHWADDR, (char *) &tmp_req) < 0)
	    {
	      g_warning ("error getting the hardware address for interface %s",
			 tmp_req.ifr_name);
	      continue;
	    }
	  interface.hw_address = gsk_socket_address_ethernet_new ((guint8*)tmp_req.ifr_addr.sa_data);
	}
#else
      interface.hw_address = NULL;
#endif

      if (is_p2p)
	{
	  struct sockaddr *saddr;
	  if (ioctl (tmp_socket, SIOCGIFDSTADDR, (char *) &tmp_req) < 0)
	    {
	      g_warning ("error getting the ip address for interface %s",
			 tmp_req.ifr_name);
	      continue;
	    }
	  saddr = &tmp_req.ifr_addr;
	  interface.p2p_address = gsk_socket_address_from_native (saddr,
								  sizeof (struct sockaddr));
	}
      else
	interface.p2p_address = NULL;

      if (has_broadcast)
	{
	  struct sockaddr *saddr;
	  if (ioctl (tmp_socket, SIOCGIFBRDADDR, (char *) &tmp_req) < 0)
	    {
	      g_warning ("error getting the broadcast address for interface %s",
			 tmp_req.ifr_name);
	      continue;
	    }
	  saddr = &tmp_req.ifr_addr;
          interface.broadcast = gsk_socket_address_from_native (saddr,
								sizeof (struct sockaddr));
	}
      else
	interface.broadcast = NULL;

      interface.ifname = g_strdup (tmp_req.ifr_name);
      g_array_append_val (rv, interface);
    }

  close (tmp_socket);

  g_array_free (ifreq_array, TRUE);
  {
    GskNetworkInterfaceSet *set;
    set = g_new (GskNetworkInterfaceSet, 1);
    set->num_interfaces = rv->len;
    set->interfaces = (GskNetworkInterface *) rv->data;
    return set;
  }
}

/**
 * gsk_network_interface_set_destroy:
 * @set: the list of interfaces to destroy.
 *
 * Free the memory used by the list of interfaces.
 */
void
gsk_network_interface_set_destroy(GskNetworkInterfaceSet    *set)
{
  guint i;
  for (i = 0; i < set->num_interfaces; i++)
    {
      g_free ((char*) (set->interfaces[i].ifname));

      if (set->interfaces[i].address != NULL)
	g_object_unref (set->interfaces[i].address);
      if (set->interfaces[i].hw_address != NULL)
        g_object_unref (set->interfaces[i].hw_address);
      if (set->interfaces[i].p2p_address != NULL)
        g_object_unref (set->interfaces[i].p2p_address);
      if (set->interfaces[i].broadcast != NULL)
        g_object_unref (set->interfaces[i].broadcast);
    }
  g_free (set->interfaces);
  g_free (set);
}



syntax highlighted by Code2HTML, v. 0.9.1