--- /dev/null
- 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
--- /dev/null
+/*
+ * 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(¤t_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>");
+}
--- /dev/null
+/*
+ * 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
#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);
*/
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
#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
#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 */
/*
{
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)
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
--- /dev/null
--- /dev/null
++S igmp.c
++S packets.c
--- /dev/null
+ src := igmp.c packets.c
+ obj := $(src-o-files)
+ $(all-daemon)
+ $(cf-local)
++
++tests_objs := $(tests_objs) $(src-o-files)
--- /dev/null
- * 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
--- /dev/null
- * 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,
+ };
--- /dev/null
- * 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
--- /dev/null
- * 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;
+ }