]> git.ipfire.org Git - thirdparty/bird.git/blobdiff - proto/rip/rip.c
RIP: Fix handling of passive mode for demand circuit interfaces
[thirdparty/bird.git] / proto / rip / rip.c
index 1f356738dd470d6ee029bbe9e75b0d9e156c3ca6..d18ff5adfab6e06b09cf85e5aa5686f83a7d99ec 100644 (file)
 /*
- *     Rest in pieces - RIP protocol
+ *     BIRD -- Routing Information Protocol (RIP)
  *
- *     Copyright (c) 1998, 1999 Pavel Machek <pavel@ucw.cz>
+ *     (c) 1998--1999 Pavel Machek <pavel@ucw.cz>
+ *     (c) 2004--2013 Ondrej Filip <feela@network.cz>
+ *     (c) 2009--2015 Ondrej Zajicek <santiago@crfreenet.org>
+ *     (c) 2009--2015 CZ.NIC z.s.p.o.
  *
  *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Routing Information Protocol (RIP)
+ *
+ * The RIP protocol is implemented in two files: |rip.c| containing the protocol
+ * logic, route management and the protocol glue with BIRD core, and |packets.c|
+ * handling RIP packet processing, RX, TX and protocol sockets.
+ *
+ * Each instance of RIP is described by a structure &rip_proto, which contains
+ * an internal RIP routing table, a list of protocol interfaces and the main
+ * timer responsible for RIP routing table cleanup.
+ *
+ * RIP internal routing table contains incoming and outgoing routes. For each
+ * network (represented by structure &rip_entry) there is one outgoing route
+ * stored directly in &rip_entry and an one-way linked list of incoming routes
+ * (structures &rip_rte). The list contains incoming routes from different RIP
+ * neighbors, but only routes with the lowest metric are stored (i.e., all
+ * stored incoming routes have the same metric).
+ *
+ * Note that RIP itself does not select outgoing route, that is done by the core
+ * routing table. When a new incoming route is received, it is propagated to the
+ * RIP table by rip_update_rte() and possibly stored in the list of incoming
+ * routes. Then the change may be propagated to the core by rip_announce_rte().
+ * The core selects the best route and propagate it to RIP by rip_rt_notify(),
+ * which updates outgoing route part of &rip_entry and possibly triggers route
+ * propagation by rip_trigger_update().
+ *
+ * RIP interfaces are represented by structures &rip_iface. A RIP interface
+ * contains a per-interface socket, a list of associated neighbors, interface
+ * configuration, and state information related to scheduled interface events
+ * and running update sessions. RIP interfaces are added and removed based on
+ * core interface notifications.
  *
-       FIXME: IpV6 support: packet size
-       FIXME: IpV6 support: use right address for broadcasts
-       FIXME: IpV6 support: receive "route using" blocks
+ * There are two RIP interface events - regular updates and triggered updates.
+ * Both are managed from the RIP interface timer (rip_iface_timer()). Regular
+ * updates are called at fixed interval and propagate the whole routing table,
+ * while triggered updates are scheduled by rip_trigger_update() due to some
+ * routing table change and propagate only the routes modified since the time
+ * they were scheduled. There are also unicast-destined requested updates, but
+ * these are sent directly as a reaction to received RIP request message. The
+ * update session is started by rip_send_table(). There may be at most one
+ * active update session per interface, as the associated state (including the
+ * fib iterator) is stored directly in &rip_iface structure.
+ *
+ * RIP neighbors are represented by structures &rip_neighbor. Compared to
+ * neighbor handling in other routing protocols, RIP does not have explicit
+ * neighbor discovery and adjacency maintenance, which makes the &rip_neighbor
+ * related code a bit peculiar. RIP neighbors are interlinked with core neighbor
+ * structures (&neighbor) and use core neighbor notifications to ensure that RIP
+ * neighbors are timely removed. RIP neighbors are added based on received route
+ * notifications and removed based on core neighbor and RIP interface events.
+ *
+ * RIP neighbors are linked by RIP routes and use counter to track the number of
+ * associated routes, but when these RIP routes timeout, associated RIP neighbor
+ * is still alive (with zero counter). When RIP neighbor is removed but still
+ * has some associated routes, it is not freed, just changed to detached state
+ * (core neighbors and RIP ifaces are unlinked), then during the main timer
+ * cleanup phase the associated routes are removed and the &rip_neighbor
+ * structure is finally freed.
+ *
+ * Supported standards:
+ * - RFC 1058 - RIPv1
+ * - RFC 2453 - RIPv2
+ * - RFC 2080 - RIPng
+ * - RFC 4822 - RIP cryptographic authentication
+ */
 
-       FIXME (nonurgent): fold rip_connection into rip_interface?
+#include <stdlib.h>
+#include "rip.h"
 
-       We are not going to honour requests for sending part of
-       routing table. That would need to turn split horizont off,
-       etc.  
 
-       Triggered updates. When triggered update was sent, don't send
-       new one for something between 1 and 5 seconds (and send one
-       after that), says RFC. We do something else: once in 5 second
-       we look for any changed routes and broadcast them.
+static inline void rip_lock_neighbor(struct rip_neighbor *n);
+static inline void rip_unlock_neighbor(struct rip_neighbor *n);
+static inline int rip_iface_link_up(struct rip_iface *ifa);
+static inline void rip_kick_timer(struct rip_proto *p);
+static inline void rip_iface_kick_timer(struct rip_iface *ifa);
+static void rip_iface_timer(timer *timer);
+static void rip_trigger_update(struct rip_proto *p);
 
-       FIXME: (nonurgent) allow bigger frequencies than 1 regular update in 6 seconds (?)
-       FIXME: propagation of metric=infinity into main routing table may or may not be good idea.
 
+/*
+ *     RIP routes
  */
 
-#define LOCAL_DEBUG
+static struct rip_rte *
+rip_add_rte(struct rip_proto *p, struct rip_rte **rp, struct rip_rte *src)
+{
+  struct rip_rte *rt = sl_alloc(p->rte_slab);
 
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
+  memcpy(rt, src, sizeof(struct rip_rte));
+  rt->next = *rp;
+  *rp = rt;
 
-#include "nest/bird.h"
-#include "nest/iface.h"
-#include "nest/protocol.h"
-#include "nest/route.h"
-#include "lib/socket.h"
-#include "lib/resource.h"
-#include "lib/lists.h"
-#include "lib/timer.h"
+  rip_lock_neighbor(rt->from);
 
-#include "rip.h"
+  return rt;
+}
 
-#define P ((struct rip_proto *) p)
-#define P_CF ((struct rip_proto_config *)p->cf)
-#define E ((struct rip_entry *) e)
+static inline void
+rip_remove_rte(struct rip_proto *p, struct rip_rte **rp)
+{
+  struct rip_rte *rt = *rp;
 
-static struct rip_interface *new_iface(struct proto *p, struct iface *new, unsigned long flags, struct iface_patt *patt);
+  rip_unlock_neighbor(rt->from);
 
+  *rp = rt->next;
+  sl_free(p->rte_slab, rt);
+}
+
+static inline int rip_same_rte(struct rip_rte *a, struct rip_rte *b)
+{ return a->metric == b->metric && a->tag == b->tag && ipa_equal(a->next_hop, b->next_hop); }
+
+static inline int rip_valid_rte(struct rip_rte *rt)
+{ return rt->from->ifa != NULL; }
+
+/**
+ * rip_announce_rte - announce route from RIP routing table to the core
+ * @p: RIP instance
+ * @en: related network
+ *
+ * The function takes a list of incoming routes from @en, prepare appropriate
+ * &rte for the core and propagate it by rte_update().
+ */
 static void
-rip_reply(struct proto *p)
+rip_announce_rte(struct rip_proto *p, struct rip_entry *en)
 {
-#if 0
-  P->listen->tbuf = "ACK!";
-  sk_send_to( P->listen, 5, P->listen->faddr, P->listen->fport );
-#endif
-}
+  struct rip_rte *rt = en->routes;
 
-#define P_NAME p->name
+  /* Find first valid rte */
+  while (rt && !rip_valid_rte(rt))
+    rt = rt->next;
 
