Logo Search packages:      
Sourcecode: avahi version File versions

iface-bsd.c

/* rcs tags go here */
/* Original author: Bruce M. Simpson <bms@FreeBSD.org> */

/***
  This file is part of avahi.
 
  avahi 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.1 of the
  License, or (at your option) any later version.
 
  avahi 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 avahi; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>

#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

#include <unistd.h>

#include <libdaemon/dlog.h>

#include <avahi-common/llist.h>
#include <avahi-common/malloc.h>

#include "iface.h"

#ifndef IN_LINKLOCAL
#define IN_LINKLOCAL(i) (((u_int32_t)(i) & (0xffff0000)) == (0xa9fe0000))
#endif

#ifndef elementsof
#define elementsof(array)     (sizeof(array)/sizeof(array[0]))
#endif

#ifndef so_set_nonblock
#define so_set_nonblock(s, val) \
      do {                                \
            int __flags;                        \
            __flags = fcntl((s), F_GETFL);            \
            if (__flags == -1)                  \
                  break;                        \
            if (val != 0)                       \
                  __flags |= O_NONBLOCK;        \
            else                          \
                  __flags &= ~O_NONBLOCK;       \
            (void)fcntl((s), F_SETFL, __flags); \
      } while (0)
#endif

#define MAX_RTMSG_SIZE 2048

struct rtm_dispinfo {
      u_char            *di_buf;
      ssize_t            di_buflen;
      ssize_t            di_len;
};

union rtmunion {
      struct rt_msghdr         rtm;
      struct if_msghdr         ifm;
      struct ifa_msghdr        ifam;
      struct ifma_msghdr             ifmam;
      struct if_announcemsghdr       ifan;
};
typedef union rtmunion rtmunion_t;

struct Address;
typedef struct Address Address;

struct Address {
      in_addr_t   address;
      AVAHI_LLIST_FIELDS(Address, addresses);
};

static int rtm_dispatch(void);
static int rtm_dispatch_newdeladdr(struct rtm_dispinfo *di);
static int rtm_dispatch_ifannounce(struct rtm_dispinfo *di);
static struct sockaddr *next_sa(struct sockaddr *sa);

static int fd = -1;
static int ifindex = -1;
static AVAHI_LLIST_HEAD(Address, addresses) = NULL;

int
iface_init(int idx)
{

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

      so_set_nonblock(fd, 1);

      ifindex = idx;

      return (fd);
}

int
iface_get_initial_state(State *state)
{
      int                mib[6];
      char              *buf;
      struct if_msghdr  *ifm;
      struct ifa_msghdr *ifam;
      char              *lim;
      char              *next;
      struct sockaddr         *sa;
      size_t                   len;
      int                naddrs;

      assert(state != NULL);
      assert(fd != -1);

      naddrs = 0;

      mib[0] = CTL_NET;
      mib[1] = PF_ROUTE;
      mib[2] = 0;
      mib[3] = 0;
      mib[4] = NET_RT_IFLIST;
      mib[5] = ifindex;

      if (sysctl(mib, elementsof(mib), NULL, &len, NULL, 0) != 0) {
            daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s",
                strerror(errno));
            return (-1);
      }

      buf = malloc(len);
      if (buf == NULL) {
            daemon_log(LOG_ERR, "malloc(%d): %s", len, strerror(errno));
            return (-1);
      }

      if (sysctl(mib, elementsof(mib), buf, &len, NULL, 0) != 0) {
            daemon_log(LOG_ERR, "sysctl(NET_RT_IFLIST): %s",
                strerror(errno));
            free(buf);
            return (-1);
      }

      lim = buf + len;
      for (next = buf; next < lim; next += ifm->ifm_msglen) {
            ifm = (struct if_msghdr *)next;
            if (ifm->ifm_type == RTM_NEWADDR) {
                  ifam = (struct ifa_msghdr *)next;
                  sa = (struct sockaddr *)(ifam + 1);
                  if (sa->sa_family != AF_INET)
                        continue;
                  ++naddrs;
            }
      }
      free(buf);

      *state = (naddrs > 0) ? STATE_SLEEPING : STATE_START;

      return (0);
}

int
iface_process(Event *event)
{
      int b;

      assert(fd != -1);

      b = !!addresses;

      if (rtm_dispatch() == -1)
            return (-1);

      if (b && !addresses)
            *event = EVENT_ROUTABLE_ADDR_UNCONFIGURED;
      else if (!b && addresses)
            *event = EVENT_ROUTABLE_ADDR_CONFIGURED;

      return (0);
}

void
iface_done(void)
{
      Address *a;

      if (fd != -1) {
            close(fd);
            fd = -1;
      }

      while ((a = addresses) != NULL) {
            AVAHI_LLIST_REMOVE(Address, addresses, addresses, a);
            avahi_free(a);
      }
}

/*
 * Dispatch kernel routing socket messages.
 */
