view interface.c @ 0:49aca2b065f8 draft

Add dhcpcd-3 re-write
author Roy Marples <roy@marples.name>
date Mon, 27 Nov 2006 20:23:22 +0000
parents
children 1405eec5277b
line wrap: on
line source

/*
 * dhcpcd - DHCP client daemon -
 * Copyright (C) 2006 Roy Marples <uberlord@gentoo.org>
 * 
 * dhcpcd is an RFC2131 compliant DHCP client daemon.
 *
 * This 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.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include <arpa/inet.h>

/* Netlink suff */
#ifdef __linux__ 
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netinet/ether.h>
#include <netpacket/packet.h>
#else
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/in.h>
#endif /* __linux__ */

#include <errno.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "common.h"
#include "interface.h"
#include "logger.h"
#include "pathnames.h"

#ifndef IFF_NOTRAILERS
#define	IFF_NOTRAILERS 0
#endif

void free_address (address_t *addresses)
{
  if (!addresses)
    return;

  address_t *p = addresses;
  address_t *n = NULL;

  while (p)
    {
      n = p->next;
      free (p);
      p = n;
    }
}

void free_route (route_t *routes)
{
  if (!routes)
    return;

  route_t *p = routes;
  route_t *n = NULL;

  while (p)
    {
      n = p->next;
      free (p);
      p = n;
    }
}

interface_t *read_interface (const char *ifname, int metric)
{
  if (! ifname)
    return NULL;

  int s;
  struct ifreq ifr;
  interface_t *iface;
  unsigned char hwaddr[ETHER_ADDR_LEN];

  struct ifaddrs *ifap;
  struct ifaddrs *p;
  unsigned int flags;

  if (getifaddrs (&ifap) != 0)
    return NULL;

  for (p = ifap; p; p = p->ifa_next)
    {
      if (strcmp (p->ifa_name, ifname) != 0)
	continue;
#ifdef __linux__
      struct sockaddr_ll *sll = (struct sockaddr_ll*) p->ifa_addr;
      if (p->ifa_addr->sa_family != AF_PACKET
	  || sll->sll_hatype != ARPHRD_ETHER)
#else
	struct sockaddr_dl *sdl = (struct sockaddr_dl *) p->ifa_addr;
      if (p->ifa_addr->sa_family != AF_LINK || sdl->sdl_type != IFT_ETHER)
#endif
	{
	  logger (LOG_ERR, "not Ethernet");
	  freeifaddrs (ifap);
	  return NULL;
	}

      flags = p->ifa_flags;
#ifdef __linux__
      memcpy (hwaddr, sll->sll_addr, ETHER_ADDR_LEN);
#else
      memcpy (hwaddr, sdl->sdl_data + sdl->sdl_nlen, ETHER_ADDR_LEN);
#endif
      break;
    }
  freeifaddrs (ifap);

  if (!p)
    {
      logger (LOG_ERR, "could not find interface %s", ifname);
      return NULL;
    }

  memset (&ifr, 0, sizeof (struct ifreq));
  strncpy (ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
      logger (LOG_ERR, "socket: %s", strerror (errno));
      return NULL;
    }

  if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0)
    {
      logger (LOG_ERR, "ioctl SIOCGIFFLAGS: %s", strerror (errno));
      close (s);
      return NULL;
    }

  ifr.ifr_flags |= IFF_UP | IFF_BROADCAST | IFF_NOTRAILERS | IFF_RUNNING;
  if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0)
    {
      logger (LOG_ERR, "ioctl SIOCSIFFLAGS: %s", strerror (errno));
      close (s);
      return NULL;
    }

#ifndef __linux__
  ifr.ifr_metric = metric;
  if (ioctl(s, SIOCSIFMETRIC, &ifr) < 0)
    {
      logger (LOG_ERR, "ioctl SIOCSIFMETRIC: %s", strerror (errno));
      close (s);
      return NULL;
    }
