]> git.meshlink.io Git - catta/commitdiff
port avahi-autoipd to FreeBSD (original patch from Bruce M Simpson)
authorLennart Poettering <lennart@poettering.net>
Thu, 12 Apr 2007 18:57:31 +0000 (18:57 +0000)
committerLennart Poettering <lennart@poettering.net>
Thu, 12 Apr 2007 18:57:31 +0000 (18:57 +0000)
git-svn-id: file:///home/lennart/svn/public/avahi/trunk@1402 941a03a8-eaeb-0310-b9a0-b1bbd8fe43fe

avahi-autoipd/Makefile.am
avahi-autoipd/avahi-autoipd.action [deleted file]
avahi-autoipd/avahi-autoipd.action.bsd [new file with mode: 0755]
avahi-autoipd/avahi-autoipd.action.linux [new file with mode: 0755]
avahi-autoipd/iface-bsd.c [new file with mode: 0644]
avahi-autoipd/main.c

index 11e6493b17fc499260eee800aef56035c8cadb7c..7589688c719e1ba7d1a522f38b241d131f88a1ab 100644 (file)
@@ -36,15 +36,30 @@ sbin_PROGRAMS = avahi-autoipd
 avahi_autoipd_SOURCES = \
        main.c main.h \
         ../avahi-daemon/setproctitle.c  ../avahi-daemon/setproctitle.h \
-       iface.h iface-linux.c \
+       iface.h \
        ../avahi-common/malloc.h ../avahi-common/malloc.c \
        ../avahi-common/timeval.h ../avahi-common/timeval.c
 
 avahi_autoipd_CFLAGS = $(AM_CFLAGS) $(LIBDAEMON_CFLAGS)
 avahi_autoipd_LDADD = $(AM_LDADD) $(LIBDAEMON_LIBS)
 
+if TARGET_FREEBSD
+avahi_autoipd_SOURCES += iface-bsd.c
+avahi_autoipd_LDADD += -lpcap
+else
+avahi_autoipd_SOURCES += iface-linux.c
+endif
+
 pkgsysconf_SCRIPTS=avahi-autoipd.action
 
+if TARGET_FREEBSD
+avahi-autoipd.action: avahi-autoipd.action.bsd
+       cp $< $@
+else
+avahi-autoipd.action: avahi-autoipd.action.linux
+       cp $< $@
+endif
+
 if TARGET_DEBIAN
 
 noinst_SCRIPTS = dhclient-enter-hook dhclient-exit-hook
@@ -78,4 +93,4 @@ endif
 
 EXTRA_DIST=avahi-autoipd.action dhclient-enter-hook.in dhclient-exit-hook.in
 