static int
rtm_dispatch(void)
{
      struct msghdr mh;
      struct iovec iov[1];
      struct rt_msghdr *rtm;
      struct rtm_dispinfo *di;
      ssize_t len;
      int retval;

      di = malloc(sizeof(*di));
      if (di == NULL) {
            daemon_log(LOG_ERR, "malloc(%d): %s", sizeof(*di),
                strerror(errno));
            return (-1);
      }
      di->di_buflen = MAX_RTMSG_SIZE;
      di->di_buf = calloc(MAX_RTMSG_SIZE, 1);
      if (di->di_buf == NULL) {
            free(di);
            daemon_log(LOG_ERR, "calloc(%d): %s", MAX_RTMSG_SIZE,
                strerror(errno));
            return (-1);
      }

      memset(&mh, 0, sizeof(mh));
      iov[0].iov_base = di->di_buf;
      iov[0].iov_len = di->di_buflen;
      mh.msg_iov = iov;
      mh.msg_iovlen = 1;

      retval = 0;
      for (;;) {
            len = recvmsg(fd, &mh, MSG_DONTWAIT);
            if (len == -1) {
                  if (errno == EWOULDBLOCK)
                        break;
                  else {
                        daemon_log(LOG_ERR, "recvmsg(): %s",
                            strerror(errno));
                        retval = -1;
                        break;
                  }
            }

            rtm = (void *)di->di_buf;
            if (rtm->rtm_version != RTM_VERSION) {
                  daemon_log(LOG_ERR,
                      "unknown routing socket message (version %d)\n",
                      rtm->rtm_version);
                  /* this is non-fatal; just ignore it for now. */
                  continue;
            }

            switch (rtm->rtm_type) {
            case RTM_NEWADDR:
            case RTM_DELADDR:
                  retval = rtm_dispatch_newdeladdr(di);
                  break;
            case RTM_IFANNOUNCE:
                  retval = rtm_dispatch_ifannounce(di);
                  break;
            default:
                  break;
            }

            /*
             * If we got an error; assume our position on the call
             * stack is enclosed by a level-triggered event loop,
             * and signal the error condition.
             */
            if (retval != 0)
                  break;
      }
      free(di->di_buf);
      free(di);

      return (retval);
}

/* handle link coming or going away */
static int
rtm_dispatch_ifannounce(struct rtm_dispinfo *di)
{
      rtmunion_t *rtm = (void *)di->di_buf;

      assert(rtm->rtm.rtm_type == RTM_IFANNOUNCE);

      switch (rtm->ifan.ifan_what) {
      case IFAN_ARRIVAL:
            if (rtm->ifan.ifan_index == ifindex) {
                  daemon_log(LOG_ERR,
"RTM_IFANNOUNCE IFAN_ARRIVAL, for ifindex %d, which we already manage.",
                      ifindex);
                  return (-1);
            }
            break;
      case IFAN_DEPARTURE:
            if (rtm->ifan.ifan_index == ifindex) {
                  daemon_log(LOG_ERR, "Interface vanished.");
                  return (-1);
            }
            break;
      default:
            /* ignore */
            break;
      }

      return (0);
}

static struct sockaddr *
next_sa(struct sockaddr *sa)
{
      void        *p;
      size_t             sa_size;

      sa_size = sa->sa_len;
      if (sa_size < sizeof(u_long))
            sa_size = sizeof(u_long);
      p = ((char *)sa) + sa_size;

      return (struct sockaddr *)p;
}

/* handle address coming or going away */
static int
rtm_dispatch_newdeladdr(struct rtm_dispinfo *di)
{
      Address                 *ap;
      rtmunion_t        *rtm;
      struct sockaddr         *sa;
      struct sockaddr_in      *sin;

/* macro to skip to next RTA; has side-effects */
#define SKIPRTA(rtmsgp, rta, sa)                            \
      do {                                            \
            if ((rtmsgp)->rtm_addrs & (rta))                \
                  (sa) = next_sa((sa));                     \
      } while (0)

      rtm = (void *)di->di_buf;

      assert(rtm->rtm.rtm_type == RTM_NEWADDR ||
             rtm->rtm.rtm_type == RTM_DELADDR);

      if (rtm->rtm.rtm_index != ifindex)
            return (0);

      if (!(rtm->rtm.rtm_addrs & RTA_IFA)) {
            daemon_log(LOG_ERR, "ifa msg has no RTA_IFA.");
            return (0);
      }

      /* skip over rtmsg padding correctly */
      sa = (struct sockaddr *)((&rtm->ifam) + 1);
      SKIPRTA(&rtm->rtm, RTA_DST, sa);
      SKIPRTA(&rtm->rtm, RTA_GATEWAY, sa);
      SKIPRTA(&rtm->rtm, RTA_NETMASK, sa);
      SKIPRTA(&rtm->rtm, RTA_GENMASK, sa);
      SKIPRTA(&rtm->rtm, RTA_IFP, sa);

      /*
       * sa now points to RTA_IFA sockaddr; we are only interested
       * in updates for link-local addresses.
       */
      if (sa->sa_family != AF_INET)
            return (0);
      sin = (struct sockaddr_in *)sa;
      if (!IN_LINKLOCAL(ntohl(sin->sin_addr.s_addr)))
            return (0);

      for (ap = addresses; ap; ap = ap->addresses_next) {
            if (ap->address == sin->sin_addr.s_addr)
                  break;
      }
      if (rtm->rtm.rtm_type == RTM_DELADDR && ap != NULL) {
            AVAHI_LLIST_REMOVE(Address, addresses, addresses, ap);
            avahi_free(ap);
      }
      if (rtm->rtm.rtm_type == RTM_NEWADDR && ap == NULL) {
            ap = avahi_new(Address, 1);
            ap->address = sin->sin_addr.s_addr;
            AVAHI_LLIST_PREPEND(Address, addresses, addresses, ap);
      }

      return (0);
#undef SKIPRTA
}

Generated by  Doxygen 1.6.0   Back to index