]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Added the mkernel protocol
authorOndřej Hlavatý <aearsis@eideo.cz>
Fri, 27 May 2016 00:40:27 +0000 (02:40 +0200)
committerOndřej Hlavatý <aearsis@eideo.cz>
Fri, 27 May 2016 00:40:27 +0000 (02:40 +0200)
lib/socket.h
nest/iface.c
nest/iface.h
sysdep/unix/Makefile
sysdep/unix/io.c
sysdep/unix/mkrt.Y [new file with mode: 0644]
sysdep/unix/mkrt.c [new file with mode: 0644]
sysdep/unix/mkrt.h [new file with mode: 0644]

index 5b8f0dd38c3a1b33ea7e0de8bde126ffce90bb2f..7fed4ca3355a99a09e43f2b11ac8265d52bca95c 100644 (file)
@@ -116,6 +116,7 @@ extern int sk_priority_control;             /* Suggested priority for control traffic, shou
 #define SK_MAGIC       7          /* Internal use by sysdep code */
 #define SK_UNIX_PASSIVE        8
 #define SK_UNIX                9
+#define SK_IGMP                10         /* ?  -  ?  -  ?  ?   ?      */
 
 /*
  *     Socket subtypes
@@ -146,6 +147,11 @@ extern int sk_priority_control;            /* Suggested priority for control traffic, shou
  * per-packet basis using platform dependent options (but these are not
  * available in some corner cases). The first way is used when SKF_BIND is
  * specified, the second way is used otherwise.
+ *
+ *  SK_IGMP sockets are just IP sockets with IPPROTO_IGMP, but does not receive
+ *  packets directly from kernel. Instead, bird forwards all packets received
+ *  on multicast routing control socket to this socket internally. That means,
+ *  all IGMP packets even for groups that noone is joined are received here.
  */
 
 #endif
index d031d4cc9c5ba66747f740a9369fe2905e6521e9..4c3e077443ec52f0b3d7c1448ab9f3a840980953 100644 (file)
@@ -85,6 +85,8 @@ if_dump(struct iface *i)
     debug(" IGN");
   if (i->flags & IF_TMP_DOWN)
     debug(" TDOWN");