-CLEANFILES=dhclient-enter-hook dhclient-exit-hook
+CLEANFILES=dhclient-enter-hook dhclient-exit-hook avahi-autoipd.action
diff --git a/avahi-autoipd/avahi-autoipd.action b/avahi-autoipd/avahi-autoipd.action
deleted file mode 100755 (executable)
index 27f00bb..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-
-# $Id$
-#
-# 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 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 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.
-
-set -e
-
-# Command line arguments:
-#   $1 event that happened:
-#          BIND:     Successfully claimed address
-#          CONFLICT: An IP address conflict happened
-#          UNBIND:   The IP address is no longer needed
-#          STOP:     The daemon is terminating
-#   $2 interface name
-#   $3 IP adddress
-
-if [ -x /bin/ip -o -x /sbin/ip ] ; then
-
-    # We have the Linux ip tool from the iproute package
-
-    case "$1" in
-        BIND)
-            ip addr add "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" 
-            ;;
-
-        CONFLICT|UNBIND|STOP)
-            ip addr del "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" 
-            ;;
-
-        *)
-            echo "Unknown event $1" >&2
-            exit 1
-            ;;
-    esac
-
-elif [ -x /bin/ifconfig -o -x /sbin/ifconfig ] ; then
-
-    # We have the old ifconfig tool
-
-    case "$1" in
-        BIND)
-            ifconfig "$2:3" inet "$3" netmask 255.255.0.0 broadcast 169.254.255.255 up
-            ;;
-
-        CONFLICT|STOP|UNBIND)
-            ifconfig "$2:3" down
-            ;;
-
-        *)
-            echo "Unknown event $1" >&2
-            exit 1
-            ;;
-    esac
-
-else
-
-    echo "No network configuration tool found." >&2
-    exit 1
-
-fi
-
-exit 0
diff --git a/avahi-autoipd/avahi-autoipd.action.bsd b/avahi-autoipd/avahi-autoipd.action.bsd
new file mode 100755 (executable)
index 0000000..db39c4e
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+# $Id$
+#
+# 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 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 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.
+
+set -e
+
+# Command line arguments:
+#   $1 event that happened:
+#          BIND:     Successfully claimed address
+#          CONFLICT: An IP address conflict happened
+#          UNBIND:   The IP address is no longer needed
+#          STOP:     The daemon is terminating
+#   $2 interface name
+#   $3 IP adddress
+
+# We have the BSD ifconfig tool
+
+case "$1" in
+BIND)
+    ifconfig "$2" "$3"/16
+    ;;
+
+CONFLICT|STOP|UNBIND)
+    ifconfig "$2" "$3"/16 delete
+    ;;
+
+*)
+    echo "Unknown event $1" >&2
+    exit 1
+    ;;
+esac
+
+exit 0
diff --git a/avahi-autoipd/avahi-autoipd.action.linux b/avahi-autoipd/avahi-autoipd.action.linux
new file mode 100755 (executable)
index 0000000..27f00bb
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+# $Id$
+#
+# 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 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 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.
+
+set -e
+
+# Command line arguments:
+#   $1 event that happened:
+#          BIND:     Successfully claimed address
+#          CONFLICT: An IP address conflict happened
+#          UNBIND:   The IP address is no longer needed
+#          STOP:     The daemon is terminating
+#   $2 interface name
+#   $3 IP adddress
+
+if [ -x /bin/ip -o -x /sbin/ip ] ; then
+
+    # We have the Linux ip tool from the iproute package
+
+    case "$1" in
+        BIND)
+            ip addr add "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" 
+            ;;
+
+        CONFLICT|UNBIND|STOP)
+            ip addr del "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" 
+            ;;
+
+        *)
+            echo "Unknown event $1" >&2
+            exit 1
+            ;;
+    esac
+
+elif [ -x /bin/ifconfig -o -x /sbin/ifconfig ] ; then
+
+    # We have the old ifconfig tool
+
+    case "$1" in
+        BIND)
+            ifconfig "$2:3" inet "$3" netmask 255.255.0.0 broadcast 169.254.255.255 up
+            ;;
+
+        CONFLICT|STOP|UNBIND)
+            ifconfig "$2:3" down
+            ;;
+
+        *)
+            echo "Unknown event $1" >&2
+            exit 1
+            ;;
+    esac
+
+else
+
+    echo "No network configuration tool found." >&2
+    exit 1
+
+fi
+
+exit 0
diff --git a/avahi-autoipd/iface-bsd.c b/avahi-autoipd/iface-bsd.c
new file mode 100644 (file)
index 0000000..23c02dd
--- /dev/null
@@ -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
+}
index 60fc8aa01719cf7a971060d0984d8f57935e8e2e..58811a3e91194058a634265419623096d3fe6970 100644 (file)
 #include <config.h>
 #endif
 
-#include <stdlib.h>
-#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
 #include <sys/socket.h>
+#include <sys/wait.h>
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#endif
+
+#ifdef __linux__
 #include <netpacket/packet.h>
+#endif
 #include <net/ethernet.h>
-#include <fcntl.h>
-#include <time.h>
+#include <net/if.h>
+#ifdef __FreeBSD__
+#include <net/if_dl.h>
+#include <net/route.h>
+#endif
+#include <arpa/inet.h>
+
 #include <assert.h>
 #include <errno.h>
-#include <string.h>
 #include <inttypes.h>
-#include <sys/types.h>
-#include <arpa/inet.h>
-#include <sys/ioctl.h>
-#include <poll.h>
-#include <net/if.h>
+#include <fcntl.h>
+#include <stdlib.h>
 #include <stdio.h>
-#include <getopt.h>
 #include <signal.h>
-#include <sys/wait.h>
-#include <pwd.h>
+#include <string.h>
+#include <time.h>
+#include <getopt.h>
+
 #include <grp.h>
+#include <poll.h>
+#include <pwd.h>
+#include <unistd.h>
+
+#ifndef __linux__
+#include <pcap.h>
+#endif
 
 #include <avahi-common/malloc.h>
 #include <avahi-common/timeval.h>
-
 #include <avahi-daemon/setproctitle.h>
 
 #include <libdaemon/dfork.h>
 #include "main.h"
 #include "iface.h"
 
