From f10b82b26456f44d03d0c28d93c5118b859d3891 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 12 Apr 2007 18:57:31 +0000 Subject: port avahi-autoipd to FreeBSD (original patch from Bruce M Simpson) git-svn-id: file:///home/lennart/svn/public/avahi/trunk@1402 941a03a8-eaeb-0310-b9a0-b1bbd8fe43fe --- avahi-autoipd/Makefile.am | 19 +- avahi-autoipd/avahi-autoipd.action | 78 ------ avahi-autoipd/avahi-autoipd.action.bsd | 50 ++++ avahi-autoipd/avahi-autoipd.action.linux | 78 ++++++ avahi-autoipd/iface-bsd.c | 416 +++++++++++++++++++++++++++++++ avahi-autoipd/main.c | 344 ++++++++++++++++++++++--- 6 files changed, 865 insertions(+), 120 deletions(-) delete mode 100755 avahi-autoipd/avahi-autoipd.action create mode 100755 avahi-autoipd/avahi-autoipd.action.bsd create mode 100755 avahi-autoipd/avahi-autoipd.action.linux create mode 100644 avahi-autoipd/iface-bsd.c diff --git a/avahi-autoipd/Makefile.am b/avahi-autoipd/Makefile.am index 11e6493..7589688 100644 --- a/avahi-autoipd/Makefile.am +++ b/avahi-autoipd/Makefile.am @@ -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 index 27f00bb..0000000 --- a/avahi-autoipd/avahi-autoipd.action +++ /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 index 0000000..db39c4e --- /dev/null +++ b/avahi-autoipd/avahi-autoipd.action.bsd @@ -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 index 0000000..27f00bb --- /dev/null +++ b/avahi-autoipd/avahi-autoipd.action.linux @@ -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 index 0000000..23c02dd --- /dev/null +++ b/avahi-autoipd/iface-bsd.c @@ -0,0 +1,416 @@ +/* rcs tags go here */ +/* Original author: Bruce M. Simpson */ + +/*** + 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 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#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 +} diff --git a/avahi-autoipd/main.c b/avahi-autoipd/main.c index 60fc8aa..58811a3 100644 --- a/avahi-autoipd/main.c +++ b/avahi-autoipd/main.c @@ -23,32 +23,49 @@ #include #endif -#include -#include +#include +#include +#include +#include #include +#include +#ifdef __FreeBSD__ +#include +#endif + +#ifdef __linux__ #include +#endif #include -#include -#include +#include +#ifdef __FreeBSD__ +#include +#include +#endif +#include + #include #include -#include #include -#include -#include -#include -#include -#include +#include +#include #include -#include #include -#include -#include +#include +#include +#include + #include +#include +#include +#include + +#ifndef __linux__ +#include +#endif #include #include - #include #include @@ -60,10 +77,6 @@ #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(); -- cgit