-/*
- * Output processing
+  if (rt)
+  {
+    /* Update */
+    rta a0 = {
+      .src = p->p.main_source,
+      .source = RTS_RIP,
+      .scope = SCOPE_UNIVERSE,
+      .dest = RTD_UNICAST,
+    };
+
+    u8 rt_metric = rt->metric;
+    u16 rt_tag = rt->tag;
+
+    if (p->ecmp)
+    {
+      /* ECMP route */
+      struct nexthop *nhs = NULL;
+      int num = 0;
+
+      for (rt = en->routes; rt && (num < p->ecmp); rt = rt->next)
+      {
+       if (!rip_valid_rte(rt))
+           continue;
+
+       struct nexthop *nh = allocz(sizeof(struct nexthop));
+
+       nh->gw = rt->next_hop;
+       nh->iface = rt->from->ifa->iface;
+       nh->weight = rt->from->ifa->cf->ecmp_weight;
+
+       nexthop_insert(&nhs, nh);
+       num++;
+
+       if (rt->tag != rt_tag)
+         rt_tag = 0;
+      }
+
+      a0.nh = *nhs;
+    }
+    else
+    {
+      /* Unipath route */
+      a0.from = rt->from->nbr->addr;
+      a0.nh.gw = rt->next_hop;
+      a0.nh.iface = rt->from->ifa->iface;
+    }
+
+    rta *a = rta_lookup(&a0);
+    rte *e = rte_get_temp(a);
+
+    e->u.rip.from = a0.nh.iface;
+    e->u.rip.metric = rt_metric;
+    e->u.rip.tag = rt_tag;
+    e->pflags = EA_ID_FLAG(EA_RIP_METRIC) | EA_ID_FLAG(EA_RIP_TAG);
+
+    rte_update(&p->p, en->n.addr, e);
+  }
+  else
+  {
+    /* Withdraw */
+    rte_update(&p->p, en->n.addr, NULL);
+  }
+}
+
+/**
+ * rip_update_rte - enter a route update to RIP routing table
+ * @p: RIP instance
+ * @addr: network address
+ * @new: a &rip_rte representing the new route
+ *
+ * The function is called by the RIP packet processing code whenever it receives
+ * a reachable route. The appropriate routing table entry is found and the list
+ * of incoming routes is updated. Eventually, the change is also propagated to
+ * the core by rip_announce_rte(). Note that for unreachable routes,
+ * rip_withdraw_rte() should be called instead of rip_update_rte().
  */
+void
+rip_update_rte(struct rip_proto *p, net_addr *n, struct rip_rte *new)
+{
+  struct rip_entry *en = fib_get(&p->rtable, n);
+  struct rip_rte *rt, **rp;
+  int changed = 0;
+
+  /* If the new route is better, remove all current routes */
+  if (en->routes && new->metric < en->routes->metric)
+    while (en->routes)
+      rip_remove_rte(p, &en->routes);
+
+  /* Find the old route (also set rp for later) */
+  for (rp = &en->routes; rt = *rp; rp = &rt->next)
+    if (rt->from == new->from)
+    {
+      if (rip_same_rte(rt, new))
+      {
+       rt->expires = new->expires;
+       return;
+      }
 
-static void
-rip_tx_err( sock *s, int err )
+      /* Remove the old route */
+      rip_remove_rte(p, rp);
+      changed = 1;
+      break;
+    }
+
+  /* If the new route is optimal, add it to the list */
+  if (!en->routes || new->metric == en->routes->metric)
+  {
+    rt = rip_add_rte(p, rp, new);
+    changed = 1;
+  }
+
+  /* Announce change if on relevant position (the first or any for ECMP) */
+  if (changed && (rp == &en->routes || p->ecmp))
+    rip_announce_rte(p, en);
+}
+
+/**
+ * rip_withdraw_rte - enter a route withdraw to RIP routing table
+ * @p: RIP instance
+ * @addr: network address
+ * @from: a &rip_neighbor propagating the withdraw
+ *
+ * The function is called by the RIP packet processing code whenever it receives
+ * an unreachable route. The incoming route for given network from nbr @from is
+ * removed. Eventually, the change is also propagated by rip_announce_rte().
+ */
+void
+rip_withdraw_rte(struct rip_proto *p, net_addr *n, struct rip_neighbor *from)
 {
-  struct rip_connection *c = s->data;
-  struct proto *p = c->proto;
-  log( L_ERR "Unexpected error at rip transmit: %m" );
+  struct rip_entry *en = fib_find(&p->rtable, n);
+  struct rip_rte *rt, **rp;
+
+  if (!en)
+    return;
+
+  /* Find the old route */
+  for (rp = &en->routes; rt = *rp; rp = &rt->next)
+    if (rt->from == from)
+      break;
+
+  if (!rt)
+    return;
+
+  /* Remove the old route */
+  rip_remove_rte(p, rp);
+
+  /* Announce change if on relevant position */
+  if (rp == &en->routes || p->ecmp)
+    rip_announce_rte(p, en);
 }
 
+/*
+ * rip_rt_notify - core tells us about new route, so store
+ * it into our data structures.
+ */
 static void
