diff --git a/conf/confbase.Y b/conf/confbase.Y
index 7e0537c5..7cc74cf3 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -83,7 +83,7 @@ CF_DECLS
 %type <time> expr_us time
 %type <a> ipa
 %type <net> net_ip4_ net_ip6_ net_ip6 net_ip_ net_ip net_or_ipa
-%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_mpls_
+%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_mreq4_ net_mreq6_ net_mreq_ net_mgrp4_ net_mgrp6_ net_mgrp_ net_mpls_
 %type <mls> label_stack_start label_stack
 
 %type <t> text opttext
@@ -234,6 +234,30 @@ net_roa6_: net_ip6_ MAX NUM AS NUM
     cf_error("Invalid max prefix length %u", $3);
 };
 
+net_mreq4_: MREQ4 '(' '*' ',' IP4 ')'
+{
+  $$ = cfg_alloc(sizeof(net_addr_mreq4));
+  net_fill_mreq4($$, $5, 0); // XXXX
+}
+
+net_mreq6_: MREQ6 '(' '*' ',' IP6 ')'
+{
+  $$ = cfg_alloc(sizeof(net_addr_mreq6));
+  net_fill_mreq6($$, $5, 0); // XXXX
+}
+
+net_mgrp4_: MGRP4 '(' '*' ',' IP4 ')'
+{
+  $$ = cfg_alloc(sizeof(net_addr_mgrp4));
+  net_fill_mgrp4($$, $5);
+}
+
+net_mgrp6_: MGRP6 '(' '*' ',' IP6 ')'
+{
+  $$ = cfg_alloc(sizeof(net_addr_mgrp6));
+  net_fill_mgrp6($$, $5);
+}
+
 net_mpls_: MPLS NUM
 {
   $$ = cfg_alloc(sizeof(net_addr_roa6));
@@ -243,12 +267,16 @@ net_mpls_: MPLS NUM
 net_ip_: net_ip4_ | net_ip6_ ;
 net_vpn_: net_vpn4_ | net_vpn6_ ;
 net_roa_: net_roa4_ | net_roa6_ ;
+net_mreq_: net_mreq4_ | net_mreq6_ ;
+net_mgrp_: net_mgrp4_ | net_mgrp6_ ;
 
 net_:
    net_ip_ { $$ = cfg_alloc($1.length); net_copy($$, &($1)); }
  | net_vpn_
  | net_roa_
  | net_flow_
+ | net_mreq_
+ | net_mgrp_
  | net_mpls_
  ;
 
diff --git a/lib/birdlib.h b/lib/birdlib.h
index 428b3209..601cae58 100644
--- a/lib/birdlib.h
+++ b/lib/birdlib.h
@@ -40,6 +40,9 @@ struct align_probe { char x; long int y; };
 #define CALL(fn, args...) ({ if (fn) fn(args); })
 #define ADVANCE(w, r, l) ({ r -= l; w += l; })
 
+#define WALK_ARRAY(v,l,n) \
+  for (typeof(*(v)) *_n = (v), n; _n < ((v) + (l)) && (n = *_n, 1); _n++)
+
 static inline int uint_cmp(uint i1, uint i2)
 { return (int)(i1 > i2) - (int)(i1 < i2); }
 
diff --git a/lib/net.c b/lib/net.c
index 01af3a4d..24619fca 100644
--- a/lib/net.c
+++ b/lib/net.c
@@ -64,8 +64,8 @@ const u16 net_max_text_length[] = {
   [NET_FLOW6]	= 0,	/* "flow6 { ... }" */
   [NET_MREQ4]	= 15,	/* "255.255.255.255" */
   [NET_MREQ6]	= 39,	/* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" */
-  [NET_MGRP4]	= 15,	/* "255.255.255.255" */
-  [NET_MGRP6]	= 39,	/* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" */
+  [NET_MGRP4]	= 20,	/* "(*, 255.255.255.255)" */
+  [NET_MGRP6]	= 44,	/* "(*, ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff)" */
   [NET_MPLS]	= 7,	/* "1048575" */
 };
 
@@ -123,9 +123,9 @@ net_format(const net_addr *N, char *buf, int buflen)
   case NET_MREQ6:
     return bsnprintf(buf, buflen, "%I6", n->mreq6.grp);
   case NET_MGRP4:
-    return bsnprintf(buf, buflen, "%I4", n->mgrp4.grp);
+    return bsnprintf(buf, buflen, "(*, %I4)", n->mgrp4.grp);
   case NET_MGRP6:
-    return bsnprintf(buf, buflen, "%I6", n->mgrp6.grp);
+    return bsnprintf(buf, buflen, "(*, %I6)", n->mgrp6.grp);
   case NET_MPLS:
     return bsnprintf(buf, buflen, "%u", n->mpls.label);
   }