#endif

  close (s);

  iface = xmalloc (sizeof (interface_t));
  memset (iface, 0, sizeof (interface_t));
  strncpy (iface->name, ifname, IF_NAMESIZE);
  snprintf (iface->infofile, PATH_MAX, INFOFILE, ifname);
  memcpy (&iface->ethernet_address, &hwaddr, ETHER_ADDR_LEN);

  iface->arpable = ! (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK));

  logger (LOG_INFO, "ethernet address = %s",
	  ether_ntoa (&iface->ethernet_address));

  /* 0 is a valid fd, so init to -1 */
  iface->fd = -1;

  return iface;
}

#ifdef __FreeBSD__
static int do_address (const char *ifname, struct in_addr address,
		       struct in_addr netmask, struct in_addr broadcast, int del)
{
  if (! ifname)
    return -1;

  int s;
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
      logger (LOG_ERR, "socket: %s", strerror (errno));
      return -1;
    }

  struct ifaliasreq ifa;
  memset (&ifa, 0, sizeof (ifa));
  strcpy (ifa.ifra_name, ifname);

#define ADDADDR(_var, _addr) \
    { \
      struct sockaddr_in *_sin = (struct sockaddr_in *) &_var; \
      _sin->sin_family = AF_INET; \
      _sin->sin_len = sizeof (struct sockaddr_in); \
      memcpy (&_sin->sin_addr, &_addr, sizeof (struct in_addr)); \
    }

  ADDADDR (ifa.ifra_addr, address);
  if (! del)
    {
      ADDADDR (ifa.ifra_mask, netmask);
      ADDADDR (ifa.ifra_broadaddr, broadcast);
    }

#undef ADDADDR

  if (ioctl (s, del ? SIOCDIFADDR : SIOCAIFADDR, &ifa) == -1)
    {
      logger (LOG_ERR, "ioctl %s: %s", del ? "SIOCDIFADDR" : "SIOCAIFADDR",
	      strerror (errno));
      close (s);
      return -1;
    }

  close (s);
  return 0;
}

static int do_route (const char *ifname,
		     struct in_addr destination,
		     struct in_addr netmask,
		     struct in_addr gateway,
		     int metric,
		     int change, int del)
{
  if (! ifname)
    return -1;

  char *destd = strdup (inet_ntoa (destination));
  char *gend = strdup (inet_ntoa (netmask));
  logger (LOG_INFO, "%s route to %s (%s) via %s",
	  change ? "changing" : del ? "removing" : "adding",
	  destd, gend, inet_ntoa(gateway));
  if (destd)
    free (destd);
  if (gend)
    free (gend);

  int s;
  if ((s = socket(PF_ROUTE, SOCK_RAW, 0)) < 0) 
    {
      logger (LOG_ERR, "socket: %s", strerror (errno));
      return -1;
    }

  struct rtm
    {
      struct rt_msghdr hdr;
      struct sockaddr_in destination;
      struct sockaddr_in gateway;
      struct sockaddr_in netmask;
    } rtm;
  memset (&rtm, 0, sizeof (struct rtm));

  rtm.hdr.rtm_version = RTM_VERSION;
  static int seq;
  rtm.hdr.rtm_seq = ++seq;
  rtm.hdr.rtm_type = change ? RTM_CHANGE : del ? RTM_DELETE : RTM_ADD;

  rtm.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
  if (netmask.s_addr == 0xffffffff)
    rtm.hdr.rtm_flags |= RTF_HOST;

  rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;

#define ADDADDR(_var, _addr) \
  _var.sin_family = AF_INET; \
  _var.sin_len = sizeof (struct sockaddr_in); \
  memcpy (&_var.sin_addr, &_addr, sizeof (struct in_addr));

  ADDADDR (rtm.destination, destination);
  ADDADDR (rtm.gateway, gateway);
  ADDADDR (rtm.netmask, netmask);

#undef ADDADDR

  rtm.hdr.rtm_msglen = sizeof (rtm);

  if (write(s, &rtm, sizeof (rtm)) < 0)
    {
      /* Don't report error about routes already existing */
      if (errno != EEXIST)
	logger (LOG_ERR, "write: %s", strerror (errno));
      close (s);
      return -1;
    }

  close (s);
  return 0;
}