+  if (i->flags & IF_VIFI_ASSIGNED)
+    debug(" MR");
   debug(" MTU=%d\n", i->mtu);
   WALK_LIST(a, i->addrs)
     {
@@ -115,7 +117,7 @@ if_what_changed(struct iface *i, struct iface *j)
 {
   unsigned c;
 
-  if (((i->flags ^ j->flags) & ~(IF_UP | IF_SHUTDOWN | IF_UPDATED | IF_ADMIN_UP | IF_LINK_UP | IF_TMP_DOWN | IF_JUST_CREATED))
+  if (((i->flags ^ j->flags) & ~(IF_UP | IF_SHUTDOWN | IF_UPDATED | IF_ADMIN_UP | IF_LINK_UP | IF_TMP_DOWN | IF_JUST_CREATED | IF_VIFI_ASSIGNED))
       || i->index != j->index)
     return IF_CHANGE_TOO_MUCH;
   c = 0;
@@ -131,7 +133,7 @@ if_what_changed(struct iface *i, struct iface *j)
 static inline void
 if_copy(struct iface *to, struct iface *from)
 {
-  to->flags = from->flags | (to->flags & IF_TMP_DOWN);
+  to->flags = from->flags | (to->flags & (IF_TMP_DOWN | IF_VIFI_ASSIGNED));
   to->mtu = from->mtu;
 }
 
@@ -780,10 +782,11 @@ if_show(void)
        type = "PtP";
       else
        type = "MultiAccess";
-      cli_msg(-1004, "\t%s%s%s Admin%s Link%s%s%s MTU=%d",
+      cli_msg(-1004, "\t%s%s%s%s Admin%s Link%s%s%s MTU=%d",
              type,
              (i->flags & IF_BROADCAST) ? " Broadcast" : "",
              (i->flags & IF_MULTICAST) ? " Multicast" : "",
+             (i->flags & IF_VIFI_ASSIGNED) ? " MRoutable" : "",
              (i->flags & IF_ADMIN_UP) ? "Up" : "Down",
              (i->flags & IF_LINK_UP) ? "Up" : "Down",
              (i->flags & IF_LOOPBACK) ? " Loopback" : "",
index a0411f0aace4141966959bcde0d4343aa26fd599..042543ed03ac538e2da2630b92c01756c1164f1f 100644 (file)
@@ -33,6 +33,7 @@ struct iface {
   unsigned flags;
   unsigned mtu;
   unsigned index;                      /* OS-dependent interface index */
+  unsigned vifi;                       /* VIF index for multicast routing */
   list addrs;                          /* Addresses assigned to this interface */
   struct ifa *addr;                    /* Primary address */
   list neighbors;                      /* All neighbors on this interface */
@@ -47,6 +48,7 @@ struct iface {
 #define IF_IGNORE 0x40                 /* Not to be used by routing protocols (loopbacks etc.) */
 #define IF_ADMIN_UP 0x80               /* Administrative up (e.g. IFF_UP in Linux) */
 #define IF_LINK_UP 0x100               /* Link available (e.g. IFF_LOWER_UP in Linux) */
+#define IF_VIFI_ASSIGNED 0x200         /* This device is set up for multicast routing */
 
 #define IA_PRIMARY 0x10000             /* This address is primary */
 #define IA_SECONDARY 0x20000           /* This address has been reported as secondary by the kernel */
@@ -101,6 +103,12 @@ struct iface *if_find_by_name(char *);
 struct iface *if_get_by_name(char *);
 void ifa_recalc_all_primary_addresses(void);
 
+static inline int
+if_get_vifi(struct iface *iface)
+{
+  return (iface && (iface->flags & IF_VIFI_ASSIGNED)) ? iface->vifi : -1;
+}
+
 
 /* The Neighbor Cache */
 
index c5d554310a0536a099b462792d27dcda6c90cd6c..88b7a95e4ef0c6c73fd107a64e0796274b309230 100644 (file)
@@ -1,5 +1,5 @@
-src := io.c krt.c log.c main.c random.c
+src := io.c krt.c log.c main.c random.c mkrt.c
 obj := $(src-o-files)
 $(all-daemon)
 $(cf-local)
-$(conf-y-targets): $(s)krt.Y
+$(conf-y-targets): $(s)krt.Y $(s)mkrt.Y
index bd5f3d0644e8bba31894b7fe2152c26cba5170f0..d6e51dcc8b3051308e639f725cb26006b6b9a2de 100644 (file)
@@ -40,6 +40,7 @@
 
 #include "sysdep/unix/unix.h"
 #include CONFIG_INCLUDE_SYSIO_H
+#include "sysdep/unix/mkrt.h"
 
 /* Maximum number of calls of tx handler for one socket in one
  * poll iteration. Should be small enough to not monopolize CPU by
@@ -825,7 +826,7 @@ sk_skip_ip_header(byte *pkt, int *len)
 byte *
 sk_rx_buffer(sock *s, int *len)
 {
-  if (sk_is_ipv4(s) && s->type == SK_IP)
+  if (sk_is_ipv4(s) && (s->type == SK_IP || s->type == SK_IGMP))
     return sk_skip_ip_header(s->rbuf, len);
   else
     return s->rbuf;
@@ -1232,7 +1233,7 @@ sk_setup(sock *s)
     s->flags |= SKF_PKTINFO;
 
 #ifdef CONFIG_USE_HDRINCL
-  if (sk_is_ipv4(s) && (s->type == SK_IP) && (s->flags & SKF_PKTINFO))
+  if (sk_is_ipv4(s) && (s->type == SK_IP || s->type == SK_IGMP) && (s->flags & SKF_PKTINFO))
   {
     s->flags &= ~SKF_PKTINFO;
     s->flags |= SKF_HDRINCL;
@@ -1270,7 +1271,7 @@ sk_setup(sock *s)
       if (sk_request_cmsg4_ttl(s) < 0)
        return -1;
 
-    if ((s->type == SK_UDP) || (s->type == SK_IP))
+    if ((s->type == SK_UDP) || (s->type == SK_IP) || (s->type == SK_IGMP))
       if (sk_disable_mtu_disc4(s) < 0)
        return -1;
 
@@ -1297,7 +1298,7 @@ sk_setup(sock *s)
       if (sk_request_cmsg6_ttl(s) < 0)
        return -1;
 
-    if ((s->type == SK_UDP) || (s->type == SK_IP))
+    if ((s->type == SK_UDP) || (s->type == SK_IP) || (s->type == SK_IGMP))
       if (sk_disable_mtu_disc6(s) < 0)
        return -1;
 
@@ -1458,6 +1459,11 @@ sk_open(sock *s)
     do_bind = 1;
     break;
 
+  case SK_IGMP:
+    af = AF_INET;
+    s->dport = IPPROTO_IGMP;
+    s->rbsize = 0; /* read buffer is shared */
+    /* Fall thru */
   case SK_IP:
     fd = socket(af, SOCK_RAW, s->dport);
     bind_port = 0;
@@ -1538,6 +1544,12 @@ sk_open(sock *s)
     sk_alloc_bufs(s);
   }
 
+  if (s->type == SK_IGMP)
+    {
+      mkrt_listen(s);
+      return 0;
+    }
+
   if (!(s->flags & SKF_THREAD))
     sk_insert(s);
   return 0;
@@ -1724,6 +1736,7 @@ sk_maybe_write(sock *s)
 
   case SK_UDP:
   case SK_IP:
+  case SK_IGMP:
     {
       if (s->tbuf == s->tpos)
        return 1;
@@ -2112,6 +2125,7 @@ io_init(void)
   init_list(&sock_list);
   init_list(&global_event_list);
   krt_io_init();
+  mkrt_io_init();
   init_times();
   update_times();
   boot_time = now;
diff --git a/sysdep/unix/mkrt.Y b/sysdep/unix/mkrt.Y
new file mode 100644 (file)
index 0000000..8be6fac
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *     BIRD -- UNIX Multicast route syncer configuration
+ *
+ *     (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
+ *
+ *     Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "sysdep/unix/mkrt.h"
+
+CF_DEFINES
+
+CF_DECLS
+
+CF_KEYWORDS(MKERNEL)
+
+CF_GRAMMAR
+
+/* Kernel interface protocol */
+
+CF_ADDTO(proto, mkrt_proto '}' { mkrt_config_finish(this_proto); })
+
+mkrt_proto_start: proto_start MKERNEL { this_proto = mkrt_config_init($1); }
+ ;
+
+mkrt_proto:
+   mkrt_proto_start proto_name '{'
+ | mkrt_proto mkrt_proto_item ';'
+
+mkrt_proto_item:
+   proto_item
+ | proto_channel
+ ;
+
+CF_CODE
+
+CF_END
diff --git a/sysdep/unix/mkrt.c b/sysdep/unix/mkrt.c
new file mode 100644 (file)
index 0000000..77a8135
--- /dev/null
@@ -0,0 +1,585 @@
+/*
+ *  BIRD -- Multicast routing kernel
+ *
+ *  (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
+ *
+ *  Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/*
+ * DOC: Multicast route kernel synchronization
+ *
+ * This protocol is the BIRD's interface to the kernel part of multicast
+ * routing. It assignes the VIF indices to interfaces, forwards the IGMP
+ * packets to SK_IGMP sockets and, of course, adds MFC entries to the kernel.
+ *
+ * Multicast in current kernel is a bit tricky. There must be exactly one
+ * socket on which setsockopt MRT_INIT is called, then multicast forwarding is
+ * enabled. Every multicast routing table update must be done through this
+ * protocol.
+ *
+ * Also, that socket is the only one that receives IGMP packets on non-joined
+ * groups. These packets IGMP protocol needs to receive, so we forward them
+ * internally. To simulate sane behavior, protocol can open socket with type
+ * SK_IGMP, which is almost as a SK_IP, IPPROTO_IGMP socket but receives copy
+ * of all packets.
+ *
+ * As always with system-dependent code, prepare for everything. Because the
+ * BSD kernel knows nothing apart from (S,G) routes, and Linux even blocked the
+ * (*,G) routes for something not being a regular (*,G) routes, we must add the
+ * routes in reaction to missed packets. This is very bad, but probably the
+ * only solution, until someone rewrites the kernel part.
+ *
+ * Part of the protocol is global and "static", without the need to configure.
+ * Another part is a usual proto instance.
+ */
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "lib/socket.h"
+#include "sysdep/unix/unix.h"
+#include "sysdep/unix/mkrt.h"
+
+#define HASH_I_KEY(n)  n->iface->index
+#define HASH_I_NEXT(n) n->next
+#define HASH_I_EQ(a,b) (a == b)
+#define HASH_I_FN(n)   n
+
+#define HASH_MFC_KEY(n)                n->ga
+#define HASH_MFC_NEXT(n)       n->next
+#define HASH_MFC_EQ(a,b)       ipa_equal(a, b)
+#define HASH_MFC_FN(k)         ipa_hash(k)
+
+static struct mkrt_config *mkrt_cf;
+
+/* Global code for SK_IGMP sockets */
+
+struct mkrt_iface {
+  struct mkrt_iface *next;
+  struct iface *iface;
+  list sockets;
+};
+
+static struct mkrt_global {
+  pool *pool;
+  list sockets;
+  HASH(struct mkrt_iface) ifaces;
+} mkrt_global;
+
+void
+mkrt_io_init(void)
+{
+  mkrt_global.pool = rp_new(&root_pool, "Multicast kernel Syncer");
+  HASH_INIT(mkrt_global.ifaces, mkrt_global.pool, 6);
+  init_list(&mkrt_global.sockets);
+}
+
+static struct mkrt_iface *
+mkrt_iface_find(struct mkrt_proto *p, unsigned ifindex)
+{
+  return HASH_FIND(mkrt_global.ifaces, HASH_I, ifindex);
+}
+
+static struct mkrt_iface *
+mkrt_iface_get(unsigned ifindex)
+{
+  struct mkrt_iface *ifa = HASH_FIND(mkrt_global.ifaces, HASH_I, ifindex);
+  if (ifa)
+    return ifa;
+
+  ifa = mb_allocz(mkrt_global.pool, sizeof(struct mkrt_iface));
+  init_list(&ifa->sockets);
+  ifa->iface = if_find_by_index(ifindex);
+
+  HASH_INSERT(mkrt_global.ifaces, HASH_I, ifa);
+  return ifa;
+}
+
+/*
+ * Add the socket into the list of sockets that are passed a copy of every IGMP
+ * packet received on the control socket.
+ */
+void
+mkrt_listen(sock *s)
+{
+  ASSERT(s->type == SK_IGMP);
+
+  if (s->iface)
+    {
+      struct mkrt_iface *i = mkrt_iface_get(s->iface->index);
+      add_tail(&i->sockets, &s->n);
+    }
+  else
+    add_tail(&mkrt_global.sockets, &s->n);
+
+  log(L_INFO "Socket fd %i getting IGMP", s->fd);
+}
+
+/*
+ * Forward a packet from one socket to another. Emulates the receiving routine.
+ * Socket is in exactly the same state as if it received the packet itself, but
+ * must not modify it to preserve it for others.
+ */
+static inline void
+mkrt_rx_forward(sock *from, sock *to, int len)
+{
+  if (!to->rx_hook)
+    return;
+
+  to->faddr = from->faddr;
+  if (to->flags & SKF_LADDR_RX)
+    {
+      to->laddr = from->laddr;
+      to->lifindex = from->lifindex;
+    }
+
+  to->rbuf = from->rbuf;
+  to->rpos = from->rpos;
+  to->rbsize = from->rbsize;
+
+  to->rx_hook(to, len);
+
+  to->faddr = to->laddr = IPA_NONE;
+  to->lifindex = 0;
+  to->rbuf = to->rpos = NULL;
+  to->rbsize = 0;
+}
+
+/*
+ * Forward a packet to all sockets on a list.
+ */
+static inline void
+mkrt_rx_forward_all(list *sockets, sock *sk, int len)
+{
+  node *n, *next;
+
+  WALK_LIST_DELSAFE(n, next, *sockets)
+    mkrt_rx_forward(sk, SKIP_BACK(sock, n, n), len);
+}
+
+/***************
+  Mkernel proto
+ ***************/
+
+/*
+ * Call a setsockopt with a MRT_ option.
+ */
+static inline int
+mkrt_call(struct mkrt_proto *mkrt, int option_name, const void *val, socklen_t len)
+{
+  return setsockopt(mkrt->igmp_sock->fd, IPPROTO_IP, option_name, val, len);
+}
+
+static inline vifi_t
+mkrt_alloc_vifi(struct mkrt_proto *p, struct iface *iface)
+{
+  if (p->vif_count >= MAXVIFS)
+    {
+      log(L_ERR "Maximum number of interfaces for multicast routing reached.");
+      return -1;
+    }
+
+  for (vifi_t i = 0; ; i = (i + 1) % MAXVIFS)
+    if (p->vif_map[i] == NULL)
+      {
+       p->vif_map[i] = iface;
+       iface->vifi = i;
+       p->vif_count++;
+       return i;
+      }
+}
+
+static inline void
+mkrt_free_vifi(struct mkrt_proto *p, vifi_t vifi)
+{
+  p->vif_count -= p->vif_map[vifi] != NULL;
+  p->vif_map[vifi] = NULL;
+}
+
+static void
+mkrt_add_vif(struct mkrt_proto *p, struct iface *i)
+{
+  int err;
+
+  if (i->flags & IF_VIFI_ASSIGNED)
+    return;
+
+  mkrt_alloc_vifi(p, i);
+
+  struct vifctl vc = {0};
+  vc.vifc_vifi = i->vifi;
+  vc.vifc_flags = VIFF_USE_IFINDEX;
+  vc.vifc_lcl_ifindex = i->index;
+
+  if ((err = mkrt_call(p, MRT_ADD_VIF, &vc, sizeof(vc))) < 0)
+    goto err;
+
+  TRACE(D_EVENTS, "Iface %s (%i) assigned VIF %i", i->name, i->index, i->vifi);
+
+  i->flags |= IF_VIFI_ASSIGNED;
+  return;
+
+err:
+  log(L_ERR "Error while assigning %s VIF %i: %m", i->name, i->vifi, err);
+  mkrt_free_vifi(p, i->vifi);
+}
+
+static int
+mkrt_del_vif(struct mkrt_proto *p, struct iface *i)
+{
+  int err;
+
+  if (!i->flags & IF_VIFI_ASSIGNED)
+    return 0;
+
+  struct vifctl vc = {0};
+  vc.vifc_vifi = i->vifi;
+
+  if ((err = mkrt_call(p, MRT_DEL_VIF, &vc, sizeof(vc))) < 0)
+    goto err;
+
+  mkrt_free_vifi(p, i->vifi);
+  i->flags &= ~IF_VIFI_ASSIGNED;
+  return 0;
+err:
+  log(L_ERR "Error while unassigning %s VIF %i: %m", i->name, i->vifi, err);
+  return err;
+}
+
+static struct mkrt_mfc_group *
+mkrt_mfc_get(struct mkrt_proto *p, ip_addr ga)
+{
+  struct mkrt_mfc_group *mg = HASH_FIND(p->mfc_groups, HASH_MFC, ga);
+  if (mg)
+    return mg;
+
+  mg = mb_allocz(p->p.pool, sizeof(struct mkrt_mfc_group));
+  mg->ga = ga;
+  init_list(&mg->sources);
+  HASH_INSERT(p->mfc_groups, HASH_MFC, mg);
+  return mg;
+}
+
+/*
+ * Add a MFC entry for (S, G) with parent vifi, according to the route.
+ */
+static int
+mkrt_mfc_update(struct mkrt_proto *p, ip_addr group, ip_addr source, int vifi, struct rte *rte)
+{
+  struct mfcctl mc = {0};
+  int err;
+
+  mc.mfcc_origin = ipa_to_in4(source);
+  mc.mfcc_mcastgrp = ipa_to_in4(group);
+  mc.mfcc_parent = vifi;
+
+  if (rte && RTE_MGRP_ISSET(p->vif_map[vifi], rte->u.mkrt.iifs))
+    for (int i = 0; i < MAXVIFS; i++)
+      if (RTE_MGRP_ISSET(p->vif_map[i], rte->u.mkrt.oifs))
+       mc.mfcc_ttls[i] = 1;
+
+  TRACE(D_EVENTS, "%s MFC entry for (%I, %I)", (vifi > 0) ? "Add" : "Delete", source, group);
+  if ((err = mkrt_call(p, (vifi > 0) ? MRT_ADD_MFC : MRT_DEL_MFC, &mc, sizeof(mc)) < 0))
+    log(L_WARN "Mkernel: failed to %s MFC entry: %m", (vifi > 0) ? "add" : "delete", err);
+
+  return err;
+}
+
+struct mfc_request {
+  ip_addr *group, *source;
+  vifi_t vifi;
+  u32 iifs, oifs;
+};
+
+/*
+ * Expand the attributes from the struct mfc_request and call mkrt_mfc_update,
+ * and pass back the result.
+ */
+static void
+mkrt_mfc_call_update(struct proto *p, void *data, rte *rte)
+{
+  struct mfc_request *req = data;
+  mkrt_mfc_update((struct mkrt_proto *) p, *req->group, *req->source, req->vifi, rte);
+  if (rte)
+    {
+      req->iifs = rte->u.mkrt.iifs;
+      req->oifs = rte->u.mkrt.oifs;
+    }
+}
+
+/*
+ * Resolve the MFC miss by adding a MFC entry. If no matching entry in the
+ * routing table exists, add an empty one to satisfy the kernel.
+ */
+static void
+mkrt_mfc_resolve(struct mkrt_proto *p, ip_addr group, ip_addr source, vifi_t vifi)
+{
+  struct iface *iface = p->vif_map[vifi];
+
+  TRACE(D_EVENTS, "MFC miss for (%I, %I, %s)", source, group, iface ? iface->name : "??");
+
+  net_addr_mgrp4 n0 = NET_ADDR_MGRP4(ipa_to_ip4(group));
+
+  struct mfc_request req = { &group, &source, vifi };
+  if (!rt_route(p->p.main_channel, (net_addr *) &n0, mkrt_mfc_call_update, &req))
+    mkrt_mfc_call_update((struct proto *) p, &req, NULL);
+
+  struct mkrt_mfc_group *grp = mkrt_mfc_get(p, group);
+  struct mkrt_mfc_source *src = mb_alloc(p->p.pool, sizeof(struct mkrt_mfc_source));
+  src->addr = source;
+  src->vifi = vifi;
+  src->iifs = req.iifs;
+  src->oifs = req.oifs;
+  add_tail(&grp->sources, NODE src);
+}
+
+/*
+ * Because a route in the internal table has changed, all the corresponding MFC
+ * entries are now wrong. Instead of correcting them, flush the cache.
+ */
+static void
+mkrt_mfc_clean(struct mkrt_proto *p, struct mkrt_mfc_group *mg)
+{
+  struct mkrt_mfc_source *n, *next;
+  WALK_LIST_DELSAFE(n, next, mg->sources)
+    {
+      mkrt_mfc_update(p, mg->ga, n->addr, -1, NULL);
+      rem_node(NODE n);
+      mb_free(n);
+    }
+}
+
+static void
+mkrt_mfc_free(struct mkrt_proto *p, struct mkrt_mfc_group *mg)
+{
+  mkrt_mfc_clean(p, mg);
+  HASH_REMOVE(p->mfc_groups, HASH_MFC, mg);
+  mb_free(mg);
+}
+
+/*
+ * An IGMP message received on the socket can be not only a packet received
+ * from the network, but also a so-called upcall from the kernel. We must process them here.
+ */
+static int
+mkrt_control_message(struct mkrt_proto *p, sock *sk, int len)
+{
+  struct igmpmsg *msg = (struct igmpmsg *) sk->rbuf;
+  u8 igmp_type = * (u8 *) sk_rx_buffer(sk, &len);
+
+  switch (igmp_type)
+    {
+    case IGMPMSG_NOCACHE:
+      mkrt_mfc_resolve(p, ipa_from_in4(msg->im_dst), ipa_from_in4(msg->im_src), msg->im_vif);
+      return 1;
+
+    case IGMPMSG_WRONGVIF:
+    case IGMPMSG_WHOLEPKT:
+      /* Neither should ever happen. IGMPMSG_WRONGVIF is a common situation,
+       * and this upcall is called only when switching to (S,G) tree in other
+       * PIM variants.
+       *
+       * Similarly, the WHOLEPKT should be called only when we add the register
+       * VIF and ask kernel for giving us whole packets
+       */
+      return 1;
+
+    default:
+      return 0;
+    }
+}
+
+static int
+mkrt_rx_hook(sock *sk, int len)
+{
+  struct mkrt_proto *p = sk->data;
+
+  /* Do not forward upcalls, IGMP cannot parse them */
+  if (mkrt_control_message(p, sk, len))
+    return 1;
+
+  mkrt_rx_forward_all(&mkrt_global.sockets, sk, len);
+
+  struct mkrt_iface *ifa = mkrt_iface_find(p, sk->lifindex);
+  if (ifa)
+    mkrt_rx_forward_all(&ifa->sockets, sk, len);
+
+  return 1;
+}
+
+static void
+mkrt_err_hook(sock *sk, int err)
+{
+  log(L_TRACE "IGMP error: %m", err);
+}
+
+static void
+mkrt_preconfig(struct protocol *P UNUSED, struct config *c UNUSED)
+{
+  mkrt_cf = NULL;
+}
+
+struct proto_config *
+mkrt_config_init(int class)
+{
+  if (mkrt_cf)
+    cf_error("Kernel multicast route syncer already defined");
+
+  mkrt_cf = (struct mkrt_config *) proto_config_new(&proto_mkrt, class);
+  return (struct proto_config *) mkrt_cf;
+}
+
+void
+mkrt_config_finish(struct proto_config *pc)
+{
+  struct channel_config *cc = proto_cf_main_channel(pc);
+
+  if (!cc)
+    cc = channel_config_new(NULL, NET_MGRP4, pc);
+
+  cc->ra_mode = RA_OPTIMAL;
+}
+
+static int
+mkrt_init_sock(struct mkrt_proto *p)
+{
+  sock *sk;
+
+  if (!(sk = sk_new(p->p.pool)))
+    goto err;
+
+  sk->type = SK_IP;
+  sk->subtype = SK_IPV4;
+  sk->dport = IPPROTO_IGMP;
+  sk->flags = SKF_LADDR_RX;
+
+  sk->data = p;
+  sk->ttl = 1;
+  sk->rx_hook = mkrt_rx_hook;
+  sk->err_hook = mkrt_err_hook;
+
+  sk->rbsize = 4096;
+  sk->tbsize = 0;
+
+  if (sk_open(sk) < 0)
+    goto err_sk;
+
+  p->igmp_sock = sk;
+
+  int v = 1;
+  if (mkrt_call(p, MRT_INIT, &v, sizeof(v)) < 0)
+    {
+      if (errno == EADDRINUSE)
+       log(L_ERR "Mkernel: Another multicast routing daemon is running");
+      else
+       log(L_ERR "Mkernel: Cannot enable multicast features in kernel: %m", errno);
+      goto err_sk;
+    }
+
+  log(L_DEBUG "Multicast control socket open with fd %i", sk->fd);
+  return 0;
+
+err_sk:
+  rfree(sk);
+  p->igmp_sock = NULL;
+err:
+  return -1;
+}
+
+void
+mkrt_rt_notify(struct proto *P, struct channel *c, net *net, rte *new, rte *old, ea_list *attrs)
+{
+  struct mkrt_proto *p = (struct mkrt_proto *) P;
+  net_addr *n = net->n.addr;
+  struct mkrt_mfc_group *mg = mkrt_mfc_get(p, net_prefix(n));
+
+  /* Drop all MFC entries (possibly along with the state information) for a group */
+  if (new)
+      mkrt_mfc_clean(p, mg);
+  else
+      mkrt_mfc_free(p, mg);
+}
+
+static void
+mkrt_if_notify(struct proto *P, uint flags, struct iface *iface)
+{
+  struct mkrt_proto *p = (struct mkrt_proto *) P;
+
+  if (iface->flags & IF_IGNORE)
+    return;
+
+  if (flags & IF_CHANGE_UP)
+    mkrt_add_vif(p, iface);
+
+  if (flags & IF_CHANGE_DOWN)
+    mkrt_del_vif(p, iface);
+}
+
+static struct proto *
+mkrt_init(struct proto_config *c)
+{
+  struct mkrt_proto *p = proto_new(c);
+
+  p->p.main_channel = proto_add_channel(&p->p, proto_cf_main_channel(c));
+
+  p->p.rt_notify = mkrt_rt_notify;
+  p->p.if_notify = mkrt_if_notify;
+
+  return &p->p;
+}
+
+static int
+mkrt_start(struct proto *P)
+{
+  struct mkrt_proto *p = (struct mkrt_proto *) P;
+
+  p->vif_count = 0;
+
+  HASH_INIT(p->mfc_groups, p->p.pool, 6);
+
+  if (mkrt_init_sock(p) < 0)
+    return PS_DOWN;
+
+  return PS_UP;
+}
+
+static int
+mkrt_shutdown(struct proto *P)
+{
+  struct mkrt_proto *p = (struct mkrt_proto *) P;
+  mkrt_call(p, MRT_DONE, NULL, 0);
+  rfree(p->igmp_sock);
+  return PS_DOWN;
+}
+
+static void
+mkrt_dump(struct proto *P)
+{
+  struct mkrt_proto *p = (struct mkrt_proto *) P;
+  struct mkrt_mfc_source *s;
+
+  debug("\tVIFs as in bitmaps:\n\t\t");
+  for (int i = MAXVIFS; i >= 0; i--)
+    if (p->vif_map[i])
+      debug("%s ", p->vif_map[i]->name);
+  debug("\n\t(S,G) entries in MFC in kernel:\n");
+  HASH_WALK(p->mfc_groups, next, group)
+    {
+      WALK_LIST(s, group->sources)
+       debug("\t\t(%I, %I, %s) -> %b %b\n", s->addr, group->ga, p->vif_map[s->vifi]->name, s->iifs, s->oifs);
+    }
+  HASH_WALK_END;
+}
+
+struct protocol proto_mkrt = {
+    .name = "mkernel",
+    .template = "mkernel%d",
+    .proto_size = sizeof(struct mkrt_proto),
+    .config_size = sizeof(struct proto_config),
+    .channel_mask = NB_MGRP,
+    .preconfig = mkrt_preconfig,
+    .init = mkrt_init,
+    .start = mkrt_start,
+    .shutdown = mkrt_shutdown,
+    .dump = mkrt_dump,
+};
diff --git a/sysdep/unix/mkrt.h b/sysdep/unix/mkrt.h
new file mode 100644 (file)
index 0000000..586dfc9
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  BIRD -- Multicast routing kernel
+ *
+ *  (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
+ *
+ *  Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#include "nest/bird.h"
+#include "nest/protocol.h"
+#include "lib/socket.h"
+#include "lib/hash.h"
+
+#include <linux/mroute.h>
+
+extern struct protocol proto_mkrt;
+
+/* Proto state - kernel comm */
+
+struct mkrt_config {
+  struct proto_config cf;
+};
+
+struct mkrt_mfc_group {
+  struct mkrt_mfc_group *next;
+  ip_addr ga;
+  list sources;
+};
+
+struct mkrt_mfc_source {
+  node n;
+  ip_addr addr;
+
+  /* remember these for dumping  */
+  vifi_t vifi;
+  u32 iifs, oifs;
+};
+
+struct mkrt_proto {
+  struct proto p;
+
+  sock *igmp_sock;
+  unsigned vif_count;
+  struct iface *vif_map[MAXVIFS];
+
+  HASH(struct mkrt_mfc_group) mfc_groups;
+};
+
+void mkrt_io_init(void);
+unsigned mkrt_get_vif(struct iface *i);
+void mkrt_listen(sock *s);
+void mkrt_stop(sock *s);
+
+struct proto_config *mkrt_config_init(int class);
+void mkrt_config_finish(struct proto_config *);