diff --git a/lib/net.h b/lib/net.h
index de25ad6d..39f0f905 100644
--- a/lib/net.h
+++ b/lib/net.h
@@ -49,10 +49,14 @@
 #define NB_MREQ		(NB_MREQ4 | NB_MREQ6)
 #define NB_MGRP		(NB_MGRP4 | NB_MGRP6)
 
+/* FIXME: Better validation of (NET, RTD) combinations */
 #define NB_HOST		(NB_IP | NB_VPN | NB_ROA)
-#define NB_DEST		(NB_IP | NB_VPN | NB_MPLS)
+// #define NB_DEST	(NB_IP | NB_VPN | NB_MPLS)
+#define NB_DEST		(NB_IP | NB_VPN | NB_MGRP | NB_MPLS)
 #define NB_ANY		0xffffffff
 
+#define MIFS_MAX	32
+
 
 typedef struct net_addr {
   u8 type;
diff --git a/nest/iface.c b/nest/iface.c
index 54c16c58..b165e6e7 100644
--- a/nest/iface.c
+++ b/nest/iface.c
@@ -37,8 +37,10 @@
 static pool *if_pool;
 
 list iface_list;
+struct mif_group *global_mif_group;
 
 static void if_recalc_preferred(struct iface *i);
+static struct mif_group *mif_get_group(void);
 
 /**
  * ifa_dump - dump interface address
@@ -714,6 +716,61 @@ if_init(void)
   if_pool = rp_new(&root_pool, "Interfaces");
   init_list(&iface_list);
   neigh_init(if_pool);
+  global_mif_group = mif_get_group();
+}
+
+/*
+ *	Multicast Ifaces
+ */
+
+static struct mif_group *
+mif_get_group(void)
+{
+  struct mif_group *grp = mb_allocz(if_pool, sizeof(struct mif_group));
+  init_list(&grp->sockets);
+
+  return grp;
+}
+
+static inline int u32_cto(uint x) { return ffs(~x) - 1; }
+
+struct mif *
+mif_get(struct mif_group *grp, struct iface *iface)
+{
+  WALK_ARRAY(grp->mifs, MIFS_MAX, mif)
+    if (mif && (mif->iface == iface))
+      return mif->uc++, mif;
+
+  int i = u32_cto(grp->indexes);
+  if (i < 0)
+    return NULL;
+
+  struct mif *mif = mb_allocz(if_pool, sizeof(struct mif));
+  mif->iface = iface;
+  mif->index = i;
+  mif->uc = 1;
+  init_list(&mif->sockets);
+
+  grp->mifs[mif->index] = mif;
+  MIFS_SET(mif, grp->indexes);
+
+  return mif;
+}
+
+void
+mif_free(struct mif_group *grp, struct mif *mif)
+{
+  if (--mif->uc)
+    return;
+
+  node *n;
+  WALK_LIST_FIRST(n, mif->sockets)
+    rem_node(n);
+
+  grp->mifs[mif->index] = NULL;
+  MIFS_CLR(mif, grp->indexes);
+
+  mb_free(mif);
 }
 
 /*
diff --git a/nest/iface.h b/nest/iface.h
index ab3f8f35..dc44dfad 100644
--- a/nest/iface.h
+++ b/nest/iface.h
@@ -13,6 +13,7 @@
 #include "lib/ip.h"
 
 extern list iface_list;
+extern struct mif_group *global_mif_group;
 
 struct proto;
 struct pool;
@@ -44,6 +45,20 @@ struct iface {
   list neighbors;			/* All neighbors on this interface */
 };
 
+struct mif {
+  struct iface *iface;
+  uint index;				/* MIF (VIF) index for multicast routes */
+  uint uc;				/* Use count */
+  list sockets;				/* Listening per-iface IGMP sockets */
+};
+
+struct mif_group {
+  uint indexes;
+  uint uc;				/* Use count, not implemented */
+  list sockets;				/* Listening global IGMP sockets */
+  struct mif *mifs[MIFS_MAX];
+};
+
 #define IF_UP 1				/* Currently just IF_ADMIN_UP */
 #define IF_MULTIACCESS 2
 #define IF_BROADCAST 4