-rip_tx_prepare(struct proto *p, ip_addr daddr, struct rip_block *b, struct rip_entry *e )
-{
-  DBG( "." );
-  b->family  = htons( 2 ); /* AF_INET */
-  b->tag     = htons( e->tag );
-  b->network = e->n.prefix;
-#ifndef IPV6
-  b->netmask = ipa_mkmask( e->n.pxlen );
-  ipa_hton( b->netmask );
-  b->nexthop = IPA_NONE;       
+rip_rt_notify(struct proto *P, struct channel *ch UNUSED, struct network *net, struct rte *new,
+             struct rte *old UNUSED)
+{
+  struct rip_proto *p = (struct rip_proto *) P;
+  struct rip_entry *en;
+  int old_metric;
+
+  if (new)
   {
+    /* Update */
+    u32 rt_metric = ea_get_int(new->attrs->eattrs, EA_RIP_METRIC, 1);
+    u32 rt_tag = ea_get_int(new->attrs->eattrs, EA_RIP_TAG, 0);
+
+    if (rt_metric > p->infinity)
+    {
+      log(L_WARN "%s: Invalid rip_metric value %u for route %N",
+         p->p.name, rt_metric, net->n.addr);
+      rt_metric = p->infinity;
+    }
+
+    if (rt_tag > 0xffff)
+    {
+      log(L_WARN "%s: Invalid rip_tag value %u for route %N",
+         p->p.name, rt_tag, net->n.addr);
+      rt_metric = p->infinity;
+      rt_tag = 0;
+    }
+
     /*
-     *  FIXME:  This DOESN'T work. The s->daddr will be typically
-     *  a broadcast or multicast address which will be of course
-     *  rejected by the neighbor cache. I've #ifdef'd out the whole
-     *  test to avoid dereferencing NULL pointer.  --mj
+     * Note that we accept exported routes with infinity metric (this could
+     * happen if rip_metric is modified in filters). Such entry has infinity
+     * metric but is RIP_ENTRY_VALID and therefore is not subject to garbage
+     * collection.
      */
-#if 0
-    neighbor *n1, *n2;
-    n1 = neigh_find( p, &daddr, 0 );   /* FIXME, mj: this is neccessary for responses, still it is too complicated for common case */
-    n2 = neigh_find( p, &e->nexthop, 0 );
-    if (n1->iface == n2->iface)
-      b->nexthop = e->nexthop;
-    else
-#endif
-      b->nexthop = IPA_NONE;   
+
+    en = fib_get(&p->rtable, net->n.addr);
+
+    old_metric = en->valid ? en->metric : -1;
+
+    en->valid = RIP_ENTRY_VALID;
+    en->metric = rt_metric;
+    en->tag = rt_tag;
+    en->from = (new->attrs->src->proto == P) ? new->u.rip.from : NULL;
+    en->iface = new->attrs->nh.iface;
+    en->next_hop = new->attrs->nh.gw;
   }
-  ipa_hton( b->nexthop );
-#else
-  b->pxlen = e->n.pxlen;
-#endif
-  b->metric  = htonl( e->metric );
-  if (ipa_equal(e->whotoldme, daddr)) {        /* FIXME: ouch, daddr is some kind of broadcast address. How am I expected to do split horizont?!?!? */
-    DBG( "(split horizont)" );
-    b->metric = htonl( P_CF->infinity );
+  else
+  {
+    /* Withdraw */
+    en = fib_find(&p->rtable, net->n.addr);
+
+    if (!en || en->valid != RIP_ENTRY_VALID)
+      return;
+
+    old_metric = en->metric;
+
+    en->valid = RIP_ENTRY_STALE;
+    en->metric = p->infinity;
+    en->tag = 0;
+    en->from = NULL;
+    en->iface = NULL;
+    en->next_hop = IPA_NONE;
+  }
+
+  /* Activate triggered updates */
+  if (en->metric != old_metric)
+  {
+    en->changed = current_time();
+    rip_trigger_update(p);
   }
-  ipa_hton( b->network );
 }
 
-static void
-rip_tx( sock *s )
-{
-  struct rip_interface *rif = s->data;
-  struct rip_connection *c = rif->busy;
-  struct proto *p = c->proto;
-  struct rip_packet *packet = (void *) s->tbuf;
-  int i, packetlen;
-
-  DBG( "Sending to %I\n", s->daddr );
-  do {
-
-    if (c->done) {
-      DBG( "Looks like I'm" );
-      c->rif->busy = NULL;
-      rem_node(NODE c);
-      mb_free(c);
-      DBG( " done\n" );
-      return;
-    }
+void
+rip_flush_table(struct rip_proto *p, struct rip_neighbor *n)
+{
+  btime expires = current_time() + n->ifa->cf->timeout_time;
 
-    DBG( "Preparing packet to send: " );
+  FIB_WALK(&p->rtable, struct rip_entry, en)
+  {
+    for (struct rip_rte *e = en->routes; e; e = e->next)
+      if ((e->from == n) && (e->expires == TIME_INFINITY))
+       e->expires = expires;
+  }
+  FIB_WALK_END;
+}
 
-    packet->heading.command = RIPCMD_RESPONSE;
-    packet->heading.version = RIP_V2;
-    packet->heading.unused  = 0;
 
-    i = !!P_CF->authtype;
-    FIB_ITERATE_START(&P->rtable, &c->iter, z) {
-      struct rip_entry *e = (struct rip_entry *) z;
+/*
+ *     RIP neighbors
+ */
 
-      if (!rif->triggered || (!(e->updated < now-5))) {
+struct rip_neighbor *
+rip_get_neighbor(struct rip_proto *p, ip_addr *a, struct rip_iface *ifa)
+{
+  neighbor *nbr = neigh_find(&p->p, *a, ifa->iface, 0);
 
-       rip_tx_prepare( p, s->daddr, packet->block + i, e );
-       if (i++ == ((P_CF->authtype == AT_MD5) ? PACKET_MD5_MAX : PACKET_MAX)) {
-         FIB_ITERATE_PUT(&c->iter, z);
-         goto break_loop;
-       }
-      }
-    } FIB_ITERATE_END(z);
-    c->done = 1;
+  if (!nbr || (nbr->scope == SCOPE_HOST) || !rip_iface_link_up(ifa))
+    return NULL;
 
-  break_loop:
+  if (nbr->data)
+    return nbr->data;
 
-    packetlen = rip_outgoing_authentication(p, (void *) &packet->block[0], packet, i);
+  TRACE(D_EVENTS, "New neighbor %I on %s", *a, ifa->iface->name);
 
-    DBG( ", sending %d blocks, ", i );
-#if 0  /* FIXME: enable this for production! */
-    if (i == !!P_CF->authtype)
-      continue;
-#endif
-    if (!i)
-      DBG( "not sending NULL update\n" );
-    else {
-      if (ipa_nonzero(c->daddr))
-       i = sk_send_to( s, packetlen, c->daddr, c->dport );
-      else
-       i = sk_send( s, packetlen );
-    }
+  struct rip_neighbor *n = mb_allocz(p->p.pool, sizeof(struct rip_neighbor));
+  n->ifa = ifa;
+  n->nbr = nbr;
+  nbr->data = n;
+  n->csn = nbr->aux;
 
-    DBG( "it wants more\n" );
-  
-  } while (i>0);
-  
-  if (i<0) rip_tx_err( s, i );
-  DBG( "blocked\n" );
-  return;
+  add_tail(&ifa->neigh_list, NODE n);
 
+  return n;
 }
 
 static void
-rip_sendto( struct proto *p, ip_addr daddr, int dport, struct rip_interface *rif )
+rip_remove_neighbor(struct rip_proto *p, struct rip_neighbor *n)
 {
-  struct iface *iface = rif->iface;
-  struct rip_connection *c = mb_alloc( p->pool, sizeof( struct rip_connection ));
-  static int num = 0;
+  neighbor *nbr = n->nbr;
 
-  if (rif->busy) {
-    log (L_WARN "Interface %s is much too slow, dropping request", iface->name);
-    /* FIXME: memory leak */
-    return;
-  }
-  rif->busy = c;
-  
-  c->addr = daddr;
-  c->proto = p;
-  c->num = num++;
-  c->rif = rif;
-
-  c->dport = dport;
-  c->daddr = daddr;
-  if (c->rif->sock->data != rif)
-    bug("not enough send magic");
-#if 0
-  if (sk_open(c->send)<0) {
-    log( L_ERR "Could not open socket for data send to %I:%d on %s", daddr, dport, rif->iface->name );
+  TRACE(D_EVENTS, "Removing neighbor %I on %s", nbr->addr, nbr->ifreq->name);
+
+  rem_node(NODE n);
+  n->ifa = NULL;
+  n->nbr = NULL;
+  nbr->data = NULL;
+  nbr->aux = n->csn;
+
+  rfree(n->bfd_req);
+  n->bfd_req = NULL;
+  n->last_seen = 0;
+
+  if (!n->uc)
+    mb_free(n);
+
+  /* Related routes are removed in rip_timer() */
+  rip_kick_timer(p);
+}
+
+static inline void
+rip_lock_neighbor(struct rip_neighbor *n)
+{
+  n->uc++;
+}
+
+static inline void
+rip_unlock_neighbor(struct rip_neighbor *n)
+{
+  n->uc--;
+
+  if (!n->nbr && !n->uc)
+    mb_free(n);
+}
+
+static void
+rip_neigh_notify(struct neighbor *nbr)
+{
+  struct rip_proto *p = (struct rip_proto *) nbr->proto;
+  struct rip_neighbor *n = nbr->data;
+
+  if (!n)
     return;
-  }
-#endif
 
-  c->done = 0;
-  fit_init( &c->iter, &P->rtable );
-  add_head( &P->connections, NODE c );
-  debug( "Sending my routing table to %I:%d on %s\n", daddr, dport, rif->iface->name );
+  /*
+   * We assume that rip_neigh_notify() is called before rip_if_notify() for
+   * IF_CHANGE_DOWN and therefore n->ifa is still valid. We have no such
+   * ordering assumption for IF_CHANGE_LINK, so we test link state of the
+   * underlying iface instead of just rip_iface state.
+   */
+  if ((nbr->scope <= 0) || !rip_iface_link_up(n->ifa))
+    rip_remove_neighbor(p, n);
+}
+
+static void
+rip_bfd_notify(struct bfd_request *req)
+{
+  struct rip_neighbor *n = req->data;
+  struct rip_proto *p = n->ifa->rip;
 
-  rip_tx(c->rif->sock);
+  if (req->down)
+  {
+    TRACE(D_EVENTS, "BFD session down for nbr %I on %s",
+         n->nbr->addr, n->ifa->iface->name);
+    rip_remove_neighbor(p, n);
+  }
 }
 
-static struct rip_interface*
-find_interface(struct proto *p, struct iface *what)
+void
+rip_update_bfd(struct rip_proto *p, struct rip_neighbor *n)
 {
-  struct rip_interface *i;
+  int use_bfd = n->ifa->cf->bfd && n->last_seen;
 
-  WALK_LIST (i, P->interfaces)
-    if (i->iface == what)
-      return i;
-  return NULL;
+  if (use_bfd && !n->bfd_req)
+  {
+    /*
+     * For RIPv2, use the same address as rip_open_socket(). For RIPng, neighbor
+     * should contain an address from the same prefix, thus also link-local. It
+     * may cause problems if two link-local addresses are assigned to one iface.
+     */
+    ip_addr saddr = rip_is_v2(p) ? n->ifa->sk->saddr : n->nbr->ifa->ip;
+    n->bfd_req = bfd_request_session(p->p.pool, n->nbr->addr, saddr,
+                                    n->nbr->iface, p->p.vrf,
+                                    rip_bfd_notify, n);
+  }
+
+  if (!use_bfd && n->bfd_req)
+  {
+    rfree(n->bfd_req);
+    n->bfd_req = NULL;
+  }
 }
 
+
 /*
- * Input processing
+ *     RIP interfaces
  */
 
-/* Let main routing table know about our new entry */
 static void
-advertise_entry( struct proto *p, struct rip_block *b, ip_addr whotoldme )
-{
-  rta *a, A;
-  rte *r;
-  net *n;
-  neighbor *neighbor;
-  struct rip_interface *rif;
-  int pxlen;
-
-  bzero(&A, sizeof(A));
-  A.proto = p;
-  A.source = RTS_RIP;
-  A.scope = SCOPE_UNIVERSE;
-  A.cast = RTC_UNICAST;
-  A.dest = RTD_ROUTER;
-  A.flags = 0;
-#ifndef IPV6
-  A.gw = ipa_nonzero(b->nexthop) ? b->nexthop : whotoldme;
-  pxlen = ipa_mklen(b->netmask);
-#else
-  A.gw = whotoldme; /* FIXME: next hop is in other packet for v6 */
-  pxlen = b->pxlen;
-#endif
-  A.from = whotoldme;
-
-  /* No need to look if destination looks valid - ie not net 0 or 127 -- core will do for us. */
-
-  neighbor = neigh_find( p, &A.gw, 0 );
-  if (!neighbor) {
-    log( L_ERR "%I asked me to route %I/%d using not-neighbor %I.", A.from, b->network, pxlen, A.gw );
-    return;
-  }
+rip_iface_start(struct rip_iface *ifa)
+{
+  struct rip_proto *p = ifa->rip;
 
-  A.iface = neighbor->iface;
-  if (!(rif = neighbor->data)) {
-    rif = neighbor->data = find_interface(p, A.iface);
+  TRACE(D_EVENTS, "Starting interface %s", ifa->iface->name);
+
+  if (! ifa->cf->demand_circuit)
+  {
+    ifa->next_regular = current_time() + (random() % ifa->cf->update_time) + 100 MS;
+    tm_set(ifa->timer, ifa->next_regular);
   }
-  if (!rif) {
-    bug("Route packet using unknown interface? No.");
-    return;
+  else
+  {
+    ifa->next_regular = TIME_INFINITY;
   }
-    
-  /* set to: interface of nexthop */
-  a = rta_lookup(&A);
-  if (pxlen==-1)  {
-    log( L_ERR "%I gave me invalid pxlen/netmask for %I.", A.from, b->network );
+
+  ifa->up = 1;
+
+  if (ifa->cf->passive)
     return;
-  }
-  n = net_get( p->table, b->network, pxlen );
-  r = rte_get_temp(a);
-  r->u.rip.metric = ntohl(b->metric) + rif->patt->metric;
-  if (r->u.rip.metric > P_CF->infinity) r->u.rip.metric = P_CF->infinity;
-  r->u.rip.tag = ntohl(b->tag);
-  r->net = n;
-  r->pflags = 0; /* Here go my flags */
-  rte_update( p->table, n, p, r );
-  DBG( "done\n" );
+
+  rip_send_request(p, ifa);
+  rip_send_table(p, ifa, ifa->addr, 0);
+}
+
+static void
+rip_iface_stop(struct rip_iface *ifa)
+{
+  struct rip_proto *p = ifa->rip;
+  struct rip_neighbor *n;
+
+  TRACE(D_EVENTS, "Stopping interface %s", ifa->iface->name);
+
+  rip_reset_tx_session(p, ifa);
+
+  ifa->next_regular = 0;
+  ifa->next_triggered = 0;
+  ifa->want_triggered = 0;
+
+  if (ifa->tx_pending)
+    ifa->tx_seqnum++;
+
+  ifa->tx_pending = 0;
+  ifa->req_pending = 0;
+
+  if (ifa->cf->demand_circuit && !ifa->cf->passive)
+    rip_send_flush(p, ifa);
+
+  WALK_LIST_FIRST(n, ifa->neigh_list)
+    rip_remove_neighbor(p, n);
+
+  tm_stop(ifa->timer);
+  tm_stop(ifa->rxmt_timer);
+  ifa->up = 0;
+}
+
+static inline int
+rip_iface_link_up(struct rip_iface *ifa)
+{
+  return !ifa->cf->check_link || (ifa->iface->flags & IF_LINK_UP);
 }
 
 static void
-process_block( struct proto *p, struct rip_block *block, ip_addr whotoldme )
+rip_iface_update_state(struct rip_iface *ifa)
 {
-  int metric = ntohl( block->metric );
-  ip_addr network = block->network;
+  int up = ifa->sk && rip_iface_link_up(ifa);
 
-  CHK_MAGIC;
-  debug( "block: %I tells me: %I/??? available, metric %d... ", whotoldme, network, metric );
-  if ((!metric) || (metric > P_CF->infinity)) {
-    log( L_WARN "Got metric %d from %I", metric, whotoldme );
+  if (up == ifa->up)
     return;
-  }
 
-  advertise_entry( p, block, whotoldme );
+  if (up)
+    rip_iface_start(ifa);
+  else
+    rip_iface_stop(ifa);
 }
 
-#define BAD( x ) { log( L_WARN "RIP/%s: " x, P_NAME ); return 1; }
+static void
+rip_iface_update_buffers(struct rip_iface *ifa)
+{
+  if (!ifa->sk)
+    return;
+
+  uint rbsize = ifa->cf->rx_buffer ?: ifa->iface->mtu;
+  uint tbsize = ifa->cf->tx_length ?: ifa->iface->mtu;
+  rbsize = MAX(rbsize, tbsize);
 
-static int
-rip_process_packet( struct proto *p, struct rip_packet *packet, int num, ip_addr whotoldme, int port )
+  sk_set_rbsize(ifa->sk, rbsize);
+  sk_set_tbsize(ifa->sk, tbsize);
+
+  uint headers = (rip_is_v2(ifa->rip) ? IP4_HEADER_LENGTH : IP6_HEADER_LENGTH) + UDP_HEADER_LENGTH;
+  ifa->tx_plen = tbsize - headers;
+
+  if (ifa->cf->auth_type == RIP_AUTH_CRYPTO)
+    ifa->tx_plen -= RIP_AUTH_TAIL_LENGTH + max_mac_length(ifa->cf->passwords);
+}
+
+static inline void
+rip_iface_update_bfd(struct rip_iface *ifa)
 {
-  int i;
-  int native_class = 0, authenticated = 0;
+  struct rip_proto *p = ifa->rip;
+  struct rip_neighbor *n;
+
+  WALK_LIST(n, ifa->neigh_list)
+    rip_update_bfd(p, n);
+}
 
-  switch( packet->heading.version ) {
-  case RIP_V1: DBG( "Rip1: " ); break;
-  case RIP_V2: DBG( "Rip2: " ); break;
-  default: BAD( "Unknown version" );
-  }
 
-  switch( packet->heading.command ) {
-  case RIPCMD_REQUEST: DBG( "Asked to send my routing table\n" ); 
-         if (P_CF->honour == HO_NEVER) {
-           log( L_WARN "They asked me to send routing table, but I was told not to do it\n" );
-           return 0;
-         }
-         if ((P_CF->honour == HO_NEIGHBOUR) && (!neigh_find( p, &whotoldme, 0 ))) {
-           log( L_WARN "They asked me to send routing table, but he is not my neighbour\n" );
-           return 0;
-         }
-         rip_sendto( p, whotoldme, port, HEAD(P->interfaces) ); /* no broadcast */
-          break;
-  case RIPCMD_RESPONSE: DBG( "*** Rtable from %I\n", whotoldme ); 
-          if (port != P_CF->port) {
-           log( L_ERR "%I send me routing info from port %d", whotoldme, port );
-#if 0
-           return 0;
-#else
-           log( L_ERR "...ignoring" );
-#endif
-         }
-
-         if (!neigh_find( p, &whotoldme, 0 )) {
-           log( L_ERR "%I send me routing info but he is not my neighbour", whotoldme );
-#if 0
-           return 0;
-#else
-           log( L_ERR "...ignoring" );
-#endif
-         }
-
-          for (i=0; i<num; i++) {
-           struct rip_block *block = &packet->block[i];
-           if (block->family == 0xffff) {
-             if (i)
-               continue;       /* md5 tail has this family */
-             if (rip_incoming_authentication(p, (void *) block, packet, num, whotoldme))
-               BAD( "Authentication failed" );
-             authenticated = 1;
-             continue;
-           }
-           if ((!authenticated) && (P_CF->authtype != AT_NONE))
-             BAD( "Packet is not authenticated and it should be" );
-           ipa_ntoh( block->network );
-#ifndef IPV6
-           ipa_ntoh( block->netmask );
-           ipa_ntoh( block->nexthop );
-           if (packet->heading.version == RIP_V1)      /* FIXME: switch to disable this? (nonurgent) */
-             block->netmask = ipa_class_mask(block->network);
-#endif
-           process_block( p, block, whotoldme );
-         }
-          break;
-  case RIPCMD_TRACEON:
-  case RIPCMD_TRACEOFF: BAD( "I was asked for traceon/traceoff" );
-  case 5: BAD( "Some Sun extension around here" );
-  default: BAD( "Unknown command" );
+static void
+rip_iface_locked(struct object_lock *lock)
+{
+  struct rip_iface *ifa = lock->data;
+  struct rip_proto *p = ifa->rip;
+
+  if (!rip_open_socket(ifa))
+  {
+    log(L_ERR "%s: Cannot open socket for %s", p->p.name, ifa->iface->name);
+    return;
   }
 
-  rip_reply(p);
-  return 0;
+  rip_iface_update_buffers(ifa);
+  rip_iface_update_state(ifa);
 }
 
-static int
-rip_rx(sock *s, int size)
+
+static struct rip_iface *
+rip_find_iface(struct rip_proto *p, struct iface *what)
 {
-  struct rip_interface *i = s->data;
-  struct proto *p = i->proto;
-  int num;
+  struct rip_iface *ifa;
 
-  CHK_MAGIC;
-  DBG( "RIP: message came: %d bytes\n", size );
-  size -= sizeof( struct rip_packet_heading );
-  if (size < 0) BAD( "Too small packet" );
-  if (size % sizeof( struct rip_block )) BAD( "Odd sized packet" );
-  num = size / sizeof( struct rip_block );
-  if (num>25) BAD( "Too many blocks" );
+  WALK_LIST(ifa, p->iface_list)
+    if (ifa->iface == what)
+      return ifa;
 
-  rip_process_packet( p, (struct rip_packet *) s->rbuf, num, s->faddr, s->fport );
-  return 1;
+  return NULL;
 }
 
-/*
- * Interface to rest of bird
- */
-
 static void
-rip_dump_entry( struct rip_entry *e )
+rip_add_iface(struct rip_proto *p, struct iface *iface, struct rip_iface_config *ic)
 {
-  debug( "%I told me %d/%d ago: to %I/%d go via %I, metric %d ", 
-  e->whotoldme, e->updated-now, e->changed-now, e->n.prefix, e->n.pxlen, e->nexthop, e->metric );
-  if (e->flags & RIP_F_EXTERNAL) debug( "[external]" );
-  debug( "\n" );
+  struct rip_iface *ifa;
+
+  TRACE(D_EVENTS, "Adding interface %s", iface->name);
+
+  ifa = mb_allocz(p->p.pool, sizeof(struct rip_iface));
+  ifa->rip = p;
+  ifa->iface = iface;
+  ifa->cf = ic;
+
+  if (ipa_nonzero(ic->address))
+    ifa->addr = ic->address;
+  else if (ic->mode == RIP_IM_MULTICAST)
+    ifa->addr = rip_is_v2(p) ? IP4_RIP_ROUTERS : IP6_RIP_ROUTERS;
+  else /* Broadcast */
+    ifa->addr = iface->addr4->brd;
+  /*
+   * The above is just a workaround for BSD as it can't send broadcasts
+   * to 255.255.255.255. BSD systems need the network broadcast address instead.
+   *
+   * TODO: move this to sysdep code
+   */
+
+  init_list(&ifa->neigh_list);
+
+  add_tail(&p->iface_list, NODE ifa);
+
+  ifa->timer = tm_new_init(p->p.pool, rip_iface_timer, ifa, 0, 0);
+  ifa->rxmt_timer = tm_new_init(p->p.pool, rip_rxmt_timeout, ifa, 0, 0);
+
+  struct object_lock *lock = olock_new(p->p.pool);
+  lock->type = OBJLOCK_UDP;
+  lock->port = ic->port;
+  lock->iface = iface;
+  lock->data = ifa;
+  lock->hook = rip_iface_locked;
+  ifa->lock = lock;
+
+  olock_acquire(lock);
 }
 
 static void
-rip_timer(timer *t)
+rip_remove_iface(struct rip_proto *p, struct rip_iface *ifa)
 {
-  struct proto *p = t->data;
-  struct rip_entry *e, *et;
+  rip_iface_stop(ifa);
 
-  CHK_MAGIC;
-  DBG( "RIP: tick tock\n" );
-  
-  WALK_LIST_DELSAFE( e, et, P->garbage ) {
-    rte *rte;
-    rte = SKIP_BACK( struct rte, u.rip.garbage, e );
-    DBG( "Garbage: " ); rte_dump( rte );
+  TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name);
 
-    if (now - rte->u.rip.lastmodX > P_CF->timeout_time) {
-      debug( "RIP: entry is too old: " ); rte_dump( rte );
-      e->metric = P_CF->infinity;
-    }
+  rem_node(NODE ifa);
+
+  rfree(ifa->sk);
+  rfree(ifa->lock);
+  rfree(ifa->timer);
+
+  mb_free(ifa);
+}
+
+static int
+rip_reconfigure_iface(struct rip_proto *p, struct rip_iface *ifa, struct rip_iface_config *new)
+{
+  struct rip_iface_config *old = ifa->cf;
+
+  /* Change of these options would require to reset the iface socket */
+  if ((new->mode != old->mode) ||
+      (new->port != old->port) ||
+      (new->tx_tos != old->tx_tos) ||
+      (new->tx_priority != old->tx_priority) ||
+      (new->ttl_security != old->ttl_security) ||
+      (new->demand_circuit != old->demand_circuit))
+    return 0;
+
+  TRACE(D_EVENTS, "Reconfiguring interface %s", ifa->iface->name);
+
+  ifa->cf = new;
 
-    if (now - rte->u.rip.lastmodX > P_CF->garbage_time) {
-      debug( "RIP: entry is much too old: " ); rte_dump( rte );
-      rte_discard(p->table, rte);
+  rip_iface_update_buffers(ifa);
+
+  if ((! ifa->cf->demand_circuit) &&
+      (ifa->next_regular > (current_time() + new->update_time)))
+    ifa->next_regular = current_time() + (random() % new->update_time) + 100 MS;
+
+  if (ifa->up && new->demand_circuit && (new->passive != old->passive))
+  {
+    if (new->passive)
+      rip_send_flush(p, ifa);
+    else
+    {
+      rip_send_request(p, ifa);
+      rip_send_table(p, ifa, ifa->addr, 0);
     }
   }
 
-  DBG( "RIP: Broadcasting routing tables\n" );
+  if (new->check_link != old->check_link)
+    rip_iface_update_state(ifa);
+
+  if (new->bfd != old->bfd)
+    rip_iface_update_bfd(ifa);
+
+  if (ifa->up)
+    rip_iface_kick_timer(ifa);
+
+  return 1;
+}
+
+static void
+rip_reconfigure_ifaces(struct rip_proto *p, struct rip_config *cf)
+{
+  struct iface *iface;
+
+  WALK_LIST(iface, iface_list)
   {
-    struct rip_interface *rif;
-    P->tx_count ++;
+    if (!(iface->flags & IF_UP))
+      continue;
+
+    /* Ignore ifaces without appropriate address */
+    if (rip_is_v2(p) ? !iface->addr4 : !iface->llv6)
+      continue;
 
-    WALK_LIST( rif, P->interfaces ) {
-      struct iface *iface = rif->iface;
+    struct rip_iface *ifa = rip_find_iface(p, iface);
+    struct rip_iface_config *ic = (void *) iface_patt_find(&cf->patt_list, iface, NULL);
 
-      if (!iface) continue;
-      if (rif->patt->mode & IM_QUIET) continue;
-      if (!(iface->flags & IF_UP)) continue;
+    if (ifa && ic)
+    {
+      if (rip_reconfigure_iface(p, ifa, ic))
+       continue;
 
-      rif->triggered = (P->tx_count % 6);
-      rip_sendto( p, IPA_NONE, 0, rif );
+      /* Hard restart */
+      log(L_INFO "%s: Restarting interface %s", p->p.name, ifa->iface->name);
+      rip_remove_iface(p, ifa);
+      rip_add_iface(p, iface, ic);
     }
-  }
 
-  DBG( "RIP: tick tock done\n" );
-}
+    if (ifa && !ic)
+      rip_remove_iface(p, ifa);
 
-static int
-rip_start(struct proto *p)
-{
-  struct rip_interface *rif;
-  DBG( "RIP: starting instance...\n" );
-
-  P->magic = RIP_MAGIC;
-  fib_init( &P->rtable, p->pool, sizeof( struct rip_entry ), 0, NULL );
-  init_list( &P->connections );
-  init_list( &P->garbage );
-  init_list( &P->interfaces );
-  P->timer = tm_new( p->pool );
-  P->timer->data = p;
-  P->timer->randomize = 5;
-  P->timer->recurrent = (P_CF->period / 6)+1; 
-  P->timer->hook = rip_timer;
-  tm_start( P->timer, 5 );
-  rif = new_iface(p, NULL, 0, NULL);   /* Initialize dummy interface */
-  add_head( &P->interfaces, NODE rif );
-  CHK_MAGIC;
-
-  rip_init_instance(p);
-
-  DBG( "RIP: ...done\n");
-  return PS_UP;
+    if (!ifa && ic)
+      rip_add_iface(p, iface, ic);
+  }
 }
 
-static struct proto *
-rip_init(struct proto_config *cfg)
+static void
+rip_if_notify(struct proto *P, unsigned flags, struct iface *iface)
 {
-  struct proto *p = proto_new(cfg, sizeof(struct rip_proto));
+  struct rip_proto *p = (void *) P;
+  struct rip_config *cf = (void *) P->cf;
+  struct rip_iface *ifa = rip_find_iface(p, iface);
+
+  if (iface->flags & IF_IGNORE)
+    return;
+
+  /* Add, remove or restart interface */
+  if (flags & (IF_CHANGE_UPDOWN | (rip_is_v2(p) ? IF_CHANGE_ADDR4 : IF_CHANGE_LLV6)))
+  {
+    if (ifa)
+      rip_remove_iface(p, ifa);
+
+    if (!(iface->flags & IF_UP))
+      return;
+
+    /* Ignore ifaces without appropriate address */
+    if (rip_is_v2(p) ? !iface->addr4 : !iface->llv6)
+      return;
+
+    struct rip_iface_config *ic = (void *) iface_patt_find(&cf->patt_list, iface, NULL);
+    if (ic)
+      rip_add_iface(p, iface, ic);
+
+    return;
+  }
+
+  if (!ifa)
+    return;
 
-  return p;
+  if (flags & IF_CHANGE_MTU)
+    rip_iface_update_buffers(ifa);
+
+  if (flags & IF_CHANGE_LINK)
+    rip_iface_update_state(ifa);
 }
 
+
+/*
+ *     RIP timer events
+ */
+
+/**
+ * rip_timer - RIP main timer hook
+ * @t: timer
+ *
+ * The RIP main timer is responsible for routing table maintenance. Invalid or
+ * expired routes (&rip_rte) are removed and garbage collection of stale routing
+ * table entries (&rip_entry) is done. Changes are propagated to core tables,
+ * route reload is also done here. Note that garbage collection uses a maximal
+ * GC time, while interfaces maintain an illusion of per-interface GC times in
+ * rip_send_response().
+ *
+ * Keeping incoming routes and the selected outgoing route are two independent
+ * functions, therefore after garbage collection some entries now considered
+ * invalid (RIP_ENTRY_DUMMY) still may have non-empty list of incoming routes,
+ * while some valid entries (representing an outgoing route) may have that list
+ * empty.
+ *
+ * The main timer is not scheduled periodically but it uses the time of the
+ * current next event and the minimal interval of any possible event to compute
+ * the time of the next run.
+ */
 static void
-rip_dump(struct proto *p)
+rip_timer(timer *t)
 {
-  int i;
-  node *w, *e;
-  struct rip_interface *rif;
-  i = 0;
+  struct rip_proto *p = t->data;
+  struct rip_config *cf = (void *) (p->p.cf);
+  struct rip_iface *ifa;
+  struct rip_neighbor *n, *nn;
+  struct fib_iterator fit;
+  btime now_ = current_time();
+  btime next = now_ + MIN(cf->min_timeout_time, cf->max_garbage_time);
+  btime expires = 0;
 
-  CHK_MAGIC;
-  WALK_LIST( w, P->connections ) {
-    struct rip_connection *n = (void *) w;
-    debug( "RIP: connection #%d: %I\n", n->num, n->addr );
+  TRACE(D_EVENTS, "Main timer fired");
+
+  FIB_ITERATE_INIT(&fit, &p->rtable);
+
+  loop:
+  FIB_ITERATE_START(&p->rtable, &fit, struct rip_entry, en)
+  {
+    struct rip_rte *rt, **rp;
+    int changed = 0;
+
+    /* Checking received routes for timeout and for dead neighbors */
+    for (rp = &en->routes; rt = *rp; /* rp = &rt->next */)
+    {
+      if (!rip_valid_rte(rt) || (rt->expires <= now_))
+      {
+       rip_remove_rte(p, rp);
+       changed = 1;
+       continue;
+      }
+
+      next = MIN(next, rt->expires);
+      rp = &rt->next;
+    }
+
+    /* Propagating eventual change */
+    if (changed || p->rt_reload)
+    {
+      /*
+       * We have to restart the iteration because there may be a cascade of
+       * synchronous events rip_announce_rte() -> nest table change ->
+       * rip_rt_notify() -> p->rtable change, invalidating hidden variables.
+       */
+
+      FIB_ITERATE_PUT_NEXT(&fit, &p->rtable);
+      rip_announce_rte(p, en);
+      goto loop;
+    }
+
+    /* Checking stale entries for garbage collection timeout */
+    if (en->valid == RIP_ENTRY_STALE)
+    {
+      expires = en->changed + cf->max_garbage_time;
+
+      if (expires <= now_)
+      {
+       // TRACE(D_EVENTS, "entry is too old: %N", en->n.addr);
+       en->valid = 0;
+      }
+      else
+       next = MIN(next, expires);
+    }
+
+    /* Remove empty nodes */
+    if (!en->valid && !en->routes)
+    {
+      FIB_ITERATE_PUT(&fit);
+      fib_delete(&p->rtable, en);
+      goto loop;
+    }
   }
-  i = 0;
-  FIB_WALK( &P->rtable, e ) {
-    debug( "RIP: entry #%d: ", i++ );
-    rip_dump_entry( E );
-  } FIB_WALK_END;
-  i = 0;
-  WALK_LIST( rif, P->interfaces ) {
-    debug( "RIP: interface #%d: %s, %I, busy = %x\n", i++, rif->iface?rif->iface->name:"(dummy)", rif->sock->daddr, rif->busy );
+  FIB_ITERATE_END;
+
+  p->rt_reload = 0;
+
+  /* Handling neighbor expiration */
+  WALK_LIST(ifa, p->iface_list)
+  {
+    /* No expiration for demand circuit ifaces */
+    if (ifa->cf->demand_circuit)
+      continue;
+
+    WALK_LIST_DELSAFE(n, nn, ifa->neigh_list)
+      if (n->last_seen)
+      {
+       expires = n->last_seen + n->ifa->cf->timeout_time;
+
+       if (expires <= now_)
+         rip_remove_neighbor(p, n);
+       else
+         next = MIN(next, expires);
+      }
   }
+
+  tm_start(p->timer, MAX(next - now_, 100 MS));
 }
 
+static inline void
+rip_kick_timer(struct rip_proto *p)
+{
+  if ((p->timer->expires > (current_time() + 100 MS)))
+    tm_start(p->timer, 100 MS);
+}
+
+/**
+ * rip_iface_timer - RIP interface timer hook
+ * @t: timer
+ *
+ * RIP interface timers are responsible for scheduling both regular and
+ * triggered updates. Fixed, delay-independent period is used for regular
+ * updates, while minimal separating interval is enforced for triggered updates.
+ * The function also ensures that a new update is not started when the old one
+ * is still running.
+ */
 static void
-rip_get_route_info(rte *rte, byte *buf)
+rip_iface_timer(timer *t)
 {
-  buf += sprintf(buf, " (%d/%d)", rte->pref, rte->u.rip.metric );
-  sprintf(buf, " t%04x", rte->u.rip.tag );
+  struct rip_iface *ifa = t->data;
+  struct rip_proto *p = ifa->rip;
+  btime now_ = current_time();
+  btime period = ifa->cf->update_time;
+
+  if (ifa->cf->passive)
+    return;
+
+  TRACE(D_EVENTS, "Interface timer fired for %s", ifa->iface->name);
+
+  if (ifa->tx_active)
+  {
+    tm_start(ifa->timer, 100 MS);
+    return;
+  }
+
+  if (now_ >= ifa->next_regular)
+  {
+    /* Send regular update, set timer for next period (or following one if necessay) */
+    TRACE(D_EVENTS, "Sending regular updates for %s", ifa->iface->name);
+    rip_send_table(p, ifa, ifa->addr, 0);
+    ifa->next_regular += period * (1 + ((now_ - ifa->next_regular) / period));
+    ifa->want_triggered = 0;
+    p->triggered = 0;
+  }
+  else if (ifa->want_triggered && (now_ >= ifa->next_triggered))
+  {
+    /* Send triggered update, enforce interval between triggered updates */
+    TRACE(D_EVENTS, "Sending triggered updates for %s", ifa->iface->name);
+    rip_send_table(p, ifa, ifa->addr, ifa->want_triggered);
+    ifa->next_triggered = now_ + MIN(5 S, period / 2);
+    ifa->want_triggered = 0;
+    p->triggered = 0;
+  }
+
+  if (ifa->want_triggered && (ifa->next_triggered < ifa->next_regular))
+    tm_set(ifa->timer, ifa->next_triggered);
+  else if (ifa->next_regular != TIME_INFINITY)
+    tm_set(ifa->timer, ifa->next_regular);
 }
 
-static int
-rip_want_this_if(struct rip_interface *iface)
+
+static inline void
+rip_iface_kick_timer(struct rip_iface *ifa)
 {
-  return 1;
+  if ((! tm_active(ifa->timer)) || (ifa->timer->expires > (current_time() + 100 MS)))
+    tm_start(ifa->timer, 100 MS);
 }
 
 static void
-kill_iface(struct proto *p, struct rip_interface *i)
+rip_trigger_update(struct rip_proto *p)
 {
-  DBG( "RIP: Interface %s disappeared\n", i->iface->name);
-  rfree(i->sock);
-  mb_free(i);
+  if (p->triggered)
+    return;
+
+  struct rip_iface *ifa;
+  WALK_LIST(ifa, p->iface_list)
+  {
+    /* Interface not active */
+    if (! ifa->up)
+      continue;
+
+    /* Already scheduled */
+    if (ifa->want_triggered)
+      continue;
+
+    TRACE(D_EVENTS, "Scheduling triggered updates for %s", ifa->iface->name);
+    ifa->want_triggered = current_time();
+    rip_iface_kick_timer(ifa);
+    p->triggered = 1;
+  }
 }
 
+
 /*
- * new maybe null if we are creating initial send socket 
+ *     RIP protocol glue
  */
-static struct rip_interface *
-new_iface(struct proto *p, struct iface *new, unsigned long flags, struct iface_patt *patt )
-{
-  struct rip_interface *rif;
-  int want_multicast = 0;
-
-  rif = mb_allocz(p->pool, sizeof( struct rip_interface ));
-  rif->iface = new;
-  rif->proto = p;
-  rif->busy = NULL;
-  rif->patt = (struct rip_patt *) patt;
-
-  if (rif->patt)
-    want_multicast = (!(rif->patt->mode & IM_BROADCAST)) && (flags & IF_MULTICAST);
-  /* lookup multicasts over unnumbered links - no: rip is not defined over unnumbered links */
-
-  if (want_multicast)
-    DBG( "Doing multicasts!\n" );
-
-  rif->sock = sk_new( p->pool );
-  rif->sock->type = want_multicast?SK_UDP_MC:SK_UDP;
-  rif->sock->sport = P_CF->port;
-  rif->sock->rx_hook = rip_rx;
-  rif->sock->data = rif;
-  rif->sock->rbsize = 10240;
-  rif->sock->iface = new;              /* Automagically works for dummy interface */
-  rif->sock->tbuf = mb_alloc( p->pool, sizeof( struct rip_packet ));
-  rif->sock->tx_hook = rip_tx;
-  rif->sock->err_hook = rip_tx_err;
-  rif->sock->daddr = IPA_NONE;
-  rif->sock->dport = P_CF->port;
-  if (new)
-    rif->sock->ttl = 1;
-  else
-    rif->sock->ttl = 30;
-  rif->sock->tos = IP_PREC_INTERNET_CONTROL;
-
-  rif->sock->daddr = new->addr->brd;
-  if (new->addr->flags & IA_UNNUMBERED)
-    log( L_WARN "RIP/%s: rip is not defined over unnumbered links\n", P_NAME );
-  if (want_multicast) {
-    rif->sock->daddr = ipa_from_u32(0xe0000009);
-    rif->sock->saddr = ipa_from_u32(0xe0000009);
-  }
 
-  if (!ipa_nonzero(rif->sock->daddr)) {
-    log( L_WARN "RIP/%s: interface %s is too strange for me", P_NAME, rif->iface ? rif->iface->name : "(dummy)" );
-  } else
-    if (!(rif->patt->mode & IM_NOLISTEN))
-      if (sk_open(rif->sock)<0) {
-       log( L_ERR "RIP/%s: could not listen on %s", P_NAME, rif->iface ? rif->iface->name : "(dummy)" );
-       /* Don't try to transmit into this one? Well, why not? This should not happen, anyway :-) */
-      }
+static void
+rip_reload_routes(struct channel *C)
+{
+  struct rip_proto *p = (struct rip_proto *) C->proto;
 
-  log( L_DEBUG "RIP/%s: listening on %s, port %d, mode %s (%I)", P_NAME, rif->iface ? rif->iface->name : "(dummy)", P_CF->port, want_multicast ? "multicast" : "broadcast", rif->sock->daddr );
-  
-  return rif;
+  if (p->rt_reload)
+    return;
+
+  TRACE(D_EVENTS, "Scheduling route reload");
+  p->rt_reload = 1;
+  rip_kick_timer(p);
 }
 
 static void
-rip_if_notify(struct proto *p, unsigned c, struct iface *iface)
+rip_make_tmp_attrs(struct rte *rt, struct linpool *pool)
 {
-  DBG( "RIP: if notify\n" );
-  if (iface->flags & IF_IGNORE)
-    return;
-  if (c & IF_CHANGE_DOWN) {
-    struct rip_interface *i;
-    i = find_interface(p, iface);
-    if (i) {
-      rem_node(NODE i);
-      kill_iface(p, i);
-    }
-  }
-  if (c & IF_CHANGE_UP) {
-    struct rip_interface *rif;
-    struct iface_patt *k = iface_patt_match(&P_CF->iface_list, iface);
-
-    if (!k) return; /* We are not interested in this interface */
-    DBG("adding interface %s\n", iface->name );
-    rif = new_iface(p, iface, iface->flags, k);
-    add_head( &P->interfaces, NODE rif );
-  }
+  rte_init_tmp_attrs(rt, pool, 2);
+  rte_make_tmp_attr(rt, EA_RIP_METRIC, EAF_TYPE_INT, rt->u.rip.metric);
+  rte_make_tmp_attr(rt, EA_RIP_TAG, EAF_TYPE_INT, rt->u.rip.tag);
 }
 
-static struct ea_list *
-rip_gen_attrs(struct proto *p, struct linpool *pool, int metric, u16 tag)
+static void
+rip_store_tmp_attrs(struct rte *rt, struct linpool *pool)
 {
-  struct ea_list *l = lp_alloc(pool, sizeof(struct ea_list) + 2*sizeof(eattr));
+  rte_init_tmp_attrs(rt, pool, 2);
+  rt->u.rip.metric = rte_store_tmp_attr(rt, EA_RIP_METRIC);
+  rt->u.rip.tag = rte_store_tmp_attr(rt, EA_RIP_TAG);
+}
 
-  l->next = NULL;
-  l->flags = EALF_SORTED;
-  l->count = 2;
-  l->attrs[0].id = EA_RIP_TAG;
-  l->attrs[0].flags = 0;
-  l->attrs[0].type = EAF_TYPE_INT | EAF_TEMP;
-  l->attrs[0].u.data = tag;
-  l->attrs[1].id = EA_RIP_METRIC;
-  l->attrs[1].flags = 0;
-  l->attrs[1].type = EAF_TYPE_INT | EAF_TEMP;
-  l->attrs[1].u.data = metric;
-  return l;
+static int
+rip_rte_better(struct rte *new, struct rte *old)
+{
+  return new->u.rip.metric < old->u.rip.metric;
 }
 
 static int
-rip_import_control(struct proto *p, struct rte **rt, struct ea_list **attrs, struct linpool *pool)
+rip_rte_same(struct rte *new, struct rte *old)
 {
-  if ((*rt)->attrs->proto == p)        /* My own must not be touched */
-    return 1;
+  return ((new->u.rip.metric == old->u.rip.metric) &&
+         (new->u.rip.tag == old->u.rip.tag) &&
+         (new->u.rip.from == old->u.rip.from));
+}
 
-  if ((*rt)->attrs->source != RTS_RIP) {
-    struct ea_list *new = rip_gen_attrs(p, pool, 1, 0);
-    new->next = *attrs;
-    *attrs = new;
-  }
-  return 0;
+
+static void
+rip_postconfig(struct proto_config *CF)
+{
+  // struct rip_config *cf = (void *) CF;
+
+  /* Define default channel */
+  if (EMPTY_LIST(CF->channels))
+    channel_config_new(NULL, net_label[CF->net_type], CF->net_type, CF);
 }
 
-static struct ea_list *
-rip_make_tmp_attrs(struct rte *rt, struct linpool *pool)
+static struct proto *
+rip_init(struct proto_config *CF)
 {
-  struct proto *p = rt->attrs->proto;
-  return rip_gen_attrs(p, pool, rt->u.rip.metric, rt->u.rip.tag);
+  struct proto *P = proto_new(CF);
+
+  P->main_channel = proto_add_channel(P, proto_cf_main_channel(CF));
+
+  P->if_notify = rip_if_notify;
+  P->rt_notify = rip_rt_notify;
+  P->neigh_notify = rip_neigh_notify;
+  P->reload_routes = rip_reload_routes;
+  P->make_tmp_attrs = rip_make_tmp_attrs;
+  P->store_tmp_attrs = rip_store_tmp_attrs;
+  P->rte_better = rip_rte_better;
+  P->rte_same = rip_rte_same;
+
+  return P;
 }
 
-static void 
-rip_store_tmp_attrs(struct rte *rt, struct ea_list *attrs)
+static int
+rip_start(struct proto *P)
 {
-  struct proto *p = rt->attrs->proto;
+  struct rip_proto *p = (void *) P;
+  struct rip_config *cf = (void *) (P->cf);
+
+  init_list(&p->iface_list);
+  fib_init(&p->rtable, P->pool, cf->rip2 ? NET_IP4 : NET_IP6,
+          sizeof(struct rip_entry), OFFSETOF(struct rip_entry, n), 0, NULL);
+  p->rte_slab = sl_new(P->pool, sizeof(struct rip_rte));
+  p->timer = tm_new_init(P->pool, rip_timer, p, 0, 0);
 
-  rt->u.rip.tag = ea_find(attrs, EA_RIP_TAG)->u.data;
-  rt->u.rip.metric = ea_find(attrs, EA_RIP_METRIC)->u.data;
+  p->rip2 = cf->rip2;
+  p->ecmp = cf->ecmp;
+  p->infinity = cf->infinity;
+  p->triggered = 0;
+
+  p->log_pkt_tbf = (struct tbf){ .rate = 1, .burst = 5 };
+  p->log_rte_tbf = (struct tbf){ .rate = 4, .burst = 20 };
+
+  tm_start(p->timer, MIN(cf->min_timeout_time, cf->max_garbage_time));
+
+  return PS_UP;
 }
 
-static void
-rip_rt_notify(struct proto *p, struct network *net, struct rte *new, struct rte *old, struct ea_list *attrs)
+static int
+rip_shutdown(struct proto *P)
 {
-  CHK_MAGIC;
+  struct rip_proto *p = (void *) P;
 
-  if (old) {
-    struct rip_entry *e = fib_find( &P->rtable, &net->n.prefix, net->n.pxlen );
-    if (!e)
-      log( L_BUG "Deleting nonexistent entry?!" );
-    fib_delete( &P->rtable, e );
-  }
+  TRACE(D_EVENTS, "Shutdown requested");
 
-  if (new) {
-    struct rip_entry *e;
-    if (fib_find( &P->rtable, &net->n.prefix, net->n.pxlen ))
-      log( L_BUG "Inserting entry which is already there?" );
-    e = fib_get( &P->rtable, &net->n.prefix, net->n.pxlen );
-
-    e->nexthop = new->attrs->gw;
-    e->tag = ea_find(attrs, EA_RIP_TAG)->u.data;
-    e->metric = ea_find(attrs, EA_RIP_METRIC)->u.data;
-    if (e->metric > P_CF->infinity)
-      e->metric = P_CF->infinity;
-    if (!e->metric)    /* FIXME: this is metric for external routes. Should it be configurable? */
-      e->metric = 5;
-    e->whotoldme = new->attrs->from;
-    e->updated = e->changed = now;
-    e->flags = 0;
-  }
+  struct rip_iface *ifa;
+  WALK_LIST(ifa, p->iface_list)
+    rip_iface_stop(ifa);
+
+  return PS_DOWN;
 }
 
 static int
-rip_rte_better(struct rte *new, struct rte *old)
+rip_reconfigure(struct proto *P, struct proto_config *CF)
 {
-  if (ipa_equal(old->attrs->from, new->attrs->from))
-    return 1;
+  struct rip_proto *p = (void *) P;
+  struct rip_config *new = (void *) CF;
+  // struct rip_config *old = (void *) (P->cf);
 
-  if (old->u.rip.metric < new->u.rip.metric)
+  if (new->rip2 != p->rip2)
     return 0;
 
-  if (old->u.rip.metric > new->u.rip.metric)
-    return 1;
+  if (new->infinity != p->infinity)
+    return 0;
 
-  if ((old->u.rip.metric != 16) && (new->u.rip.metric == 16)) {        /* FIXME: check wrt. strange infinity values */
-    struct proto *p = new->attrs->proto;
-    new->u.rip.lastmodX = now - P_CF->timeout_time;    /* Check this: if new metric is 16, act as it was timed out */
-  }
+  if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
+    return 0;
+
+  TRACE(D_EVENTS, "Reconfiguring");
 
-  if ((old->u.rip.metric == new->u.rip.metric) &&
-      ((now - old->u.rip.lastmodX) > 60))      /* FIXME (nonurgent): this probably should be P_CF->timeout_time / 2 if old->attrs->proto == new->attrs->proto, else don't do this check */
-    return 1;
+  p->p.cf = CF;
+  p->ecmp = new->ecmp;
+  rip_reconfigure_ifaces(p, new);
 
-  return 0;
+  p->rt_reload = 1;
+  rip_kick_timer(p);
+
+  return 1;
 }
 
 static void
-rip_rte_insert(net *net, rte *rte)
+rip_get_route_info(rte *rte, byte *buf)
 {
-  struct proto *p = rte->attrs->proto;
-  rte->u.rip.lastmodX = now;
-  add_head( &P->garbage, &rte->u.rip.garbage );
+  buf += bsprintf(buf, " (%d/%d)", rte->pref, rte->u.rip.metric);
+
+  if (rte->u.rip.tag)
+    bsprintf(buf, " [%04x]", rte->u.rip.tag);
 }
 
-static void
-rip_rte_remove(net *net, rte *rte)
+static int
+rip_get_attr(const eattr *a, byte *buf, int buflen UNUSED)
 {
-  struct proto *p = rte->attrs->proto;
-  rem_node( &rte->u.rip.garbage );
+  switch (a->id)
+  {
+  case EA_RIP_METRIC:
+    bsprintf(buf, "metric: %d", a->u.data);
+    return GA_FULL;
+
+  case EA_RIP_TAG:
+    bsprintf(buf, "tag: %04x", a->u.data);
+    return GA_FULL;
+
+  default:
+    return GA_UNKNOWN;
+  }
 }
 
 void
-rip_init_instance(struct proto *p)
+rip_show_interfaces(struct proto *P, const char *iff)
 {
-  p->preference = DEF_PREF_RIP;
-  p->if_notify = rip_if_notify;
-  p->rt_notify = rip_rt_notify;
-  p->import_control = rip_import_control;
-  p->make_tmp_attrs = rip_make_tmp_attrs;
-  p->store_tmp_attrs = rip_store_tmp_attrs;
-  p->rte_better = rip_rte_better;
-  p->rte_insert = rip_rte_insert;
-  p->rte_remove = rip_rte_remove;
+  struct rip_proto *p = (void *) P;
+  struct rip_iface *ifa = NULL;
+  struct rip_neighbor *n = NULL;
+
+  if (p->p.proto_state != PS_UP)
+  {
+    cli_msg(-1021, "%s: is not up", p->p.name);
+    cli_msg(0, "");
+    return;
+  }
+
+  cli_msg(-1021, "%s:", p->p.name);
+  cli_msg(-1021, "%-10s %-6s %6s %6s %7s",
+         "Interface", "State", "Metric", "Nbrs", "Timer");
+
+  WALK_LIST(ifa, p->iface_list)
+  {
+    if (iff && !patmatch(iff, ifa->iface->name))
+      continue;
+
+    int nbrs = 0;
+    WALK_LIST(n, ifa->neigh_list)
+      if (n->last_seen)
+       nbrs++;
+
+    btime now_ = current_time();
+    btime timer = ((ifa->next_regular < TIME_INFINITY) && (ifa->next_regular > now_)) ?
+      (ifa->next_regular - now_) : 0;
+    cli_msg(-1021, "%-10s %-6s %6u %6u %7t",
+           ifa->iface->name, (ifa->up ? "Up" : "Down"), ifa->cf->metric, nbrs, timer);
+  }
+
+  cli_msg(0, "");
 }
 
 void
-rip_init_config(struct rip_proto_config *c)
+rip_show_neighbors(struct proto *P, const char *iff)
 {
-  init_list(&c->iface_list);
-  c->infinity  = 16;
-  c->port      = 520;
-  c->period    = 30;
-  c->garbage_time = 120+180;
-  c->timeout_time = 120;
-  c->passwords = NULL;
-  c->authtype  = AT_NONE;
-}
+  struct rip_proto *p = (void *) P;
+  struct rip_iface *ifa = NULL;
+  struct rip_neighbor *n = NULL;
 
-static void
-rip_preconfig(struct protocol *x, struct config *c)
-{
-  DBG( "RIP: preconfig\n" );
+  if (p->p.proto_state != PS_UP)
+  {
+    cli_msg(-1022, "%s: is not up", p->p.name);
+    cli_msg(0, "");
+    return;
+  }
+
+  cli_msg(-1022, "%s:", p->p.name);
+  cli_msg(-1022, "%-25s %-10s %6s %6s %7s",
+         "IP address", "Interface", "Metric", "Routes", "Seen");
+
+  WALK_LIST(ifa, p->iface_list)
+  {
+    if (iff && !patmatch(iff, ifa->iface->name))
+      continue;
+
+    WALK_LIST(n, ifa->neigh_list)
+    {
+      if (!n->last_seen)
+       continue;
+
+      btime timer = current_time() - n->last_seen;
+      cli_msg(-1022, "%-25I %-10s %6u %6u %7t",
+             n->nbr->addr, ifa->iface->name, ifa->cf->metric, n->uc, timer);
+    }
+  }
+
+  cli_msg(0, "");
 }
 
 static void
-rip_postconfig(struct proto_config *c)
+rip_dump(struct proto *P)
 {
+  struct rip_proto *p = (struct rip_proto *) P;
+  struct rip_iface *ifa;
+  int i;
+
+  i = 0;
+  FIB_WALK(&p->rtable, struct rip_entry, en)
+  {
+    debug("RIP: entry #%d: %N via %I dev %s valid %d metric %d age %t\n",
+         i++, en->n.addr, en->next_hop, en->iface ? en->iface->name : "(null)",
+         en->valid, en->metric, current_time() - en->changed);
+
+    for (struct rip_rte *e = en->routes; e; e = e->next)
+      debug("RIP:   via %I metric %d expires %t\n",
+           e->next_hop, e->metric, e->expires - current_time());
+  }
+  FIB_WALK_END;
+
+  i = 0;
+  WALK_LIST(ifa, p->iface_list)
+  {
+    debug("RIP: interface #%d: %s, %I, up = %d, busy = %d\n",
+         i++, ifa->iface->name, ifa->sk ? ifa->sk->daddr : IPA_NONE,
+         ifa->up, ifa->tx_active);
+  }
 }
 
+
 struct protocol proto_rip = {
-  name: "RIP",
-  template: "rip%d",
-  preconfig: rip_preconfig,
-  postconfig: rip_postconfig,
-  get_route_info: rip_get_route_info,
-
-  init: rip_init,
-  dump: rip_dump,
-  start: rip_start,
+  .name =              "RIP",
+  .template =          "rip%d",
+  .class =             PROTOCOL_RIP,
+  .preference =                DEF_PREF_RIP,
+  .channel_mask =      NB_IP,
+  .proto_size =                sizeof(struct rip_proto),
+  .config_size =       sizeof(struct rip_config),
+  .postconfig =                rip_postconfig,
+  .init =              rip_init,
+  .dump =              rip_dump,
+  .start =             rip_start,
+  .shutdown =          rip_shutdown,
+  .reconfigure =       rip_reconfigure,
+  .get_route_info =    rip_get_route_info,
+  .get_attr =          rip_get_attr
 };