#elif __linux__
/* This netlink stuff is overly compex IMO.
   The BSD implementation is much cleaner and a lot less code.
   send_netlink handles the actual transmission so we can work out
   if there was an error or not.

   As always throughout this code, credit is due :)
   This blatently taken from libnetlink.c from the iproute2 package
   which is the only good source of netlink code.
   */
static int send_netlink(struct nlmsghdr *hdr)
{
  int s;
  if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) 
    {
      logger (LOG_ERR, "socket: %s", strerror (errno));
      return -1;
    }

  int mypid = getpid ();
  struct sockaddr_nl nl;
  memset (&nl, 0, sizeof (struct sockaddr_nl));
  nl.nl_family = AF_NETLINK;
  if (bind (s, (struct sockaddr *) &nl, sizeof (nl)) < 0)
    {
      logger (LOG_ERR, "bind: %s", strerror (errno));
      close (s);
      return -1;
    }

  struct iovec iov;
  memset (&iov, 0, sizeof (struct iovec));
  iov.iov_base = hdr;
  iov.iov_len = hdr->nlmsg_len;

  struct msghdr msg;
  memset (&msg, 0, sizeof (struct msghdr));
  msg.msg_name = &nl;
  msg.msg_namelen = sizeof (nl);
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  /* Request a reply */
  hdr->nlmsg_flags |= NLM_F_ACK;
  static int seq;
  hdr->nlmsg_seq = ++seq;

  if (sendmsg (s, &msg, 0) < 0)
    {
      logger (LOG_ERR, "write: %s", strerror (errno));
      close (s);
      return -1;
    }

  char buffer[16384];
  memset (&buffer, 0, sizeof (buffer));
  iov.iov_base = buffer;

  struct nlmsghdr *h;
  while (1) 
    {
      iov.iov_len = sizeof (buffer);
      int bytes = recvmsg(s, &msg, 0);

      if (bytes < 0)
	{
	  if (errno != EINTR)
	    logger (LOG_ERR, "overrun");
	  continue;
	}

      if (bytes == 0)
	{
	  logger (LOG_ERR, "EOF on netlink");
	  goto eexit;
	}

      if (msg.msg_namelen != sizeof (nl))
	{
	  logger (LOG_ERR, "sender address length == %d", msg.msg_namelen);
	  goto eexit;
	}

      for (h = (struct nlmsghdr *) buffer; bytes >= sizeof (*h); )
	{
	  int len = h->nlmsg_len;
	  int l = len - sizeof (*h);

	  if (l < 0 || len > bytes)
	    {
	      if (msg.msg_flags & MSG_TRUNC)
		logger (LOG_ERR, "truncated message");
	      else
		logger (LOG_ERR, "malformed message");
	      goto eexit;
	    }

	  if (nl.nl_pid != 0 ||
	      h->nlmsg_pid != mypid ||
	      h->nlmsg_seq != seq)
	    /* Message isn't for us, so skip it */
	    goto next;

	  /* We get an NLMSG_ERROR back with a code of zero for success */
	  if (h->nlmsg_type == NLMSG_ERROR)
	    {
	      struct nlmsgerr *err = (struct nlmsgerr *) NLMSG_DATA (h);
	      if (l < sizeof (struct nlmsgerr))
		logger (LOG_ERR, "truncated error message");
	      else
		{
		  errno = -err->error;
		  if (errno == 0)
		    {
		      close (s);
		      return 0;
		    }

		  /* Don't report on something already existing */
		  if (errno != EEXIST)
		    logger (LOG_ERR, "RTNETLINK answers: %s", strerror (errno));
		}
	      goto eexit;
	    }

	  logger (LOG_ERR, "unexpected reply");
next:
	  bytes -= NLMSG_ALIGN (len);
	  h = (struct nlmsghdr *) ((char *) h + NLMSG_ALIGN (len));
	}

      if (msg.msg_flags & MSG_TRUNC)
	{
	  logger (LOG_ERR, "message truncated");
	  continue;
	}

      if (bytes)
	{
	  logger (LOG_ERR, "remnant of size %d", bytes);
	  goto eexit;
	}
    }