@@ -115,6 +130,13 @@ struct iface *if_find_by_name(char *);
 struct iface *if_get_by_name(char *);
 void if_recalc_all_preferred_addresses(void);
 
+struct mif *mif_get(struct mif_group *grp, struct iface *iface);
+void mif_free(struct mif_group *grp, struct mif *mif);
+
+#define MIFS_SET(mif,m)		((m) |= (1 << (mif)->index))
+#define MIFS_CLR(mif,m)		((m) &= ~(1 << (mif)->index))
+#define MIFS_ISSET(mif,m)	((m) & (1 << (mif)->index))
+
 
 /* The Neighbor Cache */
 
diff --git a/nest/route.h b/nest/route.h
index d47645f8..b2ae4686 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -152,6 +152,7 @@ typedef struct rtable {
   int pipe_busy;			/* Pipe loop detection */
   int use_count;			/* Number of protocols using this table */
   struct hostcache *hostcache;
+  // struct mif_group *mif_group;
   struct rtable_config *config;		/* Configuration of this table */
   struct config *deleted;		/* Table doesn't exist in current configuration,
 					 * delete as soon as use_count becomes 0 and remove
@@ -249,9 +250,6 @@ typedef struct rte {
       u8 best;				/* Best route in network, propagated to core */
       u32 metric;			/* Kernel metric */
     } krt;
-    struct {
-      u32 iifs, oifs;			/* Bitmaps for iifs and oifs. Use RTE_MGRP_* macros to manipulate. */
-    } mkrt;
   } u;
 } rte;
 
@@ -637,6 +635,19 @@ rta_set_recursive_next_hop(rtable *dep, rta *a, rtable *tab, ip_addr gw, ip_addr
   rta_apply_hostentry(a, rt_get_hostentry(tab, gw, ll, dep), mls);
 }
 
+/* For RTD_MULTICAST, we encode iifs and oifs to nh.gw */
+static inline u32 rta_iifs(rta *a)
+{ return _I0(a->nh.gw); }
+
+static inline void rta_set_iifs(rta *a, u32 val)
+{ _I0(a->nh.gw) = val; }
+
+static inline u32 rta_oifs(rta *a)
+{ return _I1(a->nh.gw); }
+
+static inline void rta_set_oifs(rta *a, u32 val)
+{ _I1(a->nh.gw) = val; }
+
 /*
  * rta_set_recursive_next_hop() acquires hostentry from hostcache and fills
  * rta->hostentry field.  New hostentry has zero use count. Cached rta locks its
diff --git a/nest/rt-show.c b/nest/rt-show.c
index 41a141a2..b8366881 100644
--- a/nest/rt-show.c
+++ b/nest/rt-show.c
@@ -28,6 +28,30 @@ rt_show_table(struct cli *c, struct rt_show_data *d)
   d->last_table = d->tab;
 }
 
+static void
+rt_show_mifs(char *key, struct mif_group *grp, u32 mifs)
+{
+  uint blen = 512;
+  char *buf = alloca(blen + 8);
+  char *pos = buf;
+  pos[0] = 0;
+
+  WALK_ARRAY(grp->mifs, MIFS_MAX, mif)
+    if (mif && MIFS_ISSET(mif, mifs))
+    {
+      int i = bsnprintf(pos, blen, " %s", mif->iface->name);
+      if (i < 0)
+      {
+	bsprintf(pos, " ...");
+	break;
+      }
+
+      ADVANCE(pos, blen, i);
+    }
+
+  cli_msg(-1007, "%s%s", key, buf);
+}
+
 static void
 rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, ea_list *tmpa)
 {
@@ -92,6 +116,12 @@ rt_show_rte(struct cli *c, byte *ia, rte *e, struct rt_show_data *d, ea_list *tm
 		   nh->iface->name, mpls,  onlink, weight);
     }
 
+  if (a->dest == RTD_MULTICAST)
+    {
+      rt_show_mifs("\tfrom", global_mif_group, rta_iifs(a));
+      rt_show_mifs("\tto", global_mif_group, rta_oifs(a));
+    }
+
   if (d->verbose)
     rta_show(c, a, tmpa);
 }