-#ifndef __linux__
-#error "avahi-autoipd is only available on Linux for now"
-#endif
-
 /* An implementation of RFC 3927 */
 
 /* Constants from the RFC */
@@ -84,6 +97,7 @@
 #define IPV4LL_BROADCAST 0xA9FEFFFFL
 
 #define ETHER_ADDRLEN 6
+#define ETHER_HDR_SIZE (2+2*ETHER_ADDRLEN)
 #define ARP_PACKET_SIZE (8+4+4+2*ETHER_ADDRLEN)
 
 typedef enum ArpOperation {
@@ -98,6 +112,11 @@ typedef struct ArpPacketInfo {
     uint8_t sender_hw_address[ETHER_ADDRLEN], target_hw_address[ETHER_ADDRLEN];
 } ArpPacketInfo;
 
+typedef struct ArpPacket {
+    uint8_t *ether_header;
+    uint8_t *ether_payload;
+} ArpPacket;
+
 static State state = STATE_START;
 static int n_iteration = 0;
 static int n_conflict = 0;
@@ -249,16 +268,44 @@ fail:
     return -1;
 }
 
-static void* packet_new(const ArpPacketInfo *info, size_t *packet_len) {
+/*
+ * Allocate a buffer with two pointers in front, one of which is
+ * guaranteed to point ETHER_HDR_SIZE bytes into it.
+ */
+static ArpPacket* packet_new(size_t packet_len) {
+    ArpPacket *p;
+    uint8_t *b;
+
+    assert(packet_len > 0);
+
+#ifdef __linux__
+    b = avahi_new0(uint8_t, sizeof(struct ArpPacket) + packet_len);
+    p = (ArpPacket*) b;
+    p->ether_header = NULL;
+    p->ether_payload = b + sizeof(struct ArpPacket);
+    
+#else
+    b = avahi_new0(uint8_t, sizeof(struct ArpPacket) + ETHER_HDR_SIZE + packet_len);
+    p = (ArpPacket*) b;
+    p->ether_header = b + sizeof(struct ArpPacket);
+    p->ether_payload = b + sizeof(struct ArpPacket) + ETHER_HDR_SIZE;
+#endif
+
+    return p;
+}
+
+static ArpPacket* packet_new_with_info(const ArpPacketInfo *info, size_t *packet_len) {
+    ArpPacket *p = NULL;
     uint8_t *r;
 
     assert(info);
-    assert(packet_len);
     assert(info->operation == ARP_REQUEST || info->operation == ARP_RESPONSE);
+    assert(packet_len != NULL);
 
     *packet_len = ARP_PACKET_SIZE;
-    r = avahi_new0(uint8_t, *packet_len);
-    
+    p = packet_new(*packet_len);
+    r = p->ether_payload;
+
     r[1] = 1; /* HTYPE */
     r[2] = 8; /* PTYPE */
     r[4] = ETHER_ADDRLEN; /* HLEN */
@@ -270,10 +317,10 @@ static void* packet_new(const ArpPacketInfo *info, size_t *packet_len) {
     memcpy(r+18, info->target_hw_address, ETHER_ADDRLEN);
     memcpy(r+24, &info->target_ip_address, 4);
 
-    return r;
+    return p;
 }
 
-static void *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, size_t *packet_len) {
+static ArpPacket *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, size_t *packet_len) {
     ArpPacketInfo info;
     
     memset(&info, 0, sizeof(info));
@@ -281,10 +328,10 @@ static void *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, siz
     memcpy(info.sender_hw_address, hw_address, ETHER_ADDRLEN);
     info.target_ip_address = ip_address;
 
-    return packet_new(&info, packet_len);
+    return packet_new_with_info(&info, packet_len);
 }
 
-static void *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_address, size_t *packet_len) {
+static ArpPacket *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_address, size_t *packet_len) {
     ArpPacketInfo info;
 
     memset(&info, 0, sizeof(info));
@@ -293,13 +340,15 @@ static void *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_addr
     info.target_ip_address = ip_address;
     info.sender_ip_address = ip_address;
 
-    return packet_new(&info, packet_len);
+    return packet_new_with_info(&info, packet_len);
 }
 
