summaryrefslogtreecommitdiffstats
path: root/avahi-autoipd/iface-bsd.c
diff options
context:
space:
mode:
Diffstat (limited to 'avahi-autoipd/iface-bsd.c')
-rw-r--r--avahi-autoipd/iface-bsd.c416
1 files changed, 416 insertions, 0 deletions
diff --git a/avahi-autoipd/iface-bsd.c b/avahi-autoipd/iface-bsd.c
new file mode 100644
index 0000000..23c02dd
--- /dev/null
+++ b/avahi-autoipd/iface-bsd.c
@@ -0,0 +1,416 @@
+/* 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
+}