eexit:
  close (s);
  return -1;
}

#define NLMSG_TAIL(nmsg) \
 ((struct rtattr *) (((unsigned char *) (nmsg)) \
		     + NLMSG_ALIGN((nmsg)->nlmsg_len)))
static int add_attr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
		      int alen)
{
  int len = RTA_LENGTH(alen);
  struct rtattr *rta;

  if (NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len) > maxlen)
    {
      logger (LOG_ERR, "add_attr_l: message exceeded bound of %d\n", maxlen);
      return -1;
    }

  rta = NLMSG_TAIL (n);
  rta->rta_type = type;
  rta->rta_len = len;
  memcpy (RTA_DATA (rta), data, alen);
  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + RTA_ALIGN (len);

  return 0;
}

static int add_attr_32(struct nlmsghdr *n, int maxlen, int type, uint32_t data)
{
  int len = RTA_LENGTH (sizeof (uint32_t));
  struct rtattr *rta;
  if (NLMSG_ALIGN (n->nlmsg_len) + len > maxlen)
    {
      logger (LOG_ERR, "add_attr32: message exceeded bound of %d\n", maxlen);
      return -1;
    }

  rta = NLMSG_TAIL (n);
  rta->rta_type = type;
  rta->rta_len = len;
  memcpy (RTA_DATA (rta), &data, sizeof (uint32_t));
  n->nlmsg_len = NLMSG_ALIGN (n->nlmsg_len) + len;

  return 0;
}


static int do_address(const char *ifname,
		      struct in_addr address, struct in_addr netmask,
		      struct in_addr broadcast, int del)
{
  if (!ifname)
    return -1;

  struct
    {
      struct nlmsghdr hdr;
      struct ifaddrmsg ifa;
      char buffer[256];
    }
  nlm;

  memset (&nlm, 0, sizeof (nlm));

  nlm.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct ifaddrmsg));
  nlm.hdr.nlmsg_flags = NLM_F_REQUEST;
  nlm.hdr.nlmsg_type = del ? RTM_DELADDR : RTM_NEWADDR;
  nlm.ifa.ifa_index = if_nametoindex (ifname);
  nlm.ifa.ifa_family = AF_INET;

  /* Store the netmask in the prefix */
  uint32_t mask = htonl (netmask.s_addr);
  while (mask)
    {
      nlm.ifa.ifa_prefixlen++;
      mask <<= 1;
    }

  add_attr_l (&nlm.hdr, sizeof (nlm), IFA_LOCAL, &address.s_addr,
	      sizeof (address.s_addr));
  if (! del)
    add_attr_l (&nlm.hdr, sizeof (nlm), IFA_BROADCAST, &broadcast.s_addr,
		sizeof (broadcast.s_addr));

  return send_netlink (&nlm.hdr);
}

