From 60d86f779725e78ae3d295380f15208859cc06eb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 30 Aug 2006 16:07:26 +0000 Subject: add initial version of a RFC3927/IPv4LL implementation git-svn-id: file:///home/lennart/svn/public/avahi/trunk@1286 941a03a8-eaeb-0310-b9a0-b1bbd8fe43fe --- Makefile.am | 4 +- avahi-autoipd/Makefile.am | 36 +++ avahi-autoipd/main.c | 628 ++++++++++++++++++++++++++++++++++++++++++++++ configure.ac | 16 ++ 4 files changed, 682 insertions(+), 2 deletions(-) create mode 100644 avahi-autoipd/Makefile.am create mode 100644 avahi-autoipd/main.c diff --git a/Makefile.am b/Makefile.am index 0dc9042..1b8e350 100644 --- a/Makefile.am +++ b/Makefile.am @@ -67,8 +67,8 @@ SUBDIRS = \ tests \ service-type-database \ avahi-compat-libdns_sd \ - avahi-compat-howl - + avahi-compat-howl \ + avahi-autoipd DX_INPUT = \ $(srcdir)/avahi-common/address.h \ diff --git a/avahi-autoipd/Makefile.am b/avahi-autoipd/Makefile.am new file mode 100644 index 0000000..33df1f5 --- /dev/null +++ b/avahi-autoipd/Makefile.am @@ -0,0 +1,36 @@ +# $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. + +if ENABLE_AUTOIPD +if HAVE_LIBDAEMON + +AM_CFLAGS= \ + -I$(top_srcdir) + +# This cool debug trap works on i386/gcc only +AM_CFLAGS+='-DDEBUG_TRAP=__asm__("int $$3")' + +sbin_PROGRAMS = avahi-autoipd + +avahi_autoipd_SOURCES = main.c +avahi_autoipd_CFLAGS = $(AM_CFLAGS) $(LIBDAEMON_CFLAGS) +avahi_autoipd_LDADD = $(AM_LDADD) ../avahi-common/libavahi-common.la $(LIBDAEMON_LIBS) + +endif +endif diff --git a/avahi-autoipd/main.c b/avahi-autoipd/main.c new file mode 100644 index 0000000..8ce7da1 --- /dev/null +++ b/avahi-autoipd/main.c @@ -0,0 +1,628 @@ +/* $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.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 +#include +#include +#include +#include + +#ifndef __linux__ +#error "avahi-autoipd is only available on Linux for now" +#endif + +/* An implementation of RFC 3927 */ + +/* Constants from the RFC */ +#define PROBE_WAIT 1 +#define PROBE_NUM 3 +#define PROBE_MIN 1 +#define PROBE_MAX 2 +#define ANNOUNCE_WAIT 2 +#define ANNOUNCE_NUM 2 +#define ANNOUNCE_INTERVAL 2 +#define MAX_CONFLICTS 10 +#define RATE_LIMIT_INTERVAL 60 +#define DEFEND_INTERVAL 10 + +#define IPV4LL_NETWORK 0xA9FE0000L +#define IPV4LL_NETMASK 0xFFFF0000L +#define IPV4LL_HOSTMASK 0x0000FFFFL + +#define ETHER_ADDRLEN 6 +#define ARP_PACKET_SIZE (8+4+4+2*ETHER_ADDRLEN) + +typedef enum State { + STATE_INVALID, + STATE_WAITING_PROBE, + STATE_PROBING, + STATE_WAITING_ANNOUNCE, + STATE_ANNOUNCING, + STATE_RUNNING, + STATE_SLEEPING, + STATE_MAX +} State; + +typedef enum ArpOperation { + ARP_REQUEST = 1, + ARP_RESPONSE = 2 +} ArpOperation; + +typedef struct ArpPacketInfo { + ArpOperation operation; + + uint32_t sender_ip_address, target_ip_address; + uint8_t sender_hw_address[ETHER_ADDRLEN], target_hw_address[ETHER_ADDRLEN]; +} ArpPacketInfo; + +static State state = STATE_INVALID; +static int n_iteration = 0; +static int n_conflict = 0; + +#define RANDOM_DEVICE "/dev/urandom" + +static void init_rand_seed(void) { + int fd; + unsigned seed = 0; + + /* Try to initialize seed from /dev/urandom, to make it a little + * less predictable, and to make sure that multiple machines + * booted at the same time choose different random seeds. */ + if ((fd = open(RANDOM_DEVICE, O_RDONLY)) >= 0) { + read(fd, &seed, sizeof(seed)); + close(fd); + } + + /* If the initialization failed by some reason, we add the time to the seed */ + seed ^= (unsigned) time(NULL); + + srand(seed); +} + +static uint32_t pick_addr(uint32_t old_addr) { + uint32_t addr; + + do { + unsigned r = (unsigned) rand(); + + /* Reduce to 16 bits */ + while (r > 0xFFFF) + r = (r >> 16) ^ (r & 0xFFFF); + + addr = htonl(IPV4LL_NETWORK | (uint32_t) r); + + } while (addr == old_addr); + + return addr; +} + +static void* packet_new(const ArpPacketInfo *info, size_t *packet_len) { + uint8_t *r; + + assert(info); + assert(packet_len); + assert(info->operation == ARP_REQUEST || info->operation == ARP_RESPONSE); + + *packet_len = ARP_PACKET_SIZE; + r = avahi_new0(uint8_t, *packet_len); + + r[1] = 1; /* HTYPE */ + r[2] = 8; /* PTYPE */ + r[4] = ETHER_ADDRLEN; /* HLEN */ + r[5] = 4; /* PLEN */ + r[7] = (uint8_t) info->operation; + + memcpy(r+8, info->sender_hw_address, ETHER_ADDRLEN); + memcpy(r+14, &info->sender_ip_address, 4); + memcpy(r+18, info->target_hw_address, ETHER_ADDRLEN); + memcpy(r+24, &info->target_ip_address, 4); + + return r; +} + +static void *packet_new_probe(uint32_t ip_address, const uint8_t*hw_address, size_t *packet_len) { + ArpPacketInfo info; + + memset(&info, 0, sizeof(info)); + info.operation = ARP_REQUEST; + memcpy(info.sender_hw_address, hw_address, ETHER_ADDRLEN); + info.target_ip_address = ip_address; + + return packet_new(&info, packet_len); +} + +static void *packet_new_announcement(uint32_t ip_address, const uint8_t* hw_address, size_t *packet_len) { + ArpPacketInfo info; + + memset(&info, 0, sizeof(info)); + info.operation = ARP_REQUEST; + memcpy(info.sender_hw_address, hw_address, ETHER_ADDRLEN); + info.target_ip_address = ip_address; + info.sender_ip_address = ip_address; + + return packet_new(&info, packet_len); +} + +static int packet_parse(const void *data, size_t packet_len, ArpPacketInfo *info) { + const uint8_t *p = data; + + assert(data); + + if (packet_len < ARP_PACKET_SIZE) + return -1; + + /* Check HTYPE and PTYPE */ + if (p[0] != 0 || p[1] != 1 || p[2] != 8 || p[3] != 0) + return -1; + + /* Check HLEN, PLEN, OPERATION */ + if (p[4] != ETHER_ADDRLEN || p[5] != 4 || p[6] != 0 || (p[7] != 1 && p[7] != 2)) + return -1; + + info->operation = p[7]; + memcpy(info->sender_hw_address, p+8, ETHER_ADDRLEN); + memcpy(&info->sender_ip_address, p+14, 4); + memcpy(info->target_hw_address, p+18, ETHER_ADDRLEN); + memcpy(&info->target_ip_address, p+24, 4); + + return 0; +} + +static void set_state(State st, int reset_counter) { + const char* const state_table[] = { + [STATE_INVALID] = "INVALID", + [STATE_WAITING_PROBE] = "WAITING_PROBE", + [STATE_PROBING] = "PROBING", + [STATE_WAITING_ANNOUNCE] = "WAITING_ANNOUNCE", + [STATE_ANNOUNCING] = "ANNOUNCING", + [STATE_RUNNING] = "RUNNING", + [STATE_SLEEPING] = "SLEEPING" + }; + + assert(st < STATE_MAX); + + if (st == state && !reset_counter) { + n_iteration++; + daemon_log(LOG_DEBUG, "State iteration %s-%i", state_table[state], n_iteration); + } else { + daemon_log(LOG_DEBUG, "State transition %s-%i -> %s-0", state_table[state], n_iteration, state_table[st]); + state = st; + n_iteration = 0; + } +} + +static int add_address(int iface, uint32_t addr) { + char buf[64]; + + daemon_log(LOG_INFO, "Selected address %s", inet_ntop(AF_INET, &addr, buf, sizeof(buf))); + return 0; +} + +static int remove_address(int iface, uint32_t addr) { + char buf[64]; + + daemon_log(LOG_INFO, "Removing address %s", inet_ntop(AF_INET, &addr, buf, sizeof(buf))); + return 0; +} + +static int open_socket(int iface, uint8_t *hw_address) { + int fd = -1; + struct sockaddr_ll sa; + socklen_t sa_len; + + if ((fd = socket(PF_PACKET, SOCK_DGRAM, 0)) < 0) { + daemon_log(LOG_ERR, "socket() failed: %s", strerror(errno)); + goto fail; + } + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(ETH_P_ARP); + sa.sll_ifindex = iface; + + if (bind(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) { + daemon_log(LOG_ERR, "bind() failed: %s", strerror(errno)); + goto fail; + } + + sa_len = sizeof(sa); + if (getsockname(fd, (struct sockaddr*) &sa, &sa_len) < 0) { + daemon_log(LOG_ERR, "getsockname() failed: %s", strerror(errno)); + goto fail; + } + + if (sa.sll_halen != ETHER_ADDRLEN) { + daemon_log(LOG_ERR, "getsockname() returned invalid hardware address."); + goto fail; + } + + memcpy(hw_address, sa.sll_addr, ETHER_ADDRLEN); + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} + +static int send_packet(int fd, int iface, void *packet, size_t packet_len) { + struct sockaddr_ll sa; + + assert(fd >= 0); + assert(packet); + assert(packet_len > 0); + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(ETH_P_ARP); + sa.sll_ifindex = iface; + sa.sll_halen = ETHER_ADDRLEN; + memset(sa.sll_addr, 0xFF, ETHER_ADDRLEN); + + if (sendto(fd, packet, packet_len, 0, (struct sockaddr*) &sa, sizeof(sa)) < 0) { + daemon_log(LOG_ERR, "sendto() failed: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static int recv_packet(int fd, void **packet, size_t *packet_len) { + int s; + struct sockaddr_ll sa; + socklen_t sa_len; + + assert(fd >= 0); + assert(packet); + assert(packet_len); + + *packet = NULL; + + if (ioctl(fd, FIONREAD, &s) < 0) { + daemon_log(LOG_ERR, "FIONREAD failed: %s", strerror(errno)); + goto fail; + } + + assert(s > 0); + + *packet_len = (size_t) s; + *packet = avahi_new(uint8_t, s); + + sa_len = sizeof(sa); + if (recvfrom(fd, *packet, s, 0, (struct sockaddr*) &sa, &sa_len) < 0) { + daemon_log(LOG_ERR, "recvfrom() failed: %s", strerror(errno)); + goto fail; + } + + return 0; + +fail: + if (*packet) + avahi_free(*packet); + + return -1; +} + +static int is_ll_address(uint32_t addr) { + return (ntohl(addr) & IPV4LL_NETMASK) == IPV4LL_NETWORK; +} + +static struct timeval *elapse_time(struct timeval *tv, unsigned msec, unsigned jitter) { + assert(tv); + + gettimeofday(tv, NULL); + + if (msec) + avahi_timeval_add(tv, (AvahiUsec) msec*1000); + + if (jitter) + avahi_timeval_add(tv, (AvahiUsec) (jitter*1000.0*rand()/(RAND_MAX+1.0))); + + return tv; +} + +static int loop(int iface, uint32_t addr) { + int fd = -1, ret = -1; + struct timeval next_wakeup; + int next_wakeup_valid = 0; + char buf[64]; + void *in_packet = NULL; + size_t in_packet_len; + void *out_packet = NULL; + size_t out_packet_len; + uint8_t hw_address[ETHER_ADDRLEN]; + struct pollfd pollfds[1]; + + enum { + EVENT_NULL, + EVENT_PACKET, + EVENT_TIMEOUT + } event = EVENT_NULL; + + if ((fd = open_socket(iface, hw_address)) < 0) + goto fail; + + if (addr && !is_ll_address(addr)) { + daemon_log(LOG_WARNING, "Requested address %s is not from IPv4LL range 169.254/16, ignoring.", inet_ntop(AF_INET, &addr, buf, sizeof(buf))); + addr = 0; + } + + if (!addr) { + int i; + uint32_t a = 1; + + for (i = 0; i < ETHER_ADDRLEN; i++) + a += hw_address[i]*i; + + addr = htonl(IPV4LL_NETWORK | (uint32_t) a); + } + + daemon_log(LOG_INFO, "Starting with address %s", inet_ntop(AF_INET, &addr, buf, sizeof(buf))); + + memset(pollfds, 0, sizeof(pollfds)); + pollfds[0].fd = fd; + pollfds[0].events = POLLIN; + + for (;;) { + int r, timeout; + AvahiUsec usec; + + if (state == STATE_INVALID) { + + /* First, wait a random time */ + set_state(STATE_WAITING_PROBE, 1); + + elapse_time(&next_wakeup, 0, PROBE_WAIT*1000); + next_wakeup_valid = 1; + + } else if ((state == STATE_WAITING_PROBE && event == EVENT_TIMEOUT) || + (state == STATE_PROBING && event == EVENT_TIMEOUT && n_iteration < PROBE_NUM-2)) { + + /* Send a probe */ + out_packet = packet_new_probe(addr, hw_address, &out_packet_len); + set_state(STATE_PROBING, 0); + + elapse_time(&next_wakeup, PROBE_MIN*1000, (PROBE_MAX-PROBE_MIN)*1000); + next_wakeup_valid = 1; + + } else if (state == STATE_PROBING && event == EVENT_TIMEOUT && n_iteration >= PROBE_NUM-2) { + + /* Send the last probe */ + out_packet = packet_new_probe(addr, hw_address, &out_packet_len); + set_state(STATE_WAITING_ANNOUNCE, 1); + + elapse_time(&next_wakeup, ANNOUNCE_WAIT*1000, 0); + next_wakeup_valid = 1; + + } else if ((state == STATE_WAITING_ANNOUNCE && event == EVENT_TIMEOUT) || + (state == STATE_ANNOUNCING && event == EVENT_TIMEOUT && n_iteration < ANNOUNCE_NUM-1)) { + + /* Send announcement packet */ + out_packet = packet_new_announcement(addr, hw_address, &out_packet_len); + set_state(STATE_ANNOUNCING, 0); + + elapse_time(&next_wakeup, ANNOUNCE_INTERVAL*1000, 0); + next_wakeup_valid = 1; + + if (n_iteration == 0) { + add_address(iface, addr); + n_conflict = 0; + } + + } else if ((state == STATE_ANNOUNCING && event == EVENT_TIMEOUT && n_iteration >= ANNOUNCE_NUM-1)) { + + daemon_log(LOG_INFO, "Successfully claimed IP address %s", inet_ntop(AF_INET, &addr, buf, sizeof(buf))); + set_state(STATE_RUNNING, 0); + + } else if (event == EVENT_PACKET) { + ArpPacketInfo info; + + assert(in_packet); + + if (packet_parse(in_packet, in_packet_len, &info) < 0) + daemon_log(LOG_WARNING, "Failed to parse incoming ARP packet."); + else { + int conflict = 0; + + if (info.sender_ip_address == addr) { + /* Normal conflict */ + conflict = 1; + daemon_log(LOG_INFO, "Recieved conflicting normal ARP packet."); + } else if (state == STATE_WAITING_PROBE || state == STATE_PROBING || state == STATE_WAITING_ANNOUNCE) { + /* Probe conflict */ + conflict = info.target_ip_address == addr && memcmp(hw_address, info.sender_hw_address, ETHER_ADDRLEN); + daemon_log(LOG_INFO, "Recieved conflicting probe ARP packet."); + } + + if (conflict) { + + if (state == STATE_RUNNING || state == STATE_ANNOUNCING) + remove_address(iface, addr); + + /* Pick a new address */ + addr = pick_addr(addr); + + daemon_log(LOG_INFO, "Trying address %s", inet_ntop(AF_INET, &addr, buf, sizeof(buf))); + + set_state(STATE_WAITING_PROBE, 1); + + n_conflict++; + + if (n_conflict >= MAX_CONFLICTS) { + daemon_log(LOG_WARNING, "Got too many conflicts, rate limiting new probes."); + elapse_time(&next_wakeup, RATE_LIMIT_INTERVAL*1000, PROBE_WAIT*1000); + } else + elapse_time(&next_wakeup, 0, PROBE_WAIT*1000); + + next_wakeup_valid = 1; + } else + daemon_log(LOG_DEBUG, "Ignoring ARP packet."); + } + } + + if (out_packet) { + daemon_log(LOG_DEBUG, "sending..."); + + if (send_packet(fd, iface, out_packet, out_packet_len) < 0) + goto fail; + + avahi_free(out_packet); + out_packet = NULL; + } + + if (in_packet) { + avahi_free(in_packet); + in_packet = NULL; + } + + timeout = -1; + + if (next_wakeup_valid) { + usec = avahi_age(&next_wakeup); + timeout = usec < 0 ? (int) (-usec/1000) : 0; + } + + daemon_log(LOG_DEBUG, "sleeping %ims", timeout); + + while ((r = poll(pollfds, 1, timeout)) < 0 && errno == EINTR) + ; + + if (r < 0) { + daemon_log(LOG_ERR, "poll() failed: %s", strerror(r)); + break; + } else if (r == 0) { + event = EVENT_TIMEOUT; + next_wakeup_valid = 0; + } else { + assert(pollfds[0].revents == POLLIN); + + if (recv_packet(fd, &in_packet, &in_packet_len) < 0) + goto fail; + + if (in_packet) + event = EVENT_PACKET; + } + } + + ret = 0; + +fail: + + avahi_free(out_packet); + avahi_free(in_packet); + + if (fd >= 0) + close(fd); + + return ret; +} + +static int get_ifindex(const char *name) { + int fd = -1; + struct ifreq ifreq; + + if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + daemon_log(LOG_ERR, "socket() failed: %s", strerror(errno)); + goto fail; + } + + memset(&ifreq, 0, sizeof(ifreq)); + strncpy(ifreq.ifr_name, name, IFNAMSIZ-1); + ifreq.ifr_name[IFNAMSIZ-1] = 0; + + if (ioctl(fd, SIOCGIFINDEX, &ifreq) < 0) { + daemon_log(LOG_ERR, "SIOCGIFINDEX failed: %s", strerror(errno)); + goto fail; + } + + return ifreq.ifr_ifindex; + +fail: + + if (fd >= 0) + close(fd); + + return -1; +} + +int main(int argc, char*argv[]) { + int ret = 1; + int ifindex; + uint32_t addr = 0; + + init_rand_seed(); + + if ((ifindex = get_ifindex(argc >= 2 ? argv[1] : "eth0")) < 0) + goto fail; + + if (argc >= 3) + addr = inet_addr(argv[2]); + + if (loop(ifindex, addr) < 0) + goto fail; + + ret = 0; + + +fail: + + return ret; +} + +/* TODO: + +- netlink +- man page +- user script +- chroot/drop privs/caps +- daemonize +- defend +- signals +- store last used address +- cmdline + +*/ diff --git a/configure.ac b/configure.ac index 6b21c30..8c2b1b5 100644 --- a/configure.ac +++ b/configure.ac @@ -659,6 +659,20 @@ if test "x$HAVE_MONO" = "xyes" && test "x$HAVE_MONODOC" = "xyes" ; then fi AM_CONDITIONAL(HAVE_MONODOC, test "x$HAVE_MONODOC" = "xyes") +# +# Build autoipd? +# +AC_ARG_ENABLE(autoipd, + AS_HELP_STRING([--disable-autoipd],[Disable building of avahi-autoipd]), + [case "${enableval}" in + yes) ENABLE_AUTOIPD=yes ;; + no) ENABLE_AUTOIPD=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-autoipd) ;; + esac], + [ENABLE_AUTOIPD=yes]) + +AM_CONDITIONAL(ENABLE_AUTOIPD, test "x$ENABLE_AUTOIPD" = "xyes") + # # Defining Avahi User and Group. # @@ -844,6 +858,7 @@ avahi-sharp/Makefile avahi-compat-libdns_sd/Makefile avahi-compat-howl/Makefile avahi-compat-howl/samples/Makefile +avahi-autoipd/Makefile ]) AC_OUTPUT @@ -923,4 +938,5 @@ echo "\ Building avahi-compat-howl: ${ENABLE_COMPAT_HOWL} Building tests: ${ENABLE_TESTS} Building avahi-core documentation: ${ENABLE_CORE_DOCS} + Building avahi-autoipd: ${ENABLE_AUTOIPD} " -- cgit