]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge commit 'aearsis/pim^' into pim
authorOndrej Zajicek (work) <santiago@crfreenet.org>
Sun, 11 Mar 2018 23:15:13 +0000 (00:15 +0100)
committerOndrej Zajicek (work) <santiago@crfreenet.org>
Sun, 11 Mar 2018 23:15:13 +0000 (00:15 +0100)
15 files changed:
1  2 
configure.ac
lib/ip.h
lib/timer.c
lib/timer.h
nest/proto.c
nest/protocol.h
nest/route.h
nest/rt-attr.c
proto/Doc
proto/igmp/Doc
proto/igmp/Makefile
proto/igmp/config.Y
proto/igmp/igmp.c
proto/igmp/igmp.h
proto/igmp/packets.c

diff --cc configure.ac
index f4de8f9301c7f0559064be39e8272ae68bccdcb2,0000000000000000000000000000000000000000..10089e5f9cdb1a0f951bca574a676e24c899e64e
mode 100644,000000..100644
--- /dev/null
@@@ -1,406 -1,0 +1,406 @@@
- all_protocols="$proto_bfd babel bgp ospf pipe radv rip $proto_rpki static"
 +dnl ** This is a configure script template for BIRD
 +dnl ** Process it with autoconf to get ./configure
 +dnl ** (c) 1999--2000 Martin Mares <mj@ucw.cz>
 +
 +AC_INIT
 +AC_CONFIG_SRCDIR([conf/confbase.Y])
 +AC_CONFIG_AUX_DIR([tools])
 +
 +AC_ARG_ENABLE([client],
 +  [AS_HELP_STRING([--enable-client], [enable building of BIRD client @<:@yes@:>@])],
 +  [],
 +  [enable_client=yes]
 +)
 +
 +AC_ARG_ENABLE([debug],
 +  [AS_HELP_STRING([--enable-debug], [enable internal debugging routines @<:@no@:>@])],
 +  [],
 +  [enable_debug=no]
 +)
 +
 +AC_ARG_ENABLE([memcheck],
 +  [AS_HELP_STRING([--enable-memcheck], [check memory allocations when debugging @<:@yes@:>@])],
 +  [],
 +  [enable_memcheck=yes]
 +)
 +
 +AC_ARG_ENABLE([pthreads],
 +  [AS_HELP_STRING([--enable-pthreads], [enable POSIX threads support @<:@try@:>@])],
 +  [],
 +  [enable_pthreads=try]
 +)
 +
 +AC_ARG_ENABLE([libssh],
 +  [AS_HELP_STRING([--enable-libssh], [enable LibSSH support together with RPKI @<:@try@:>@])],
 +  [],
 +  [enable_libssh=try]
 +)
 +
 +AC_ARG_ENABLE([mpls-kernel],
 +  [AS_HELP_STRING([--enable-mpls-kernel], [enable MPLS support in kernel protocol @<:@try@:>@])],
 +  [],
 +  [enable_mpls_kernel=try]
 +)
 +
 +AC_ARG_WITH([protocols],
 +  [AS_HELP_STRING([--with-protocols=LIST], [include specified routing protocols @<:@all@:>@])],
 +  [],
 +  [with_protocols="all"]
 +)
 +
 +AC_ARG_WITH([sysconfig],
 +  [AS_HELP_STRING([--with-sysconfig=FILE], [use specified BIRD system configuration file])],
 +  []
 +)
 +
 +AC_ARG_WITH([runtimedir],
 +  [AS_HELP_STRING([--with-runtimedir=PATH], [path for runtime files @<:@LOCALSTATEDIR/run@:>@])],
 +  [runtimedir="$with_runtimedir"],
 +  [runtimedir="\$(localstatedir)/run"]
 +)
 +
 +AC_ARG_WITH([iproutedir],
 +  [AS_HELP_STRING([--with-iproutedir=PATH], [path to iproute2 config files @<:@/etc/iproute2@:>@])],
 +  [given_iproutedir="yes"]
 +)
 +
 +AC_ARG_VAR([FLEX], [location of the Flex program])
 +AC_ARG_VAR([BISON], [location of the Bison program])
 +AC_ARG_VAR([M4], [location of the M4 program])
 +
 +
 +if test "$srcdir" = . ; then
 +  # Building in current directory => create obj directory holding all objects
 +  objdir=obj
 +else
 +  # Building in separate directory
 +  objdir=.
 +fi
 +
 +exedir=.
 +
 +AC_SUBST([objdir])
 +AC_SUBST([exedir])
 +AC_SUBST([srcdir])
 +AC_SUBST([runtimedir])
 +
 +
 +if test "$enable_debug" = yes ; then
 +  CONFIG_FILE="bird.conf"
 +  CONTROL_SOCKET="bird.ctl"
 +else
 +  CONFIG_FILE="\$(sysconfdir)/bird.conf"
 +  CONTROL_SOCKET="$runtimedir/bird.ctl"
 +fi
 +AC_SUBST([CONFIG_FILE])
 +AC_SUBST([CONTROL_SOCKET])
 +
 +AC_SEARCH_LIBS([clock_gettime], [rt posix4],
 +  [],
 +  [AC_MSG_ERROR([Function clock_gettime not available.])]
 +)
 +
 +AC_CANONICAL_HOST
 +
 +# Store this value because ac_test_CFLAGS is overwritten by AC_PROG_CC
 +if test "$ac_test_CFLAGS" != set ; then
 +  bird_cflags_default=yes
 +fi
 +
 +AC_PROG_CC
 +AC_PROG_CC_C99
 +if test -z "$GCC" ; then
 +  AC_MSG_ERROR([This program requires the GNU C Compiler.])
 +fi
 +
 +if test "$enable_pthreads" != no ; then
 +  BIRD_CHECK_PTHREADS
 +
 +  if test "$bird_cv_lib_pthreads" = yes ; then
 +    AC_DEFINE([USE_PTHREADS], [1], [Define to 1 if pthreads are enabled])
 +    CFLAGS="$CFLAGS -pthread"
 +    LDFLAGS="$LDFLAGS -pthread"
 +    proto_bfd=bfd
 +  elif test "$enable_pthreads" = yes ; then
 +    AC_MSG_ERROR([POSIX threads not available.])
 +  fi
 +
 +  if test "$enable_pthreads" = try ; then
 +    enable_pthreads="$bird_cv_lib_pthreads"
 +  fi
 +fi
 +
 +if test "$bird_cflags_default" = yes ; then
 +  BIRD_CHECK_GCC_OPTION([bird_cv_c_option_wno_pointer_sign], [-Wno-pointer-sign], [-Wall])
 +  BIRD_CHECK_GCC_OPTION([bird_cv_c_option_wno_missing_init], [-Wno-missing-field-initializers], [-Wall -Wextra])
 +  BIRD_CHECK_GCC_OPTION([bird_cv_c_option_fno_strict_aliasing], [-fno-strict-aliasing])
 +  BIRD_CHECK_GCC_OPTION([bird_cv_c_option_fno_strict_overflow], [-fno-strict-overflow])
 +
 +  CFLAGS="$CFLAGS -Wall -Wextra -Wstrict-prototypes -Wno-parentheses"
 +  BIRD_ADD_GCC_OPTION([bird_cv_c_option_wno_pointer_sign], [-Wno-pointer-sign])
 +  BIRD_ADD_GCC_OPTION([bird_cv_c_option_wno_missing_init], [-Wno-missing-field-initializers])
 +  BIRD_ADD_GCC_OPTION([bird_cv_c_option_fno_strict_aliasing], [-fno-strict-aliasing])
 +  BIRD_ADD_GCC_OPTION([bird_cv_c_option_fno_strict_overflow], [-fno-strict-overflow])
 +fi
 +AC_MSG_CHECKING([CFLAGS])
 +AC_MSG_RESULT([$CFLAGS])
 +
 +
 +AC_PROG_CPP
 +AC_PROG_INSTALL
 +AC_PROG_RANLIB
 +AC_CHECK_PROG([FLEX], [flex], [flex])
 +AC_CHECK_PROG([BISON], [bison], [bison])
 +AC_CHECK_PROGS([M4], [gm4 m4])
 +
 +test -z "$FLEX"        && AC_MSG_ERROR([Flex is missing.])
 +test -z "$BISON" && AC_MSG_ERROR([Bison is missing.])
 +test -z "$M4"  && AC_MSG_ERROR([M4 is missing.])
 +
 +BIRD_CHECK_PROG_FLAVOR_GNU([$M4],
 +  [],
 +  [AC_MSG_ERROR([Provided M4 is not GNU M4.])]
 +)
 +
 +if test -n "$with_sysconfig" -a "$with_sysconfig" != no ; then
 +  if test -f $with_sysconfig ; then
 +    sysdesc=$with_sysconfig
 +  else
 +    sysdesc=$srcdir/sysdep/cf/$with_sysconfig
 +    if ! test -f $sysdesc ; then
 +      sysdesc=$sysdesc.h
 +    fi
 +  fi
 +elif test -f sysconfig.h ; then
 +  sysdesc=sysconfig
 +else
 +  case "$host_os" in
 +    linux*)
 +      sysdesc=linux
 +      default_iproutedir="/etc/iproute2"
 +      ;;
 +    freebsd*)
 +      sysdesc=bsd
 +      CPPFLAGS="$CPPFLAGS -I/usr/local/include"
 +      LDFLAGS="$LDFLAGS -L/usr/local/lib"
 +      ;;
 +    kfreebsd*)
 +      sysdesc=bsd
 +      ;;
 +    netbsd*)
 +      sysdesc=bsd
 +      CPPFLAGS="$CPPFLAGS -I/usr/pkg/include"
 +      LDFLAGS="$LDFLAGS -L/usr/pkg/lib -R/usr/pkg/lib"
 +      ;;
 +    openbsd*)
 +      sysdesc=bsd
 +      ;;
 +    dragonfly*)
 +      sysdesc=bsd
 +      ;;
 +    *)
 +      AC_MSG_ERROR([Cannot determine correct system configuration. Please use --with-sysconfig to set it manually.])
 +      ;;
 +    esac
 +    sysdesc=$srcdir/sysdep/cf/$sysdesc.h
 +fi
 +AC_MSG_CHECKING([which OS configuration should we use])
 +AC_MSG_RESULT([$sysdesc])
 +if ! test -f $sysdesc ; then
 +  AC_MSG_ERROR([The system configuration file is missing.])
 +fi
 +sysname=`echo $sysdesc | sed 's/\.h$//'`
 +AC_DEFINE_UNQUOTED([SYSCONF_INCLUDE], ["$sysdesc"], [Which sysdep header to include])
 +
 +AC_MSG_CHECKING([system-dependent directories])
 +sysdep_dirs="`sed <$sysdesc '/^Link: /!d;s/^Link: \(.*\)$/\1/' | tr '\012' ' '`"
 +AC_MSG_RESULT([$sysdep_dirs])
 +AC_SUBST([sysdep_dirs])
 +
 +if test "$with_iproutedir" = no ; then with_iproutedir= ; fi
 +
 +if test -n "$given_iproutedir"
 +then iproutedir=$with_iproutedir
 +else iproutedir=$default_iproutedir
 +fi
 +
 +AC_SUBST([iproutedir])
 +
 +DAEMON_LIBS=
 +AC_SUBST(DAEMON_LIBS)
 +
 +if test "$enable_libssh" != no ; then
 +  AC_CHECK_HEADER([libssh/libssh.h], [true], [fail=yes], [ ])
 +  AC_CHECK_LIB([ssh], [ssh_connect], [true], [fail=yes])
 +
 +  if test "$fail" != yes ; then
 +    AC_DEFINE([HAVE_LIBSSH], [1], [Define to 1 if you have the `ssh' library (-lssh).])
 +    DAEMON_LIBS="-lssh $DAEMON_LIBS"
 +    proto_rpki=rpki
 +    enable_libssh=yes
 +  else
 +    if test "$enable_libssh" = yes ; then
 +      AC_MSG_ERROR([LibSSH not available.])
 +    else
 +      enable_libssh=no
 +    fi
 +  fi
 +fi
 +
 +if test "$enable_mpls_kernel" != no ; then
 +  BIRD_CHECK_MPLS_KERNEL
 +
 +  if test "$bird_cv_mpls_kernel" = yes ; then
 +    AC_DEFINE([HAVE_MPLS_KERNEL], [1], [Define to 1 if kernel is MPLS capable])
 +  elif test "$enable_mpls_kernel" = yes ; then
 +    AC_MSG_ERROR([Kernel MPLS support not found.])
 +  fi
 +
 +  if test "$enable_mpls_kernel" = try ; then
 +    enable_mpls_kernel="$bird_cv_mpls_kernel"
 +  fi
 +fi
 +
++all_protocols="$proto_bfd babel bgp igmp ospf pipe radv rip $proto_rpki static"
 +
 +all_protocols=`echo $all_protocols | sed 's/ /,/g'`
 +
 +if test "$with_protocols" = all ; then
 +  with_protocols="$all_protocols"
 +fi
 +
 +AH_TEMPLATE([CONFIG_BABEL],   [Babel protocol])
 +AH_TEMPLATE([CONFIG_BFD],     [BFD protocol])
 +AH_TEMPLATE([CONFIG_BGP],     [BGP protocol])
 +AH_TEMPLATE([CONFIG_OSPF],    [OSPF protocol])
 +AH_TEMPLATE([CONFIG_PIPE],    [Pipe protocol])
 +AH_TEMPLATE([CONFIG_RADV],    [RAdv protocol])
 +AH_TEMPLATE([CONFIG_RIP],     [RIP protocol])
 +AH_TEMPLATE([CONFIG_RPKI],    [RPKI protocol])
 +AH_TEMPLATE([CONFIG_STATIC],  [Static protocol])
 +
 +AC_MSG_CHECKING([protocols])
 +protocols=`echo "$with_protocols" | sed 's/,/ /g'`
 +if test "$protocols" = no ; then protocols= ; fi
 +for a in $protocols ; do
 +  if ! test -f $srcdir/proto/$a/Makefile ; then
 +    AC_MSG_RESULT([failed])
 +    AC_MSG_ERROR([Requested protocol $a not found])
 +  fi
 +  AC_DEFINE_UNQUOTED([CONFIG_`echo $a | tr 'a-z' 'A-Z'`])
 +done
 +AC_MSG_RESULT([ok])
 +AC_SUBST([protocols])
 +
 +case $sysdesc in
 +  */linux*)
 +    AC_CHECK_HEADER([linux/rtnetlink.h],
 +      [],
 +      [AC_MSG_ERROR([Appropriate version of Linux kernel headers not found.])],
 +      [
 +      dnl Some older versions of Linux kernel headers require these includes
 +      #include <asm/types.h>
 +      #include <sys/socket.h>
 +      ]
 +    )
 +    ;;
 +esac
 +
 +AC_CHECK_HEADERS_ONCE([alloca.h syslog.h])
 +AC_CHECK_MEMBERS([struct sockaddr.sa_len], [], [], [#include <sys/socket.h>])
 +
 +AC_C_BIGENDIAN(
 +  [AC_DEFINE([CPU_BIG_ENDIAN], [1], [Define to 1 if cpu is big endian])],
 +  [AC_DEFINE([CPU_LITTLE_ENDIAN], [1], [Define to 1 if cpu is little endian])],
 +  [AC_MSG_ERROR([Cannot determine CPU endianity.])]
 +)
 +
 +if test "$enable_debug" = yes ; then
 +  AC_DEFINE([DEBUGGING], [1], [Define to 1 if debugging is enabled])
 +  LDFLAGS="$LDFLAGS -rdynamic"
 +  CFLAGS="$CFLAGS -O0 -ggdb -g3 -gdwarf-4"
 +
 +  AC_CHECK_HEADER([execinfo.h],
 +    [
 +      AC_DEFINE([HAVE_EXECINFO_H], [1], [Define to 1 if you have the <execinfo.h> header file.])
 +      AC_SEARCH_LIBS([backtrace], [execinfo],
 +      [],
 +      [AC_MSG_ERROR([Function backtrace not available.])]
 +      )
 +    ]
 +  )
 +
 +  if test "$enable_memcheck" = yes ; then
 +    AC_CHECK_LIB([dmalloc], [dmalloc_debug])
 +    if test $ac_cv_lib_dmalloc_dmalloc_debug != yes ; then
 +      AC_CHECK_LIB([efence], [malloc])
 +    fi
 +  fi
 +fi
 +
 +CLIENT=birdcl
 +CLIENT_LIBS=
 +if test "$enable_client" = yes ; then
 +  CLIENT="$CLIENT birdc"
 +  BASE_LIBS="$LIBS"
 +  LIBS=""
 +
 +  AC_CHECK_HEADERS([curses.h],
 +    [],
 +    [AC_MSG_ERROR([The client requires ncurses library. Either install the library or use --disable-client to compile without the client.])],
 +    [AC_INCLUDES_DEFAULT]
 +  )
 +
 +  AC_SEARCH_LIBS([tgetent], [tinfo tinfow ncurses curses termcap],
 +    [TINFO_LIBS="$LIBS"; LIBS=""],
 +    [AC_MSG_ERROR([The client requires ncurses library. Either install the library or use --disable-client to compile without the client.])],
 +  )
 +
 +  AC_CHECK_HEADERS([readline/readline.h readline/history.h],
 +    [],
 +    [AC_MSG_ERROR([The client requires GNU Readline library. Either install the library or use --disable-client to compile without the client.])],
 +    [AC_INCLUDES_DEFAULT]
 +  )
 +
 +  AC_SEARCH_LIBS([rl_callback_read_char], [readline],
 +    [READLINE_LIBS="$LIBS"; LIBS=""],
 +    [AC_MSG_ERROR([The client requires GNU Readline library. Either install the library or use --disable-client to compile without the client.])],
 +    [$TINFO_LIBS]
 +  )
 +
 +  AC_CHECK_LIB([readline], [rl_crlf],
 +    [AC_DEFINE([HAVE_RL_CRLF], [1], [Define to 1 if you have rl_crlf()])],
 +    [],
 +    [$TINFO_LIBS]
 +  )
 +
 +  AC_CHECK_LIB([readline], [rl_ding],
 +    [AC_DEFINE([HAVE_RL_DING], [1], [Define to 1 if you have rl_ding()])],
 +    [],
 +    [$TINFO_LIBS]
 +  )
 +
 +  LIBS="$BASE_LIBS"
 +  CLIENT_LIBS="$READLINE_LIBS $TINFO_LIBS"
 +fi
 +AC_SUBST([CLIENT])
 +AC_SUBST([CLIENT_LIBS])
 +
 +mkdir -p $objdir/sysdep
 +AC_CONFIG_HEADERS([$objdir/sysdep/autoconf.h:sysdep/autoconf.h.in])
 +AC_CONFIG_FILES([Makefile:Makefile.in])
 +AC_OUTPUT
 +
 +AC_MSG_RESULT()
 +AC_MSG_RESULT([BIRD was configured with the following options:])
 +AC_MSG_RESULT([        Source directory:      $srcdir])
 +AC_MSG_RESULT([        Object directory:      $objdir])
 +AC_MSG_RESULT([        Iproute2 directory:    $iproutedir])
 +AC_MSG_RESULT([        System configuration:  $sysdesc])
 +AC_MSG_RESULT([        Debugging:             $enable_debug])
 +AC_MSG_RESULT([        POSIX threads:         $enable_pthreads])
 +AC_MSG_RESULT([        Routing protocols:     $protocols])
 +AC_MSG_RESULT([        Kernel MPLS support:   $enable_mpls_kernel])
 +AC_MSG_RESULT([        Client:                        $enable_client])
 +
 +rm -f $objdir/.*-stamp
diff --cc lib/ip.h
Simple merge
diff --cc lib/timer.c
index ed731d26e7b88a0b42b5a0f8545027f4ca813d8f,0000000000000000000000000000000000000000..a6d499afce1e312c0425c4af0c691f0a3ce7c09c
mode 100644,000000..100644
--- /dev/null
@@@ -1,378 -1,0 +1,386 @@@
 +/*
 + *    BIRD -- Timers
 + *
 + *    (c) 2013--2017 Ondrej Zajicek <santiago@crfreenet.org>
 + *    (c) 2013--2017 CZ.NIC z.s.p.o.
 + *
 + *    Can be freely distributed and used under the terms of the GNU GPL.
 + */
 +
 +/**
 + * DOC: Timers
 + *
 + * Timers are resources which represent a wish of a module to call a function at
 + * the specified time. The timer code does not guarantee exact timing, only that
 + * a timer function will not be called before the requested time.
 + *
 + * In BIRD, time is represented by values of the &btime type which is signed
 + * 64-bit integer interpreted as a relative number of microseconds since some
 + * fixed time point in past. The current time can be obtained by current_time()
 + * function with reasonable accuracy and is monotonic. There is also a current
 + * 'wall-clock' real time obtainable by current_real_time() reported by OS.
 + *
 + * Each timer is described by a &timer structure containing a pointer to the
 + * handler function (@hook), data private to this function (@data), time the
 + * function should be called at (@expires, 0 for inactive timers), for the other
 + * fields see |timer.h|.
 + */
 +
 +#include <stdio.h>
 +#include <stdlib.h>
 +#include <time.h>
 +
 +#include "nest/bird.h"
 +
 +#include "lib/heap.h"
 +#include "lib/resource.h"
 +#include "lib/timer.h"
 +
 +
 +struct timeloop main_timeloop;
 +
 +
 +#ifdef USE_PTHREADS
 +
 +#include <pthread.h>
 +
 +/* Data accessed and modified from proto/bfd/io.c */
 +pthread_key_t current_time_key;
 +
 +static inline struct timeloop *
 +timeloop_current(void)
 +{
 +  return pthread_getspecific(current_time_key);
 +}
 +
 +static inline void
 +timeloop_init_current(void)
 +{
 +  pthread_key_create(&current_time_key, NULL);
 +  pthread_setspecific(current_time_key, &main_timeloop);
 +}
 +
 +void wakeup_kick_current(void);
 +
 +#else
 +
 +/* Just use main timelooop */
 +static inline struct timeloop * timeloop_current(void) { return &main_timeloop; }
 +static inline void timeloop_init_current(void) { }
 +
 +#endif
 +
 +btime
 +current_time(void)
 +{
 +  return timeloop_current()->last_time;
 +}
 +
 +btime
 +current_real_time(void)
 +{
 +  struct timeloop *loop = timeloop_current();
 +
 +  if (!loop->real_time)
 +    times_update_real_time(loop);
 +
 +  return loop->real_time;
 +}
 +
 +
 +#define TIMER_LESS(a,b)               ((a)->expires < (b)->expires)
 +#define TIMER_SWAP(heap,a,b,t)        (t = heap[a], heap[a] = heap[b], heap[b] = t, \
 +                                 heap[a]->index = (a), heap[b]->index = (b))
 +
 +
 +static void
 +tm_free(resource *r)
 +{
 +  timer *t = (void *) r;
 +
 +  tm_stop(t);
 +}
 +
 +static void
 +tm_dump(resource *r)
 +{
 +  timer *t = (void *) r;
 +
 +  debug("(code %p, data %p, ", t->hook, t->data);
 +  if (t->randomize)
 +    debug("rand %d, ", t->randomize);
 +  if (t->recurrent)
 +    debug("recur %d, ", t->recurrent);
 +  if (t->expires)
 +    debug("expires in %d ms)\n", (t->expires - current_time()) TO_MS);
 +  else
 +    debug("inactive)\n");
 +}
 +
 +
 +static struct resclass tm_class = {
 +  "Timer",
 +  sizeof(timer),
 +  tm_free,
 +  tm_dump,
 +  NULL,
 +  NULL
 +};
 +
 +timer *
 +tm_new(pool *p)
 +{
 +  timer *t = ralloc(p, &tm_class);
 +  t->index = -1;
 +  return t;
 +}
 +
 +void
 +tm_set(timer *t, btime when)
 +{
 +  struct timeloop *loop = timeloop_current();
 +  uint tc = timers_count(loop);
 +
 +  if (!t->expires)
 +  {
 +    t->index = ++tc;
 +    t->expires = when;
 +    BUFFER_PUSH(loop->timers) = t;
 +    HEAP_INSERT(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP);
 +  }
 +  else if (t->expires < when)
 +  {
 +    t->expires = when;
 +    HEAP_INCREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
 +  }
 +  else if (t->expires > when)
 +  {
 +    t->expires = when;
 +    HEAP_DECREASE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
 +  }
 +
 +#ifdef CONFIG_BFD
 +  /* Hack to notify BFD loops */
 +  if ((loop != &main_timeloop) && (t->index == 1))
 +    wakeup_kick_current();
 +#endif
 +}
 +
 +void
 +tm_start(timer *t, btime after)
 +{
 +  tm_set(t, current_time() + MAX(after, 0));
 +}
 +
++void
++tm_shift(timer *t, btime delta)
++{
++  btime now = current_time();
++  if (t->expires && (t->expires > now))
++    tm_set(t, MAX(now, t->expires + delta));
++}
++
 +void
 +tm_stop(timer *t)
 +{
 +  if (!t->expires)
 +    return;
 +
 +  struct timeloop *loop = timeloop_current();
 +  uint tc = timers_count(loop);
 +
 +  HEAP_DELETE(loop->timers.data, tc, timer *, TIMER_LESS, TIMER_SWAP, t->index);
 +  BUFFER_POP(loop->timers);
 +
 +  t->index = -1;
 +  t->expires = 0;
 +}
 +
 +void
 +timers_init(struct timeloop *loop, pool *p)
 +{
 +  times_init(loop);
 +
 +  BUFFER_INIT(loop->timers, p, 4);
 +  BUFFER_PUSH(loop->timers) = NULL;
 +}
 +
 +void io_log_event(void *hook, void *data);
 +
 +void
 +timers_fire(struct timeloop *loop)
 +{
 +  btime base_time;
 +  timer *t;
 +
 +  times_update(loop);
 +  base_time = loop->last_time;
 +
 +  while (t = timers_first(loop))
 +  {
 +    if (t->expires > base_time)
 +      return;
 +
 +    if (t->recurrent)
 +    {
 +      btime when = t->expires + t->recurrent;
 +
 +      if (when <= loop->last_time)
 +      when = loop->last_time + t->recurrent;
 +
 +      if (t->randomize)
 +      when += random() % (t->randomize + 1);
 +
 +      tm_set(t, when);
 +    }
 +    else
 +      tm_stop(t);
 +
 +    /* This is ugly hack, we want to log just timers executed from the main I/O loop */
 +    if (loop == &main_timeloop)
 +      io_log_event(t->hook, t->data);
 +
 +    t->hook(t);
 +  }
 +}
 +
 +void
 +timer_init(void)
 +{
 +  timers_init(&main_timeloop, &root_pool);
 +  timeloop_init_current();
 +}
 +
 +
 +/**
 + * tm_parse_time - parse a date and time
 + * @x: time string
 + *
 + * tm_parse_time() takes a textual representation of a date and time
 + * (yyyy-mm-dd[ hh:mm:ss[.sss]]) and converts it to the corresponding value of
 + * type &btime.
 + */
 +btime
 +tm_parse_time(char *x)
 +{
 +  struct tm tm;
 +  int usec, n1, n2, n3, r;
 +
 +  r = sscanf(x, "%d-%d-%d%n %d:%d:%d%n.%d%n",
 +           &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &n1,
 +           &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &n2,
 +           &usec, &n3);
 +
 +  if ((r == 3) && !x[n1])
 +    tm.tm_hour = tm.tm_min = tm.tm_sec = usec = 0;
 +  else if ((r == 6) && !x[n2])
 +    usec = 0;
 +  else if ((r == 7) && !x[n3])
 +  {
 +    /* Convert subsecond digits to proper precision */
 +    int digits = n3 - n2 - 1;
 +    if ((usec < 0) || (usec > 999999) || (digits < 1) || (digits > 6))
 +      return 0;
 +
 +    while (digits++ < 6)
 +      usec *= 10;
 +  }
 +  else
 +    return 0;
 +
 +  tm.tm_mon--;
 +  tm.tm_year -= 1900;
 +  s64 ts = mktime(&tm);
 +  if ((ts == (s64) (time_t) -1) || (ts < 0) || (ts > ((s64) 1 << 40)))
 +    return 0;
 +
 +  return ts S + usec;
 +}
 +
 +/**
 + * tm_format_time - convert date and time to textual representation
 + * @x: destination buffer of size %TM_DATETIME_BUFFER_SIZE
 + * @fmt: specification of resulting textual representation of the time
 + * @t: time
 + *
 + * This function formats the given relative time value @t to a textual
 + * date/time representation (dd-mm-yyyy hh:mm:ss) in real time.
 + */
 +void
 +tm_format_time(char *x, struct timeformat *fmt, btime t)
 +{
 +  btime dt = current_time() - t;
 +  btime rt = current_real_time() - dt;
 +  int v1 = !fmt->limit || (dt < fmt->limit);
 +
 +  tm_format_real_time(x, v1 ? fmt->fmt1 : fmt->fmt2, rt);
 +}
 +
 +/* Replace %f in format string with usec scaled to requested precision */
 +static int
 +strfusec(char *buf, int size, const char *fmt, uint usec)
 +{
 +  char *str = buf;
 +  int parity = 0;
 +
 +  while (*fmt)
 +  {
 +    if (!size)
 +      return 0;
 +
 +    if ((fmt[0] == '%') && (!parity) &&
 +      ((fmt[1] == 'f') || (fmt[1] >= '1') && (fmt[1] <= '6') && (fmt[2] == 'f')))
 +    {
 +      int digits = (fmt[1] == 'f') ? 6 : (fmt[1] - '0');
 +      uint d = digits, u = usec;
 +
 +      /* Convert microseconds to requested precision */
 +      while (d++ < 6)
 +      u /= 10;
 +
 +      int num = bsnprintf(str, size, "%0*u", digits, u);
 +      if (num < 0)
 +      return 0;
 +
 +      fmt += (fmt[1] == 'f') ? 2 : 3;
 +      ADVANCE(str, size, num);
 +    }
 +    else
 +    {
 +      /* Handle '%%' expression */
 +      parity = (*fmt == '%') ? !parity : 0;
 +      *str++ = *fmt++;
 +      size--;
 +    }
 +  }
 +
 +  if (!size)
 +    return 0;
 +
 +  *str = 0;
 +  return str - buf;
 +}
 +
 +void
 +tm_format_real_time(char *x, const char *fmt, btime t)
 +{
 +  s64 t1 = t TO_S;
 +  s64 t2 = t - t1 S;
 +
 +  time_t ts = t1;
 +  struct tm tm;
 +  if (!localtime_r(&ts, &tm))
 +    goto err;
 +
 +  byte tbuf[TM_DATETIME_BUFFER_SIZE];
 +  if (!strfusec(tbuf, TM_DATETIME_BUFFER_SIZE, fmt, t2))
 +    goto err;
 +
 +  if (!strftime(x, TM_DATETIME_BUFFER_SIZE, tbuf, &tm))
 +    goto err;
 +
 +  return;
 +
 +err:
 +  strcpy(x, "<error>");
 +}
diff --cc lib/timer.h
index ed8f0d025035a4b38d2690fbcdd23af23f934a93,0000000000000000000000000000000000000000..80317581df5888ba3734e6ba05d9b0c5645bb486
mode 100644,000000..100644
--- /dev/null
@@@ -1,127 -1,0 +1,128 @@@
 +/*
 + *    BIRD -- Timers
 + *
 + *    (c) 2013--2017 Ondrej Zajicek <santiago@crfreenet.org>
 + *    (c) 2013--2017 CZ.NIC z.s.p.o.
 + *
 + *    Can be freely distributed and used under the terms of the GNU GPL.
 + */
 +
 +#ifndef _BIRD_TIMER_H_
 +#define _BIRD_TIMER_H_
 +
 +#include "nest/bird.h"
 +#include "lib/buffer.h"
 +#include "lib/resource.h"
 +
 +
 +typedef struct timer
 +{
 +  resource r;
 +  void (*hook)(struct timer *);
 +  void *data;
 +
 +  btime expires;                      /* 0=inactive */
 +  uint randomize;                     /* Amount of randomization */
 +  uint recurrent;                     /* Timer recurrence */
 +
 +  int index;
 +} timer;
 +
 +struct timeloop
 +{
 +  BUFFER_(timer *) timers;
 +  btime last_time;
 +  btime real_time;
 +};
 +
 +static inline uint timers_count(struct timeloop *loop)
 +{ return loop->timers.used - 1; }
 +
 +static inline timer *timers_first(struct timeloop *loop)
 +{ return (loop->timers.used > 1) ? loop->timers.data[1] : NULL; }
 +
 +extern struct timeloop main_timeloop;
 +
 +btime current_time(void);
 +btime current_real_time(void);
 +
 +//#define now (current_time() TO_S)
 +//#define now_real (current_real_time() TO_S)
 +extern btime boot_time;
 +
 +timer *tm_new(pool *p);
 +void tm_set(timer *t, btime when);
 +void tm_start(timer *t, btime after);
++void tm_shift(timer *t, btime delta);
 +void tm_stop(timer *t);
 +
 +static inline int
 +tm_active(timer *t)
 +{
 +  return t->expires != 0;
 +}
 +
 +static inline btime
 +tm_remains(timer *t)
 +{
 +  btime now_ = current_time();
 +  return (t->expires > now_) ? (t->expires - now_) : 0;
 +}
 +
 +static inline timer *
 +tm_new_init(pool *p, void (*hook)(struct timer *), void *data, uint rec, uint rand)
 +{
 +  timer *t = tm_new(p);
 +  t->hook = hook;
 +  t->data = data;
 +  t->recurrent = rec;
 +  t->randomize = rand;
 +  return t;
 +}
 +
 +static inline void
 +tm_set_max(timer *t, btime when)
 +{
 +  if (when > t->expires)
 +    tm_set(t, when);
 +}
 +
 +static inline void
 +tm_start_max(timer *t, btime after)
 +{
 +  btime rem = tm_remains(t);
 +  tm_start(t, MAX_(rem, after));
 +}
 +
 +/* In sysdep code */
 +void times_init(struct timeloop *loop);
 +void times_update(struct timeloop *loop);
 +void times_update_real_time(struct timeloop *loop);
 +
 +/* For I/O loop */
 +void timers_init(struct timeloop *loop, pool *p);
 +void timers_fire(struct timeloop *loop);
 +
 +void timer_init(void);
 +
 +
 +struct timeformat {
 +  char *fmt1, *fmt2;
 +  btime limit;
 +};
 +
 +#define TM_ISO_SHORT_S        (struct timeformat){"%T",     "%F", (s64) (20*3600) S_}
 +#define TM_ISO_SHORT_MS       (struct timeformat){"%T.%3f", "%F", (s64) (20*3600) S_}
 +#define TM_ISO_SHORT_US       (struct timeformat){"%T.%6f", "%F", (s64) (20*3600) S_}
 +
 +#define TM_ISO_LONG_S (struct timeformat){"%F %T",     NULL, 0}
 +#define TM_ISO_LONG_MS        (struct timeformat){"%F %T.%3f", NULL, 0}
 +#define TM_ISO_LONG_US        (struct timeformat){"%F %T.%6f", NULL, 0}
 +
 +#define TM_DATETIME_BUFFER_SIZE 32    /* Buffer size required by tm_format_time() */
 +
 +btime tm_parse_time(char *x);
 +void tm_format_time(char *x, struct timeformat *fmt, btime t);
 +void tm_format_real_time(char *x, const char *fmt, btime t);
 +
 +#endif
diff --cc nest/proto.c
index 15d6f4de6d9d056b77c5bbe3ab49c0ed64da7c67,634ac51db3ea9a42bff97719364931a49b5b7368..21ceca8de9c530b1fca770199210db3493177b3c
@@@ -1307,9 -1262,9 +1307,12 @@@ protos_build(void
  #ifdef CONFIG_BABEL
    proto_build(&proto_babel);
  #endif
 +#ifdef CONFIG_RPKI
 +  proto_build(&proto_rpki);
 +#endif
+ #ifdef CONFIG_IGMP
+   proto_build(&proto_igmp);
+ #endif
  
    proto_pool = rp_new(&root_pool, "Protocols");
    proto_shutdown_timer = tm_new(proto_pool);
diff --cc nest/protocol.h
index 9afd3a0a2f6fc7a63cc6bddfe1258975f5491aa0,14ebb889995f5069c4fb329788e9687d6abe752f..cfafa5873ba913975962ea7d2f7eb159fa923a7e
@@@ -79,8 -80,9 +79,8 @@@ void protos_dump_all(void)
   */
  
  extern struct protocol
--  proto_device, proto_radv, proto_rip, proto_static,
-   proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki;
 -  proto_ospf, proto_pipe, proto_bgp, proto_bfd,
 -  proto_igmp;
++  proto_device, proto_radv, proto_rip, proto_static, proto_ospf, proto_pipe,
++  proto_bgp, proto_bfd, proto_babel, proto_rpki, proto_igmp;
  
  /*
   *    Routing Protocol Instance
diff --cc nest/route.h
index 27f1192805ff7e12d0a8aedbdf49a50da8a10eb3,4d6151116ce664e7398e0c625bab8bafb3a655fc..2f7059463386fec9bf7bed069541acc739697415
@@@ -421,8 -399,7 +421,8 @@@ typedef struct rta 
  #define RTS_BGP 11                    /* BGP route */
  #define RTS_PIPE 12                   /* Inter-table wormhole */
  #define RTS_BABEL 13                  /* Babel route */
 -#define RTS_IGMP 14                   /* IGMP multicast request */
 +#define RTS_RPKI 14                   /* Route Origin Authorization */
++#define RTS_IGMP 15                   /* IGMP multicast request */
  
  #define RTC_UNICAST 0
  #define RTC_BROADCAST 1
@@@ -687,7 -579,6 +687,8 @@@ extern struct protocol *attr_class_to_p
  #define DEF_PREF_BABEL                130     /* Babel */
  #define DEF_PREF_RIP          120     /* RIP */
  #define DEF_PREF_BGP          100     /* BGP */
 +#define DEF_PREF_RPKI         100     /* RPKI */
++#define DEF_PREF_IGMP         100     /* IGMP */
  #define DEF_PREF_INHERITED    10      /* Routes inherited from other routing daemons */
  
  /*
diff --cc nest/rt-attr.c
index a7b4a74916aa36e8664f52b87dc29ed689269994,167bfc44a9dd76d5eb64666bd4750b4c8f375eb4..7b3824d2f72a95b13496fb275092f71a2510a786
@@@ -1204,12 -1113,13 +1204,12 @@@ rta_dump(rta *a
  {
    static char *rts[] = { "RTS_DUMMY", "RTS_STATIC", "RTS_INHERIT", "RTS_DEVICE",
                         "RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
--                       "RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
-                          "RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL" };
 -                         "RTS_OSPF_EXT2", "RTS_BGP" };
 -  static char *rtc[] = { "", " BC", " MC", " AC" };
++                       "RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1", "RTS_OSPF_EXT2",
++                       "RTS_BGP", "RTS_PIPE", "RTS_BABEL", "RTS_IGMP" };
    static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
  
 -  debug("p=%s uc=%d %s %s%s%s h=%04x",
 -      a->src->proto->name, a->uc, rts[a->source], ip_scope_text(a->scope), rtc[a->cast],
 +  debug("p=%s uc=%d %s %s%s h=%04x",
 +      a->src->proto->name, a->uc, rts[a->source], ip_scope_text(a->scope),
        rtd[a->dest], a->hash_key);
    if (!(a->aflags & RTAF_CACHED))
      debug(" !CACHED");
  rta_show(struct cli *c, rta *a, ea_list *eal)
  {
    static char *src_names[] = { "dummy", "static", "inherit", "device", "static-device", "redirect",
--                             "RIP", "OSPF", "OSPF-IA", "OSPF-E1", "OSPF-E2", "BGP", "pipe" };
 -  static char *cast_names[] = { "unicast", "broadcast", "multicast", "anycast" };
++                             "RIP", "OSPF", "OSPF-IA", "OSPF-E1", "OSPF-E2", "BGP", "pipe", "Babel", "RPKI", "IGMP" };
    int i;
  
 -  cli_printf(c, -1008, "\tType: %s %s %s", src_names[a->source], cast_names[a->cast], ip_scope_text(a->scope));
 +  cli_printf(c, -1008, "\tType: %s %s", src_names[a->source], ip_scope_text(a->scope));
    if (!eal)
      eal = a->eattrs;
    for(; eal; eal=eal->next)
diff --cc proto/Doc
index ef573d2afa64772eae8fdbea75fbec81d4809b93,04c25bc0acb0eed6e2025ec6ed5adbd8e0fc2ef8..fba4491a6513de06a9810a2e1ab8839035480d55
+++ b/proto/Doc
@@@ -2,10 -2,9 +2,11 @@@ H Protocol
  C babel
  C bfd
  C bgp
++C igmp
  C ospf
  C pipe
 -C rip
  C radv
 +C rip
 +C rpki
  C static
  S ../nest/rt-dev.c
diff --cc proto/igmp/Doc
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..1c8e46e4d615644387bd5d5842e9ae0bf5611951
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,2 @@@
++S igmp.c
++S packets.c
index 0000000000000000000000000000000000000000,b7fd5d5daa4f0ab2bf65abeedddfcfff622f162b..7b0030a6e119ecd095cb10f17f43a288e1f6d58b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,4 +1,6 @@@
+ src := igmp.c packets.c
+ obj := $(src-o-files)
+ $(all-daemon)
+ $(cf-local)
++
++tests_objs := $(tests_objs) $(src-o-files)
index 0000000000000000000000000000000000000000,1094dbc86d0d46a266757f47c3a9c772cd609347..7e4a2ebb40b19ebfeabf08f694d112faa3b9faf0
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,77 +1,86 @@@
 - *  BIRD -- IGMP protocol
+ /*
 - *  (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    BIRD -- Internet Group Management Protocol (IGMP)
+  *
 - *  Can be freely distributed and used under the terms of the GNU GPL.
++ *    (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
++ *    (c) 2018 CZ.NIC z.s.p.o.
+  *
 -CF_ADDTO(proto, igmp_proto '}' { igmp_config_finish(this_proto); })
++ *    Can be freely distributed and used under the terms of the GNU GPL.
+  */
+ CF_HDR
+ #include "proto/igmp/igmp.h"
+ CF_DEFINES
+ #define IGMP_CFG ((struct igmp_config *) this_proto)
+ #define IGMP_IFACE ((struct igmp_iface_config *) this_ipatt)
+ CF_DECLS
+ CF_KEYWORDS(IGMP, ROBUSTNESS, STARTUP, QUERY, COUNT, INTERVAL, LAST, MEMBER, RESPONSE)
+ CF_GRAMMAR
 -igmp_proto_start: proto_start IGMP {
 -   this_proto = proto_config_new(&proto_igmp, $1);
 -   igmp_config_init(IGMP_CFG);
 - }
 - ;
 -
 -igmp_proto:
 -   igmp_proto_start proto_name '{'
 - | igmp_proto igmp_proto_item ';'
 - ;
++CF_ADDTO(proto, igmp_proto)
 -  add_tail(&IGMP_CFG->patt_list, NODE this_ipatt);
 -  igmp_iface_config_init(IGMP_IFACE);
++igmp_proto_start: proto_start IGMP
++{
++  this_proto = proto_config_new(&proto_igmp, $1);
++  init_list(&IGMP_CFG->iface_list);
++};
+ igmp_proto_item:
+    proto_item
+  | proto_channel
+  | INTERFACE igmp_iface
+  ;
++igmp_proto_opts:
++   /* empty */
++ | igmp_proto_opts igmp_proto_item ';'
++ ;
++
++igmp_proto:
++   igmp_proto_start proto_name '{' igmp_proto_opts '}';
++
++
+ igmp_iface_start:
+ {
+   this_ipatt = cfg_allocz(sizeof(struct igmp_iface_config));
 -  igmp_iface_start iface_patt_list_nopx igmp_iface_opt_list { igmp_iface_config_finish(IGMP_IFACE); }
 -
 -
++  add_tail(&IGMP_CFG->iface_list, NODE this_ipatt);
++  init_list(&IGMP_IFACE->i.ipn_list);
++
++  IGMP_IFACE->robustness = IGMP_DEFAULT_ROBUSTNESS;
++  IGMP_IFACE->query_int = IGMP_DEFAULT_QUERY_INT;
++  IGMP_IFACE->query_response_int = IGMP_DEFAULT_RESPONSE_INT;
++  IGMP_IFACE->last_member_query_int = IGMP_DEFAULT_LAST_MEMBER_INT;
+ };
+ igmp_iface_item:
+    ROBUSTNESS expr { IGMP_IFACE->robustness = $2; }
+  | QUERY INTERVAL expr_us { IGMP_IFACE->query_int = $3; }
+  | STARTUP QUERY COUNT expr { IGMP_IFACE->startup_query_cnt = $4; }
+  | STARTUP QUERY INTERVAL expr_us { IGMP_IFACE->startup_query_int = $4; }
+  | QUERY RESPONSE INTERVAL expr_us { IGMP_IFACE->query_response_int = $4; }
+  | LAST MEMBER QUERY COUNT expr { IGMP_IFACE->last_member_query_cnt = $5; }
+  | LAST MEMBER QUERY INTERVAL expr_us { IGMP_IFACE->last_member_query_int = $5; }
+  ;
+ igmp_iface_opts:
+    /* empty */
+  | igmp_iface_opts igmp_iface_item ';'
+  ;
+ igmp_iface_opt_list:
+    /* empty */
+  | '{' igmp_iface_opts '}'
+  ;
+ igmp_iface:
++  igmp_iface_start iface_patt_list_nopx igmp_iface_opt_list { igmp_finish_iface_config(IGMP_IFACE); }
+ CF_CODE
+ CF_END
index 0000000000000000000000000000000000000000,877a0a82a9852d4e709c404beabc129b518c3164..8b4ab57a180c6f1eac273f1695e4128ba3d5b7ad
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,490 +1,624 @@@
 - *  BIRD --IGMP protocol
+ /*
 - *  (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    BIRD -- Internet Group Management Protocol (IGMP)
+  *
 - *  Can be freely distributed and used under the terms of the GNU GPL.
++ *    (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
++ *    (c) 2018 CZ.NIC z.s.p.o.
+  *
 -/*
++ *    Can be freely distributed and used under the terms of the GNU GPL.
+  */
 - * their multicast group memberships to any immediately- neighboring multicast
 - * routers.  This memo describes only the use of IGMP between hosts and routers
++/**
+  * DOC: Internet Group Management Protocol, Version 2
+  *
+  * The Internet Group Management Protocol (IGMP) is used by IP hosts to report
 - * their own queries.  IGMP may also be used between routers, but such use is
 - * not specified here.
++ * their multicast group memberships to any immediately-neighboring multicast
++ * routers. This memo describes only the use of IGMP between hosts and routers
+  * to determine group membership. Routers that are members of multicast groups
+  * are expected to behave as hosts as well as routers, and may even respond to
 - * interfaces to the BIRD's mutlicast request table. It needs to hold state for
++ * their own queries. IGMP may also be used between routers, but such use is not
++ * specified here.
+  *
+  * Its implementation is split into three files, |packets.c| handling low-level
+  * packet formats and |igmp.c| implementing BIRD interface and protocol logic.
+  *
+  * IGMP communicates with hosts, and publishes requests for groups and
 -#include "lib/ip.h"
 -#include "conf/conf.h"
++ * interfaces to the BIRD mutlicast request table. It needs to hold state for
+  * every group with local listeners.
+  */
+ #include "igmp.h"
 -#define HASH_GRP_KEY(n)               n->ga
 - * A change occured, update the request tables.
++
++#define HASH_GRP_KEY(n)               n->address
+ #define HASH_GRP_NEXT(n)      n->next
+ #define HASH_GRP_EQ(a,b)      ip4_equal(a,b)
+ #define HASH_GRP_FN(k)                ip4_hash(k)
++static const char *igmp_join_state_names[] = {
++  [IGMP_JS_NO_MEMBERS]        = "NoMembers",
++  [IGMP_JS_MEMBERS]   = "Members",
++  [IGMP_JS_V1_MEMBERS]        = "MembersV1",
++  [IGMP_JS_CHECKING]  = "Checking",
++};
++
++static const char *igmp_query_state_names[] = {
++  [IGMP_QS_INIT]      = "Init",
++  [IGMP_QS_QUERIER]   = "Querier",
++  [IGMP_QS_NONQUERIER]        = "Listener",
++};
++
++static struct igmp_group *igmp_find_group(struct igmp_iface *ifa, ip4_addr addr);
++static struct igmp_group *igmp_get_group(struct igmp_iface *ifa, ip4_addr addr);
++static void igmp_remove_group(struct igmp_group *grp);
++static void igmp_announce_group(struct igmp_group *grp, int up);
++static void igmp_group_set_state(struct igmp_group *grp, uint state);
++static void igmp_iface_set_state(struct igmp_iface *ifa, uint state);
++
+ /*
 -igmp_notify_routing(struct igmp_grp *grp, int join)
++ *    IGMP protocol logic
+  */
++
+ static void
 -  net_addr_mreq4 addr = NET_ADDR_MREQ4(grp->ga, grp->ifa->iface->index);
 -  struct igmp_proto *p = grp->ifa->proto;
++igmp_query_timeout(struct timer *tm)
+ {
 -  TRACE(D_EVENTS, "iface %s %s group %I", grp->ifa->iface->name, join ? "joined" : "left", ipa_from_ip4(grp->ga));
++  struct igmp_iface *ifa = tm->data;
 -  net *n = net_get(p->mreq_channel->table, (net_addr *) &addr);
 -  if (join)
 -    {
 -      rta a0 = {
 -        .src = p->p.main_source,
 -        .source = RTS_IGMP,
 -        .dest = RTD_MREQUEST,
 -        .iface = grp->ifa->iface,
 -      };
 -      rta *a = rta_lookup(&a0);
 -      rte *e = rte_get_temp(a);
 -
 -      e->net = n;
 -      rte_update2(p->mreq_channel, (net_addr *) &addr, e, p->p.main_source);
 -    }
 -  else
 -    {
 -      rte_update2(p->mreq_channel, (net_addr *) &addr, NULL, p->p.main_source);
 -    }
++  ASSERT(ifa->query_state == IGMP_QS_QUERIER);
 -igmp_gen_query_hook(struct timer *tm)
++  igmp_send_query(ifa, IP4_NONE, ifa->cf->query_response_int);
++
++  if (ifa->startup_query_cnt > 0)
++    ifa->startup_query_cnt--;
++
++  tm_start(ifa->query_timer, ifa->startup_query_cnt
++      ? ifa->cf->startup_query_int
++      : ifa->cf->query_int);
+ }
+ static void
 -  if (ifa->startup_query_cnt > 0)
 -    ifa->startup_query_cnt--;
 -
 -  igmp_tx_query(ifa, IP4_NONE);
++igmp_other_present_timeout(struct timer *tm)
+ {
+   struct igmp_iface *ifa = tm->data;
 -  tm_start(ifa->gen_query, ifa->startup_query_cnt
 -      ? ifa->cf->startup_query_int TO_S
 -      : ifa->cf->query_int TO_S);
++  ASSERT(ifa->query_state == IGMP_QS_NONQUERIER);
 -igmp_other_present_expire(struct timer *tm)
++  igmp_iface_set_state(ifa, IGMP_QS_QUERIER);
++  tm_start(ifa->query_timer, 0);
+ }
+ static void
 -  struct igmp_iface *ifa = tm->data;
++igmp_join_timeout(struct timer *tm)
+ {
 -  ifa->query_state = IGMP_QS_QUERIER;
 -  tm_start(ifa->gen_query, 0);
++  struct igmp_group *grp = tm->data;
++
++  ASSERT(grp->join_state != IGMP_JS_NO_MEMBERS);
 -/******************************************************************************
 -                                Group state management
 - ******************************************************************************/
++  igmp_group_set_state(grp, IGMP_JS_NO_MEMBERS);
++  igmp_announce_group(grp, 0);
++  igmp_remove_group(grp);
+ }
 -igmp_grp_free(struct igmp_grp *grp)
++static void
++igmp_v1_host_timeout(struct timer *tm)
++{
++  struct igmp_group *grp = tm->data;
++  ASSERT(grp->join_state == IGMP_JS_V1_MEMBERS);
++
++  igmp_group_set_state(grp, IGMP_JS_MEMBERS);
++}
+ static void
 -  rfree(grp->join_timer);
 -  rfree(grp->v1_host_timer);
 -  rfree(grp->rxmt_timer);
++igmp_rxmt_timeout(struct timer *tm)
+ {
 -  HASH_REMOVE(grp->ifa->groups, HASH_GRP, grp);
 -  mb_free(grp);
++  struct igmp_group *grp = tm->data;
++  struct igmp_iface *ifa = grp->ifa;
 -static void
 -igmp_grp_v1_timer_expire(struct timer *tm)
++  ASSERT(grp->join_state == IGMP_JS_CHECKING);
++
++  igmp_send_query(ifa, grp->address, ifa->cf->last_member_query_int);
++  tm_start(grp->rxmt_timer, ifa->cf->last_member_query_int);
+ }
 -  struct igmp_grp *grp = tm->data;
++void
++igmp_handle_query(struct igmp_iface *ifa, ip4_addr addr, ip4_addr from, btime resp_time)
+ {
 -  if (grp->join_state == IGMP_JS_V1MEMB)
 -    grp->join_state = IGMP_JS_MEMB;
 -  else
 -    bug("V1 timer expired without v1 hosts");
++  /* Another router with lower IP shall be the Querier */
++  if ((ifa->query_state == IGMP_QS_QUERIER) &&
++      (ip4_compare(from, ipa_to_ip4(ifa->sk->saddr)) < 0))
++  {
++    igmp_iface_set_state(ifa, IGMP_QS_NONQUERIER);
++    ifa->startup_query_cnt = 0;
++    tm_stop(ifa->query_timer);
++    tm_start(ifa->other_present, ifa->cf->other_querier_int);
++  }
 -static void
 -igmp_grp_join_timer_expire(struct timer *tm)
++  if ((ifa->query_state == IGMP_QS_NONQUERIER) && ip4_nonzero(addr))
++  {
++    struct igmp_group *grp = igmp_find_group(ifa, addr);
++
++    if (grp && (grp->join_state == IGMP_JS_MEMBERS))
++    {
++      igmp_group_set_state(grp, IGMP_JS_CHECKING);
++      tm_start(grp->join_timer, resp_time * ifa->cf->last_member_query_cnt);
++      tm_stop(grp->rxmt_timer);
++    }
++  }
+ }
 -  struct igmp_grp *grp = tm->data;
++void
++igmp_handle_report(struct igmp_iface *ifa, ip4_addr addr, int version)
+ {
 -  switch (grp->join_state) {
 -    case IGMP_JS_NOMEMB:
 -      bug("IGMP GRP without members expired.");
 -      return;
 -    case IGMP_JS_V1MEMB:
 -    case IGMP_JS_MEMB:
 -    case IGMP_JS_CHECK:
 -      grp->join_state = IGMP_JS_NOMEMB;
 -      igmp_notify_routing(grp, 0);
 -      igmp_grp_free(grp);
++  struct igmp_group *grp = igmp_get_group(ifa, addr);
++  uint last_state = grp->join_state;
 -static void
 -igmp_grp_retransmit_expire(struct timer *tm)
++  if (version == 1)
++  {
++    igmp_group_set_state(grp, IGMP_JS_V1_MEMBERS);
++    tm_start(grp->v1_host_timer, ifa->cf->group_member_int + 100 MS);
+   }
++  else
++  {
++    if (last_state != IGMP_JS_V1_MEMBERS)
++      igmp_group_set_state(grp, IGMP_JS_MEMBERS);
++  }
++
++  tm_start(grp->join_timer, ifa->cf->group_member_int);
++  tm_stop(grp->rxmt_timer);
++
++  if (last_state == IGMP_JS_NO_MEMBERS)
++    igmp_announce_group(grp, 1);
+ }
 -  struct igmp_grp *grp = tm->data;
++void
++igmp_handle_leave(struct igmp_iface *ifa, ip4_addr addr)
+ {
 -  if (grp->join_state != IGMP_JS_CHECK)
 -    return;
++  if (ifa->query_state == IGMP_QS_QUERIER)
++  {
++    struct igmp_group *grp = igmp_find_group(ifa, addr);
 -  igmp_tx_query(grp->ifa, grp->ga);
 -  tm_start(grp->rxmt_timer, grp->ifa->cf->last_member_query_int TO_S);
++    if (grp && (grp->join_state == IGMP_JS_MEMBERS))
++    {
++      igmp_group_set_state(grp, IGMP_JS_CHECKING);
++      tm_start(grp->join_timer, ifa->cf->last_member_query_int * ifa->cf->last_member_query_cnt);
++      tm_start(grp->rxmt_timer, 0);
++    }
++  }
++}
++
++
++/*
++ *    IGMP groups
++ */
 -struct igmp_grp *
 -igmp_grp_new(struct igmp_iface *ifa, ip4_addr *ga)
++static struct igmp_group *
++igmp_find_group(struct igmp_iface *ifa, ip4_addr addr)
++{
++  return HASH_FIND(ifa->groups, HASH_GRP, addr);
+ }
 -  struct igmp_grp *grp = mb_allocz(p->p.pool, sizeof(struct igmp_grp));
 -  grp->ga = *ga;
++static struct igmp_group *
++igmp_get_group(struct igmp_iface *ifa, ip4_addr addr)
+ {
+   struct igmp_proto *p = ifa->proto;
 -  grp->join_timer = tm_new_set(ifa->proto->p.pool, igmp_grp_join_timer_expire, grp, 0, 0);
 -  grp->rxmt_timer = tm_new_set(ifa->proto->p.pool, igmp_grp_retransmit_expire, grp, 0, 0);
 -  grp->v1_host_timer = tm_new_set(ifa->proto->p.pool, igmp_grp_v1_timer_expire, grp, 0, 0);
++  struct igmp_group *grp = igmp_find_group(ifa, addr);
++
++  if (grp)
++    return grp;
++
++  grp = mb_allocz(p->p.pool, sizeof(struct igmp_group));
++  grp->address = addr;
+   grp->ifa = ifa;
+   HASH_INSERT(ifa->groups, HASH_GRP, grp);
 -struct igmp_grp *
 -igmp_grp_find(struct igmp_iface *ifa, ip4_addr *ga)
++  grp->join_timer = tm_new_init(p->p.pool, igmp_join_timeout, grp, 0, 0);
++  grp->rxmt_timer = tm_new_init(p->p.pool, igmp_rxmt_timeout, grp, 0, 0);
++  grp->v1_host_timer = tm_new_init(p->p.pool, igmp_v1_host_timeout, grp, 0, 0);
+   return grp;
+ }
 -  return HASH_FIND(ifa->groups, HASH_GRP, *ga);
 -}
++static void
++igmp_remove_group(struct igmp_group *grp)
+ {
 -/******************************************************************************
 -                                Iface management
 - ******************************************************************************/
++  rfree(grp->join_timer);
++  rfree(grp->v1_host_timer);
++  rfree(grp->rxmt_timer);
++  HASH_REMOVE(grp->ifa->groups, HASH_GRP, grp);
++  mb_free(grp);
++}
 -static inline int
 -igmp_iface_is_up(struct igmp_iface *ifa)
++static void
++igmp_announce_group(struct igmp_group *grp, int up)
++{
++  struct igmp_proto *p = grp->ifa->proto;
++  net_addr_mreq4 addr = NET_ADDR_MREQ4(grp->address, grp->ifa->iface->index);
++
++  if (up)
++  {
++    rta a0 = {
++      .src = p->p.main_source,
++      .source = RTS_IGMP,
++      .scope = SCOPE_UNIVERSE,
++      .dest = RTD_NONE,
++      .nh.iface = grp->ifa->iface,
++    };
++    rta *a = rta_lookup(&a0);
++    rte *e = rte_get_temp(a);
++
++    e->pflags = 0;
++    rte_update(&p->p, (net_addr *) &addr, e);
++  }
++  else
++  {
++    rte_update(&p->p, (net_addr *) &addr, NULL);
++  }
++}
 -  return !!ifa->sk;
++static void
++igmp_group_set_state(struct igmp_group *grp, uint state)
+ {
 -igmp_iface_new(struct igmp_proto *p, struct iface *iface, struct igmp_iface_config *ic)
++  struct igmp_proto *p = grp->ifa->proto;
++  uint last_state = grp->join_state;
++
++  if (state == last_state)
++    return;
++
++  TRACE(D_EVENTS, "Group %I4 on %s changed state from %s to %s",
++      grp->address, grp->ifa->iface->name,
++      igmp_join_state_names[last_state], igmp_join_state_names[state]);
++
++  grp->join_state = state;
+ }
++
++/*
++ *    IGMP interfaces
++ */
++
+ static struct igmp_iface *
 -  struct igmp_iface *ifa = mb_allocz(p->p.pool, sizeof(struct igmp_iface));
++igmp_find_iface(struct igmp_proto *p, struct iface *what)
++{
++  struct igmp_iface *ifa;
++
++  WALK_LIST(ifa, p->iface_list)
++    if (ifa->iface == what)
++      return ifa;
++
++  return NULL;
++}
++
++
++static void
++igmp_iface_locked(struct object_lock *lock)
++{
++  struct igmp_iface *ifa = lock->data;
++  struct igmp_proto *p = ifa->proto;
++
++  if (!igmp_open_socket(ifa))
++  {
++    log(L_ERR "%s: Cannot open socket for %s", p->p.name, ifa->iface->name);
++    return;
++  }
++
++  igmp_iface_set_state(ifa, IGMP_QS_QUERIER);
++  ifa->startup_query_cnt = ifa->cf->startup_query_cnt;
++  tm_start(ifa->query_timer, 0);
++}
++
++static void
++igmp_add_iface(struct igmp_proto *p, struct iface *iface, struct igmp_iface_config *ic)
+ {
 -  ifa->proto = p;
 -  ifa->gen_id = random_u32();
 -  ifa->startup_query_cnt = ifa->cf->startup_query_cnt;
++  struct igmp_iface *ifa;
++
++  TRACE(D_EVENTS, "Adding interface %s", iface->name);
++
++  ifa = mb_allocz(p->p.pool, sizeof(struct igmp_iface));
+   add_tail(&p->iface_list, NODE ifa);
++  ifa->proto = p;
+   ifa->iface = iface;
+   ifa->cf = ic;
 -  ifa->gen_query = tm_new_set(p->p.pool, igmp_gen_query_hook, ifa, 0, 0);
 -  ifa->other_present = tm_new_set(p->p.pool, igmp_other_present_expire, ifa, 0, 0);
 -  if (igmp_sk_open(ifa))
 -      log(L_ERR "Failed opening socket for IGMP");
++  ifa->query_timer = tm_new_init(p->p.pool, igmp_query_timeout, ifa, 0, 0);
++  ifa->other_present = tm_new_init(p->p.pool, igmp_other_present_timeout, ifa, 0, 0);
+   HASH_INIT(ifa->groups, p->p.pool, 8);
 -  ifa->query_state = IGMP_QS_QUERIER;
 -  tm_start(ifa->gen_query, 0);
++  ifa->mif = mif_get(p->mif_group, iface);
++  if (!ifa->mif)
++  {
++    log(L_ERR "%s: Cannot enable multicast on %s, too many MIFs", p->p.name, ifa->iface->name);
++    return;
++  }
 -  return ifa;
++  struct object_lock *lock = olock_new(p->p.pool);
++  lock->type = OBJLOCK_IP;
++  lock->port = IGMP_PROTO;
++  lock->iface = iface;
++  lock->data = ifa;
++  lock->hook = igmp_iface_locked;
++  ifa->lock = lock;
 -static int
 -igmp_iface_down(struct igmp_iface *ifa)
++  olock_acquire(lock);
+ }
 -  if (!igmp_iface_is_up(ifa))
 -    return 0;
++static void
++igmp_remove_iface(struct igmp_proto *p, struct igmp_iface *ifa)
+ {
 -  rfree(ifa->sk);
 -  ifa->sk = NULL;
 -  return 0;
 -}
++  TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name);
 -static int
 -igmp_iface_free(struct igmp_iface* ifa)
 -{
++  HASH_WALK_DELSAFE(ifa->groups, next, grp)
++  {
++    igmp_announce_group(grp, 0);
++    igmp_remove_group(grp);
++  }
++  HASH_WALK_END;
 -  HASH_WALK_DELSAFE(ifa->groups, next, grp)
 -    {
 -      igmp_notify_routing(grp, 0);
 -      igmp_grp_free(grp);
 -    }
 -  HASH_WALK_END;
+   rem_node(NODE ifa);
 -  rfree(ifa->gen_query);
++  /* This is not a resource */
++  if (ifa->mif)
++    mif_free(p->mif_group, ifa->mif);
 -  return 0;
++  rfree(ifa->sk);
++  rfree(ifa->lock);
++  rfree(ifa->query_timer);
+   rfree(ifa->other_present);
++
+   mb_free(ifa);
 -static char *join_states[] = {
 -  [IGMP_JS_NOMEMB] = "no members",
 -  [IGMP_JS_MEMB] = "members",
 -  [IGMP_JS_V1MEMB] = "v1 members",
 -  [IGMP_JS_CHECK] = "about to expire",
 -};
 -
 -static char *query_states[] = {
 -  [IGMP_QS_INIT] = "initializing",
 -  [IGMP_QS_QUERIER] = "querier",
 -  [IGMP_QS_NONQUERIER] = "other querier present",
 -};
 -
+ }
 -igmp_iface_dump(struct igmp_iface *ifa)
+ static void
 -    struct igmp_proto *p = ifa->proto;
 -
 -    debug("\tInterface %s is %s, %s\n", ifa->iface->name, igmp_iface_is_up(ifa) ? "up" : "down", query_states[ifa->query_state]);
++igmp_iface_set_state(struct igmp_iface *ifa, uint state)
+ {
 -    HASH_WALK(ifa->groups, next, grp)
 -      TRACE(D_EVENTS, "\t\tGroup %I4: %s\n", grp->ga, join_states[grp->join_state]);
 -    HASH_WALK_END;
 -}
++  struct igmp_proto *p = ifa->proto;
++  uint last_state = ifa->query_state;
 -struct igmp_iface *
 -igmp_iface_find(struct igmp_proto *p, struct iface * ifa)
 -{
 -  struct igmp_iface * pif;
 -  WALK_LIST(pif, p->iface_list)
 -    if (pif->iface == ifa)
 -      return pif;
++  if (state == last_state)
++    return;
 -  return NULL;
++  TRACE(D_EVENTS, "Interface %s changed state from %s to %s", ifa->iface->name,
++      igmp_query_state_names[last_state], igmp_query_state_names[state]);
 -void
 -igmp_iface_config_init(struct igmp_iface_config * ifc)
++  ifa->query_state = state;
+ }
 -  init_list(&ifc->i.ipn_list);
++static void
++igmp_iface_dump(struct igmp_iface *ifa)
+ {
 -  ifc->robustness = 2;
 -  ifc->query_int = 125 S;
 -  ifc->query_response_int = 10 S;
 -  ifc->last_member_query_int = 1 S;
 -  ifc->last_member_query_cnt = -1U;
 -
 -  ifc->startup_query_cnt = -1U;
 -  ifc->startup_query_int = -1U;
++  debug("\tInterface %s: %s\n", ifa->iface->name, igmp_query_state_names[ifa->query_state]);
 -igmp_iface_config_finish(struct igmp_iface_config * ifc)
++  HASH_WALK(ifa->groups, next, grp)
++    debug("\t\tGroup %I4: %s\n", grp->address, igmp_join_state_names[grp->join_state]);
++  HASH_WALK_END;
+ }
+ void
 -  /* Explicit constraints - probably bail out? */
 -  if (ifc->robustness == 0)
 -    cf_error("IGMP: Robustness must be at least 1.");
 -
 -  if (ifc->query_response_int > ifc->query_int)
 -    cf_error("IGMP: The query response interval must not be greater than the query interval.");
++igmp_finish_iface_config(struct igmp_iface_config *cf)
+ {
 -  if (ifc->startup_query_int == -1U)
 -    ifc->startup_query_int = ifc->query_int / 4;
++  if (cf->query_response_int >= cf->query_int)
++    cf_error("Query response interval must be less than query interval");
+   /* Dependent default values */
 -  if (ifc->startup_query_cnt == -1U)
 -    ifc->startup_query_cnt = ifc->robustness;
++  if (!cf->startup_query_int)
++    cf->startup_query_int = cf->query_int / 4;
 -  if (ifc->last_member_query_cnt == -1U)
 -    ifc->last_member_query_cnt = ifc->robustness;
++  if (!cf->startup_query_cnt)
++    cf->startup_query_cnt = cf->robustness;
 -  ifc->group_memb_int = ifc->robustness * ifc->query_int + ifc->query_response_int;
 -  ifc->other_querier_int = ifc->robustness * ifc->query_int + ifc->query_response_int / 2;
++  if (!cf->last_member_query_cnt)
++    cf->last_member_query_cnt = cf->robustness;
 -/******************************************************************************
 -                              Protocol logic
 - ******************************************************************************/
 -
 -int
 -igmp_query_received(struct igmp_iface *ifa, ip4_addr from)
++  cf->group_member_int = cf->robustness * cf->query_int + cf->query_response_int;
++  cf->other_querier_int = cf->robustness * cf->query_int + cf->query_response_int / 2;
+ }
 -  if (ifa->query_state != IGMP_QS_QUERIER)
 -    return 0;
++static int
++igmp_reconfigure_iface(struct igmp_proto *p, struct igmp_iface *ifa, struct igmp_iface_config *new)
+ {
++  struct igmp_iface_config *old = ifa->cf;
 -  /* Find first IPv4 address of the interface */
 -  /* XXX: cache and update in if_notify? */
 -  struct ifa *my_addr = ifa_find_match(ifa->iface, NB_IP4);
++  TRACE(D_EVENTS, "Reconfiguring interface %s", ifa->iface->name);
 -  /* Another router with lower IP shall be the Querier */
 -  if (ip4_compare(ipa_to_ip4(my_addr->ip), from) > 0)
++  ifa->cf = new;
 -      ifa->query_state = IGMP_QS_NONQUERIER;
 -      tm_start(ifa->other_present, ifa->cf->other_querier_int TO_S);
++  /* We reconfigure just the query timer, as it is periodic */
++  if (ifa->query_state == IGMP_QS_QUERIER)
++  {
++    if (ifa->startup_query_cnt)
+     {
 -  return 0;
++      if (new->startup_query_cnt != old->startup_query_cnt)
++      {
++      int delta = (int) new->startup_query_cnt - (int) old->startup_query_cnt;
++      ifa->startup_query_cnt = MAX(1, ifa->startup_query_cnt + delta);
++      }
++
++      if (new->startup_query_int != old->startup_query_int)
++      tm_shift(ifa->query_timer, new->startup_query_int - old->startup_query_int);
+     }
++    else
++    {
++      if (new->query_int != old->query_int)
++      tm_shift(ifa->query_timer, new->query_int - old->query_int);
++    }
++  }
 -int
 -igmp_membership_report(struct igmp_grp *grp, u8 igmp_version, u8 resp_time)
++  return 1;
+ }
 -  struct igmp_proto *p = grp->ifa->proto;
 -  uint last_state = grp->join_state;
 -  TRACE(D_PACKETS, "Membership report received for group %I4 on iface %s", grp->ga, grp->ifa->iface->name);
++static void
++igmp_reconfigure_ifaces(struct igmp_proto *p, struct igmp_config *cf)
+ {
 -  if (grp->ifa->query_state == IGMP_QS_QUERIER && igmp_version == 1)
 -    {
 -      grp->join_state = IGMP_JS_V1MEMB;
 -      tm_start(grp->v1_host_timer, grp->ifa->cf->group_memb_int TO_S);
 -    }
++  struct iface *iface;
 -  if (grp->join_state != IGMP_JS_V1MEMB)
 -    grp->join_state = IGMP_JS_MEMB;
++  WALK_LIST(iface, iface_list)
++  {
++    if (!(iface->flags & IF_UP))
++      continue;
 -  tm_stop(grp->rxmt_timer);
 -  tm_start(grp->join_timer, grp->ifa->cf->group_memb_int TO_S);
++    /* Ignore non-multicast ifaces */
++    if (!(iface->flags & IF_MULTICAST))
++      continue;
 -  if (last_state == IGMP_JS_NOMEMB)
 -    igmp_notify_routing(grp, 1);
++    /* Ignore ifaces without IPv4 address */
++    if (!iface->addr4)
++      continue;
 -  return 0;
 -}
++    struct igmp_iface *ifa = igmp_find_iface(p, iface);
++    struct igmp_iface_config *ic = (void *) iface_patt_find(&cf->iface_list, iface, NULL);
 -int
 -igmp_leave(struct igmp_grp *grp, u8 resp_time)
 -{
 -  if (!grp)
 -    return 0;
++    if (ifa && ic)
++    {
++      if (igmp_reconfigure_iface(p, ifa, ic))
++      continue;
 -  struct igmp_proto *p = grp->ifa->proto;
++      /* Hard restart */
++      log(L_INFO "%s: Restarting interface %s", p->p.name, ifa->iface->name);
++      igmp_remove_iface(p, ifa);
++      igmp_add_iface(p, iface, ic);
++    }
 -  TRACE(D_PACKETS, "Leave received for group %I4 on iface %s", grp->ga, grp->ifa->iface->name);
 -  grp->join_state = IGMP_JS_CHECK;
 -  tm_start(grp->rxmt_timer, 0);
 -  tm_start(grp->join_timer, (grp->ifa->cf->last_member_query_int TO_S) * grp->ifa->cf->last_member_query_cnt);
 -  return 0;
++    if (ifa && !ic)
++      igmp_remove_iface(p, ifa);
 -/******************************************************************************
 -                              Others
 - ******************************************************************************/
++    if (!ifa && ic)
++      igmp_add_iface(p, iface, ic);
++  }
+ }
 -void
 -igmp_config_init(struct igmp_config *cf)
 -{
 -   init_list(&cf->patt_list);
 -   igmp_iface_config_init(&cf->default_iface_cf);
 -   igmp_iface_config_finish(&cf->default_iface_cf);
 -}
 -igmp_config_finish(struct proto_config *c)
++/*
++ *    IGMP protocol glue
++ */
+ void
 -  if (NULL == proto_cf_find_channel(c, NET_MREQ4))
 -    channel_config_new(NULL, NET_MREQ4, c);
++igmp_postconfig(struct proto_config *CF)
+ {
 -  struct igmp_proto *p = (struct igmp_proto *) P;
 -  struct igmp_config *c = (struct igmp_config *) P->cf;
++  // struct igmp_config *cf = (void *) CF;
++
++  /* Define default channel */
++  if (EMPTY_LIST(CF->channels))
++    channel_config_new(NULL, net_label[NET_MREQ4], NET_MREQ4, CF);
+ }
+ static void
+ igmp_if_notify(struct proto *P, uint flags, struct iface *iface)
+ {
 -    {
 -      struct igmp_iface_config *ic;
 -      ic = (struct igmp_iface_config *) iface_patt_find(&c->patt_list, iface, iface->addr);
 -      if (!ic)
 -      ic = &c->default_iface_cf;
 -      igmp_iface_new(p, iface, ic);
++  struct igmp_proto *p = (void *) P;
++  struct igmp_config *cf = (void *) P->cf;
+   if (iface->flags & IF_IGNORE)
+     return;
+   if (flags & IF_CHANGE_UP)
 -    }
++  {
++    struct igmp_iface_config *ic = (void *) iface_patt_find(&cf->iface_list, iface, NULL);
++
++    /* Ignore non-multicast ifaces */
++    if (!(iface->flags & IF_MULTICAST))
+       return;
 -    {
 -      struct igmp_iface * ifa = igmp_iface_find(p, iface);
 -      igmp_iface_down(ifa);
 -      igmp_iface_free(ifa);
 -    }
++    /* Ignore ifaces without IPv4 address */
++    if (!iface->addr4)
++      return;
++
++    if (ic)
++      igmp_add_iface(p, iface, ic);
++
++    return;
++  }
++
++  struct igmp_iface *ifa = igmp_find_iface(p, iface);
++
++  if (!ifa)
++    return;
+   if (flags & IF_CHANGE_DOWN)
 -  struct igmp_proto *p = (struct igmp_proto *) P;
++  {
++    igmp_remove_iface(p, ifa);
++    return;
++  }
++}
++
++static struct proto *
++igmp_init(struct proto_config *CF)
++{
++  struct igmp_proto *p = proto_new(CF);
++
++  p->p.main_channel = proto_add_channel(&p->p, proto_cf_main_channel(CF));
++
++  p->p.if_notify = igmp_if_notify;
++
++  p->mif_group = global_mif_group;
++
++  return &p->p;
+ }
+ static int
+ igmp_start(struct proto *P)
+ {
 -  struct igmp_proto *p = (struct igmp_proto *) P;
++  struct igmp_proto *p = (void *) P;
++
+   init_list(&p->iface_list);
++  p->log_pkt_tbf = (struct tbf){ .rate = 1, .burst = 5 };
++
+   return PS_UP;
+ }
+ static int
+ igmp_shutdown(struct proto *P)
+ {
 -    {
 -      igmp_iface_down(ifa);
 -      igmp_iface_free(ifa);
 -    }
++  struct igmp_proto *p = (void *) P;
+   struct igmp_iface *ifa;
++
+   WALK_LIST_FIRST(ifa, p->iface_list)
 -  struct igmp_proto *p = (struct igmp_proto *) P;
++    igmp_remove_iface(p, ifa);
+   return PS_DOWN;
+ }
++static int
++igmp_reconfigure(struct proto *P, struct proto_config *CF)
++{
++  struct igmp_proto *p = (void *) P;
++  struct igmp_config *new = (void *) CF;
++
++  if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
++    return 0;
++
++  TRACE(D_EVENTS, "Reconfiguring");
++
++  p->p.cf = CF;
++  igmp_reconfigure_ifaces(p, new);
++
++  return 1;
++}
++
+ static void
+ igmp_dump(struct proto *P)
+ {
 -/*
 - * We do not want to receive any route updates.
 - */
 -static int
 -igmp_reject(struct proto *p, rte **e, ea_list **attrs, struct linpool *pool)
 -{ return -1; }
 -
 -static struct proto *
 -igmp_init(struct proto_config *C)
 -{
 -  struct proto *P = proto_new(C);
 -  struct igmp_proto *p = (struct igmp_proto *) P;
 -
 -  p->mreq_channel = proto_add_channel(P, proto_cf_find_channel(C, NET_MREQ4));
 -
 -  p->cf = (struct igmp_config *) C;
 -  P->if_notify = igmp_if_notify;
 -  P->import_control = igmp_reject;
 -
 -  return P;
 -}
++  struct igmp_proto *p = (void *) P;
+   struct igmp_iface *ifa;
++
+   WALK_LIST(ifa, p->iface_list)
+     igmp_iface_dump(ifa);
+ }
 -      .name =         "IGMP",
 -      .template =     "igmp%d",
 -      .preference =   DEF_PREF_STATIC,
 -      .proto_size =   sizeof(struct igmp_proto),
 -      .config_size =  sizeof(struct igmp_config),
 -      .channel_mask = NB_MREQ4,
 -      .init =         igmp_init,
 -      .dump =         igmp_dump,
 -      .start =        igmp_start,
 -      .shutdown =     igmp_shutdown,
+ struct protocol proto_igmp = {
 -
++  .name =             "IGMP",
++  .template =         "igmp%d",
++  .preference =               DEF_PREF_IGMP,
++  .channel_mask =     NB_MREQ4,
++  .proto_size =               sizeof(struct igmp_proto),
++  .config_size =      sizeof(struct igmp_config),
++  .postconfig =               igmp_postconfig,
++  .init =             igmp_init,
++  .dump =             igmp_dump,
++  .start =            igmp_start,
++  .shutdown =         igmp_shutdown,
++  .reconfigure =      igmp_reconfigure,
+ };
index 0000000000000000000000000000000000000000,3a3f1d3220e9bd0387d7211fc741bb55a5a94079..46ad462e49421c84992a44cc660f1b3747b3569d
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,113 +1,110 @@@
 - *  BIRD --IGMP protocol
+ /*
 - *  (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    BIRD -- IGMP protocol
+  *
 - *  Can be freely distributed and used under the terms of the GNU GPL.
++ *    (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
++ *    (c) 2018 CZ.NIC z.s.p.o.
+  *
 -#include "filter/filter.h"
++ *    Can be freely distributed and used under the terms of the GNU GPL.
+  */
+ #ifndef _BIRD_IGMP_H_
+ #define _BIRD_IGMP_H_
+ #include "nest/bird.h"
+ #include "nest/iface.h"
+ #include "nest/locks.h"
+ #include "nest/protocol.h"
+ #include "nest/route.h"
+ #include "conf/conf.h"
++#include "lib/lists.h"
+ #include "lib/hash.h"
+ #include "lib/socket.h"
 -#include <linux/mroute.h>
++#include "lib/timer.h"
 -  btime query_int, query_response_int, startup_query_int,
 -      last_member_query_int;
++#define IGMP_PROTO  2
++
++#define IGMP_DEFAULT_ROBUSTNESS               2
++#define IGMP_DEFAULT_QUERY_INT                (125 S_)
++#define IGMP_DEFAULT_RESPONSE_INT     (10 S_)
++#define IGMP_DEFAULT_LAST_MEMBER_INT  (1 S_)
++
++
++struct igmp_config
++{
++  struct proto_config c;
++  list iface_list;                    /* List of ifaces (struct igmp_iface_config) */
++};
+ struct igmp_iface_config
+ {
+   struct iface_patt i;
+   /* These are configurable */
+   uint robustness, startup_query_cnt, last_member_query_cnt;
 -  btime group_memb_int, other_querier_int;
++  btime query_int, query_response_int, startup_query_int, last_member_query_int;
+   /* These are not */
 -struct igmp_config
 -{
 -  struct proto_config c;
 -  list patt_list;                     /* list of ifaces (struct igmp_iface_config) */
 -
 -  struct igmp_iface_config default_iface_cf;
 -};
 -
 -struct igmp_grp
++  btime group_member_int, other_querier_int;
+ };
 -  struct igmp_grp *next;              /* member of igmp_iface->groups */
 -  struct igmp_iface *ifa;
++struct igmp_proto
+ {
 -  ip4_addr ga;
 -  uint join_state;
 -  timer *join_timer, *v1_host_timer, *rxmt_timer;
++  struct proto p;
++  list iface_list;                    /* List of interfaces (struct igmp_iface) */
++  struct mif_group *mif_group;                /* Associated MIF group for multicast routes */
 -#define IGMP_JS_NOMEMB  0
 -#define IGMP_JS_MEMB    1
 -#define IGMP_JS_V1MEMB  2
 -#define IGMP_JS_CHECK   3
 -
++  struct tbf log_pkt_tbf;             /* TBF for packet messages */
+ };
 -  node n;                             /* member of igmp_proto->iface_list */
+ struct igmp_iface
+ {
 -  struct iface *iface;
 -  struct igmp_iface_config *cf;
++  node n;                             /* Member of igmp_proto->iface_list */
+   struct igmp_proto *proto;
 -  sock *sk;                           /* The one receiving packets */
++  struct iface *iface;                        /* Underyling core interface */
++  struct mif *mif;                    /* Associated multicast iface */
++  struct igmp_iface_config *cf;               /* Related config, must be updated in reconfigure */
++  struct object_lock *lock;           /* Interface lock */
++  sock *sk;                           /* IGMP socket */
++
++  HASH(struct igmp_group) groups;
 -  vifi_t vifi;                                /* VIF containing just this device */
+   uint query_state;                   /* initial / querier / non-querier */
 -  HASH(struct igmp_grp) groups;
++  int startup_query_cnt;              /* Remaining startup queries to send */
++  timer *query_timer, *other_present;
++};
 -  u32 gen_id;
 -  uint startup_query_cnt;             /* Remaining startup queries to send */
 -  timer *gen_query, *other_present;
++struct igmp_group
++{
++  struct igmp_group *next;            /* Member of igmp_iface->groups */
++  struct igmp_iface *ifa;
++  ip4_addr address;
 -struct igmp_proto
 -{
 -  struct proto p;
 -  struct igmp_config *cf;
 -
 -  struct channel *mreq_channel;               /* Channel to multicast requests table */
 -
 -  list iface_list;                    /* list of managed ifaces (struct igmp_iface) */
 -};
 -
 -#define IGMP_PROTO  2
++  uint join_state;
++  timer *join_timer, *v1_host_timer, *rxmt_timer;
+ };
++
++#define IGMP_JS_NO_MEMBERS    0
++#define IGMP_JS_MEMBERS               1
++#define IGMP_JS_V1_MEMBERS    2
++#define IGMP_JS_CHECKING      3
++
+ #define IGMP_QS_INIT            0
+ #define IGMP_QS_QUERIER         1
+ #define IGMP_QS_NONQUERIER      2
 -int igmp_query_received(struct igmp_iface *ifa, ip4_addr from);
 -int igmp_membership_report(struct igmp_grp *grp, u8 igmp_version, u8 resp_time);
 -int igmp_leave(struct igmp_grp *grp, u8 resp_time);
 -
 -struct igmp_grp *igmp_grp_new(struct igmp_iface *ifa, ip4_addr *ga);
 -struct igmp_grp *igmp_grp_find(struct igmp_iface *ifa, ip4_addr *ga);
 -
 -void igmp_config_init(struct igmp_config *cf);
 -void igmp_config_finish(struct proto_config *cf);
 -void igmp_iface_config_init(struct igmp_iface_config * ifc);
 -void igmp_iface_config_finish(struct igmp_iface_config * ifc);
+ /* igmp.c */
 -int igmp_sk_open(struct igmp_iface * ifa);
 -int igmp_tx_query(struct igmp_iface *ifa, ip4_addr addr);
++void igmp_handle_query(struct igmp_iface *ifa, ip4_addr addr, ip4_addr from, btime resp_time);
++void igmp_handle_report(struct igmp_iface *ifa, ip4_addr addr, int version);
++void igmp_handle_leave(struct igmp_iface *ifa, ip4_addr addr);
++void igmp_finish_iface_config(struct igmp_iface_config *cf);
+ /* packets.c */
++int igmp_open_socket(struct igmp_iface *ifa);
++void igmp_send_query(struct igmp_iface *ifa, ip4_addr addr, btime resp_time);
+ #endif
index 0000000000000000000000000000000000000000,3c8f85c4e6fac4d70c5c2a9755055ef02d65f902..c387144a481d76ed6af83c07cef87679c08e411c
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,152 +1,167 @@@
 - *  BIRD --IGMP protocol
+ /*
 - *  (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    BIRD -- Internet Group Management Protocol (IGMP)
+  *
 - *  Can be freely distributed and used under the terms of the GNU GPL.
++ *    (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
++ *    (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
++ *    (c) 2018 CZ.NIC z.s.p.o.
+  *
 -struct igmp_pkt {
++ *    Can be freely distributed and used under the terms of the GNU GPL.
+  */
+ #include "igmp.h"
+ #include "lib/checksum.h"
 -};
 -
 -#define IGMP_TP_MS_QUERY      0x11
 -#define IGMP_TP_V1_MS_REPORT  0x12
 -#define IGMP_TP_V2_MS_REPORT  0x16
 -#define IGMP_TP_LEAVE         0x17
 -
 -#define DROP(args...) do { TRACE(D_PACKETS, "Dropping packet: " args); goto drop; } while(0)
 -int
 -igmp_accept(struct igmp_iface *ifa, ip4_addr from, struct igmp_pkt *pkt)
 -{
 -  struct igmp_proto *p = ifa->proto;
++#include <linux/mroute.h>
++
++
++struct igmp_packet
++{
+   u8 type;
+   u8 resp_time;
+   u16 checksum;
+   u32 addr;
 -  if (pkt->type == IGMP_TP_MS_QUERY)
 -    return igmp_query_received(ifa, from);
 -
 -  ip4_addr addr = get_ip4(&pkt->addr);
 -  struct igmp_grp *grp = igmp_grp_find(ifa, &addr);
++} PACKED;
 -  if (pkt->type == IGMP_TP_LEAVE)
 -    return igmp_leave(grp, pkt->resp_time);
++#define IGMP_MSG_QUERY                0x11
++#define IGMP_MSG_V1_REPORT    0x12
++#define IGMP_MSG_V2_REPORT    0x16
++#define IGMP_MSG_LEAVE                0x17
 -  if (!grp)
 -    grp = igmp_grp_new(ifa, &addr);
 -  switch (pkt->type) {
 -    case IGMP_TP_V1_MS_REPORT:
 -      igmp_membership_report(grp, 1, 10);
 -      break;
 -
 -    case IGMP_TP_V2_MS_REPORT:
 -      igmp_membership_report(grp, 2, pkt->resp_time);
 -      break;
 -
 -    default:
 -      DROP("Unknown type");
 -      break;
 -  }
 -
 -drop:
 -  return 0;
 -}
++#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
 -igmp_rx_hook(sock *sk, int len)
++#define LOG_PKT(msg, args...) \
++  log_rl(&p->log_pkt_tbf, L_REMOTE "%s: " msg, p->p.name, args)
+ int
 -  struct igmp_pkt *pkt = (struct igmp_pkt *) sk_rx_buffer(sk, &len);
++igmp_rx_hook(sock *sk, uint len)
+ {
+   struct igmp_iface *ifa = sk->data;
+   struct igmp_proto *p = ifa->proto;
++  const char *err_dsc = NULL;
++  uint err_val = 0;
 -  if (len < sizeof(struct igmp_pkt))
 -    DROP("Shorter than 8 bytes");
++  struct igmp_packet *pkt = (void *) sk_rx_buffer(sk, &len);
 -  /* Longer packets are in IGMPv3 */
 -  if (len != 8)
 -    DROP("Expected pkt length 8, not %i (probably newer IGMP)", len);
++  if (pkt == NULL)
++    DROP("bad IP header", len);
 -    DROP("Invalid checksum");
++  if (len < sizeof(struct igmp_packet))
++    DROP("too short", len);
+   if (!ipsum_verify(pkt, len, NULL))
 -  return igmp_accept(sk->data, ipa_to_ip4(sk->faddr), pkt);
++    DROP("invalid checksum", pkt->checksum);
 -  return 0;
++  ip4_addr from = ipa_to_ip4(sk->faddr);
++  ip4_addr addr = get_ip4(&pkt->addr);
++
++  switch (pkt->type)
++  {
++  case IGMP_MSG_QUERY:
++    TRACE(D_PACKETS, "Query received from %I4 on %s", from, ifa->iface->name);
++    /* FIXME: Warning if resp_time == 0 */
++    igmp_handle_query(ifa, addr, from, pkt->resp_time * (btime) 100000);
++    break;
++
++  case IGMP_MSG_V1_REPORT:
++    TRACE(D_PACKETS, "Report (v1) received from %I4 on %s for %I4", from, ifa->iface->name, addr);
++    igmp_handle_report(ifa, addr, 1);
++    break;
++
++  case IGMP_MSG_V2_REPORT:
++    TRACE(D_PACKETS, "Report (v2) received from %I4 on %s for %I4", from, ifa->iface->name, addr);
++    igmp_handle_report(ifa, addr, 2);
++    break;
++
++  case IGMP_MSG_LEAVE:
++    TRACE(D_PACKETS, "Leave received from %I4 on %s for %I4", from, ifa->iface->name, addr);
++    igmp_handle_leave(ifa, addr);
++    break;
++
++  default:
++    TRACE(D_PACKETS, "Unknown IGMP packet (0x%x) from %I4 on %s", pkt->type, from, ifa->iface->name);
++    break;
++  }
++  return 1;
+ drop:
 -  TRACE(D_EVENTS, "IGMP err %m", err);
++  LOG_PKT("Bad packet from %I on %s - %s (%u)",
++        sk->faddr, sk->iface->name, err_dsc, err_val);
++
++  return 1;
+ }
+ void
+ igmp_err_hook(sock *sk, int err)
+ {
+   struct igmp_iface *ifa = sk->data;
+   struct igmp_proto *p = ifa->proto;
 -int
 -igmp_tx_query(struct igmp_iface *ifa, ip4_addr addr)
++  log(L_ERR "%s: Socket error on %s: %M", p->p.name, ifa->iface->name, err);
+ }
 -  struct igmp_pkt *pkt = (struct igmp_pkt *) ifa->sk->tbuf;
++void
++igmp_send_query(struct igmp_iface *ifa, ip4_addr addr, btime resp_time)
+ {
+   struct igmp_proto *p = ifa->proto;
 -  pkt->type = IGMP_TP_MS_QUERY;
 -  pkt->resp_time = (ifa->cf->query_response_int TO_MS) / 100;
++  struct igmp_packet *pkt = (void *) ifa->sk->tbuf;
 -  pkt->checksum = ipsum_calculate(pkt, sizeof(struct igmp_pkt), NULL);
++  pkt->type = IGMP_MSG_QUERY;
++  pkt->resp_time = resp_time / 100000;
+   put_ip4(&pkt->addr, addr);
+   pkt->checksum = 0;
 -    TRACE(D_PACKETS, "Sending general query on iface %s", ifa->iface->name);
++  pkt->checksum = ipsum_calculate(pkt, sizeof(struct igmp_packet), NULL);
+   ifa->sk->daddr = ip4_zero(addr) ? IP4_ALL_NODES : ipa_from_ip4(addr);
+   if (ip4_zero(addr))
 -    TRACE(D_PACKETS, "Sending query to grp %I4 on iface %s", addr, ifa->iface->name);
++    TRACE(D_PACKETS, "Sending query on %s", ifa->iface->name);
+   else
 -  sk_send(ifa->sk, 8);
 -  return 0;
++    TRACE(D_PACKETS, "Sending query on %s for %I4", ifa->iface->name, addr);
 -igmp_sk_open(struct igmp_iface *ifa)
++  sk_send(ifa->sk, sizeof(struct igmp_packet));
+ }
+ int
 -  sock *sk = sk_new(ifa->proto->p.pool);
 -  sk->type = SK_IGMP;
 -  sk->saddr = ifa->iface->addr->ip;
++igmp_open_socket(struct igmp_iface *ifa)
+ {
 -  sk->data = ifa;
 -  sk->ttl = 1;
 -  sk->tos = IP_PREC_INTERNET_CONTROL;
++  struct igmp_proto *p = ifa->proto;
++
++  sock *sk = sk_new(p->p.pool);
++  sk->type = SK_IP;
++  sk->dport = IGMP_PROTO;
++  sk->saddr = ifa->iface->addr4->ip;
+   sk->iface = ifa->iface;
 -  return 0;
+   sk->rx_hook = igmp_rx_hook;
+   sk->err_hook = igmp_err_hook;
++  sk->data = ifa;
+   sk->tbsize = ifa->iface->mtu;
++  sk->tos = IP_PREC_INTERNET_CONTROL;
++  sk->ttl = 1;
+   if (sk_open(sk) < 0)
+     goto err;
+   if (sk_setup_multicast(sk) < 0)
+     goto err;
++  if (sk_setup_igmp(sk, p->mif_group, ifa->mif) < 0)
++    goto err;
++
+   if (sk_join_group(sk, IP4_IGMP_ROUTERS) < 0)
+     goto err;
+   if (sk_join_group(sk, IP4_ALL_ROUTERS) < 0)
+     goto err;
+   ifa->sk = sk;
 -  return -1;
++  return 1;
+ err:
+   log(L_ERR "%s: Socket error: %s%#m", ifa->proto->p.name, sk->err);
+   rfree(sk);
++  return 0;
+ }