static int do_route (const char *ifname,
		     struct in_addr destination,
		     struct in_addr netmask,
		     struct in_addr gateway,
		     int metric, int change, int del)
{
  if (! ifname)
    return -1;

  char *dstd = strdup (inet_ntoa (destination));
  char *gend = strdup (inet_ntoa (netmask));
  logger (LOG_INFO, "%s route to %s (%s) via %s, metric %d",
	  change ? "changing" : del ? "removing" : "adding",
	  dstd, gend, inet_ntoa (gateway), metric);
  if (dstd)
    free (dstd);
  if (gend)
    free (gend);

  struct
    {
      struct nlmsghdr hdr;
      struct rtmsg rt;
      char buffer[256];
    }
  nlm;
  memset (&nlm, 0, sizeof (nlm));

  nlm.hdr.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg));
  if (change)
    nlm.hdr.nlmsg_flags = NLM_F_REPLACE;
  else if (! del)
    nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL;
  nlm.hdr.nlmsg_flags |= NLM_F_REQUEST;
  nlm.hdr.nlmsg_type = del ? RTM_DELROUTE : RTM_NEWROUTE;
  nlm.rt.rtm_family = AF_INET;
  nlm.rt.rtm_table = RT_TABLE_MAIN;

  if (del)
    nlm.rt.rtm_scope = RT_SCOPE_NOWHERE;
  else
    {
      nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL;
      nlm.rt.rtm_protocol = RTPROT_BOOT;
      if (gateway.s_addr == 0)
	nlm.rt.rtm_scope = RT_SCOPE_LINK;
      else
	nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE;
      nlm.rt.rtm_type = RTN_UNICAST;
    }

  /* Store the netmask in the prefix */
  uint32_t mask = htonl (netmask.s_addr);
  while (mask)
    {
      nlm.rt.rtm_dst_len++;
      mask <<= 1;
    }

  add_attr_l (&nlm.hdr, sizeof (nlm), RTA_DST, &destination.s_addr,
	      sizeof (destination.s_addr));
  if (gateway.s_addr != 0)
    add_attr_l (&nlm.hdr, sizeof (nlm), RTA_GATEWAY, &gateway.s_addr,
		sizeof (gateway.s_addr));

  add_attr_32 (&nlm.hdr, sizeof (nlm), RTA_OIF, if_nametoindex (ifname));
  add_attr_32 (&nlm.hdr, sizeof (nlm), RTA_PRIORITY, metric);

  return send_netlink (&nlm.hdr);
}

#else
#error "Platform not supported!"
#error "We currently support BPF and Linux sockets."
#error "Other platforms may work using BPF. If yours does, please let me know"
#error "so I can add it to our list."
#endif


int add_address (const char *ifname, struct in_addr address,
		 struct in_addr netmask, struct in_addr broadcast)
{
  char *daddress = strdup (inet_ntoa (address));
  logger (LOG_INFO, "adding IP address %s netmask %s",
	  daddress, inet_ntoa (netmask));
  free (daddress);

  return (do_address (ifname, address, netmask, broadcast, 0));
}

int del_address (const char *ifname, struct in_addr address)
{
  logger (LOG_INFO, "deleting IP address %s", inet_ntoa (address));

  struct in_addr t;
  memset (&t, 0, sizeof (t));
  return (do_address (ifname, address, t, t, 1));
}

/* This should work on all platforms */
int flush_addresses (const char *ifname)
{
  if (! ifname)
    return -1;

  struct ifaddrs *ifap;
  struct ifaddrs *p;

  if (getifaddrs (&ifap) != 0)
    return -1;

  for (p = ifap; p; p = p->ifa_next)
    {
      if (strcmp (p->ifa_name, ifname) != 0)
	continue;

      struct sockaddr_in *sin = (struct sockaddr_in*) p->ifa_addr;
      if (sin->sin_family == AF_INET)
	del_address (ifname, sin->sin_addr);
    }
  freeifaddrs (ifap);

  return 0;
}

int add_route (const char *ifname, struct in_addr destination,
	       struct in_addr netmask, struct in_addr gateway, int metric)
{
  return (do_route (ifname, destination, netmask, gateway, metric, 0, 0));
}

int change_route (const char *ifname, struct in_addr destination,
		  struct in_addr netmask, struct in_addr gateway, int metric)
{
  return (do_route (ifname, destination, netmask, gateway, metric, 1, 0));
}

int del_route (const char *ifname, struct in_addr destination,
	       struct in_addr netmask, struct in_addr gateway, int metric)
{
  return (do_route (ifname, destination, netmask, gateway, metric, 0, 1));
}