-static int packet_parse(const void *data, size_t packet_len, ArpPacketInfo *info) {
-    const uint8_t *p = data;
+static int packet_parse(const ArpPacket *packet, size_t packet_len, ArpPacketInfo *info) {
+    const uint8_t *p;
     
-    assert(data);
+    assert(packet);
+    p = (uint8_t *)packet->ether_payload;
+    assert(p);
 
     if (packet_len < ARP_PACKET_SIZE)
         return -1;
@@ -392,6 +441,10 @@ fail:
     return -1;
 }
 
+#ifdef __linux__
+
+/* Linux 'packet socket' specific implementation */
+
 static int open_socket(int iface, uint8_t *hw_address) {
     int fd = -1;
     struct sockaddr_ll sa;
@@ -437,7 +490,7 @@ fail:
     return -1;
 }
 
-static int send_packet(int fd, int iface, void *packet, size_t packet_len) {
+static int send_packet(int fd, int iface, ArpPacket *packet, size_t packet_len) {
     struct sockaddr_ll sa;
     
     assert(fd >= 0);
@@ -459,7 +512,7 @@ static int send_packet(int fd, int iface, void *packet, size_t packet_len) {
     return 0;
 }
 
-static int recv_packet(int fd, void **packet, size_t *packet_len) {
+static int recv_packet(int fd, ArpPacket **packet, size_t *packet_len) {
     int s;
     struct sockaddr_ll sa;
     socklen_t sa_len;
@@ -479,10 +532,10 @@ static int recv_packet(int fd, void **packet, size_t *packet_len) {
     if (s <= 0)
         s = 4096;
 
-    *packet = avahi_new(uint8_t, s);
+    *packet = packet_new(s);
 
     sa_len = sizeof(sa);
-    if ((r = recvfrom(fd, *packet, s, 0, (struct sockaddr*) &sa, &sa_len)) < 0) {
+    if ((r = recvfrom(fd, (*packet)->ether_payload, s, 0, (struct sockaddr*) &sa, &sa_len)) < 0) {
         daemon_log(LOG_ERR, "recvfrom() failed: %s", strerror(errno));
         goto fail;
     }
@@ -499,7 +552,214 @@ fail:
 
     return -1;
 }
+
+static void
+close_socket(int fd) {
+    close(fd);
+}
+
+#else /* !__linux__ */
+/* PCAP-based implementation */
+
+static pcap_t *__pp;
+static char __pcap_errbuf[PCAP_ERRBUF_SIZE];
+static uint8_t __lladdr[ETHER_ADDRLEN];
+
+#ifndef elementsof
+#define elementsof(array)      (sizeof(array)/sizeof(array[0]))
+#endif
+
+static int
+__get_ether_addr(int ifindex, u_char *lladdr)
+{
+       int                      mib[6];
+       char                    *buf;
+       struct if_msghdr        *ifm;
+       char                    *lim;
+       char                    *next;
+       struct sockaddr_dl      *sdl;
+       size_t                   len;
+
+       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_IFINFO) {
+                       sdl = (struct sockaddr_dl *)(ifm + 1);
+                       memcpy(lladdr, LLADDR(sdl), ETHER_ADDRLEN);
+               }
+       }
+       free(buf);
+
+       return (0);
+}
+
+static int
+open_socket(int iface, uint8_t *hw_address)
+{
+       struct bpf_program       bpf;
+       char                     ifname[IFNAMSIZ];
+       pcap_t                  *pp;
+       int                      err;
+       int                      fd;
+
+       assert(__pp == NULL);
+
+       if (interface_up(iface) < 0) {
+               return (-1);
+       }
+       if (__get_ether_addr(iface, __lladdr) == -1) {
+               return (-1);
+       }
+       if (if_indextoname(iface, ifname) == NULL) {
+               return (-1);
+       }
+
+       pp = pcap_open_live(ifname, 1500, 0, 0, __pcap_errbuf);
+       if (pp == NULL) {
+               return (-1);
+       }
+       err = pcap_set_datalink(pp, DLT_EN10MB);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_set_datalink: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+       err = pcap_setdirection(pp, PCAP_D_IN);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_setdirection: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+
+       fd = pcap_get_selectable_fd(pp);
+       if (fd == -1) {
+               pcap_close(pp);
+               return (-1);
+       }
+#if 0
+       /* XXX: can we use this with pcap_next_ex() ? */
+       err = pcap_setnonblock(pp, 1, __pcap_errbuf);
+       if (err == -1) {
+               pcap_close(pp);
+               return (-1);
+       }
+#endif
+
+       err = pcap_compile(pp, &bpf,
+                          "arp and ether dst ff:ff:ff:ff:ff:ff", 1, 0);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_compile: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+       err = pcap_setfilter(pp, &bpf);
+       if (err == -1) {
+               daemon_log(LOG_ERR, "pcap_setfilter: %s", pcap_geterr(pp));
+               pcap_close(pp);
+               return (-1);
+       }
+       pcap_freecode(&bpf);
+
+       /* Stash pcap-specific context away. */
+       memcpy(hw_address, __lladdr, ETHER_ADDRLEN);
+       __pp = pp;
+
+       return (fd);
+}
+
+static void
+close_socket(int fd __unused)
+{
+
+       assert(__pp != NULL);
+       pcap_close(__pp);
+       __pp = NULL;
+}
+
+/*
+ * We trick avahi into allocating sizeof(packet) + sizeof(ether_header),
+ * and prepend the required ethernet header information before sending.
+ */
+static int
+send_packet(int fd __unused, int iface __unused, ArpPacket *packet,
+    size_t packet_len)
+{
+       struct ether_header *eh;
+
+       assert(__pp != NULL);
+       assert(packet != NULL);
+
+       eh = (struct ether_header *)packet->ether_header;
+       memset(eh->ether_dhost, 0xFF, ETHER_ADDRLEN);
+       memcpy(eh->ether_shost, __lladdr, ETHER_ADDRLEN);
+       eh->ether_type = htons(0x0806);
+
+       return (pcap_inject(__pp, (void *)eh, packet_len + sizeof(*eh)));
+}
+
+static int
+recv_packet(int fd __unused, ArpPacket **packet, size_t *packet_len)
+{
+       struct pcap_pkthdr      *ph;
+       u_char                  *pd;
+       ArpPacket               *ap;
+       int                      err;
+       int                      retval;
+
+       assert(__pp != NULL);
+       assert(packet != NULL);
+       assert(packet_len != NULL);
+
+       *packet = NULL;
+       *packet_len = 0;
+       retval = -1;
+
+       err = pcap_next_ex(__pp, &ph, (const u_char **)&pd);
+       if (err == 1 && ph->caplen <= ph->len) {
+               ap = packet_new(ph->caplen);
+               memcpy(ap->ether_header, pd, ph->caplen);
+               *packet = ap;
+               *packet_len = (ph->caplen - sizeof(struct ether_header));
+               retval = 0;
+       } else {
+               if (err == 1) {
+                       daemon_log(LOG_ERR, "pcap len > caplen");
+               } else {
+                       daemon_log(LOG_ERR, "pcap_next_ex: %s",
+                           pcap_geterr(__pp));
+               }
+       }
+
+       return (retval);
+}
+#endif /* __linux__ */
+
 int is_ll_address(uint32_t addr) {
     return
         (ntohl(addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK &&
@@ -507,6 +767,7 @@ int is_ll_address(uint32_t addr) {
         ntohl(addr) != IPV4LL_BROADCAST;
 }
 
+
 static struct timeval *elapse_time(struct timeval *tv, unsigned msec, unsigned jitter) {
     assert(tv);
 
@@ -656,6 +917,9 @@ static int drop_privs(void) {
     int r;
     mode_t u;
 
+    pw = NULL;
+    gr = NULL;
+
     /* Get user/group ID */
     
     if (!no_drop_root) {
@@ -780,13 +1044,13 @@ static int loop(int iface, uint32_t addr) {
     struct timeval next_wakeup;
     int next_wakeup_valid = 0;
     char buf[64];
-    void *in_packet = NULL;
+    ArpPacket *in_packet = NULL;
     size_t in_packet_len;
-    void *out_packet = NULL;
+    ArpPacket *out_packet = NULL;
     size_t out_packet_len;
     uint8_t hw_address[ETHER_ADDRLEN];
     struct pollfd pollfds[FD_MAX];
-    int iface_fd;
+    int iface_fd = -1;
     Event event = EVENT_NULL;
     int retval_sent = !daemonize;
     State st;
@@ -1054,7 +1318,7 @@ static int loop(int iface, uint32_t addr) {
                 if (pollfds[FD_ARP].revents == POLLERR) {
                     /* The interface is probably down, let's recreate our socket */
                     
-                    close(fd);
+                    close_socket(fd);
 
                     if ((fd = open_socket(iface, hw_address)) < 0)
                         goto fail;
@@ -1124,7 +1388,7 @@ fail:
     avahi_free(in_packet);
     
     if (fd >= 0)
-        close(fd);
+        close_socket(fd);
 
     if (iface_fd >= 0)
         iface_done();