diff --git a/Makefile.am b/Makefile.am
index 96da94f..520dd62 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,15 @@
+##
+## Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
+##
 ## Process this file with automake to produce Makefile.in.
 
 SUBDIRS = lib @ZEBRA@ @BGPD@ @RIPD@ @RIPNGD@ @OSPFD@ @OSPF6D@ \
          @ISISD@ @WATCHQUAGGA@ @VTYSH@ @OSPFCLIENT@ doc m4 @pkgsrcdir@ \
-         redhat @SOLARIS@
+         redhat @SOLARIS@ @PIMD@
 
 DIST_SUBDIRS = lib zebra bgpd ripd ripngd ospfd ospf6d \
 	  isisd watchquagga vtysh ospfclient doc m4 pkgsrc redhat tests \
-	  solaris
+	  solaris pimd
 
 EXTRA_DIST = aclocal.m4 SERVICES TODO REPORTING-BUGS INSTALL.quagga.txt \
 	update-autotools \
diff --git a/SERVICES b/SERVICES
index 9c3546b..c69d0c1 100644
--- a/SERVICES
+++ b/SERVICES
@@ -17,3 +17,4 @@ bgpd		2605/tcp
 ospf6d		2606/tcp
 ospfapi		2607/tcp
 isisd		2608/tcp
+pimd		2611/tcp
diff --git a/configure.ac b/configure.ac
index a80589d..1802a11 100755
--- a/configure.ac
+++ b/configure.ac
@@ -4,6 +4,7 @@
 ##
 ##  Copyright (c) 1996, 97, 98, 99, 2000 Kunihiro Ishiguro <kunihiro@zebra.org>
 ##  Portions Copyright (c) 2003 Paul Jakma <paul@dishone.st>
+##  Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
 ##
 ## $Id$
 AC_PREREQ(2.53)
@@ -198,6 +199,8 @@ AC_ARG_ENABLE(watchquagga,
 [  --disable-watchquagga   do not build watchquagga])
 AC_ARG_ENABLE(isisd,
 [  --enable-isisd          build isisd])
+AC_ARG_ENABLE(pimd,
+[  --enable-pimd           build pimd])
 AC_ARG_ENABLE(solaris,
 [  --enable-solaris          build solaris])
 AC_ARG_ENABLE(bgp-announce,
@@ -1203,6 +1206,12 @@ case "${enable_isisd}" in
   *    ) ;;
 esac
 
+case "${enable_pimd}" in
+  "yes") PIMD="pimd";;
+  "no" ) PIMD="";;
+  *    ) ;;
+esac
+
 # XXX Perhaps auto-enable on Solaris, but that's messy for cross builds.
 case "${enable_solaris}" in
   "yes") SOLARIS="solaris";;
@@ -1224,6 +1233,7 @@ AC_SUBST(OSPFD)
 AC_SUBST(OSPF6D)
 AC_SUBST(WATCHQUAGGA)
 AC_SUBST(ISISD)
+AC_SUBST(PIMD)
 AC_SUBST(SOLARIS)
 AC_SUBST(VTYSH)
 AC_SUBST(INCLUDES)
@@ -1277,7 +1287,8 @@ dnl sockaddr and netinet checks
 dnl ---------------------------
 AC_CHECK_TYPES([struct sockaddr, struct sockaddr_in,
 	struct sockaddr_in6, struct sockaddr_un, struct sockaddr_dl,
-	socklen_t,
+	socklen_t, struct vifctl, struct mfcctl, struct sioc_sg_req,
+	vifi_t, struct sioc_vif_req, struct igmpmsg,
 	struct ifaliasreq, struct if6_aliasreq, struct in6_aliasreq,
 	struct nd_opt_adv_interval, struct rt_addrinfo,
 	struct nd_opt_homeagent_info, struct nd_opt_adv_interval],
@@ -1305,6 +1316,45 @@ AC_CHECK_TYPES([struct in_pktinfo],
     AC_MSG_ERROR(['IRDP requires in_pktinfo at the moment!'])
   fi], [QUAGGA_INCLUDES])
 
+dnl -----------------------
+dnl checking for IP_PKTINFO
+dnl -----------------------
+AC_MSG_CHECKING(for IP_PKTINFO)
+AC_TRY_COMPILE([#include <netdb.h>], [
+  int opt = IP_PKTINFO;
+], [
+  AC_MSG_RESULT(yes)
+  AC_DEFINE(HAVE_IP_PKTINFO, 1, [Have IP_PKTINFO])
+], [
+  AC_MSG_RESULT(no)
+])
+
+dnl ---------------------------
+dnl checking for IP_RECVDSTADDR
+dnl ---------------------------
+AC_MSG_CHECKING(for IP_RECVDSTADDR)
+AC_TRY_COMPILE([#include <netinet/in.h>], [
+  int opt = IP_RECVDSTADDR;
+], [
+  AC_MSG_RESULT(yes)
+  AC_DEFINE(HAVE_IP_RECVDSTADDR, 1, [Have IP_RECVDSTADDR])
+], [
+  AC_MSG_RESULT(no)
+])
+
+dnl ----------------------
+dnl checking for IP_RECVIF
+dnl ----------------------
+AC_MSG_CHECKING(for IP_RECVIF)
+AC_TRY_COMPILE([#include <netinet/in.h>], [
+  int opt = IP_RECVIF;
+], [
+  AC_MSG_RESULT(yes)
+  AC_DEFINE(HAVE_IP_RECVIF, 1, [Have IP_RECVIF])
+], [
+  AC_MSG_RESULT(no)
+])
+
 dnl --------------------------------------
 dnl checking for getrusage struct and call
 dnl --------------------------------------
@@ -1450,6 +1500,7 @@ AC_DEFINE_UNQUOTED(PATH_BGPD_PID, "$quagga_statedir/bgpd.pid",bgpd PID)
 AC_DEFINE_UNQUOTED(PATH_OSPFD_PID, "$quagga_statedir/ospfd.pid",ospfd PID)
 AC_DEFINE_UNQUOTED(PATH_OSPF6D_PID, "$quagga_statedir/ospf6d.pid",ospf6d PID)
 AC_DEFINE_UNQUOTED(PATH_ISISD_PID, "$quagga_statedir/isisd.pid",isisd PID)
+AC_DEFINE_UNQUOTED(PATH_PIMD_PID, "$quagga_statedir/pimd.pid",pimd PID)
 AC_DEFINE_UNQUOTED(PATH_WATCHQUAGGA_PID, "$quagga_statedir/watchquagga.pid",watchquagga PID)
 AC_DEFINE_UNQUOTED(ZEBRA_SERV_PATH, "$quagga_statedir/zserv.api",zebra api socket)
 AC_DEFINE_UNQUOTED(ZEBRA_VTYSH_PATH, "$quagga_statedir/zebra.vty",zebra vty socket)
@@ -1459,6 +1510,7 @@ AC_DEFINE_UNQUOTED(BGP_VTYSH_PATH, "$quagga_statedir/bgpd.vty",bgpd vty socket)
 AC_DEFINE_UNQUOTED(OSPF_VTYSH_PATH, "$quagga_statedir/ospfd.vty",ospfd vty socket)
 AC_DEFINE_UNQUOTED(OSPF6_VTYSH_PATH, "$quagga_statedir/ospf6d.vty",ospf6d vty socket)
 AC_DEFINE_UNQUOTED(ISIS_VTYSH_PATH, "$quagga_statedir/isisd.vty",isisd vty socket)
+AC_DEFINE_UNQUOTED(PIM_VTYSH_PATH, "$quagga_statedir/pimd.vty",pimd vty socket)
 AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$quagga_statedir",daemon vty directory)
 
 dnl -------------------------------
@@ -1484,6 +1536,7 @@ AC_CONFIG_FILES([Makefile lib/Makefile zebra/Makefile ripd/Makefile
 	  ripngd/Makefile bgpd/Makefile ospfd/Makefile watchquagga/Makefile
 	  ospf6d/Makefile isisd/Makefile vtysh/Makefile doc/Makefile 
 	  ospfclient/Makefile tests/Makefile m4/Makefile redhat/Makefile
+	  pimd/Makefile
 	  pkgsrc/Makefile
 	  redhat/quagga.spec 
 	  lib/version.h
diff --git a/doc/Makefile.am b/doc/Makefile.am
index ed3a6d5..e7049a9 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,3 +1,6 @@
+##
+## Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
+##
 ## Process this file with automake to produce Makefile.in.
 
 # Dia, the version i have at least, doesn't do very good EPS output
@@ -60,7 +63,7 @@ quagga_TEXINFOS = appendix.texi basic.texi bgpd.texi filter.texi install.texi \
 %.png: %.dia
 	$(DIATOPNG) "$@" $<
 
-man_MANS = vtysh.1 bgpd.8 ospf6d.8 ospfd.8 ripd.8 ripngd.8 zebra.8 isisd.8
+man_MANS = vtysh.1 bgpd.8 ospf6d.8 ospfd.8 ripd.8 ripngd.8 zebra.8 isisd.8 pimd.8
 
 EXTRA_DIST = BGP-TypeCode draft-zebra-00.ms draft-zebra-00.txt $(man_MANS) \
 	mpls/ChangeLog.opaque.txt mpls/cli_summary.txt \
diff --git a/doc/install.texi b/doc/install.texi
index 16e29c9..fbc2401 100644
--- a/doc/install.texi
+++ b/doc/install.texi
@@ -267,6 +267,7 @@ bgpd          2605/tcp		  # BGPd vty
 ospf6d        2606/tcp		  # OSPF6d vty
 ospfapi       2607/tcp		  # ospfapi
 isisd         2608/tcp		  # ISISd vty
+pimd          2611/tcp		  # PIMd vty
 @end example
 
 If you use a FreeBSD newer than 2.2.8, the above entries are already
diff --git a/doc/pimd.8 b/doc/pimd.8
new file mode 100644
index 0000000..b26b112
--- /dev/null
+++ b/doc/pimd.8
@@ -0,0 +1,108 @@
+.TH PIM 8 "10 December 2008" "Quagga PIM daemon" "Version 0.99.11"
+.SH NAME
+pimd \- a PIM routing for use with Quagga Routing Suite.
+.SH SYNOPSIS
+.B pimd
+[
+.B \-dhvZ
+] [
+.B \-f
+.I config-file
+] [
+.B \-i
+.I pid-file
+] [
+.B \-P
+.I port-number
+] [
+.B \-A
+.I vty-address
+] [
+.B \-u
+.I user
+] [
+.B \-g
+.I group
+]
+.SH DESCRIPTION
+.B pimd
+is a protocol-independent multicast component that works with the
+.B Quagga
+Routing Suite.
+.SH OPTIONS
+Options available for the
+.B pimd
+command:
+.TP
+\fB\-d\fR, \fB\-\-daemon\fR
+Runs in daemon mode, forking and exiting from tty.
+.TP
+\fB\-f\fR, \fB\-\-config-file \fR\fIconfig-file\fR 
+Specifies the config file to use for startup. If not specified this
+option will likely default to \fB\fI/usr/local/etc/pimd.conf\fR.
+.TP
+\fB\-g\fR, \fB\-\-group \fR\fIgroup\fR
+Specify the group to run as. Default is \fIquagga\fR.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+A brief message.
+.TP
+\fB\-i\fR, \fB\-\-pid_file \fR\fIpid-file\fR
+When pimd starts its process identifier is written to
+\fB\fIpid-file\fR.  The init system uses the recorded PID to stop or
+restart pimd.  The likely default is \fB\fI/var/run/pimd.pid\fR.
+.TP
+\fB\-P\fR, \fB\-\-vty_port \fR\fIport-number\fR 
+Specify the port that the pimd VTY will listen on. This defaults to
+2611, as specified in \fB\fI/etc/services\fR.
+.TP
+\fB\-A\fR, \fB\-\-vty_addr \fR\fIvty-address\fR
+Specify the address that the pimd VTY will listen on. Default is all
+interfaces.
+.TP
+\fB\-u\fR, \fB\-\-user \fR\fIuser\fR
+Specify the user to run as. Default is \fIquagga\fR.
+.TP
+\fB\-v\fR, \fB\-\-version\fR
+Print the version and exit.
+.TP
+\fB\-Z\fR, \fB\-\-debug_zclient\fR
+Enable logging information for zclient debugging.
+.SH FILES
+.TP
+.BI /usr/local/sbin/pimd
+The default location of the 
+.B pimd
+binary.
+.TP
+.BI /usr/local/etc/pimd.conf
+The default location of the 
+.B pimd
+config file.
+.TP
+.BI $(PWD)/pimd.log 
+If the 
+.B pimd
+process is config'd to output logs to a file, then you will find this
+file in the directory where you started \fBpimd\fR.
+.SH WARNING
+This man page is intended to be a quick reference for command line
+options.
+.SH DIAGNOSTICS
+The pimd process may log to standard output, to a VTY, to a log
+file, or through syslog to the system logs.
+.SH "SEE ALSO"
+.BR zebra (8),
+.BR vtysh (1)
+.SH BUGS
+\fBpimd\fR is in early development at the moment and is not ready for
+production use.
+
+.B pimd
+eats bugs for breakfast. If you have food for the maintainers try
+.BI http://savannah.nongnu.org/projects/qpimd
+.SH AUTHORS
+See
+.BI http://savannah.nongnu.org/projects/qpimd
+for an accurate list of authors.
+
diff --git a/lib/command.h b/lib/command.h
index d093df3..b7ede65 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -1,6 +1,7 @@
 /*
  * Zebra configuration command interface routine
  * Copyright (C) 1997, 98 Kunihiro Ishiguro
+ * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
  *
  * This file is part of GNU Zebra.
  *
@@ -87,6 +88,7 @@ enum node_type
   OSPF_NODE,			/* OSPF protocol mode */
   OSPF6_NODE,			/* OSPF protocol for IPv6 mode */
   ISIS_NODE,			/* ISIS protocol mode */
+  PIM_NODE,			/* PIM protocol mode */
   MASC_NODE,			/* MASC for multicast.  */
   IRDP_NODE,			/* ICMP Router Discovery Protocol mode. */ 
   IP_NODE,			/* Static ip route node. */
diff --git a/lib/log.c b/lib/log.c
index 407904d..4c190a4 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -3,6 +3,7 @@
  *
  * Logging of zebra
  * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro
+ * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
  *
  * This file is part of GNU Zebra.
  *
@@ -50,6 +51,7 @@ const char *zlog_proto_names[] =
   "RIPNG",
   "OSPF6",
   "ISIS",
+  "PIM",
   "MASC",
   NULL,
 };
diff --git a/lib/log.h b/lib/log.h
index 777fd77..e21790e 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -3,6 +3,7 @@
  *
  * Zebra logging funcions.
  * Copyright (C) 1997, 1998, 1999 Kunihiro Ishiguro
+ * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
  *
  * This file is part of GNU Zebra.
  *
@@ -54,6 +55,7 @@ typedef enum
   ZLOG_RIPNG,  
   ZLOG_OSPF6,
   ZLOG_ISIS,
+  ZLOG_PIM,
   ZLOG_MASC
 } zlog_proto_t;
 
diff --git a/lib/memory.c b/lib/memory.c
index f5d0cba..fff1b4b 100644
--- a/lib/memory.c
+++ b/lib/memory.c
@@ -1,6 +1,7 @@
 /*
  * Memory management routine
  * Copyright (C) 1998 Kunihiro Ishiguro
+ * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
  *
  * This file is part of GNU Zebra.
  *
@@ -441,6 +442,17 @@ DEFUN (show_memory_isis,
   return CMD_SUCCESS;
 }
 
+DEFUN (show_memory_pim,
+       show_memory_pim_cmd,
+       "show memory pim",
+       SHOW_STR
+       "Memory statistics\n"
+       "PIM memory\n")
+{
+  show_memory_vty (vty, memory_list_pim);
+  return CMD_SUCCESS;
+}
+
 void
 memory_init (void)
 {
@@ -463,6 +475,7 @@ memory_init (void)
   install_element (VIEW_NODE, &show_memory_ospf_cmd);
   install_element (VIEW_NODE, &show_memory_ospf6_cmd);
   install_element (VIEW_NODE, &show_memory_isis_cmd);
+  install_element (VIEW_NODE, &show_memory_pim_cmd);
 
   install_element (ENABLE_NODE, &show_memory_cmd);
   install_element (ENABLE_NODE, &show_memory_all_cmd);
@@ -473,7 +486,7 @@ memory_init (void)
   install_element (ENABLE_NODE, &show_memory_bgp_cmd);
   install_element (ENABLE_NODE, &show_memory_ospf_cmd);
   install_element (ENABLE_NODE, &show_memory_ospf6_cmd);
-  install_element (ENABLE_NODE, &show_memory_isis_cmd);
+  install_element (ENABLE_NODE, &show_memory_pim_cmd);
 }
 
 /* Stats querying from users */
diff --git a/lib/memtypes.c b/lib/memtypes.c
index dd365dd..bca8140 100644
--- a/lib/memtypes.c
+++ b/lib/memtypes.c
@@ -1,4 +1,6 @@
 /*
+ * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
+ *
  * Memory type definitions. This file is parsed by memtypes.awk to extract
  * MTYPE_ and memory_list_.. information in order to autogenerate 
  * memtypes.h.
@@ -243,6 +245,19 @@ struct memory_list memory_list_isis[] =
   { -1, NULL },
 };
 
+struct memory_list memory_list_pim[] =
+{
+  { MTYPE_PIM_CHANNEL_OIL,       "PIM SSM (S,G) channel OIL" },
+  { MTYPE_PIM_INTERFACE,         "PIM interface"	     },
+  { MTYPE_PIM_IGMP_SOCKET,       "PIM interface IGMP socket" },
+  { MTYPE_PIM_IGMP_GROUP,        "PIM interface IGMP group"  },
+  { MTYPE_PIM_IGMP_GROUP_SOURCE, "PIM interface IGMP source" },
+  { MTYPE_PIM_NEIGHBOR,          "PIM interface neighbor"    },
+  { MTYPE_PIM_IFCHANNEL,         "PIM interface (S,G) state" },
+  { MTYPE_PIM_UPSTREAM,          "PIM upstream (S,G) state"  },
+  { -1, NULL },
+};
+
 struct memory_list memory_list_vtysh[] =
 {
   { MTYPE_VTYSH_CONFIG,		"Vtysh configuration",		},
@@ -259,5 +274,6 @@ struct mlist mlists[] __attribute__ ((unused)) = {
   { memory_list_ospf6,	"OSPF6"	},
   { memory_list_isis,	"ISIS"	},
   { memory_list_bgp,	"BGP"	},
+  { memory_list_pim,	"PIM"	},
   { NULL, NULL},
 };
diff --git a/lib/thread.h b/lib/thread.h
index 1c324d8..eb04b94 100644
--- a/lib/thread.h
+++ b/lib/thread.h
@@ -1,5 +1,6 @@
 /* Thread management routine header.
  * Copyright (C) 1998 Kunihiro Ishiguro
+ * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
  *
  * This file is part of GNU Zebra.
  *
@@ -135,6 +136,12 @@ enum quagga_clkid {
       thread = thread_add_timer (master, func, arg, time); \
   } while (0)
 
+#define THREAD_TIMER_MSEC_ON(master,thread,func,arg,time) \
+  do { \
+    if (! thread) \
+      thread = thread_add_timer_msec (master, func, arg, time); \
+  } while (0)
+
 #define THREAD_OFF(thread) \
   do { \
     if (thread) \
diff --git a/lib/zebra.h b/lib/zebra.h
index 2716460..61b0b70 100644
--- a/lib/zebra.h
+++ b/lib/zebra.h
@@ -1,5 +1,6 @@
 /* Zebra common header.
    Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Kunihiro Ishiguro
+   Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
 
 This file is part of GNU Zebra.
 
@@ -413,7 +414,8 @@ struct in_pktinfo
 #define ZEBRA_ROUTER_ID_ADD               20
 #define ZEBRA_ROUTER_ID_DELETE            21
 #define ZEBRA_ROUTER_ID_UPDATE            22
-#define ZEBRA_MESSAGE_MAX                 23
+#define ZEBRA_IPV4_NEXTHOP_LOOKUP_V2      23
+#define ZEBRA_MESSAGE_MAX                 24
 
 /* Marker value used in new Zserv, in the byte location corresponding
  * the command value in the old zserv header. To allow old and new
@@ -433,7 +435,8 @@ struct in_pktinfo
 #define ZEBRA_ROUTE_ISIS                 8
 #define ZEBRA_ROUTE_BGP                  9
 #define ZEBRA_ROUTE_HSLS		 10
-#define ZEBRA_ROUTE_MAX                  11
+#define ZEBRA_ROUTE_PIM                  11
+#define ZEBRA_ROUTE_MAX                  12
 
 /* Note: whenever a new route-type or zserv-command is added the
  * corresponding {command,route}_types[] table in lib/log.c MUST be
diff --git a/pimd/AUTHORS b/pimd/AUTHORS
new file mode 100644
index 0000000..f6135a4
--- /dev/null
+++ b/pimd/AUTHORS
@@ -0,0 +1,9 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+# Everton da Silva Marques <everton.marques@gmail.com>
+$ more ~/.gitconfig
+[user]
+        name = Everton Marques
+        email = everton.marques@gmail.com
+
+-x-
diff --git a/pimd/CAVEATS b/pimd/CAVEATS
new file mode 100644
index 0000000..7d18a02
--- /dev/null
+++ b/pimd/CAVEATS
@@ -0,0 +1,105 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+C1 IGMPv3 backward compatibility with IGMPv1 and IGMPv2 is not
+   implemented. See RFC 3376, 7.3. Multicast Router Behavior. That's
+   because only Source-Specific Multicast is currently targeted.
+
+C2 IGMPv3 support for forwarding any-source groups is not
+   implemented. Traffic for groups in mode EXCLUDE {empty} won't be
+   forwarded.  See RFC 3376, 6.3. Source-Specific Forwarding
+   Rules. That's because only Source-Specific Multicast is currently
+   targeted.
+
+C3 Load Splitting of IP Multicast Traffic over ECMP is not supported.
+   See also: RFC 2991
+   Multipath Issues in Unicast and Multicast Next-Hop Selection
+   http://www.rfc-editor.org/rfc/rfc2991.txt
+
+C4 IPSec AH authentication is not supported (RFC 4601:
+   6.3. Authentication Using IPsec).
+
+C5 PIM support is limited to SSM mode as defined in section 4.8.2
+   (PIM-SSM-Only Routers) of RFC4601. That's because only
+   Source-Specific Multicast is currently targeted.
+
+C6 PIM implementation currently does not support IPv6. PIM-SSM
+   requires IGMPv3 for IPv4 and MLDv2 for IPv6. MLDv2 is currently
+   missing. See also CAVEAT C9.
+
+C7 (S,G) Assert state machine (RFC 4601, section 4.6.1) is not
+   implemented. See also TODO T6. See also CAVEAT C10.
+
+C8 It is not possible to disable join suppression in order to
+   explicitly track the join membership of individual downstream
+   routers.
+   - IGMPv3 Explicit Membership Tracking is not supported.
+     When explicit tracking is enabled on a router, the router can
+     individually track the Internet Group Management Protocol (IGMP)
+     membership state of all reporting hosts. This feature allows the
+     router to achieve minimal leave latencies when hosts leave a
+     multicast group or channel. Example:
+       conf t
+        interface eth0
+         ip igmp explicit-tracking
+
+C9 Only IPv4 Address Family (number=1) is supported in the PIM Address
+   Family field (see RFC 4601: 5.1. PIM Address Family). See also
+   CAVEAT C6.
+
+C10 FIXED Assert metric depends on metric_preference and
+    route_metric. Those parameters should be fetched from RIB
+    (zebra). See also pim_rpf.c, pim_rpf_update().
+
+C11 SSM Mapping is not supported
+
+    SSM Mapping Overview:
+
+    SSM mapping introduces a means for the last hop router to discover
+    sources sending to groups. When SSM mapping is configured, if a
+    router receives an IGMPv1 or IGMPv2 membership report for a
+    particular group G, the router translates this report into one or
+    more (S, G) channel memberships for the well-known sources
+    associated with this group.
+
+    When the router receives an IGMPv1 or IGMPv2 membership report for
+    a group G, the router uses SSM mapping to determine one or more
+    source IP addresses for the group G. SSM mapping then translates
+    the membership report as an IGMPv3 report INCLUDE (G, [S1, G],
+    [S2, G]...[Sn, G] and continues as if it had received an IGMPv3
+    report. The router then sends out PIM joins toward (S1, G) to (Sn,
+    G) and continues to be joined to these groups as long as it
+    continues to receive the IGMPv1 or IGMPv2 membership reports and
+    as long as the SSM mapping for the group remains the same. SSM
+    mapping, thus, enables you to leverage SSM for video delivery to
+    legacy STBs that do not support IGMPv3 or for applications that do
+    not take advantage of the IGMPv3 host stack.
+
+    SSM mapping enables the last hop router to determine the source
+    addresses either by a statically configured table on the router or
+    by consulting a DNS server. When the statically configured table
+    is changed, or when the DNS mapping changes, the router will leave
+    the current sources associated with the joined groups.
+
+C12 MRIB for incongruent unicast/multicast topologies is not supported.
+    RPF mechanism currently just looks up the information in the
+    unicast routing table.
+
+    See also:
+    RFC5110: 2.2.3.  Issue: Overlapping Unicast/Multicast Topology
+    
+    Sometimes, multicast RPF mechanisms first look up the multicast
+    routing table, or M-RIB ("topology database") with a longest
+    prefix match algorithm, and if they find any entry (including a
+    default route), that is used; if no match is found, the unicast
+    routing table is used instead.
+
+C13 Can't detect change of primary address before the actual change
+    See also:
+
+    RFC 4601: 4.3.1.  Sending Hello Messages
+
+    Before an interface goes down or changes primary IP address, a
+    Hello message with a zero HoldTime should be sent immediately
+    (with the old IP address if the IP address changed).
+
+-x-
diff --git a/pimd/COMMANDS b/pimd/COMMANDS
new file mode 100644
index 0000000..46778fd
--- /dev/null
+++ b/pimd/COMMANDS
@@ -0,0 +1,57 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+global configuration commands:
+       ip multicast-routing	Enable IP multicast forwarding
+
+interface configuration commands:
+       ip igmp						Enable IGMP operation
+       ip igmp query-interval <1-1800>			IGMP host query interval
+       ip igmp query-max-response-time <1-25>		IGMP max query response (seconds)
+       ip igmp query-max-response-time-dsec <10-250>	IGMP max query response (deciseconds)
+       ip pim ssm					Enable PIM SSM operation
+
+verification commands:
+       show ip igmp interface			IGMP interface information
+       show ip igmp parameters			IGMP parameters information
+       show ip igmp groups			IGMP groups information
+       show ip igmp groups retransmissions	IGMP group retransmission
+       show ip igmp sources			IGMP sources information
+       show ip igmp sources retransmissions	IGMP source retransmission
+       show ip pim assert			PIM interface assert
+       show ip pim assert-metric		PIM interface assert metric
+       show ip pim designated-router		PIM interface designated router
+       show ip pim hello			PIM interface hello information
+       show ip pim interface			PIM interface information
+       show ip pim lan-prune-delay              PIM neighbors LAN prune delay parameters
+       show ip pim local-membership		PIM interface local-membership
+       show ip pim jp-override-interval         PIM interface J/P override interval
+       show ip pim join                         PIM interface join information
+       show ip pim neighbor			PIM neighbor information
+       show ip pim rpf				PIM cached source rpf information
+       show ip pim secondary                    PIM neighbor addresses
+       show ip pim upstream			PIM upstream information
+       show ip pim upstream-join-desired	PIM upstream join-desired
+       show ip pim upstream-rpf			PIM upstream source rpf
+       show ip multicast			Multicast global information
+       show ip mroute				IP multicast routing table
+       show ip mroute count			Route and packet count data
+       show ip route				IP routing table
+
+debug commands:
+       clear ip interfaces		Reset interfaces
+       clear ip igmp interfaces		Reset IGMP interfaces
+       clear ip pim interfaces		Reset PIM interfaces
+       debug igmp			IGMP protocol activity
+       debug pim			PIM protocol activity
+       debug pim zebra			ZEBRA protocol activity
+       show debugging			State of each debugging option
+       test igmp receive report		Test reception of IGMPv3 report
+       test pim receive assert          Test reception of PIM assert
+       test pim receive hello		Test reception of PIM hello
+       test pim receive join		Test reception of PIM join
+       test pim receive prune		Test reception of PIM prune
+
+statistics commands:
+       show memory pim		PIM memory statistics
+
+-x-
diff --git a/pimd/COPYING b/pimd/COPYING
new file mode 100644
index 0000000..3912109
--- /dev/null
+++ b/pimd/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/pimd/DEBUG b/pimd/DEBUG
new file mode 100644
index 0000000..aeed9da
--- /dev/null
+++ b/pimd/DEBUG
@@ -0,0 +1,49 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+DEBUG HINTS
+
+  - Check the source is issuing multicast packets with TTL high enough
+    to reach the recipients.
+
+  - Check the multicast packets are not being dropped due to
+    fragmentation problems.
+
+  - The following command generates a 100-kbps multicast stream for
+    channel 1.1.1.1,239.1.1.1 with TTL 10 and 1000-byte payload per UDP
+    packet (to avoid fragmentation):
+
+    nepim -b 1.1.1.1 -c 239.1.1.1 -T 10 -W 1000 -r 100k -a 1d
+
+  - Remotely you can receive that stream by running:
+
+    nepim -j 1.1.1.1+239.1.1.1@eth0
+    (Remember of enabling both "ip pim ssm" and "ip igmp" under eth0.)
+
+
+SAMPLE DEBUG COMMANDS
+
+  conf t
+   int eth0
+    ip pim ssm
+
+  test pim receive hello eth0 192.168.0.2 600 10 111 1000 3000 0
+  test pim receive join eth0 600 192.168.0.1 192.168.0.2 239.1.1.1 1.1.1.1
+
+  show ip pim join                                                         
+
+
+INTEROPERABILITY WITH CISCO
+
+  ! Cisco IP Multicast command reference:
+  ! ftp://ftpeng.cisco.com/ipmulticast/Multicast-Commands
+  !
+  ip pim ssm default ! enable SSM mode for groups 232.0.0.0/8
+  ip multicast-routing
+  ip pim state-refresh disable
+  no ip pim dm-fallback
+  !
+  interface FastEthernet0
+   ip pim sparse-mode
+   ip igmp version 3
+
+-x-
diff --git a/pimd/LINUX_KERNEL_MROUTE_MFC b/pimd/LINUX_KERNEL_MROUTE_MFC
new file mode 100644
index 0000000..7dc44b0
--- /dev/null
+++ b/pimd/LINUX_KERNEL_MROUTE_MFC
@@ -0,0 +1,22 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+#
+# The Linux Kernel MFC (Multicast Forwarding Cache)
+#
+
+# Check Linux kernel multicast interfaces:
+cat /proc/net/dev_mcast
+
+# Check that interface eth0 is forwarding multicast:
+cat /proc/sys/net/ipv4/conf/eth0/mc_forwarding
+
+# Check Linux kernel multicast VIFs:
+cat /proc/net/ip_mr_vif
+Interface      BytesIn  PktsIn  BytesOut PktsOut Flags Local    Remote
+
+# Check Linux kernel MFC:
+# Oifs format = vifi:TTL
+cat /proc/net/ip_mr_cache
+Group    Origin   Iif     Pkts    Bytes    Wrong Oifs
+
+# -- end-of-file --
diff --git a/pimd/Makefile.am b/pimd/Makefile.am
new file mode 100644
index 0000000..95ef2bf
--- /dev/null
+++ b/pimd/Makefile.am
@@ -0,0 +1,70 @@
+## Process this file with automake to produce Makefile.in.
+## $QuaggaId: $Format:%an, %ai, %h$ $
+
+# qpimd - pimd for quagga
+# Copyright (C) 2008 Everton da Silva Marques
+#
+# qpimd is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2,
+# or (at your option) any later version.
+# 
+# qpimd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public
+# License along with qpimd; see the file COPYING.  If not, write
+# to the Free Software Foundation, Inc., 59 Temple Place - Suite
+# 330, Boston, MA 02111-1307, USA.
+
+# PIM_DEBUG_BYDEFAULT: Automatically enables all pimd "debug ..." commands
+# PIM_ZCLIENT_DEBUG: Support for internal ZEBRA client debugging
+# PIM_MOTD_VERSION: Includes pimd version in default MOTD
+# PIM_USE_QUAGGA_INET_CHECKSUM: Prefer Quagga inet checksum
+# PIM_CHECK_RECV_IFINDEX_SANITY: Compare socket ifindex with recv ifindex
+# PIM_REPORT_RECV_IFINDEX_MISMATCH: Report sock/recv ifindex mismatch
+# PIM_ENFORCE_LOOPFREE_MFC: Refuse adding looping MFC entries
+# PIM_UNEXPECTED_KERNEL_UPCALL: Report unexpected kernel upcall
+
+PIM_DEFS =
+#PIM_DEFS += -DPIM_DEBUG_BYDEFAULT
+PIM_DEFS += -DPIM_CHECK_RECV_IFINDEX_SANITY
+#PIM_DEFS += -DPIM_REPORT_RECV_IFINDEX_MISMATCH
+PIM_DEFS += -DPIM_ZCLIENT_DEBUG
+PIM_DEFS += -DPIM_MOTD_VERSION
+PIM_DEFS += -DPIM_USE_QUAGGA_INET_CHECKSUM
+PIM_DEFS += -DPIM_ENFORCE_LOOPFREE_MFC
+#PIM_DEFS += -DPIM_UNEXPECTED_KERNEL_UPCALL
+
+INCLUDES = @INCLUDES@ -I.. -I$(top_srcdir) -I$(top_srcdir)/lib
+DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\" $(PIM_DEFS)
+INSTALL_SDATA=@INSTALL@ -m 600
+LIBS = @LIBS@ 
+noinst_LIBRARIES = libpim.a
+sbin_PROGRAMS = pimd 
+
+libpim_a_SOURCES = \
+	pimd.c pim_version.c pim_cmd.c pim_signals.c pim_iface.c \
+	pim_vty.c pim_igmp.c pim_sock.c pim_zebra.c \
+	pim_igmpv3.c pim_str.c pim_mroute.c pim_util.c pim_time.c \
+	pim_oil.c pim_zlookup.c pim_pim.c pim_tlv.c pim_neighbor.c \
+	pim_hello.c pim_ifchannel.c pim_join.c pim_assert.c \
+	pim_msg.c pim_upstream.c pim_rpf.c pim_rand.c
+
+noinst_HEADERS = \
+	pimd.h pim_version.h pim_cmd.h pim_signals.h pim_iface.h \
+	pim_vty.h pim_igmp.h pim_sock.h pim_zebra.h \
+	pim_igmpv3.h pim_str.h pim_mroute.h pim_util.h pim_time.h \
+	pim_oil.h pim_zlookup.h pim_pim.h pim_tlv.h pim_neighbor.h \
+	pim_hello.h pim_ifchannel.h pim_join.h pim_assert.h \
+	pim_msg.h pim_upstream.h pim_rpf.h pim_rand.h
+
+pimd_SOURCES = \
+	pim_main.c $(libpim_a_SOURCES)
+
+pimd_LDADD = ../lib/libzebra.la @LIBCAP@
+
+examplesdir = $(exampledir)
+dist_examples_DATA = pimd.conf.sample
diff --git a/pimd/README b/pimd/README
new file mode 100644
index 0000000..cfedaaf
--- /dev/null
+++ b/pimd/README
@@ -0,0 +1,158 @@
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+#
+
+INTRODUCTION
+
+        qpimd aims to implement a PIM (Protocol Independent Multicast)
+	daemon for the Quagga Routing Suite.
+
+	Initially qpimd targets only PIM SSM (Source-Specific
+	Multicast) mode as defined in section 4.8.2 (PIM-SSM-Only
+	Routers) of RFC 4601.
+
+	In order to deliver end-to-end multicast routing control
+	plane, qpimd includes the router-side of IGMPv3	(RFC 3376).
+
+LICENSE
+
+        qpimd - pimd for quagga
+        Copyright (C) 2008 Everton da Silva Marques
+
+        qpimd is free software; you can redistribute it and/or modify
+        it under the terms of the GNU General Public License as
+        published by the Free Software Foundation; either version 2,
+        or (at your option) any later version.
+
+        qpimd is distributed in the hope that it will be useful, but
+        WITHOUT ANY WARRANTY; without even the implied warranty of
+        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+        GNU General Public License for more details.
+
+        You should have received a copy of the GNU General Public
+        License along with qpimd; see the file COPYING.  If not, write
+        to the Free Software Foundation, Inc., 59 Temple Place - Suite
+        330, Boston, MA 02111-1307, USA.
+
+HOME SITE
+
+        qpimd lives at:
+
+        http://savannah.nongnu.org/projects/qpimd
+
+PLATFORMS
+
+	qpimd has been tested with Debian Lenny under Linux 2.6.
+
+REQUIREMENTS
+
+	qpimd requires Quagga (0.99.11 or higher from http://www.quagga.net)
+
+	The GNU Build System (Autotools) is required to build from
+	source code repository.
+
+	gawk is also needed to build with Autotools. Any other awk
+	usually won't work.
+
+BUILDING FROM QUAGGA GIT REPOSITORY
+
+	1) Get the latest quagga source tree
+
+	# git clone git://code.quagga.net/quagga.git quagga
+
+	2) Apply qpimd patch into quagga source tree
+
+	# patch -p1 -d quagga < pimd-0.126-quagga-0.99.11-git20081116.patch
+
+	3) Compile and install quagga
+
+	# cd quagga
+	# ./bootstrap.sh
+	# ./configure --prefix=/usr/local/quagga --enable-pimd
+	# make
+	# make install
+
+BUILDING FROM QUAGGA TARBALL
+
+	1) Get the latest quagga tarball
+
+	# wget http://www.quagga.net/download/quagga-0.99.11.tar.gz
+
+	2) Unpack the quagga tarball
+
+	# tar xzf quagga-0.99.11.tar.gz
+
+	3) Apply qpimd patch into quagga source tree
+
+	# patch -p1 -d quagga-0.99.11 < pimd-0.126-quagga-0.99.11-git20081116.patch
+
+	4) Compile and install quagga
+
+	# cd quagga-0.99.11
+	# autoreconf -i
+	# ./configure --prefix=/usr/local/quagga --enable-pimd
+	# make
+	# make install
+
+USAGE
+
+	1) Configure and start the zebra daemon
+
+	# cp /usr/local/quagga/etc/zebra.conf.sample /usr/local/quagga/etc/zebra.conf
+	# vi /usr/local/quagga/etc/zebra.conf
+	# /usr/local/quagga/sbin/zebra
+
+	2) Configure and start the pimd daemon
+
+	# cp /usr/local/quagga/etc/pimd.conf.sample /usr/local/quagga/etc/pimd.conf
+	# vi /usr/local/quagga/etc/pimd.conf
+	# /usr/local/quagga/sbin/pimd
+
+	3) Access pimd vty interface at port TCP 2611
+
+	# telnet localhost 2611
+
+CONFIGURATION COMMANDS
+
+	See available commands in the file pimd/COMMANDS.
+
+KNOWN CAVEATS
+
+	See list of known caveats in the file pimd/CAVEATS.
+
+SUPPORT
+
+	Please post comments, questions, patches, bug reports at the
+	support site:
+
+        http://savannah.nongnu.org/projects/qpimd
+
+RELATED WORK
+
+	igmprt:	An IGMPv3-router implementation
+	- http://www.loria.fr/~lahmadi/igmpv3-router.html
+
+	pimd: PIMv2-SM daemon
+	- http://netweb.usc.edu/pim/pimd (URL broken in 2008-12-23)
+	- http://packages.debian.org/source/sid/pimd (from Debian)
+
+	zpimd: zpimd is not dependent of zebra or any other routing daemon
+	- ftp://robur.slu.se/pub/Routing/Zebra
+	- http://sunsite2.icm.edu.pl/pub/unix/routing/zpimd
+
+	mrd6: an IPv6 Multicast Router for Linux systems
+	- http://fivebits.net/proj/mrd6/
+
+	MBGP: Implementation of RFC 2858 for Quagga
+	- git://git.coplanar.net/~balajig/quagga
+	- http://www.gossamer-threads.com/lists/quagga/dev/18000
+
+REFERENCES
+
+	IANA Protocol Independent Multicast (PIM) Parameters
+	http://www.iana.org/assignments/pim-parameters/pim-parameters.txt
+
+	Address Family Numbers
+	http://www.iana.org/assignments/address-family-numbers
+
+                              -- END --
diff --git a/pimd/TODO b/pimd/TODO
new file mode 100644
index 0000000..81602c0
--- /dev/null
+++ b/pimd/TODO
@@ -0,0 +1,270 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+T1 DONE Implement debug command
+   test pim receive join
+
+T2 DONE Implement debug command
+   test pim receive prune
+
+T3 DONE Per-interface Downstream (S,G) state machine
+   (RFC 4601 4.5.3. Receiving (S,G) Join/Prune Messages)
+
+T4 DONE Upstream (S,G) state machine
+   (RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages)
+
+T5 DONE Verify Data Packet Forwarding Rules
+   RFC 4601 4.2.  Data Packet Forwarding Rules
+   RFC 4601 4.8.2.  PIM-SSM-Only Routers
+
+   Additionally, the Packet forwarding rules of Section 4.2 can be
+   simplified in a PIM-SSM-only router:
+
+     iif is the incoming interface of the packet.
+     oiflist = NULL
+     if (iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined) {
+       oiflist = inherited_olist(S,G)
+     } else if (iif is in inherited_olist(S,G)) {
+       send Assert(S,G) on iif
+     }
+     oiflist = oiflist (-) iif
+     forward packet on all interfaces in oiflist
+
+   Macro:
+     inherited_olist(S,G) =
+       joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+
+T6 Implement (S,G) Assert state machine (RFC 4601, section 4.6.1).
+   Changes in pim_ifchannel.ifassert_winner should trigger
+   pim_upstream_update_join_desired().
+   Depends on TODO T27.
+   See also CAVEAT C7.
+   See also: RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages
+    Transitions from Joined State
+     RPF'(S,G) changes due to an Assert
+
+   http://www.hep.ucl.ac.uk/~ytl/multi-cast/pim-dm_01.html:
+
+     The PIM Assert mechanism is used to shutoff duplicate flows onto
+     the same multiaccess network. Routers detect this condiction when
+     they receive an (S,G) packet via a multi-access interface that is
+     in the (S,G) OIL. This causes the routers to send Assert
+     Messages.
+
+   Note that neighbors will not accept Join/Prune or Assert messages
+   from a router unless they have first heard a Hello message from that
+   router.  Thus, if a router needs to send a Join/Prune or Assert
+   message on an interface on which it has not yet sent a Hello message
+   with the currently configured IP address, then it MUST immediately
+   send the relevant Hello message without waiting for the Hello Timer
+   to expire, followed by the Join/Prune or Assert message.
+
+T7 DONE Implement hello option: LAN Prune Delay
+
+T8 DONE Implement J/P_Override_Interval(I)
+   Depends on TODO T7.
+   See pim_ifchannel.c, pim_ifchannel_prune(), jp_override_interval.
+
+T9 DONE Detect change in IGMPv3 RPF interface/next-hop for S and update.
+   channel_oil vif index accordingly ?
+   Beware accidentaly adding looped MFC entries (IIF=OIF).
+
+T10 DONE React to (S,G) join directed to another upstream address. See
+    also:
+    
+    RFC 4601: 4.5.7.  Sending (S,G) Join/Prune Messages
+
+    If a router wishes to propagate a Join(S,G) upstream, it must also
+    watch for messages on its upstream interface from other routers on
+    that subnet, and these may modify its behavior.  If it sees a
+    Join(S,G) to the correct upstream neighbor, it should suppress its
+    own Join(S,G).  If it sees a Prune(S,G), Prune(S,G,rpt), or
+    Prune(*,G) to the correct upstream neighbor towards S, it should
+    be prepared to override that prune by scheduling a Join(S,G) to be
+    sent almost immediately.
+
+T11 DONE Review protocol modifications for SSM
+    (RFC 4601 4.8.1.  Protocol Modifications for SSM Destination
+    Addresses)
+
+T12 DONE Review updates of RPF entries.
+    FIXME pim_upstream.c send_join():
+    Currently only one upstream state is affected by detection of RPF change.
+    RPF change should affect all upstream states sharing the RPF cache.
+
+T13 DONE Check that RFC macros using S,G,RPF_interface(S) are actually
+    implemented with this strategy:
+    rpf_ifch=find_ifch(up->rpf->interface).
+    See pim_rpf.c pim_rpf_find_rpf_addr() for a correct example.
+
+    $ grep -i macro pimd/*.c
+    pimd/pim_iface.c:  RFC 4601: 4.1.6.  State Summarization Macros
+    pimd/pim_ifchannel.c:    RFC 4601: 4.6.5.  Assert State Macros
+    pimd/pim_ifchannel.c:  RFC 4601: 4.1.6.  State Summarization Macros
+    pimd/pim_ifchannel.c:  RFC 4601: 4.1.6.  State Summarization Macros
+    pimd/pim_ifchannel.c:  RFC 4601: 4.6.5.  Assert State Macros
+    pimd/pim_ifchannel.c:  Macro:
+    pimd/pim_rpf.c:  RFC 4601: 4.1.6.  State Summarization Macros
+
+T14 DONE Send Assert(S,G) on iif as response to WRONGVIF kernel upcall.
+    See pim_mroute.c mroute_msg().
+
+T15 Interface command to statically join (S,G).
+    interface eth0
+     ip igmp join-group 239.1.1.1 source 1.1.1.1
+
+T16 DONE RPF'(S,G) lookup is not working for S reachable with default route.
+    See "RPF'(S,G) not found" in pim_rpf_update() from pim_rpf.c.
+    Zebra daemon RIB is not reflecting changes in kernel routes
+    accurately?
+
+T17 DONE Prevent CLI from creating bogus interfaces.
+    Example:
+    conf t
+     interface xxx
+
+T18 Consider reliable pim solution (refresh reduction)
+    A Reliable Transport Mechanism for PIM
+    http://tools.ietf.org/wg/pim/draft-ietf-pim-port/
+    PORT=PIM-Over-Reliable-Transport
+
+T19 DONE Fix self as neighbor 
+    See mailing list post:
+    http://lists.gnu.org/archive/html/qpimd-users/2009-04/msg00000.html
+
+T20 DONE Fix debug message: "pim_neighbor_update: internal error:
+    trying to replace same prefix list"
+    See mailing list post:
+    http://lists.gnu.org/archive/html/qpimd-users/2009-04/msg00000.html
+
+T21 DONE Clean-up PIM/IGMP interface mismatch debugging
+    See option PIM_CHECK_RECV_IFINDEX_SANITY in pimd/Makefile.am
+    See mailing list post:
+    http://lists.nongnu.org/archive/html/qpimd-users/2009-04/msg00003.html
+
+T22 DONE IGMP must be protected against adding looped MFC entries
+    created by both source and receiver attached to the same
+    interface.
+
+T23 DONE libzebra crash after zclient_lookup_nexthop.
+    See mailing list post:
+    http://lists.nongnu.org/archive/html/qpimd-users/2009-04/msg00008.html
+
+T24 DONE zserv may return recursive routes:
+     - nexthop type is set to ZEBRA_NEXTHOP_IPV4
+     - ifindex is not reported
+     - calls expecting ifindex (fib_lookup_if_vif_index) are disrupted
+    See also this mailing list post:
+    [PATCH 21/21] Link detect and recursive routes
+    http://www.gossamer-threads.com/lists/quagga/dev/17564
+
+T25 DONE Zclient nexthop lookup missing OSPF route to 1.1.1.1/32
+    See also:
+    pim_zlookup.c zclient_lookup_nexthop misses OSPF 1.1.1.1/32
+    zebra/zebra_vty.c show_ip_route_addr_cmd hits OSPF 1.1.1.1/32
+
+T26 DONE Zebra daemon is marking recursive static route as inactive.
+
+    FIXED: zebra daemon was incorrectly marking recursive routes
+    pointing to kernel routes as inactive:
+      zebra/zebra_rib.c nexthop_active_ipv4:
+        -- Original:
+	  else if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_INTERNAL))
+        -- Fixed:
+	  else if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_INTERNAL) ||
+		   match->type == ZEBRA_ROUTE_KERNEL)
+
+    Old problem description:
+
+    This prevents rib_match_ipv4 from returning its nexthop:
+    client: pim_zlookup.c zclient_read_nexthop
+    server: zebra/zserv.c zsend_ipv4_nexthop_lookup_v2 -> rib_match_ipv4
+
+    Kernel route is injected into zebra in zebra_rib.c rib_add_ipv4
+    Examples:
+    rt_netlink.c:726: rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, flags, &p, gate, src, index, table, metric, 0);
+    rt_netlink.c:864: rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, src, index, table, 0, 0);
+
+    This patch didn't fix the issue:
+    [PATCH 21/21] Link detect and recursive routes
+    http://www.gossamer-threads.com/lists/quagga/dev/17564
+
+    See the example below for the route 2.2.2.2.
+
+bash# route add -host 1.1.1.1 gw 127.0.0.1
+bash# route add -host 2.2.2.2 gw 1.1.1.1
+bash# netstat -nvr
+Kernel IP routing table
+Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
+2.2.2.2         1.1.1.1         255.255.255.255 UGH       0 0          0 lo
+1.1.1.1         127.0.0.1       255.255.255.255 UGH       0 0          0 lo
+192.168.0.0     0.0.0.0         255.255.255.0   U         0 0          0 eth0
+0.0.0.0         192.168.0.2     0.0.0.0         UG        0 0          0 eth0
+bash# 
+
+zebra# sh ip route         
+Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
+       I - ISIS, B - BGP, > - selected route, * - FIB route
+
+K>* 0.0.0.0/0 via 192.168.0.2, eth0
+K>* 1.1.1.1/32 via 127.0.0.1, lo
+K * 2.2.2.2/32 via 1.1.1.1, lo inactive
+C>* 127.0.0.0/8 is directly connected, lo
+C>* 192.168.0.0/24 is directly connected, eth0
+
+quagga-pimd-router# sh ip route 1.1.1.1
+Address         NextHop         Interface Metric Preference
+1.1.1.1         127.0.0.1       lo             0          0
+quagga-pimd-router# 
+quagga-pimd-router# sh ip route 2.2.2.2
+Address         NextHop         Interface Metric Preference
+2.2.2.2         192.168.0.2     eth0           0          0
+quagga-pimd-router# 
+
+T27 DONE Implement debug command
+    test pim receive assert
+    See also TODO T6: (S,G) Assert state machine.
+
+T28 DONE Bad IPv4 address family=02 in Join/Prune dump
+    Reported by Andrew Lunn <andrew.lunn@ascom.ch>
+    
+    # 58-byte pim v2 Join/Prune dump
+    # ------------------------------
+    # IPv4 address family=02 is wrong, correct IPv4 address family is 01
+    # See http://www.iana.org/assignments/address-family-numbers
+    #
+    c8XX YY03 : ip src 200.xx.yy.3
+    e000 000d : ip dst 224.0.0.13
+    9404 0000 : ip router alert option 148.4.0.0
+    2300 ab13 : pimv2,type=3 res=00 checksum=ab13
+    0200      : upstream family=02, encoding=00
+    c8XX YY08 : upstream 200.xx.yy.8
+    0001 00d2 : res=00 groups=01 holdtime=00d2
+    0200 0020 : group family=02, encoding=00, res=00, mask_len=20
+    ef01 0101 : group address 239.1.1.1
+    0001 0000 : joined=0001 pruned=0000
+    0200 0020 : source family=02, encoding=00, res=00, mask_len=20
+    0101 0101 : source address 1.1.1.1
+
+T29 DONE Reset interface PIM-hello-sent counter when primary address changes
+    See pim_ifp->pim_ifstat_hello_sent
+
+    RFC 4601: 4.3.1.  Sending Hello Messages
+
+    Thus, if a router needs to send a Join/Prune or Assert message on
+    an interface on which it has not yet sent a Hello message with the
+    currently configured IP address, then it MUST immediately send the
+    relevant Hello message without waiting for the Hello Timer to
+    expire, followed by the Join/Prune or Assert message.
+
+T30 DONE Run interface DR election when primary address changes
+    Reported by Andrew Lunn <andrew.lunn@ascom.ch>
+    See pim_if_dr_election().
+
+T31 If an interface changes one of its secondary IP addresses, a Hello
+    message with an updated Address_List option and a non-zero
+    HoldTime should be sent immediately.
+
+    See also:
+    RFC 4601: 4.3.1.  Sending Hello Messages
+
+-x-
diff --git a/pimd/pim_assert.c b/pimd/pim_assert.c
new file mode 100644
index 0000000..345e89c
--- /dev/null
+++ b/pimd/pim_assert.c
@@ -0,0 +1,291 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "prefix.h"
+
+#include "pimd.h"
+#include "pim_str.h"
+#include "pim_tlv.h"
+#include "pim_msg.h"
+#include "pim_pim.h"
+#include "pim_iface.h"
+#include "pim_hello.h"
+#include "pim_assert.h"
+
+static void on_trace(const char *label,
+		     struct interface *ifp, struct in_addr src)
+{
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src, src_str, sizeof(src_str));
+    zlog_debug("%s: from %s on %s",
+	       label, src_str, ifp->name);
+  }
+}
+
+int pim_assert_recv(struct interface *ifp,
+		    struct pim_neighbor *neigh,
+		    struct in_addr src_addr,
+		    char *buf, int buf_size)
+{
+  struct prefix            msg_group_addr;
+  struct prefix            msg_source_addr;
+  struct pim_assert_metric msg_metric;
+  int offset;
+  char *curr;
+  int curr_size;
+
+  on_trace(__PRETTY_FUNCTION__, ifp, src_addr);
+
+  curr      = buf;
+  curr_size = buf_size;
+
+  /*
+    Parse assert group addr
+   */
+  offset = pim_parse_addr_group(ifp->name, src_addr,
+				&msg_group_addr,
+				curr, curr_size);
+  if (offset < 1) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: pim_parse_addr_group() failure: from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, ifp->name);
+    return -1;
+  }
+  curr      += offset;
+  curr_size -= offset;
+
+  /*
+    Parse assert source addr
+  */
+  offset = pim_parse_addr_ucast(ifp->name, src_addr,
+				&msg_source_addr,
+				curr, curr_size);
+  if (offset < 1) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, ifp->name);
+    return -2;
+  }
+  curr      += offset;
+  curr_size -= offset;
+
+  if (curr_size != 8) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: preference/metric size is not 8: size=%d from %s on interface %s",
+	      __PRETTY_FUNCTION__,
+	      curr_size,
+	      src_str, ifp->name);
+    return -3;
+  }
+
+  /*
+    Parse assert metric preference
+  */
+
+  msg_metric.metric_preference = ntohl(*(const uint32_t *) curr);
+
+  msg_metric.rpt_bit_flag = msg_metric.metric_preference & 0x80000000; /* save highest bit */
+  msg_metric.metric_preference &= ~0x80000000; /* clear highest bit */
+
+  curr += 4;
+
+  /*
+    Parse assert route metric
+  */
+
+  msg_metric.route_metric = ntohl(*(const uint32_t *) curr);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char neigh_str[100];
+    char source_str[100];
+    char group_str[100];
+    pim_inet4_dump("<neigh?>", src_addr, neigh_str, sizeof(neigh_str));
+    pim_inet4_dump("<src?>", msg_source_addr.u.prefix4, source_str, sizeof(source_str));
+    pim_inet4_dump("<grp?>", msg_group_addr.u.prefix4, group_str, sizeof(group_str));
+    zlog_debug("%s: from %s on %s: (S,G)=(%s,%s) pref=%d metric=%d rpt_bit=%d",
+	       __PRETTY_FUNCTION__, neigh_str, ifp->name,
+	       source_str, group_str,
+	       msg_metric.metric_preference,
+	       msg_metric.route_metric,
+	       !!msg_metric.rpt_bit_flag);
+  }
+
+  zlog_debug("%s %s: FIXME WRITEME assert state machine",
+	     __FILE__, __PRETTY_FUNCTION__);
+
+  return 0;
+}
+
+/*
+  RFC 4601: 4.6.3.  Assert Metrics
+
+   Assert metrics are defined as:
+
+   When comparing assert_metrics, the rpt_bit_flag, metric_preference,
+   and route_metric field are compared in order, where the first lower
+   value wins.  If all fields are equal, the primary IP address of the
+   router that sourced the Assert message is used as a tie-breaker,
+   with the highest IP address winning.
+*/
+int pim_assert_metric_better(const struct pim_assert_metric *m1,
+			     const struct pim_assert_metric *m2)
+{
+  if (m1->rpt_bit_flag < m2->rpt_bit_flag)
+    return 1;
+  if (m1->rpt_bit_flag > m2->rpt_bit_flag)
+    return 0;
+
+  if (m1->metric_preference < m2->metric_preference)
+    return 1;
+  if (m1->metric_preference > m2->metric_preference)
+    return 0;
+
+  if (m1->route_metric < m2->route_metric)
+    return 1;
+  if (m1->route_metric > m2->route_metric)
+    return 0;
+
+  return ntohl(m1->ip_address.s_addr) > ntohl(m2->ip_address.s_addr);
+}
+
+int pim_assert_build_msg(char *pim_msg, int buf_size,
+			 struct interface *ifp,
+			 struct in_addr group_addr,
+			 struct in_addr source_addr,
+			 uint32_t metric_preference,
+			 uint32_t route_metric,
+			 uint32_t rpt_bit_flag)
+{
+  char *buf_pastend = pim_msg + buf_size;
+  char *pim_msg_curr;
+  int pim_msg_size;
+  int remain;
+
+  pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN; /* skip room for pim header */
+
+  /* Encode group */
+  remain = buf_pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_group(pim_msg_curr,
+						remain,
+						group_addr);
+  if (!pim_msg_curr) {
+    char group_str[100];
+    pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+    zlog_warn("%s: failure encoding group address %s: space left=%d",
+	      __PRETTY_FUNCTION__, group_str, remain);
+    return -1;
+  }
+
+  /* Encode source */
+  remain = buf_pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr,
+						remain,
+						source_addr);
+  if (!pim_msg_curr) {
+    char source_str[100];
+    pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+    zlog_warn("%s: failure encoding source address %s: space left=%d",
+	      __PRETTY_FUNCTION__, source_str, remain);
+    return -2;
+  }
+
+  /* Metric preference */
+  *((uint32_t *) pim_msg_curr) = htonl(rpt_bit_flag ?
+				       metric_preference | 0x80000000 :
+				       metric_preference);
+  pim_msg_curr += 4;
+
+  /* Route metric */
+  *((uint32_t *) pim_msg_curr) = htonl(route_metric);
+  pim_msg_curr += 4;
+
+  /*
+    Add PIM header
+  */
+  pim_msg_size = pim_msg_curr - pim_msg;
+  pim_msg_build_header(ifp->name,
+		       pim_msg, pim_msg_size,
+		       PIM_MSG_TYPE_ASSERT);
+
+  return pim_msg_size;
+}
+
+int pim_assert_send(struct interface *ifp,
+		    struct pim_ifchannel *ch)
+{
+  struct pim_interface *pim_ifp;
+  struct pim_assert_metric *assert_metric;
+  char pim_msg[1000];
+  int pim_msg_size;
+
+  pim_ifp = ifp->info;
+  if (!pim_ifp) {
+    zlog_warn("%s: pim not enabled on interface: %s",
+	      __PRETTY_FUNCTION__, ifp->name);
+    return -1;
+  }
+
+  assert_metric = my_assert_metric(ch);
+
+  pim_msg_size = pim_assert_build_msg(pim_msg, sizeof(pim_msg), ifp,
+				      ch->group_addr, ch->source_addr,
+				      assert_metric->metric_preference,
+				      assert_metric->route_metric,
+				      assert_metric->rpt_bit_flag);
+  if (pim_msg_size < 1) {
+    zlog_warn("%s: failure building PIM assert message: msg_size=%d",
+	      __PRETTY_FUNCTION__, pim_msg_size);
+    return -2;
+  }
+
+  /*
+    RFC 4601: 4.3.1.  Sending Hello Messages
+    
+    Thus, if a router needs to send a Join/Prune or Assert message on
+    an interface on which it has not yet sent a Hello message with the
+    currently configured IP address, then it MUST immediately send the
+    relevant Hello message without waiting for the Hello Timer to
+    expire, followed by the Join/Prune or Assert message.
+  */
+  pim_hello_require(ifp);
+
+  if (pim_msg_send(pim_ifp->pim_sock_fd,
+		   qpim_all_pim_routers_addr,
+		   pim_msg,
+		   pim_msg_size,
+		   ifp->name)) {
+    zlog_warn("%s: could not send PIM message on interface %s",
+	      __PRETTY_FUNCTION__, ifp->name);
+    return -3;
+  }
+
+  return 0;
+}
diff --git a/pimd/pim_assert.h b/pimd/pim_assert.h
new file mode 100644
index 0000000..39d4199
--- /dev/null
+++ b/pimd/pim_assert.h
@@ -0,0 +1,52 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_ASSERT_H
+#define PIM_ASSERT_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_neighbor.h"
+#include "pim_ifchannel.h"
+
+int pim_assert_recv(struct interface *ifp,
+		    struct pim_neighbor *neigh,
+		    struct in_addr src_addr,
+		    char *buf, int buf_size);
+
+int pim_assert_metric_better(const struct pim_assert_metric *m1,
+			     const struct pim_assert_metric *m2);
+
+int pim_assert_build_msg(char *pim_msg, int buf_size,
+			 struct interface *ifp,
+			 struct in_addr group_addr,
+			 struct in_addr source_addr,
+			 uint32_t metric_preference,
+			 uint32_t route_metric,
+			 uint32_t rpt_bit_flag);
+
+int pim_assert_send(struct interface *ifp,
+		    struct pim_ifchannel *ch);
+
+#endif /* PIM_ASSERT_H */
diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c
new file mode 100644
index 0000000..e563cf7
--- /dev/null
+++ b/pimd/pim_cmd.c
@@ -0,0 +1,3587 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <sys/ioctl.h>
+
+#include <zebra.h>
+
+#include "command.h"
+#include "if.h"
+#include "prefix.h"
+
+#include "pimd.h"
+#include "pim_cmd.h"
+#include "pim_iface.h"
+#include "pim_vty.h"
+#include "pim_mroute.h"
+#include "pim_str.h"
+#include "pim_igmpv3.h"
+#include "pim_sock.h"
+#include "pim_time.h"
+#include "pim_util.h"
+#include "pim_oil.h"
+#include "pim_neighbor.h"
+#include "pim_pim.h"
+#include "pim_ifchannel.h"
+#include "pim_hello.h"
+#include "pim_msg.h"
+#include "pim_upstream.h"
+#include "pim_rpf.h"
+
+static struct cmd_node pim_global_node = {
+  PIM_NODE,
+  "",
+  1 /* vtysh ? yes */
+};
+
+static struct cmd_node pim_interface_node = {
+  INTERFACE_NODE,
+  "%s(config-if-pim)# ",
+  1 /* vtysh ? yes */
+};
+
+static void pim_if_membership_clear(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  if (PIM_IF_TEST_PIM(pim_ifp->options) &&
+      PIM_IF_TEST_IGMP(pim_ifp->options)) {
+    return;
+  }
+
+  pim_ifchannel_membership_clear(ifp);
+}
+
+/*
+  When PIM is disabled on interface, IGMPv3 local membership
+  information is not injected into PIM interface state.
+
+  The function pim_if_membership_refresh() fetches all IGMPv3 local
+  membership information into PIM. It is intented to be called
+  whenever PIM is enabled on the interface in order to collect missed
+  local membership information.
+ */
+static void pim_if_membership_refresh(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *sock_node;
+  struct igmp_sock     *igmp;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  if (!PIM_IF_TEST_PIM(pim_ifp->options))
+    return;
+  if (!PIM_IF_TEST_IGMP(pim_ifp->options))
+    return;
+
+  /*
+    First clear off membership from all PIM (S,G) entries on the
+    interface
+  */
+
+  pim_ifchannel_membership_clear(ifp);
+
+  /*
+    Then restore PIM (S,G) membership from all IGMPv3 (S,G) entries on
+    the interface
+  */
+
+  /* scan igmp sockets */
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+    struct listnode   *grpnode;
+    struct igmp_group *grp;
+    
+    /* scan igmp groups */
+    for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+      struct listnode    *srcnode;
+      struct igmp_source *src;
+      
+      /* scan group sources */
+      for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) {
+
+	if (IGMP_SOURCE_TEST_FORWARDING(src->source_flags)) {
+	  pim_ifchannel_local_membership_add(ifp,
+					     src->source_addr,
+					     grp->group_addr);
+	}
+	
+      } /* scan group sources */
+    } /* scan igmp groups */
+  } /* scan igmp sockets */
+
+  /*
+    Finally delete every PIM (S,G) entry lacking all state info
+   */
+
+  pim_ifchannel_delete_on_noinfo(ifp);
+
+}
+
+static void pim_show_assert(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty,
+	  "Interface Address         Source          Group           State  Winner          Uptime   Timer%s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *ch_node;
+    struct pim_ifchannel *ch;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+      char ch_src_str[100];
+      char ch_grp_str[100];
+      char winner_str[100];
+      char uptime[10];
+      char timer[10];
+
+      pim_inet4_dump("<ch_src?>", ch->source_addr,
+		     ch_src_str, sizeof(ch_src_str));
+      pim_inet4_dump("<ch_grp?>", ch->group_addr,
+		     ch_grp_str, sizeof(ch_grp_str));
+      pim_inet4_dump("<assrt_win?>", ch->ifassert_winner,
+		     winner_str, sizeof(winner_str));
+
+      pim_time_uptime(uptime, sizeof(uptime), now - ch->ifassert_creation);
+      pim_time_timer_to_mmss(timer, sizeof(timer),
+			     ch->t_ifassert_timer);
+
+      vty_out(vty, "%-9s %-15s %-15s %-15s %-6s %-15s %-8s %-5s%s",
+	      ifp->name,
+	      inet_ntoa(ifaddr),
+	      ch_src_str,
+	      ch_grp_str,
+	      pim_ifchannel_ifassert_name(ch->ifassert_state),
+	      winner_str,
+	      uptime,
+	      timer,
+	      VTY_NEWLINE);
+    } /* scan interface channels */
+  } /* scan interfaces */
+}
+
+static void pim_show_assert_metric(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  
+  vty_out(vty,
+	  "Interface Address         Source          Group           RPT Pref Metric Address        %s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *ch_node;
+    struct pim_ifchannel *ch;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+      char ch_src_str[100];
+      char ch_grp_str[100];
+      char addr_str[100];
+      struct pim_assert_metric *am;
+
+      am = &ch->ifassert_spt_assert_metric;
+
+      pim_inet4_dump("<ch_src?>", ch->source_addr,
+		     ch_src_str, sizeof(ch_src_str));
+      pim_inet4_dump("<ch_grp?>", ch->group_addr,
+		     ch_grp_str, sizeof(ch_grp_str));
+      pim_inet4_dump("<addr?>", am->ip_address,
+		     addr_str, sizeof(addr_str));
+
+      vty_out(vty, "%-9s %-15s %-15s %-15s %-3s %4u %6u %-15s%s",
+	      ifp->name,
+	      inet_ntoa(ifaddr),
+	      ch_src_str,
+	      ch_grp_str,
+	      am->rpt_bit_flag ? "yes" : "no",
+	      am->metric_preference,
+	      am->route_metric,
+	      addr_str,
+	      VTY_NEWLINE);
+    } /* scan interface channels */
+  } /* scan interfaces */
+}
+
+static void pim_show_assert_winner_metric(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  
+  vty_out(vty,
+	  "Interface Address         Source          Group           RPT Pref Metric Address        %s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *ch_node;
+    struct pim_ifchannel *ch;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+      char ch_src_str[100];
+      char ch_grp_str[100];
+      char addr_str[100];
+      struct pim_assert_metric *am;
+
+      am = &ch->ifassert_winner_metric;
+
+      pim_inet4_dump("<ch_src?>", ch->source_addr,
+		     ch_src_str, sizeof(ch_src_str));
+      pim_inet4_dump("<ch_grp?>", ch->group_addr,
+		     ch_grp_str, sizeof(ch_grp_str));
+      pim_inet4_dump("<addr?>", am->ip_address,
+		     addr_str, sizeof(addr_str));
+
+      vty_out(vty, "%-9s %-15s %-15s %-15s %-3s %4u %6u %-15s%s",
+	      ifp->name,
+	      inet_ntoa(ifaddr),
+	      ch_src_str,
+	      ch_grp_str,
+	      am->rpt_bit_flag ? "yes" : "no",
+	      am->metric_preference,
+	      am->route_metric,
+	      addr_str,
+	      VTY_NEWLINE);
+    } /* scan interface channels */
+  } /* scan interfaces */
+}
+
+static void pim_show_membership(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+
+  vty_out(vty,
+	  "Interface Address         Source          Group           Membership%s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *ch_node;
+    struct pim_ifchannel *ch;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+      char ch_src_str[100];
+      char ch_grp_str[100];
+
+      pim_inet4_dump("<ch_src?>", ch->source_addr,
+		     ch_src_str, sizeof(ch_src_str));
+      pim_inet4_dump("<ch_grp?>", ch->group_addr,
+		     ch_grp_str, sizeof(ch_grp_str));
+
+      vty_out(vty, "%-9s %-15s %-15s %-15s %-10s%s",
+	      ifp->name,
+	      inet_ntoa(ifaddr),
+	      ch_src_str,
+	      ch_grp_str,
+	      ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO ?
+	      "NOINFO" : "INCLUDE",
+	      VTY_NEWLINE);
+    } /* scan interface channels */
+  } /* scan interfaces */
+
+}
+
+static void igmp_show_interfaces(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty,
+	  "Interface Address         ifIndex Socket Uptime   Multi Broad MLoop AllMu Prmsc Del%s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct listnode *sock_node;
+    struct igmp_sock *igmp;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+      char uptime[10];
+      int mloop;
+
+      pim_time_uptime(uptime, sizeof(uptime), now - igmp->sock_creation);
+
+      mloop = pim_socket_mcastloop_get(igmp->fd);
+      
+      vty_out(vty, "%-9s %-15s %7d %6d %8s %5s %5s %5s %5s %5s %3s%s",
+	      ifp->name,
+	      inet_ntoa(igmp->ifaddr),
+	      ifp->ifindex,
+	      igmp->fd,
+	      uptime,
+	      if_is_multicast(ifp) ? "yes" : "no",
+	      if_is_broadcast(ifp) ? "yes" : "no",
+	      (mloop < 0) ? "?" : (mloop ? "yes" : "no"),
+	      (ifp->flags & IFF_ALLMULTI) ? "yes" : "no",
+	      (ifp->flags & IFF_PROMISC) ? "yes" : "no",
+	      PIM_IF_IS_DELETED(ifp) ? "yes" : "no",
+	      VTY_NEWLINE);
+    }
+  }
+}
+
+static void pim_show_dr(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty,
+	  "NonPri: Number of neighbors missing DR Priority hello option%s%s",
+	  VTY_NEWLINE, VTY_NEWLINE);
+  
+  vty_out(vty, "Interface Address         DR              Uptime   Elections NonPri%s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    char dr_str[100];
+    char dr_uptime[10];
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->pim_sock_fd < 0)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    pim_time_uptime(dr_uptime, sizeof(dr_uptime),
+		    now - pim_ifp->pim_dr_election_last);
+
+    pim_inet4_dump("<dr?>", pim_ifp->pim_dr_addr,
+		   dr_str, sizeof(dr_str));
+
+    vty_out(vty, "%-9s %-15s %-15s %8s %9d %6d%s",
+	    ifp->name,
+	    inet_ntoa(ifaddr),
+	    dr_str,
+	    dr_uptime,
+	    pim_ifp->pim_dr_election_count,
+	    pim_ifp->pim_dr_num_nondrpri_neighbors,
+	    VTY_NEWLINE);
+  }
+}
+
+static void pim_show_hello(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+  
+  vty_out(vty, "Interface Address         Period Timer StatStart Recv Rfail Send Sfail%s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    char hello_period[10];
+    char hello_timer[10];
+    char stat_uptime[10];
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->pim_sock_fd < 0)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    pim_time_timer_to_mmss(hello_timer, sizeof(hello_timer), pim_ifp->t_pim_hello_timer);
+    pim_time_mmss(hello_period, sizeof(hello_period), pim_ifp->pim_hello_period);
+    pim_time_uptime(stat_uptime, sizeof(stat_uptime), now - pim_ifp->pim_ifstat_start);
+
+    vty_out(vty, "%-9s %-15s %6s %5s %9s %4u %5u %4u %5u%s",
+	    ifp->name,
+	    inet_ntoa(ifaddr),
+	    hello_period,
+	    hello_timer,
+	    stat_uptime,
+	    pim_ifp->pim_ifstat_hello_recv,
+	    pim_ifp->pim_ifstat_hello_recvfail,
+	    pim_ifp->pim_ifstat_hello_sent,
+	    pim_ifp->pim_ifstat_hello_sendfail,
+	    VTY_NEWLINE);
+  }
+}
+
+static void pim_show_interfaces(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty, "Interface Address         ifIndex Socket Uptime   Multi Broad MLoop AllMu Prmsc Del%s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    char uptime[10];
+    int mloop;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->pim_sock_fd < 0)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    pim_time_uptime(uptime, sizeof(uptime), now - pim_ifp->pim_sock_creation);
+
+    mloop = pim_socket_mcastloop_get(pim_ifp->pim_sock_fd);
+      
+    vty_out(vty, "%-9s %-15s %7d %6d %8s %5s %5s %5s %5s %5s %3s%s",
+	    ifp->name,
+	    inet_ntoa(ifaddr),
+	    ifp->ifindex,
+	    pim_ifp->pim_sock_fd,
+	    uptime,
+	    if_is_multicast(ifp) ? "yes" : "no",
+	    if_is_broadcast(ifp) ? "yes" : "no",
+	    (mloop < 0) ? "?" : (mloop ? "yes" : "no"),
+	    (ifp->flags & IFF_ALLMULTI) ? "yes" : "no",
+	    (ifp->flags & IFF_PROMISC) ? "yes" : "no",
+	    PIM_IF_IS_DELETED(ifp) ? "yes" : "no",
+	    VTY_NEWLINE);
+  }
+}
+
+static void pim_show_join(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty,
+	  "Interface Address         Source          Group           State  Uptime   Expire Prune%s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *ch_node;
+    struct pim_ifchannel *ch;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+      char ch_src_str[100];
+      char ch_grp_str[100];
+      char uptime[10];
+      char expire[10];
+      char prune[10];
+
+      pim_inet4_dump("<ch_src?>", ch->source_addr,
+		     ch_src_str, sizeof(ch_src_str));
+      pim_inet4_dump("<ch_grp?>", ch->group_addr,
+		     ch_grp_str, sizeof(ch_grp_str));
+
+      pim_time_uptime(uptime, sizeof(uptime), now - ch->ifjoin_creation);
+      pim_time_timer_to_mmss(expire, sizeof(expire),
+			     ch->t_ifjoin_expiry_timer);
+      pim_time_timer_to_mmss(prune, sizeof(prune),
+			     ch->t_ifjoin_prune_pending_timer);
+
+      vty_out(vty, "%-9s %-15s %-15s %-15s %-6s %8s %-6s %5s%s",
+	      ifp->name,
+	      inet_ntoa(ifaddr),
+	      ch_src_str,
+	      ch_grp_str,
+	      pim_ifchannel_ifjoin_name(ch->ifjoin_state),
+	      uptime,
+	      expire,
+	      prune,
+	      VTY_NEWLINE);
+    } /* scan interface channels */
+  } /* scan interfaces */
+
+}
+
+static void pim_show_neighbors(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty,
+	  "Recv flags: H=holdtime L=lan_prune_delay P=dr_priority G=generation_id A=address_list%s"
+	  "            T=can_disable_join_suppression%s%s",
+	  VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+  vty_out(vty, "Interface Address         Neighbor        Uptime   Timer Holdt DrPri GenId    Recv  %s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *neighnode;
+    struct pim_neighbor *neigh;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->pim_sock_fd < 0)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+      char uptime[10];
+      char holdtime[10];
+      char expire[10];
+      char neigh_src_str[100];
+      char recv[7];
+
+      pim_inet4_dump("<src?>", neigh->source_addr,
+		     neigh_src_str, sizeof(neigh_src_str));
+      pim_time_uptime(uptime, sizeof(uptime), now - neigh->creation);
+      pim_time_mmss(holdtime, sizeof(holdtime), neigh->holdtime);
+      pim_time_timer_to_mmss(expire, sizeof(expire), neigh->t_expire_timer);
+
+      recv[0] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_HOLDTIME)                     ? 'H' : ' ';
+      recv[1] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY)              ? 'L' : ' ';
+      recv[2] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY)                  ? 'P' : ' ';
+      recv[3] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_GENERATION_ID)                ? 'G' : ' ';
+      recv[4] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_ADDRESS_LIST)                 ? 'A' : ' ';
+      recv[5] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION) ? 'T' : ' ';
+      recv[6] = '\0';
+
+      vty_out(vty, "%-9s %-15s %-15s %8s %5s %5s %5u %08x %6s%s",
+	      ifp->name,
+	      inet_ntoa(ifaddr),
+	      neigh_src_str,
+	      uptime,
+	      expire,
+	      holdtime,
+	      neigh->dr_priority,
+	      neigh->generation_id,
+	      recv,
+	      VTY_NEWLINE);
+    }
+
+
+  }
+}
+
+static void pim_show_lan_prune_delay(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+
+  vty_out(vty,
+	  "PrDly=propagation_delay (msec)           OvInt=override_interval (msec)%s"
+	  "HiDly=highest_propagation_delay (msec)   HiInt=highest_override_interval (msec)%s"
+	  "NoDly=number_of_non_lan_delay_neighbors%s"
+	  "T=t_bit LPD=lan_prune_delay_hello_option%s%s",
+	  VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+  vty_out(vty, "Interface Address         PrDly OvInt NoDly HiDly HiInt T Neighbor        LPD PrDly OvInt T%s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *neighnode;
+    struct pim_neighbor *neigh;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->pim_sock_fd < 0)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+      char neigh_src_str[100];
+
+      pim_inet4_dump("<src?>", neigh->source_addr,
+		     neigh_src_str, sizeof(neigh_src_str));
+
+      vty_out(vty, "%-9s %-15s %5u %5u %5u %5u %5u %1u %-15s %s %5u %5u %1u%s",
+	      ifp->name,
+	      inet_ntoa(ifaddr),
+	      pim_ifp->pim_propagation_delay_msec,
+	      pim_ifp->pim_override_interval_msec,
+	      pim_ifp->pim_number_of_nonlandelay_neighbors,
+	      pim_ifp->pim_neighbors_highest_propagation_delay_msec,
+	      pim_ifp->pim_neighbors_highest_override_interval_msec,
+	      !!PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options),
+	      neigh_src_str,
+	      PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY) ? "yes" : "no",
+	      neigh->propagation_delay_msec,
+	      neigh->override_interval_msec,
+	      !!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION),
+	      VTY_NEWLINE);
+    }
+
+  }
+}
+
+static void pim_show_jp_override_interval(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+
+  vty_out(vty,
+	  "EffPDelay=effective_propagation_delay (msec)%s"
+	  "EffOvrInt=override_interval (msec)%s"
+	  "JPOvrInt=jp_override_interval (msec)%s%s",
+	  VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+  vty_out(vty, "Interface Address         LAN_Delay EffPDelay EffOvrInt JPOvrInt%s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->pim_sock_fd < 0)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    vty_out(vty, "%-9s %-15s %-9s %9u %9u %8u%s",
+	    ifp->name,
+	    inet_ntoa(ifaddr),
+	    pim_if_lan_delay_enabled(ifp) ? "enabled" : "disabled",
+	    pim_if_effective_propagation_delay_msec(ifp),
+	    pim_if_effective_override_interval_msec(ifp),
+	    pim_if_jp_override_interval_msec(ifp),
+	    VTY_NEWLINE);
+  }
+}
+
+static void pim_show_neighbors_secondary(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+
+  vty_out(vty, "Interface Address         Neighbor        Secondary      %s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct listnode *neighnode;
+    struct pim_neighbor *neigh;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->pim_sock_fd < 0)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+      char neigh_src_str[100];
+      struct listnode *prefix_node;
+      struct prefix *p;
+
+      if (!neigh->prefix_list)
+	continue;
+
+      pim_inet4_dump("<src?>", neigh->source_addr,
+		     neigh_src_str, sizeof(neigh_src_str));
+
+      for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, prefix_node, p)) {
+	char neigh_sec_str[100];
+
+	if (p->family != AF_INET)
+	  continue;
+
+	pim_inet4_dump("<src?>", p->u.prefix4,
+		       neigh_sec_str, sizeof(neigh_sec_str));
+
+	vty_out(vty, "%-9s %-15s %-15s %-15s%s",
+		ifp->name,
+		inet_ntoa(ifaddr),
+		neigh_src_str,
+		neigh_sec_str,
+		VTY_NEWLINE);
+      }
+    }
+  }
+}
+
+static void pim_show_upstream(struct vty *vty)
+{
+  struct listnode     *upnode;
+  struct pim_upstream *up;
+  time_t               now;
+
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty, "Source          Group           State Uptime   JoinTimer RefCnt%s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, upnode, up)) {
+      char src_str[100];
+      char grp_str[100];
+      char uptime[10];
+      char join_timer[10];
+
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      pim_time_uptime(uptime, sizeof(uptime), now - up->state_transition);
+      pim_time_timer_to_hhmmss(join_timer, sizeof(join_timer), up->t_join_timer);
+
+      vty_out(vty, "%-15s %-15s %-5s %-8s %-9s %6d%s",
+	      src_str,
+	      grp_str,
+	      up->join_state == PIM_UPSTREAM_JOINED ? "Jnd" : "NtJnd",
+	      uptime,
+	      join_timer,
+	      up->ref_count,
+	      VTY_NEWLINE);
+  }
+}
+
+static void pim_show_join_desired(struct vty *vty)
+{
+  struct listnode      *ifnode;
+  struct listnode      *chnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+  struct in_addr        me_ifaddr;
+  char src_str[100];
+  char grp_str[100];
+
+  vty_out(vty,
+	  "Interface Source          Group           LostAssert Joins PimInclude JoinDesired EvalJD%s",
+	  VTY_NEWLINE);
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    me_ifaddr = pim_ifp->primary_address;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, chnode, ch)) {
+      struct pim_upstream *up = ch->upstream;
+
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+
+      vty_out(vty, "%-9s %-15s %-15s %-10s %-5s %-10s %-11s %-6s%s",
+	      ifp->name,
+	      src_str,
+	      grp_str,
+	      pim_ifchannel_isin_lost_assert(ch, me_ifaddr) ? "yes" : "no",
+	      pim_ifchannel_isin_joins(ch) ? "yes" : "no",
+	      pim_ifchannel_isin_pim_include(ch, me_ifaddr) ? "yes" : "no",
+	      PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags) ? "yes" : "no",
+	      pim_upstream_evaluate_join_desired(up) ? "yes" : "no",
+	      VTY_NEWLINE);
+    }
+  }
+}
+
+static void pim_show_upstream_rpf(struct vty *vty)
+{
+  struct listnode     *upnode;
+  struct pim_upstream *up;
+
+  vty_out(vty,
+	  "Source          Group           RpfIface RibNextHop      RpfAddress     %s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, upnode, up)) {
+    char src_str[100];
+    char grp_str[100];
+    char rpf_nexthop_str[100];
+    char rpf_addr_str[100];
+    struct pim_rpf *rpf;
+    const char *rpf_ifname;
+    
+    rpf = &up->rpf;
+    
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<nexthop?>", rpf->source_nexthop.mrib_nexthop_addr, rpf_nexthop_str, sizeof(rpf_nexthop_str));
+    pim_inet4_dump("<rpf?>", rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
+    
+    rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "<ifname?>";
+    
+    vty_out(vty, "%-15s %-15s %-8s %-15s %-15s%s",
+	    src_str,
+	    grp_str,
+	    rpf_ifname,
+	    rpf_nexthop_str,
+	    rpf_addr_str,
+	    VTY_NEWLINE);
+  }
+}
+
+static void pim_show_rpf(struct vty *vty)
+{
+  struct listnode     *up_node;
+  struct pim_upstream *up;
+
+  vty_out(vty,
+	  "Source          Group           RpfIface RpfAddress      RibNextHop      Metric Pref%s",
+	  VTY_NEWLINE);
+
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, up_node, up)) {
+    char src_str[100];
+    char grp_str[100];
+    char rpf_addr_str[100];
+    char rib_nexthop_str[100];
+    const char *rpf_ifname;
+    struct pim_rpf  *rpf = &up->rpf;
+    
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<rpf?>", rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
+    pim_inet4_dump("<nexthop?>", rpf->source_nexthop.mrib_nexthop_addr, rib_nexthop_str, sizeof(rib_nexthop_str));
+    
+    rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "<ifname?>";
+    
+    vty_out(vty, "%-15s %-15s %-8s %-15s %-15s %6d %4d%s",
+	    src_str,
+	    grp_str,
+	    rpf_ifname,
+	    rpf_addr_str,
+	    rib_nexthop_str,
+	    rpf->source_nexthop.mrib_route_metric,
+	    rpf->source_nexthop.mrib_metric_preference,
+	    VTY_NEWLINE);
+  }
+}
+
+static void igmp_show_querier(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+  time_t            now;
+  
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty, "Interface Address         Querier StartCount Query-Timer Other-Timer%s", VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp = ifp->info;
+    struct listnode  *sock_node;
+    struct igmp_sock *igmp;
+    
+    if (!pim_ifp)
+      continue;
+
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+      char query_hhmmss[10];
+      char other_hhmmss[10];
+
+      pim_time_timer_to_hhmmss(query_hhmmss, sizeof(query_hhmmss), igmp->t_igmp_query_timer);
+      pim_time_timer_to_hhmmss(other_hhmmss, sizeof(other_hhmmss), igmp->t_other_querier_timer);
+
+      vty_out(vty, "%-9s %-15s %7s %10d %11s %11s%s",
+	      ifp->name,
+	      inet_ntoa(igmp->ifaddr),
+	      igmp->t_igmp_query_timer ? "THIS" : "OTHER",
+	      igmp->startup_query_count,
+	      query_hhmmss,
+	      other_hhmmss,
+	      VTY_NEWLINE);
+    }
+  }
+}
+
+static void igmp_show_groups(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  time_t            now;
+
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty, "Interface Address         Group           Mode Timer    Srcs V Uptime  %s", VTY_NEWLINE);
+
+  /* scan interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp = ifp->info;
+    struct listnode  *sock_node;
+    struct igmp_sock *igmp;
+    
+    if (!pim_ifp)
+      continue;
+    
+    /* scan igmp sockets */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+      char ifaddr_str[100];
+      struct listnode *grpnode;
+      struct igmp_group *grp;
+
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+      /* scan igmp groups */
+      for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+	char group_str[100];
+	char hhmmss[10];
+	char uptime[10];
+
+	pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+	pim_time_timer_to_hhmmss(hhmmss, sizeof(hhmmss), grp->t_group_timer);
+	pim_time_uptime(uptime, sizeof(uptime), now - grp->group_creation);
+
+	vty_out(vty, "%-9s %-15s %-15s %4s %8s %4d %d %8s%s",
+		ifp->name,
+		ifaddr_str,
+		group_str,
+		grp->group_filtermode_isexcl ? "EXCL" : "INCL",
+		hhmmss,
+		grp->group_source_list ? listcount(grp->group_source_list) : 0,
+		igmp_group_compat_mode(igmp, grp),
+		uptime,
+		VTY_NEWLINE);
+
+      } /* scan igmp groups */
+    } /* scan igmp sockets */
+  } /* scan interfaces */
+}
+
+static void igmp_show_group_retransmission(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  time_t            now;
+
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty, "Interface Address         Group           RetTimer Counter RetSrcs%s", VTY_NEWLINE);
+
+  /* scan interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp = ifp->info;
+    struct listnode  *sock_node;
+    struct igmp_sock *igmp;
+    
+    if (!pim_ifp)
+      continue;
+    
+    /* scan igmp sockets */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+      char ifaddr_str[100];
+      struct listnode *grpnode;
+      struct igmp_group *grp;
+
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+      /* scan igmp groups */
+      for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+	char group_str[100];
+	char grp_retr_mmss[10];
+	struct listnode    *src_node;
+	struct igmp_source *src;
+	int grp_retr_sources = 0;
+
+	pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+	pim_time_timer_to_mmss(grp_retr_mmss, sizeof(grp_retr_mmss), grp->t_group_query_retransmit_timer);
+
+
+	/* count group sources with retransmission state */
+	for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, src_node, src)) {
+	  if (src->source_query_retransmit_count > 0) {
+	    ++grp_retr_sources;
+	  }
+	}
+
+	vty_out(vty, "%-9s %-15s %-15s %-8s %7d %7d%s",
+		ifp->name,
+		ifaddr_str,
+		group_str,
+		grp_retr_mmss,
+		grp->group_specific_query_retransmit_count,
+		grp_retr_sources,
+		VTY_NEWLINE);
+
+      } /* scan igmp groups */
+    } /* scan igmp sockets */
+  } /* scan interfaces */
+}
+
+static void igmp_show_parameters(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+
+  vty_out(vty,
+	  "QRV: Robustness Variable             SQI: Startup Query Interval%s"
+	  "QQI: Query Interval                  OQPI: Other Querier Present Interval%s"
+	  "QRI: Query Response Interval         LMQT: Last Member Query Time%s"
+	  "GMI: Group Membership Interval       OHPI: Older Host Present Interval%s%s",
+	  VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+  vty_out(vty,
+	  "Interface Address         QRV QQI QRI   GMI   SQI OQPI  LMQT  OHPI %s",
+	  VTY_NEWLINE);
+
+  /* scan interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp = ifp->info;
+    struct listnode  *sock_node;
+    struct igmp_sock *igmp;
+    
+    if (!pim_ifp)
+      continue;
+    
+    /* scan igmp sockets */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+      char ifaddr_str[100];
+      long gmi_dsec;  /* Group Membership Interval */
+      long oqpi_dsec; /* Other Querier Present Interval */
+      int  sqi;
+      long lmqt_dsec;
+      long ohpi_dsec;
+      long qri_dsec;
+
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+      gmi_dsec = PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+				   igmp->querier_query_interval,
+				   pim_ifp->igmp_query_max_response_time_dsec) / 100;
+
+      sqi = PIM_IGMP_SQI(pim_ifp->igmp_default_query_interval);
+
+      oqpi_dsec = PIM_IGMP_OQPI_MSEC(igmp->querier_robustness_variable,
+				     igmp->querier_query_interval,
+				     pim_ifp->igmp_query_max_response_time_dsec) / 100;
+
+      lmqt_dsec = PIM_IGMP_LMQT_MSEC(pim_ifp->igmp_query_max_response_time_dsec,
+				     igmp->querier_robustness_variable) / 100;
+
+      ohpi_dsec = PIM_IGMP_OHPI_DSEC(igmp->querier_robustness_variable,
+				     igmp->querier_query_interval,
+				     pim_ifp->igmp_query_max_response_time_dsec);
+
+      qri_dsec = pim_ifp->igmp_query_max_response_time_dsec;
+
+      vty_out(vty,
+	      "%-9s %-15s %3d %3d %3ld.%ld %3ld.%ld %3d %3ld.%ld %3ld.%ld %3ld.%ld%s",
+	      ifp->name,
+	      ifaddr_str,
+	      igmp->querier_robustness_variable,
+	      igmp->querier_query_interval,
+	      qri_dsec / 10, qri_dsec % 10,
+	      gmi_dsec / 10, gmi_dsec % 10,
+	      sqi,
+	      oqpi_dsec / 10, oqpi_dsec % 10,
+	      lmqt_dsec / 10, lmqt_dsec % 10,
+	      ohpi_dsec / 10, ohpi_dsec % 10,
+	      VTY_NEWLINE);
+
+    } /* scan igmp sockets */
+  } /* scan interfaces */
+}
+
+static void igmp_show_sources(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  time_t            now;
+
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty, "Interface Address         Group           Source          Timer Fwd Uptime  %s", VTY_NEWLINE);
+
+  /* scan interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp = ifp->info;
+    struct listnode  *sock_node;
+    struct igmp_sock *igmp;
+    
+    if (!pim_ifp)
+      continue;
+    
+    /* scan igmp sockets */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+      char ifaddr_str[100];
+      struct listnode   *grpnode;
+      struct igmp_group *grp;
+
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+      /* scan igmp groups */
+      for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+	char group_str[100];
+	struct listnode    *srcnode;
+	struct igmp_source *src;
+
+	pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+	
+	/* scan group sources */
+	for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) {
+	  char source_str[100];
+	  char mmss[10];
+	  char uptime[10];
+
+	  pim_inet4_dump("<source?>", src->source_addr, source_str, sizeof(source_str));
+
+	  pim_time_timer_to_mmss(mmss, sizeof(mmss), src->t_source_timer);
+
+	  pim_time_uptime(uptime, sizeof(uptime), now - src->source_creation);
+
+	  vty_out(vty, "%-9s %-15s %-15s %-15s %5s %3s %8s%s",
+		  ifp->name,
+		  ifaddr_str,
+		  group_str,
+		  source_str,
+		  mmss,
+		  IGMP_SOURCE_TEST_FORWARDING(src->source_flags) ? "Y" : "N",
+		  uptime,
+		  VTY_NEWLINE);
+	  
+	} /* scan group sources */
+      } /* scan igmp groups */
+    } /* scan igmp sockets */
+  } /* scan interfaces */
+}
+
+static void igmp_show_source_retransmission(struct vty *vty)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+  time_t            now;
+
+  now = pim_time_monotonic_sec();
+
+  vty_out(vty, "Interface Address         Group           Source          Counter%s", VTY_NEWLINE);
+
+  /* scan interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp = ifp->info;
+    struct listnode  *sock_node;
+    struct igmp_sock *igmp;
+    
+    if (!pim_ifp)
+      continue;
+    
+    /* scan igmp sockets */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+      char ifaddr_str[100];
+      struct listnode   *grpnode;
+      struct igmp_group *grp;
+
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+      /* scan igmp groups */
+      for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+	char group_str[100];
+	struct listnode    *srcnode;
+	struct igmp_source *src;
+
+	pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+	
+	/* scan group sources */
+	for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) {
+	  char source_str[100];
+
+	  pim_inet4_dump("<source?>", src->source_addr, source_str, sizeof(source_str));
+
+	  vty_out(vty, "%-9s %-15s %-15s %-15s %7d%s",
+		  ifp->name,
+		  ifaddr_str,
+		  group_str,
+		  source_str,
+		  src->source_query_retransmit_count,
+		  VTY_NEWLINE);
+	  
+	} /* scan group sources */
+      } /* scan igmp groups */
+    } /* scan igmp sockets */
+  } /* scan interfaces */
+}
+
+static void clear_igmp_interfaces()
+{
+  struct listnode  *ifnode;
+  struct listnode  *ifnextnode;
+  struct interface *ifp;
+
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_if_addr_del_all_igmp(ifp);
+  }
+
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_if_addr_add_all(ifp);
+  }
+}
+
+static void clear_pim_interfaces()
+{
+  struct listnode  *ifnode;
+  struct listnode  *ifnextnode;
+  struct interface *ifp;
+
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    if (ifp->info) {
+      pim_neighbor_delete_all(ifp, "interface cleared");
+    }
+  }
+}
+
+static void clear_interfaces()
+{
+  clear_igmp_interfaces();
+  clear_pim_interfaces();
+}
+
+DEFUN (pim_interface,
+       pim_interface_cmd,
+       "interface IFNAME",
+       "Select an interface to configure\n"
+       "Interface's name\n")
+{
+  struct interface *ifp;
+  const char *ifname = argv[0];
+  size_t sl;
+
+  sl = strlen(ifname);
+  if (sl > INTERFACE_NAMSIZ) {
+    vty_out(vty, "%% Interface name %s is invalid: length exceeds "
+	    "%d characters%s",
+	    ifname, INTERFACE_NAMSIZ, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  ifp = if_lookup_by_name_len(ifname, sl);
+  if (!ifp) {
+    vty_out(vty, "%% Interface %s does not exist%s", ifname, VTY_NEWLINE);
+
+    /* Returning here would prevent pimd from booting when there are
+       interface commands in pimd.conf, since all interfaces are
+       unknown at pimd boot time (the zebra daemon has not been
+       contacted for interface discovery). */
+    
+    ifp = if_get_by_name_len(ifname, sl);
+    if (!ifp) {
+      vty_out(vty, "%% Could not create interface %s%s", ifname, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+  }
+
+  vty->index = ifp;
+  vty->node = INTERFACE_NODE;
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (clear_ip_interfaces,
+       clear_ip_interfaces_cmd,
+       "clear ip interfaces",
+       CLEAR_STR
+       IP_STR
+       "Reset interfaces\n")
+{
+  clear_interfaces();
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (clear_ip_igmp_interfaces,
+       clear_ip_igmp_interfaces_cmd,
+       "clear ip igmp interfaces",
+       CLEAR_STR
+       IP_STR
+       CLEAR_IP_IGMP_STR
+       "Reset IGMP interfaces\n")
+{
+  clear_igmp_interfaces();
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (clear_ip_pim_interfaces,
+       clear_ip_pim_interfaces_cmd,
+       "clear ip pim interfaces",
+       CLEAR_STR
+       IP_STR
+       CLEAR_IP_PIM_STR
+       "Reset PIM interfaces\n")
+{
+  clear_pim_interfaces();
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_interface,
+       show_ip_igmp_interface_cmd,
+       "show ip igmp interface",
+       SHOW_STR
+       IP_STR
+       IGMP_STR
+       "IGMP interface information\n")
+{
+  igmp_show_interfaces(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_groups,
+       show_ip_igmp_groups_cmd,
+       "show ip igmp groups",
+       SHOW_STR
+       IP_STR
+       IGMP_STR
+       IGMP_GROUP_STR)
+{
+  igmp_show_groups(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_groups_retransmissions,
+       show_ip_igmp_groups_retransmissions_cmd,
+       "show ip igmp groups retransmissions",
+       SHOW_STR
+       IP_STR
+       IGMP_STR
+       IGMP_GROUP_STR
+       "IGMP group retransmissions\n")
+{
+  igmp_show_group_retransmission(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_parameters,
+       show_ip_igmp_parameters_cmd,
+       "show ip igmp parameters",
+       SHOW_STR
+       IP_STR
+       IGMP_STR
+       "IGMP parameters information\n")
+{
+  igmp_show_parameters(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_sources,
+       show_ip_igmp_sources_cmd,
+       "show ip igmp sources",
+       SHOW_STR
+       IP_STR
+       IGMP_STR
+       IGMP_SOURCE_STR)
+{
+  igmp_show_sources(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_sources_retransmissions,
+       show_ip_igmp_sources_retransmissions_cmd,
+       "show ip igmp sources retransmissions",
+       SHOW_STR
+       IP_STR
+       IGMP_STR
+       IGMP_SOURCE_STR
+       "IGMP source retransmissions\n")
+{
+  igmp_show_source_retransmission(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_querier,
+       show_ip_igmp_querier_cmd,
+       "show ip igmp querier",
+       SHOW_STR
+       IP_STR
+       IGMP_STR
+       "IGMP querier information\n")
+{
+  igmp_show_querier(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_assert,
+       show_ip_pim_assert_cmd,
+       "show ip pim assert",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface assert\n")
+{
+  pim_show_assert(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_assert_metric,
+       show_ip_pim_assert_metric_cmd,
+       "show ip pim assert-metric",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface assert metric\n")
+{
+  pim_show_assert_metric(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_assert_winner_metric,
+       show_ip_pim_assert_winner_metric_cmd,
+       "show ip pim assert-winner-metric",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface assert winner metric\n")
+{
+  pim_show_assert_winner_metric(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_dr,
+       show_ip_pim_dr_cmd,
+       "show ip pim designated-router",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface designated router\n")
+{
+  pim_show_dr(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_hello,
+       show_ip_pim_hello_cmd,
+       "show ip pim hello",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface hello information\n")
+{
+  pim_show_hello(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_interface,
+       show_ip_pim_interface_cmd,
+       "show ip pim interface",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface information\n")
+{
+  pim_show_interfaces(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_join,
+       show_ip_pim_join_cmd,
+       "show ip pim join",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface join information\n")
+{
+  pim_show_join(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_lan_prune_delay,
+       show_ip_pim_lan_prune_delay_cmd,
+       "show ip pim lan-prune-delay",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM neighbors LAN prune delay parameters\n")
+{
+  pim_show_lan_prune_delay(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_local_membership,
+       show_ip_pim_local_membership_cmd,
+       "show ip pim local-membership",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface local-membership\n")
+{
+  pim_show_membership(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_jp_override_interval,
+       show_ip_pim_jp_override_interval_cmd,
+       "show ip pim jp-override-interval",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM interface J/P override interval\n")
+{
+  pim_show_jp_override_interval(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_neighbor,
+       show_ip_pim_neighbor_cmd,
+       "show ip pim neighbor",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM neighbor information\n")
+{
+  pim_show_neighbors(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_secondary,
+       show_ip_pim_secondary_cmd,
+       "show ip pim secondary",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM neighbor addresses\n")
+{
+  pim_show_neighbors_secondary(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_upstream,
+       show_ip_pim_upstream_cmd,
+       "show ip pim upstream",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM upstream information\n")
+{
+  pim_show_upstream(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_upstream_join_desired,
+       show_ip_pim_upstream_join_desired_cmd,
+       "show ip pim upstream-join-desired",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM upstream join-desired\n")
+{
+  pim_show_join_desired(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_upstream_rpf,
+       show_ip_pim_upstream_rpf_cmd,
+       "show ip pim upstream-rpf",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM upstream source rpf\n")
+{
+  pim_show_upstream_rpf(vty);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_rpf,
+       show_ip_pim_rpf_cmd,
+       "show ip pim rpf",
+       SHOW_STR
+       IP_STR
+       PIM_STR
+       "PIM cached source rpf information\n")
+{
+  pim_show_rpf(vty);
+
+  return CMD_SUCCESS;
+}
+
+static void show_multicast_interfaces(struct vty *vty)
+{
+  struct listnode  *node;
+  struct interface *ifp;
+
+  vty_out(vty, "%s", VTY_NEWLINE);
+  
+  vty_out(vty, "Interface Address         ifIndex VifIndex PktsIn PktsOut BytesIn BytesOut%s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct in_addr ifaddr;
+    struct sioc_vif_req vreq;
+
+    pim_ifp = ifp->info;
+    
+    if (!pim_ifp)
+      continue;
+
+    memset(&vreq, 0, sizeof(vreq));
+    vreq.vifi = pim_ifp->mroute_vif_index;
+
+    if (ioctl(qpim_mroute_socket_fd, SIOCGETVIFCNT, &vreq)) {
+      int e = errno;
+      vty_out(vty,
+	      "ioctl(SIOCGETVIFCNT=%d) failure for interface %s vif_index=%d: errno=%d: %s%s",
+	      SIOCGETVIFCNT,
+	      ifp->name,
+	      pim_ifp->mroute_vif_index,
+	      e,
+	      strerror(e),
+	      VTY_NEWLINE);	      
+      continue;
+    }
+
+    ifaddr = pim_ifp->primary_address;
+
+    vty_out(vty, "%-9s %-15s %7d %8d %6lu %7lu %7lu %8lu%s",
+	    ifp->name,
+	    inet_ntoa(ifaddr),
+	    ifp->ifindex,
+	    pim_ifp->mroute_vif_index,
+	    vreq.icount,
+	    vreq.ocount,
+	    vreq.ibytes,
+	    vreq.obytes,
+	    VTY_NEWLINE);
+  }
+}
+
+DEFUN (show_ip_multicast,
+       show_ip_multicast_cmd,
+       "show ip multicast",
+       SHOW_STR
+       IP_STR
+       "Multicast global information\n")
+{
+  if (PIM_MROUTE_IS_ENABLED) {
+    time_t now;
+    char uptime[10];
+
+    vty_out(vty, "Mroute socket descriptor: %d%s",
+	    qpim_mroute_socket_fd,
+	    VTY_NEWLINE);
+
+    now = pim_time_monotonic_sec();
+    pim_time_uptime(uptime, sizeof(uptime), now - qpim_mroute_socket_creation);
+    vty_out(vty, "Mroute socket uptime: %s%s",
+	    uptime,
+	    VTY_NEWLINE);
+  }
+  else {
+    vty_out(vty, "Multicast disabled%s",
+	    VTY_NEWLINE);
+  }
+
+  vty_out(vty, "%s", VTY_NEWLINE);
+  vty_out(vty, "Current highest VifIndex: %d%s",
+	  qpim_mroute_oif_highest_vif_index,
+	  VTY_NEWLINE);
+  vty_out(vty, "Maximum highest VifIndex: %d%s",
+	  MAXVIFS - 1,
+	  VTY_NEWLINE);
+
+  vty_out(vty, "%s", VTY_NEWLINE);
+  vty_out(vty, "Upstream Join Timer: %d secs%s",
+	  qpim_t_periodic,
+	  VTY_NEWLINE);
+  vty_out(vty, "Join/Prune Holdtime: %d secs%s",
+	  PIM_JP_HOLDTIME,
+	  VTY_NEWLINE);
+
+  vty_out(vty, "%s", VTY_NEWLINE);
+  vty_out(vty, "RPF Cache Refresh Delay: %ld msecs%s",
+	  qpim_rpf_cache_refresh_delay_msec,
+	  VTY_NEWLINE);
+  vty_out(vty, "RPF Cache Refresh Timer: %ld msecs%s",
+	  pim_time_timer_remain_msec(qpim_rpf_cache_refresher),
+	  VTY_NEWLINE);
+
+  show_multicast_interfaces(vty);
+  
+  return CMD_SUCCESS;
+}
+
+static void show_mroute(struct vty *vty)
+{
+  struct listnode    *node;
+  struct channel_oil *c_oil;
+  time_t              now;
+
+  vty_out(vty, "Proto: I=IGMP P=PIM%s%s", VTY_NEWLINE, VTY_NEWLINE);
+  
+  vty_out(vty, "Source          Group           Proto Input iVifI Output oVifI TTL Uptime  %s",
+	  VTY_NEWLINE);
+
+  now = pim_time_monotonic_sec();
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+    char group_str[100]; 
+    char source_str[100];
+    int oif_vif_index;
+
+    pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    
+    for (oif_vif_index = 0; oif_vif_index < MAXVIFS; ++oif_vif_index) {
+      struct interface *ifp_in;
+      struct interface *ifp_out;
+      char oif_uptime[10];
+      int ttl;
+      char proto[5];
+
+      ttl = c_oil->oil.mfcc_ttls[oif_vif_index];
+      if (ttl < 1)
+	continue;
+
+      ifp_in  = pim_if_find_by_vif_index(c_oil->oil.mfcc_parent);
+      ifp_out = pim_if_find_by_vif_index(oif_vif_index);
+
+      pim_time_uptime(oif_uptime, sizeof(oif_uptime), now - c_oil->oif_creation[oif_vif_index]);
+
+      proto[0] = '\0';
+      if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_PIM) {
+	strcat(proto, "P");
+      }
+      if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_IGMP) {
+	strcat(proto, "I");
+      }
+
+      vty_out(vty, "%-15s %-15s %-5s %-5s %5d %-6s %5d %3d %8s %s",
+	      source_str,
+	      group_str,
+	      proto,
+	      ifp_in ? ifp_in->name : "<iif?>",
+	      c_oil->oil.mfcc_parent,
+	      ifp_out ? ifp_out->name : "<oif?>",
+	      oif_vif_index,
+	      ttl,
+	      oif_uptime,
+	      VTY_NEWLINE);
+    }
+  }
+}
+
+DEFUN (show_ip_mroute,
+       show_ip_mroute_cmd,
+       "show ip mroute",
+       SHOW_STR
+       IP_STR
+       MROUTE_STR)
+{
+  show_mroute(vty);
+  return CMD_SUCCESS;
+}
+
+static void show_mroute_count(struct vty *vty)
+{
+  struct listnode    *node;
+  struct channel_oil *c_oil;
+
+  vty_out(vty, "%s", VTY_NEWLINE);
+  
+  vty_out(vty, "Source          Group           Packets      Bytes WrongIf  %s",
+	  VTY_NEWLINE);
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+    char group_str[100]; 
+    char source_str[100];
+    struct sioc_sg_req sgreq;
+
+    memset(&sgreq, 0, sizeof(sgreq));
+    sgreq.src = c_oil->oil.mfcc_origin;
+    sgreq.grp = c_oil->oil.mfcc_mcastgrp;
+
+    pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+
+    if (ioctl(qpim_mroute_socket_fd, SIOCGETSGCNT, &sgreq)) {
+      int e = errno;
+      vty_out(vty,
+	      "ioctl(SIOCGETSGCNT=%d) failure for (S,G)=(%s,%s): errno=%d: %s%s",
+	      SIOCGETSGCNT,
+	      source_str,
+	      group_str,
+	      e,
+	      strerror(e),
+	      VTY_NEWLINE);	      
+      continue;
+    }
+    
+    vty_out(vty, "%-15s %-15s %7ld %10ld %7ld %s",
+	    source_str,
+	    group_str,
+	    sgreq.pktcnt,
+	    sgreq.bytecnt,
+	    sgreq.wrong_if,
+	    VTY_NEWLINE);
+
+  }
+}
+
+DEFUN (show_ip_mroute_count,
+       show_ip_mroute_count_cmd,
+       "show ip mroute count",
+       SHOW_STR
+       IP_STR
+       MROUTE_STR
+       "Route and packet count data\n")
+{
+  show_mroute_count(vty);
+  return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_route,
+       show_ip_route_cmd,
+       "show ip route A.B.C.D",
+       SHOW_STR
+       IP_STR
+       ROUTE_STR
+       "Unicast address\n")
+{
+  struct in_addr addr;
+  const char *addr_str;
+  struct pim_nexthop nexthop;
+  char nexthop_addr_str[100];
+  int result;
+
+  addr_str = argv[0];
+  result = inet_pton(AF_INET, addr_str, &addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad unicast address %s: errno=%d: %s%s",
+	    addr_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  if (pim_nexthop_lookup(&nexthop, addr)) {
+    vty_out(vty, "Failure querying RIB nexthop for unicast address %s%s",
+	    addr_str, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  vty_out(vty, "Address         NextHop         Interface Metric Preference%s",
+	  VTY_NEWLINE);
+
+  pim_inet4_dump("<nexthop?>", nexthop.mrib_nexthop_addr,
+		 nexthop_addr_str, sizeof(nexthop_addr_str));
+
+  vty_out(vty, "%-15s %-15s %-9s %6d %10d%s",
+	  addr_str,
+	  nexthop_addr_str,
+	  nexthop.interface ? nexthop.interface->name : "<ifname?>",
+	  nexthop.mrib_metric_preference,
+	  nexthop.mrib_route_metric,
+	  VTY_NEWLINE);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (ip_multicast_routing,
+       ip_multicast_routing_cmd,
+       PIM_CMD_IP_MULTICAST_ROUTING,
+       IP_STR
+       "Enable IP multicast forwarding\n")
+{
+  pim_mroute_socket_enable();
+  pim_if_add_vif_all();
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_ip_multicast_routing,
+       no_ip_multicast_routing_cmd,
+       PIM_CMD_NO " " PIM_CMD_IP_MULTICAST_ROUTING,
+       NO_STR
+       IP_STR
+       "Global IP configuration subcommands\n"
+       "Enable IP multicast forwarding\n")
+{
+  pim_if_del_vif_all();
+  pim_mroute_socket_disable();
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_ip_igmp,
+       interface_ip_igmp_cmd,
+       "ip igmp",
+       IP_STR
+       IFACE_IGMP_STR)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    pim_ifp = pim_if_new(ifp, 1 /* igmp=true */, 0 /* pim=false */);
+    if (!pim_ifp) {
+      vty_out(vty, "Could not enable IGMP on interface %s%s",
+	      ifp->name, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+  }
+  else {
+    PIM_IF_DO_IGMP(pim_ifp->options);
+  }
+
+  pim_if_addr_add_all(ifp);
+  pim_if_membership_refresh(ifp);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp,
+       interface_no_ip_igmp_cmd,
+       "no ip igmp",
+       NO_STR
+       IP_STR
+       IFACE_IGMP_STR)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return CMD_SUCCESS;
+
+  PIM_IF_DONT_IGMP(pim_ifp->options);
+
+  pim_if_membership_clear(ifp);
+
+  pim_if_addr_del_all(ifp);
+
+  if (!PIM_IF_TEST_PIM(pim_ifp->options)) {
+    pim_if_delete(ifp);
+  }
+
+  return CMD_SUCCESS;
+}
+
+/*
+  CLI reconfiguration affects the interface level (struct pim_interface).
+  This function propagates the reconfiguration to every active socket
+  for that interface.
+ */
+static void igmp_sock_query_interval_reconfig(struct igmp_sock *igmp)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+
+  zassert(igmp);
+
+  /* other querier present? */
+
+  if (igmp->t_other_querier_timer)
+    return;
+
+  /* this is the querier */
+
+  zassert(igmp->interface);
+  zassert(igmp->interface->info);
+
+  ifp = igmp->interface;
+  pim_ifp = ifp->info;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char ifaddr_str[100];
+    pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+    zlog_debug("%s: Querier %s on %s reconfig query_interval=%d",
+	       __PRETTY_FUNCTION__,
+	       ifaddr_str,
+	       ifp->name,
+	       pim_ifp->igmp_default_query_interval);
+  }
+
+  /*
+    igmp_startup_mode_on() will reset QQI:
+
+    igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
+  */
+  igmp_startup_mode_on(igmp);
+}
+
+static void igmp_sock_query_reschedule(struct igmp_sock *igmp)
+{
+  if (igmp->t_igmp_query_timer) {
+    /* other querier present */
+    zassert(igmp->t_igmp_query_timer);
+    zassert(!igmp->t_other_querier_timer);
+
+    pim_igmp_general_query_off(igmp);
+    pim_igmp_general_query_on(igmp);
+
+    zassert(igmp->t_igmp_query_timer);
+    zassert(!igmp->t_other_querier_timer);
+  }
+  else {
+    /* this is the querier */
+
+    zassert(!igmp->t_igmp_query_timer);
+    zassert(igmp->t_other_querier_timer);
+
+    pim_igmp_other_querier_timer_off(igmp);
+    pim_igmp_other_querier_timer_on(igmp);
+
+    zassert(!igmp->t_igmp_query_timer);
+    zassert(igmp->t_other_querier_timer);
+  }
+}
+
+static void change_query_interval(struct pim_interface *pim_ifp,
+				  int query_interval)
+{
+  struct listnode  *sock_node;
+  struct igmp_sock *igmp;
+
+  pim_ifp->igmp_default_query_interval = query_interval;
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+    igmp_sock_query_interval_reconfig(igmp);
+    igmp_sock_query_reschedule(igmp);
+  }
+}
+
+static void change_query_max_response_time(struct pim_interface *pim_ifp,
+					   int query_max_response_time_dsec)
+{
+  struct listnode  *sock_node;
+  struct igmp_sock *igmp;
+
+  pim_ifp->igmp_query_max_response_time_dsec = query_max_response_time_dsec;
+
+  /*
+    Below we modify socket/group/source timers in order to quickly
+    reflect the change. Otherwise, those timers would eventually catch
+    up.
+   */
+
+  /* scan all sockets */
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+    struct listnode   *grp_node;
+    struct igmp_group *grp;
+
+    /* reschedule socket general query */
+    igmp_sock_query_reschedule(igmp);
+
+    /* scan socket groups */
+    for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grp_node, grp)) {
+      struct listnode    *src_node;
+      struct igmp_source *src;
+
+      /* reset group timers for groups in EXCLUDE mode */
+      if (grp->group_filtermode_isexcl) {
+	igmp_group_reset_gmi(grp);
+      }
+
+      /* scan group sources */
+      for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, src_node, src)) {
+
+	/* reset source timers for sources with running timers */
+	if (src->t_source_timer) {
+	  igmp_source_reset_gmi(igmp, grp, src);
+	}
+      }
+    }
+  }
+}
+
+#define IGMP_QUERY_INTERVAL_MIN (1)
+#define IGMP_QUERY_INTERVAL_MAX (1800)
+
+DEFUN (interface_ip_igmp_query_interval,
+       interface_ip_igmp_query_interval_cmd,
+       PIM_CMD_IP_IGMP_QUERY_INTERVAL " <1-1800>",
+       IP_STR
+       IFACE_IGMP_STR
+       IFACE_IGMP_QUERY_INTERVAL_STR
+       "Query interval in seconds\n")
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  int query_interval;
+  int query_interval_dsec;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    vty_out(vty,
+	    "IGMP not enabled on interface %s. Please enable IGMP first.%s",
+	    ifp->name,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  query_interval = atoi(argv[0]);
+  query_interval_dsec = 10 * query_interval;
+
+  /*
+    It seems we don't need to check bounds since command.c does it
+    already, but we verify them anyway for extra safety.
+  */
+  if (query_interval < IGMP_QUERY_INTERVAL_MIN) {
+    vty_out(vty, "General query interval %d lower than minimum %d%s",
+	    query_interval,
+	    IGMP_QUERY_INTERVAL_MIN,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+  if (query_interval > IGMP_QUERY_INTERVAL_MAX) {
+    vty_out(vty, "General query interval %d higher than maximum %d%s",
+	    query_interval,
+	    IGMP_QUERY_INTERVAL_MAX,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  if (query_interval_dsec <= pim_ifp->igmp_query_max_response_time_dsec) {
+    vty_out(vty,
+	    "Can't set general query interval %d dsec <= query max response time %d dsec.%s",
+	    query_interval_dsec, pim_ifp->igmp_query_max_response_time_dsec,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  change_query_interval(pim_ifp, query_interval);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_query_interval,
+       interface_no_ip_igmp_query_interval_cmd,
+       PIM_CMD_NO " " PIM_CMD_IP_IGMP_QUERY_INTERVAL,
+       NO_STR
+       IP_STR
+       IFACE_IGMP_STR
+       IFACE_IGMP_QUERY_INTERVAL_STR)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  int default_query_interval_dsec;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp)
+    return CMD_SUCCESS;
+
+  default_query_interval_dsec = IGMP_GENERAL_QUERY_INTERVAL * 10;
+
+  if (default_query_interval_dsec <= pim_ifp->igmp_query_max_response_time_dsec) {
+    vty_out(vty,
+	    "Can't set default general query interval %d dsec <= query max response time %d dsec.%s",
+	    default_query_interval_dsec, pim_ifp->igmp_query_max_response_time_dsec,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  change_query_interval(pim_ifp, IGMP_GENERAL_QUERY_INTERVAL);
+
+  return CMD_SUCCESS;
+}
+
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MIN (1)
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MAX (25)
+
+DEFUN (interface_ip_igmp_query_max_response_time,
+       interface_ip_igmp_query_max_response_time_cmd,
+       PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME " <1-25>",
+       IP_STR
+       IFACE_IGMP_STR
+       IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR
+       "Query response value in seconds\n")
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  int query_max_response_time;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    vty_out(vty,
+	    "IGMP not enabled on interface %s. Please enable IGMP first.%s",
+	    ifp->name,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  query_max_response_time = atoi(argv[0]);
+
+  /*
+    It seems we don't need to check bounds since command.c does it
+    already, but we verify them anyway for extra safety.
+  */
+  if (query_max_response_time < IGMP_QUERY_MAX_RESPONSE_TIME_MIN) {
+    vty_out(vty, "Query max response time %d sec lower than minimum %d sec%s",
+	    query_max_response_time,
+	    IGMP_QUERY_MAX_RESPONSE_TIME_MIN,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+  if (query_max_response_time > IGMP_QUERY_MAX_RESPONSE_TIME_MAX) {
+    vty_out(vty, "Query max response time %d sec higher than maximum %d sec%s",
+	    query_max_response_time,
+	    IGMP_QUERY_MAX_RESPONSE_TIME_MAX,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  if (query_max_response_time >= pim_ifp->igmp_default_query_interval) {
+    vty_out(vty,
+	    "Can't set query max response time %d sec >= general query interval %d sec%s",
+	    query_max_response_time, pim_ifp->igmp_default_query_interval,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  change_query_max_response_time(pim_ifp, 10 * query_max_response_time);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_query_max_response_time,
+       interface_no_ip_igmp_query_max_response_time_cmd,
+       PIM_CMD_NO " " PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME,
+       NO_STR
+       IP_STR
+       IFACE_IGMP_STR
+       IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  int default_query_interval_dsec;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp)
+    return CMD_SUCCESS;
+
+  default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval;
+
+  if (IGMP_QUERY_MAX_RESPONSE_TIME_DSEC >= default_query_interval_dsec) {
+    vty_out(vty,
+	    "Can't set default query max response time %d dsec >= general query interval %d dsec.%s",
+	    IGMP_QUERY_MAX_RESPONSE_TIME_DSEC, default_query_interval_dsec,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  change_query_max_response_time(pim_ifp, IGMP_QUERY_MAX_RESPONSE_TIME_DSEC);
+
+  return CMD_SUCCESS;
+}
+
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC (10)
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC (250)
+
+DEFUN (interface_ip_igmp_query_max_response_time_dsec,
+       interface_ip_igmp_query_max_response_time_dsec_cmd,
+       PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC " <10-250>",
+       IP_STR
+       IFACE_IGMP_STR
+       IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR
+       "Query response value in deciseconds\n")
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  int query_max_response_time_dsec;
+  int default_query_interval_dsec;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    vty_out(vty,
+	    "IGMP not enabled on interface %s. Please enable IGMP first.%s",
+	    ifp->name,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  query_max_response_time_dsec = atoi(argv[0]);
+
+  /*
+    It seems we don't need to check bounds since command.c does it
+    already, but we verify them anyway for extra safety.
+  */
+  if (query_max_response_time_dsec < IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC) {
+    vty_out(vty, "Query max response time %d dsec lower than minimum %d dsec%s",
+	    query_max_response_time_dsec,
+	    IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+  if (query_max_response_time_dsec > IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC) {
+    vty_out(vty, "Query max response time %d dsec higher than maximum %d dsec%s",
+	    query_max_response_time_dsec,
+	    IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval;
+
+  if (query_max_response_time_dsec >= default_query_interval_dsec) {
+    vty_out(vty,
+	    "Can't set query max response time %d dsec >= general query interval %d dsec%s",
+	    query_max_response_time_dsec, default_query_interval_dsec,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  change_query_max_response_time(pim_ifp, query_max_response_time_dsec);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_query_max_response_time_dsec,
+       interface_no_ip_igmp_query_max_response_time_dsec_cmd,
+       PIM_CMD_NO " " PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC,
+       NO_STR
+       IP_STR
+       IFACE_IGMP_STR
+       IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  int default_query_interval_dsec;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp)
+    return CMD_SUCCESS;
+
+  default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval;
+
+  if (IGMP_QUERY_MAX_RESPONSE_TIME_DSEC >= default_query_interval_dsec) {
+    vty_out(vty,
+	    "Can't set default query max response time %d dsec >= general query interval %d dsec.%s",
+	    IGMP_QUERY_MAX_RESPONSE_TIME_DSEC, default_query_interval_dsec,
+	    VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  change_query_max_response_time(pim_ifp, IGMP_QUERY_MAX_RESPONSE_TIME_DSEC);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_ip_pim_ssm,
+       interface_ip_pim_ssm_cmd,
+       "ip pim ssm",
+       IP_STR
+       PIM_STR
+       IFACE_PIM_STR)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    pim_ifp = pim_if_new(ifp, 0 /* igmp=false */, 1 /* pim=true */);
+    if (!pim_ifp) {
+      vty_out(vty, "Could not enable PIM on interface%s", VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+  }
+  else {
+    PIM_IF_DO_PIM(pim_ifp->options);
+  }
+
+  pim_if_addr_add_all(ifp);
+  pim_if_membership_refresh(ifp);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_pim_ssm,
+       interface_no_ip_pim_ssm_cmd,
+       "no ip pim ssm",
+       NO_STR
+       IP_STR
+       PIM_STR
+       IFACE_PIM_STR)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+
+  ifp = vty->index;
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return CMD_SUCCESS;
+
+  PIM_IF_DONT_PIM(pim_ifp->options);
+
+  pim_if_membership_clear(ifp);
+
+  /*
+    pim_if_addr_del_all() removes all sockets from
+    pim_ifp->igmp_socket_list.
+   */
+  pim_if_addr_del_all(ifp);
+
+  /*
+    pim_sock_delete() removes all neighbors from
+    pim_ifp->pim_neighbor_list.
+   */
+  pim_sock_delete(ifp, "pim unconfigured on interface");
+
+  if (!PIM_IF_TEST_IGMP(pim_ifp->options)) {
+    pim_if_delete(ifp);
+  }
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (debug_igmp,
+       debug_igmp_cmd,
+       "debug igmp",
+       DEBUG_STR
+       DEBUG_IGMP_STR)
+{
+  PIM_DO_DEBUG_IGMP_EVENTS;
+  PIM_DO_DEBUG_IGMP_PACKETS;
+  PIM_DO_DEBUG_IGMP_TRACE;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp,
+       no_debug_igmp_cmd,
+       "no debug igmp",
+       NO_STR
+       DEBUG_STR
+       DEBUG_IGMP_STR)
+{
+  PIM_DONT_DEBUG_IGMP_EVENTS;
+  PIM_DONT_DEBUG_IGMP_PACKETS;
+  PIM_DONT_DEBUG_IGMP_TRACE;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp,
+       undebug_igmp_cmd,
+       "undebug igmp",
+       UNDEBUG_STR
+       DEBUG_IGMP_STR)
+
+DEFUN (debug_igmp_events,
+       debug_igmp_events_cmd,
+       "debug igmp events",
+       DEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_EVENTS_STR)
+{
+  PIM_DO_DEBUG_IGMP_EVENTS;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp_events,
+       no_debug_igmp_events_cmd,
+       "no debug igmp events",
+       NO_STR
+       DEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_EVENTS_STR)
+{
+  PIM_DONT_DEBUG_IGMP_EVENTS;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp_events,
+       undebug_igmp_events_cmd,
+       "undebug igmp events",
+       UNDEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_EVENTS_STR)
+
+DEFUN (debug_igmp_packets,
+       debug_igmp_packets_cmd,
+       "debug igmp packets",
+       DEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_PACKETS_STR)
+{
+  PIM_DO_DEBUG_IGMP_PACKETS;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp_packets,
+       no_debug_igmp_packets_cmd,
+       "no debug igmp packets",
+       NO_STR
+       DEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_PACKETS_STR)
+{
+  PIM_DONT_DEBUG_IGMP_PACKETS;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp_packets,
+       undebug_igmp_packets_cmd,
+       "undebug igmp packets",
+       UNDEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_PACKETS_STR)
+
+DEFUN (debug_igmp_trace,
+       debug_igmp_trace_cmd,
+       "debug igmp trace",
+       DEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_TRACE_STR)
+{
+  PIM_DO_DEBUG_IGMP_TRACE;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp_trace,
+       no_debug_igmp_trace_cmd,
+       "no debug igmp trace",
+       NO_STR
+       DEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_TRACE_STR)
+{
+  PIM_DONT_DEBUG_IGMP_TRACE;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp_trace,
+       undebug_igmp_trace_cmd,
+       "undebug igmp trace",
+       UNDEBUG_STR
+       DEBUG_IGMP_STR
+       DEBUG_IGMP_TRACE_STR)
+
+DEFUN (debug_pim,
+       debug_pim_cmd,
+       "debug pim",
+       DEBUG_STR
+       DEBUG_PIM_STR)
+{
+  PIM_DO_DEBUG_PIM_EVENTS;
+  PIM_DO_DEBUG_PIM_PACKETS;
+  PIM_DO_DEBUG_PIM_TRACE;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim,
+       no_debug_pim_cmd,
+       "no debug pim",
+       NO_STR
+       DEBUG_STR
+       DEBUG_PIM_STR)
+{
+  PIM_DONT_DEBUG_PIM_EVENTS;
+  PIM_DONT_DEBUG_PIM_PACKETS;
+  PIM_DONT_DEBUG_PIM_TRACE;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim,
+       undebug_pim_cmd,
+       "undebug pim",
+       UNDEBUG_STR
+       DEBUG_PIM_STR)
+
+DEFUN (debug_pim_events,
+       debug_pim_events_cmd,
+       "debug pim events",
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_EVENTS_STR)
+{
+  PIM_DO_DEBUG_PIM_EVENTS;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_events,
+       no_debug_pim_events_cmd,
+       "no debug pim events",
+       NO_STR
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_EVENTS_STR)
+{
+  PIM_DONT_DEBUG_PIM_EVENTS;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_events,
+       undebug_pim_events_cmd,
+       "undebug pim events",
+       UNDEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_EVENTS_STR)
+
+DEFUN (debug_pim_packets,
+       debug_pim_packets_cmd,
+       "debug pim packets",
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_PACKETS_STR)
+{
+  PIM_DO_DEBUG_PIM_PACKETS;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_packets,
+       no_debug_pim_packets_cmd,
+       "no debug pim packets",
+       NO_STR
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_PACKETS_STR)
+{
+  PIM_DONT_DEBUG_PIM_PACKETS;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_packets,
+       undebug_pim_packets_cmd,
+       "undebug pim packets",
+       UNDEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_PACKETS_STR)
+
+DEFUN (debug_pim_trace,
+       debug_pim_trace_cmd,
+       "debug pim trace",
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_TRACE_STR)
+{
+  PIM_DO_DEBUG_PIM_TRACE;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_trace,
+       no_debug_pim_trace_cmd,
+       "no debug pim trace",
+       NO_STR
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_TRACE_STR)
+{
+  PIM_DONT_DEBUG_PIM_TRACE;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_trace,
+       undebug_pim_trace_cmd,
+       "undebug pim trace",
+       UNDEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_TRACE_STR)
+
+DEFUN (debug_pim_zebra,
+       debug_pim_zebra_cmd,
+       "debug pim zebra",
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_ZEBRA_STR)
+{
+  PIM_DO_DEBUG_ZEBRA;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_zebra,
+       no_debug_pim_zebra_cmd,
+       "no debug pim zebra",
+       NO_STR
+       DEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_ZEBRA_STR)
+{
+  PIM_DONT_DEBUG_ZEBRA;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_zebra,
+       undebug_pim_zebra_cmd,
+       "undebug pim zebra",
+       UNDEBUG_STR
+       DEBUG_PIM_STR
+       DEBUG_PIM_ZEBRA_STR)
+
+DEFUN (show_debugging,
+       show_debugging_cmd,
+       "show debugging",
+       SHOW_STR
+       "State of each debugging option\n")
+{
+  pim_debug_config_write(vty);
+  return CMD_SUCCESS;
+}
+
+static struct igmp_sock *find_igmp_sock_by_fd(int fd)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    struct pim_interface *pim_ifp;
+    struct igmp_sock     *igmp;
+    
+    if (!ifp->info)
+      continue;
+
+    pim_ifp = ifp->info;
+
+    /* lookup igmp socket under current interface */
+    igmp = igmp_sock_lookup_by_fd(pim_ifp->igmp_socket_list, fd);
+    if (igmp)
+      return igmp;
+  }
+
+  return 0;
+}
+
+DEFUN (test_igmp_receive_report,
+       test_igmp_receive_report_cmd,
+       "test igmp receive report <0-65535> A.B.C.D <1-6> .LINE",
+       "Test\n"
+       "Test IGMP protocol\n"
+       "Test IGMP message\n"
+       "Test IGMP report\n"
+       "Socket\n"
+       "IGMP group address\n"
+       "Record type\n"
+       "Sources\n")
+{
+  char              buf[1000];
+  char             *igmp_msg;
+  struct ip        *ip_hdr;
+  size_t            ip_hlen; /* ip header length in bytes */
+  int               ip_msg_len;
+  int               igmp_msg_len;
+  const char       *socket;
+  int               socket_fd;
+  const char       *grp_str;
+  struct in_addr    grp_addr;
+  const char       *record_type_str;
+  int               record_type;
+  const char       *src_str;
+  int               result;
+  struct igmp_sock *igmp;
+  char             *group_record;
+  int               num_sources;
+  struct in_addr   *sources;
+  struct in_addr   *src_addr;
+  int               argi;
+
+  socket = argv[0];
+  socket_fd = atoi(socket);
+  igmp = find_igmp_sock_by_fd(socket_fd);
+  if (!igmp) {
+    vty_out(vty, "Could not find IGMP socket %s: fd=%d%s",
+	    socket, socket_fd, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  grp_str = argv[1];
+  result = inet_pton(AF_INET, grp_str, &grp_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+	    grp_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  record_type_str = argv[2];
+  record_type = atoi(record_type_str);
+
+  /*
+    Tweak IP header
+   */
+  ip_hdr = (struct ip *) buf;
+  ip_hdr->ip_p = PIM_IP_PROTO_IGMP;
+  ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+  ip_hdr->ip_hl = ip_hlen >> 2;    /* ip header length in 4-byte words */
+  ip_hdr->ip_src = igmp->ifaddr;
+  ip_hdr->ip_dst = igmp->ifaddr;
+
+  /*
+    Build IGMP v3 report message
+   */
+  igmp_msg = buf + ip_hlen;
+  group_record = igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET;
+  *igmp_msg = PIM_IGMP_V3_MEMBERSHIP_REPORT; /* type */
+  *(uint16_t *)      (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0; /* for computing checksum */
+  *(uint16_t *)      (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET) = htons(1); /* one group record */
+  *(uint8_t  *)      (group_record + IGMP_V3_GROUP_RECORD_TYPE_OFFSET) = record_type;
+  *(struct in_addr *)(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET) = grp_addr;
+
+  /* Scan LINE sources */
+  sources = (struct in_addr *) (group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET);
+  src_addr = sources;
+  for (argi = 3; argi < argc; ++argi,++src_addr) {
+    src_str = argv[argi];
+    result = inet_pton(AF_INET, src_str, src_addr);
+    if (result <= 0) {
+      vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+	      src_str, errno, strerror(errno), VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+  }
+  num_sources = src_addr - sources;
+
+  *(uint16_t *)(group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET) = htons(num_sources);
+
+  igmp_msg_len = IGMP_V3_MSG_MIN_SIZE + (num_sources << 4);   /* v3 report for one single group record */
+
+  /* compute checksum */
+  *(uint16_t *)(igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = pim_inet_checksum(igmp_msg, igmp_msg_len);
+
+  /* "receive" message */
+
+  ip_msg_len = ip_hlen + igmp_msg_len;
+  result = pim_igmp_packet(igmp, buf, ip_msg_len);
+  if (result) {
+    vty_out(vty, "pim_igmp_packet(len=%d) returned: %d%s",
+	    ip_msg_len, result, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (test_pim_receive_hello,
+       test_pim_receive_hello_cmd,
+       "test pim receive hello INTERFACE A.B.C.D <0-65535> <0-65535> <0-65535> <0-32767> <0-65535> <0-1>[LINE]",
+       "Test\n"
+       "Test PIM protocol\n"
+       "Test PIM message reception\n"
+       "Test PIM hello reception from neighbor\n"
+       "Interface\n"
+       "Neighbor address\n"
+       "Neighbor holdtime\n"
+       "Neighbor DR priority\n"
+       "Neighbor generation ID\n"
+       "Neighbor propagation delay (msec)\n"
+       "Neighbor override interval (msec)\n"
+       "Neighbor LAN prune delay T-bit\n"
+       "Neighbor secondary addresses\n")
+{
+  char              buf[1000];
+  char             *pim_msg;
+  struct ip        *ip_hdr;
+  size_t            ip_hlen; /* ip header length in bytes */
+  int               ip_msg_len;
+  int               pim_tlv_size;
+  int               pim_msg_size;
+  const char       *neigh_str;
+  struct in_addr    neigh_addr;
+  const char       *ifname;
+  struct interface *ifp;
+  uint16_t          neigh_holdtime;
+  uint16_t          neigh_propagation_delay;
+  uint16_t          neigh_override_interval;
+  int               neigh_can_disable_join_suppression;
+  uint32_t          neigh_dr_priority;
+  uint32_t          neigh_generation_id;
+  int               argi;
+  int               result;
+
+  /* Find interface */
+  ifname = argv[0];
+  ifp = if_lookup_by_name(ifname);
+  if (!ifp) {
+    vty_out(vty, "No such interface name %s%s",
+	    ifname, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Neighbor address */
+  neigh_str = argv[1];
+  result = inet_pton(AF_INET, neigh_str, &neigh_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad neighbor address %s: errno=%d: %s%s",
+	    neigh_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  neigh_holdtime                     = atoi(argv[2]);
+  neigh_dr_priority                  = atoi(argv[3]);
+  neigh_generation_id                = atoi(argv[4]);
+  neigh_propagation_delay            = atoi(argv[5]);
+  neigh_override_interval            = atoi(argv[6]);
+  neigh_can_disable_join_suppression = atoi(argv[7]);
+
+  /*
+    Tweak IP header
+   */
+  ip_hdr = (struct ip *) buf;
+  ip_hdr->ip_p = PIM_IP_PROTO_PIM;
+  ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+  ip_hdr->ip_hl = ip_hlen >> 2;    /* ip header length in 4-byte words */
+  ip_hdr->ip_src = neigh_addr;
+  ip_hdr->ip_dst = qpim_all_pim_routers_addr;
+
+  /*
+    Build PIM hello message
+  */
+  pim_msg = buf + ip_hlen;
+
+  /* Scan LINE addresses */
+  for (argi = 8; argi < argc; ++argi) {
+    const char *sec_str = argv[argi];
+    struct in_addr sec_addr;
+    result = inet_pton(AF_INET, sec_str, &sec_addr);
+    if (result <= 0) {
+      vty_out(vty, "Bad neighbor secondary address %s: errno=%d: %s%s",
+	      sec_str, errno, strerror(errno), VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+    vty_out(vty,
+	    "FIXME WRITEME consider neighbor secondary address %s%s",
+	    sec_str, VTY_NEWLINE);
+  }
+
+  pim_tlv_size = pim_hello_build_tlv(ifp->name,
+				     pim_msg + PIM_PIM_MIN_LEN,
+				     sizeof(buf) - ip_hlen - PIM_PIM_MIN_LEN,
+				     neigh_holdtime,
+				     neigh_dr_priority,
+				     neigh_generation_id,
+				     neigh_propagation_delay,
+				     neigh_override_interval,
+				     neigh_can_disable_join_suppression,
+				     0 /* FIXME secondary address list */);
+  if (pim_tlv_size < 0) {
+    vty_out(vty, "pim_hello_build_tlv() returned failure: %d%s",
+	    pim_tlv_size, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  pim_msg_size = pim_tlv_size + PIM_PIM_MIN_LEN;
+
+  pim_msg_build_header(ifp->name,
+		       pim_msg, pim_msg_size,
+		       PIM_MSG_TYPE_HELLO);
+
+  /* "receive" message */
+
+  ip_msg_len = ip_hlen + pim_msg_size;
+  result = pim_pim_packet(ifp, buf, ip_msg_len);
+  if (result) {
+    vty_out(vty, "pim_pim_packet(len=%d) returned failure: %d%s",
+	    ip_msg_len, result, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (test_pim_receive_assert,
+       test_pim_receive_assert_cmd,
+       "test pim receive assert INTERFACE A.B.C.D A.B.C.D A.B.C.D <0-65535> <0-65535> <0-1>",
+       "Test\n"
+       "Test PIM protocol\n"
+       "Test PIM message reception\n"
+       "Test reception of PIM assert\n"
+       "Interface\n"
+       "Neighbor address\n"
+       "Assert multicast group address\n"
+       "Assert unicast source address\n"
+       "Assert metric preference\n"
+       "Assert route metric\n"
+       "Assert RPT bit flag\n")
+{
+  char              buf[1000];
+  char             *buf_pastend = buf + sizeof(buf);
+  char             *pim_msg;
+  struct ip        *ip_hdr;
+  size_t            ip_hlen; /* ip header length in bytes */
+  int               ip_msg_len;
+  int               pim_msg_size;
+  const char       *neigh_str;
+  struct in_addr    neigh_addr;
+  const char       *group_str;
+  struct in_addr    group_addr;
+  const char       *source_str;
+  struct in_addr    source_addr;
+  const char       *ifname;
+  struct interface *ifp;
+  uint32_t          assert_metric_preference;
+  uint32_t          assert_route_metric;
+  uint32_t          assert_rpt_bit_flag;
+  int               remain;
+  int               result;
+
+  /* Find interface */
+  ifname = argv[0];
+  ifp = if_lookup_by_name(ifname);
+  if (!ifp) {
+    vty_out(vty, "No such interface name %s%s",
+	    ifname, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Neighbor address */
+  neigh_str = argv[1];
+  result = inet_pton(AF_INET, neigh_str, &neigh_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad neighbor address %s: errno=%d: %s%s",
+	    neigh_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Group address */
+  group_str = argv[2];
+  result = inet_pton(AF_INET, group_str, &group_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+	    group_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Source address */
+  source_str = argv[3];
+  result = inet_pton(AF_INET, source_str, &source_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+	    source_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  assert_metric_preference = atoi(argv[4]);
+  assert_route_metric      = atoi(argv[5]);
+  assert_rpt_bit_flag      = atoi(argv[6]);
+
+  remain = buf_pastend - buf;
+  if (remain < (int) sizeof(struct ip)) {
+    vty_out(vty, "No room for ip header: buf_size=%d < ip_header_size=%d%s",
+	    remain, sizeof(struct ip), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /*
+    Tweak IP header
+   */
+  ip_hdr = (struct ip *) buf;
+  ip_hdr->ip_p = PIM_IP_PROTO_PIM;
+  ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+  ip_hdr->ip_hl = ip_hlen >> 2;    /* ip header length in 4-byte words */
+  ip_hdr->ip_src = neigh_addr;
+  ip_hdr->ip_dst = qpim_all_pim_routers_addr;
+
+  /*
+    Build PIM assert message
+  */
+  pim_msg = buf + ip_hlen; /* skip ip header */
+
+  pim_msg_size = pim_assert_build_msg(pim_msg, buf_pastend - pim_msg, ifp,
+				      group_addr, source_addr,
+				      assert_metric_preference,
+				      assert_route_metric,
+				      assert_rpt_bit_flag);
+  if (pim_msg_size < 0) {
+    vty_out(vty, "Failure building PIM assert message: size=%d%s",
+	    pim_msg_size, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* "receive" message */
+
+  ip_msg_len = ip_hlen + pim_msg_size;
+  result = pim_pim_packet(ifp, buf, ip_msg_len);
+  if (result) {
+    vty_out(vty, "pim_pim_packet(len=%d) returned failure: %d%s",
+	    ip_msg_len, result, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  return CMD_SUCCESS;
+}
+
+static int recv_joinprune(struct vty *vty,
+			  const char *argv[],
+			  int src_is_join)
+{
+  char              buf[1000];
+  const char       *buf_pastend = buf + sizeof(buf);
+  char             *pim_msg;
+  char             *pim_msg_curr;
+  int               pim_msg_size;
+  struct ip        *ip_hdr;
+  size_t            ip_hlen; /* ip header length in bytes */
+  int               ip_msg_len;
+  uint16_t          neigh_holdtime;
+  const char       *neigh_dst_str;
+  struct in_addr    neigh_dst_addr;
+  const char       *neigh_src_str;
+  struct in_addr    neigh_src_addr;
+  const char       *group_str;
+  struct in_addr    group_addr;
+  const char       *source_str;
+  struct in_addr    source_addr;
+  const char       *ifname;
+  struct interface *ifp;
+  int               result;
+  int               remain;
+  uint16_t          num_joined;
+  uint16_t          num_pruned;
+
+  /* Find interface */
+  ifname = argv[0];
+  ifp = if_lookup_by_name(ifname);
+  if (!ifp) {
+    vty_out(vty, "No such interface name %s%s",
+	    ifname, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  neigh_holdtime = atoi(argv[1]);
+
+  /* Neighbor destination address */
+  neigh_dst_str = argv[2];
+  result = inet_pton(AF_INET, neigh_dst_str, &neigh_dst_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad neighbor destination address %s: errno=%d: %s%s",
+	    neigh_dst_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Neighbor source address */
+  neigh_src_str = argv[3];
+  result = inet_pton(AF_INET, neigh_src_str, &neigh_src_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad neighbor source address %s: errno=%d: %s%s",
+	    neigh_src_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Multicast group address */
+  group_str = argv[4];
+  result = inet_pton(AF_INET, group_str, &group_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+	    group_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Multicast source address */
+  source_str = argv[5];
+  result = inet_pton(AF_INET, source_str, &source_addr);
+  if (result <= 0) {
+    vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+	    source_str, errno, strerror(errno), VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /*
+    Tweak IP header
+   */
+  ip_hdr = (struct ip *) buf;
+  ip_hdr->ip_p = PIM_IP_PROTO_PIM;
+  ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+  ip_hdr->ip_hl = ip_hlen >> 2;    /* ip header length in 4-byte words */
+  ip_hdr->ip_src = neigh_src_addr;
+  ip_hdr->ip_dst = qpim_all_pim_routers_addr;
+
+  /*
+    Build PIM message
+  */
+  pim_msg = buf + ip_hlen;
+
+  /* skip room for pim header */
+  pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN;
+
+  remain = buf_pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr,
+						remain,
+						neigh_dst_addr);
+  if (!pim_msg_curr) {
+    vty_out(vty, "Failure encoding destination address %s: space left=%d%s",
+	    neigh_dst_str, remain, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  remain = buf_pastend - pim_msg_curr;
+  if (remain < 4) {
+    vty_out(vty, "Group will not fit: space left=%d%s",
+	    remain, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  *pim_msg_curr = 0; /* reserved */
+  ++pim_msg_curr;
+  *pim_msg_curr = 1; /* number of groups */
+  ++pim_msg_curr;
+  *((uint16_t *) pim_msg_curr) = htons(neigh_holdtime);
+  ++pim_msg_curr;
+  ++pim_msg_curr;
+
+  remain = buf_pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_group(pim_msg_curr,
+						remain,
+						group_addr);
+  if (!pim_msg_curr) {
+    vty_out(vty, "Failure encoding group address %s: space left=%d%s",
+	    group_str, remain, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  remain = buf_pastend - pim_msg_curr;
+  if (remain < 4) {
+    vty_out(vty, "Sources will not fit: space left=%d%s",
+	    remain, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  if (src_is_join) {
+    num_joined = 1;
+    num_pruned = 0;
+  }
+  else {
+    num_joined = 0;
+    num_pruned = 1;
+  }
+
+  /* number of joined sources */
+  *((uint16_t *) pim_msg_curr) = htons(num_joined);
+  ++pim_msg_curr;
+  ++pim_msg_curr;
+
+  /* number of pruned sources */
+  *((uint16_t *) pim_msg_curr) = htons(num_pruned);
+  ++pim_msg_curr;
+  ++pim_msg_curr;
+
+  remain = buf_pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_source(pim_msg_curr,
+						 remain,
+						 source_addr);
+  if (!pim_msg_curr) {
+    vty_out(vty, "Failure encoding source address %s: space left=%d%s",
+	    source_str, remain, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  /* Add PIM header */
+
+  pim_msg_size = pim_msg_curr - pim_msg;
+
+  pim_msg_build_header(ifp->name,
+		       pim_msg, pim_msg_size,
+		       PIM_MSG_TYPE_JOIN_PRUNE);
+
+  /*
+    "Receive" message
+  */
+
+  ip_msg_len = ip_hlen + pim_msg_size;
+  result = pim_pim_packet(ifp, buf, ip_msg_len);
+  if (result) {
+    vty_out(vty, "pim_pim_packet(len=%d) returned failure: %d%s",
+	    ip_msg_len, result, VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (test_pim_receive_join,
+       test_pim_receive_join_cmd,
+       "test pim receive join INTERFACE <0-65535> A.B.C.D A.B.C.D A.B.C.D A.B.C.D",
+       "Test\n"
+       "Test PIM protocol\n"
+       "Test PIM message reception\n"
+       "Test PIM join reception from neighbor\n"
+       "Interface\n"
+       "Neighbor holdtime\n"
+       "Upstream neighbor unicast destination address\n"
+       "Downstream neighbor unicast source address\n"
+       "Multicast group address\n"
+       "Unicast source address\n")
+{
+  return recv_joinprune(vty, argv, 1 /* src_is_join=true */);
+}
+
+DEFUN (test_pim_receive_prune,
+       test_pim_receive_prune_cmd,
+       "test pim receive prune INTERFACE <0-65535> A.B.C.D A.B.C.D A.B.C.D A.B.C.D",
+       "Test\n"
+       "Test PIM protocol\n"
+       "Test PIM message reception\n"
+       "Test PIM prune reception from neighbor\n"
+       "Interface\n"
+       "Neighbor holdtime\n"
+       "Upstream neighbor unicast destination address\n"
+       "Downstream neighbor unicast source address\n"
+       "Multicast group address\n"
+       "Unicast source address\n")
+{
+  return recv_joinprune(vty, argv, 0 /* src_is_join=false */);
+}
+
+void pim_cmd_init()
+{
+  install_node(&pim_global_node, pim_global_config_write);       /* PIM_NODE */
+  install_node(&pim_interface_node, pim_interface_config_write); /* INTERFACE_NODE */
+
+  install_element(CONFIG_NODE, &ip_multicast_routing_cmd);
+  install_element(CONFIG_NODE, &no_ip_multicast_routing_cmd);
+#if 0
+  install_element(CONFIG_NODE, &interface_cmd); /* from if.h */
+#else
+  install_element(CONFIG_NODE, &pim_interface_cmd);
+#endif
+  install_element(CONFIG_NODE, &no_interface_cmd); /* from if.h */
+
+  install_default(INTERFACE_NODE);
+  install_element(INTERFACE_NODE, &interface_ip_igmp_cmd);
+  install_element(INTERFACE_NODE, &interface_no_ip_igmp_cmd); 
+  install_element(INTERFACE_NODE, &interface_ip_igmp_query_interval_cmd);
+  install_element(INTERFACE_NODE, &interface_no_ip_igmp_query_interval_cmd); 
+  install_element(INTERFACE_NODE, &interface_ip_igmp_query_max_response_time_cmd);
+  install_element(INTERFACE_NODE, &interface_no_ip_igmp_query_max_response_time_cmd); 
+  install_element(INTERFACE_NODE, &interface_ip_igmp_query_max_response_time_dsec_cmd);
+  install_element(INTERFACE_NODE, &interface_no_ip_igmp_query_max_response_time_dsec_cmd); 
+  install_element(INTERFACE_NODE, &interface_ip_pim_ssm_cmd);
+  install_element(INTERFACE_NODE, &interface_no_ip_pim_ssm_cmd); 
+
+  install_element(VIEW_NODE, &show_ip_igmp_interface_cmd);
+  install_element(VIEW_NODE, &show_ip_igmp_parameters_cmd);
+  install_element(VIEW_NODE, &show_ip_igmp_groups_cmd);
+  install_element(VIEW_NODE, &show_ip_igmp_groups_retransmissions_cmd);
+  install_element(VIEW_NODE, &show_ip_igmp_sources_cmd);
+  install_element(VIEW_NODE, &show_ip_igmp_sources_retransmissions_cmd);
+  install_element(VIEW_NODE, &show_ip_igmp_querier_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_assert_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_assert_metric_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_assert_winner_metric_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_dr_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_hello_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_interface_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_join_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_jp_override_interval_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_lan_prune_delay_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_local_membership_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_neighbor_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_rpf_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_secondary_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_upstream_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_upstream_join_desired_cmd);
+  install_element(VIEW_NODE, &show_ip_pim_upstream_rpf_cmd);
+  install_element(VIEW_NODE, &show_ip_multicast_cmd);
+  install_element(VIEW_NODE, &show_ip_mroute_cmd);
+  install_element(VIEW_NODE, &show_ip_mroute_count_cmd);
+  install_element(VIEW_NODE, &show_ip_route_cmd);
+  install_element(VIEW_NODE, &show_debugging_cmd);
+
+  install_element(ENABLE_NODE, &clear_ip_interfaces_cmd);
+  install_element(ENABLE_NODE, &clear_ip_igmp_interfaces_cmd);
+  install_element(ENABLE_NODE, &clear_ip_pim_interfaces_cmd);
+
+  install_element(ENABLE_NODE, &show_ip_igmp_interface_cmd);
+  install_element(ENABLE_NODE, &show_ip_igmp_parameters_cmd);
+  install_element(ENABLE_NODE, &show_ip_igmp_groups_cmd);
+  install_element(ENABLE_NODE, &show_ip_igmp_groups_retransmissions_cmd);
+  install_element(ENABLE_NODE, &show_ip_igmp_sources_cmd);
+  install_element(ENABLE_NODE, &show_ip_igmp_sources_retransmissions_cmd);
+  install_element(ENABLE_NODE, &show_ip_igmp_querier_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_assert_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_assert_metric_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_assert_winner_metric_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_dr_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_hello_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_interface_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_join_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_jp_override_interval_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_lan_prune_delay_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_local_membership_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_neighbor_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_rpf_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_secondary_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_upstream_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_upstream_join_desired_cmd);
+  install_element(ENABLE_NODE, &show_ip_pim_upstream_rpf_cmd);
+  install_element(ENABLE_NODE, &show_ip_multicast_cmd);
+  install_element(ENABLE_NODE, &show_ip_mroute_cmd);
+  install_element(ENABLE_NODE, &show_ip_mroute_count_cmd);
+  install_element(ENABLE_NODE, &show_ip_route_cmd);
+  install_element(ENABLE_NODE, &show_debugging_cmd);
+
+  install_element(ENABLE_NODE, &test_igmp_receive_report_cmd);
+  install_element(ENABLE_NODE, &test_pim_receive_assert_cmd);
+  install_element(ENABLE_NODE, &test_pim_receive_hello_cmd);
+  install_element(ENABLE_NODE, &test_pim_receive_join_cmd);
+  install_element(ENABLE_NODE, &test_pim_receive_prune_cmd);
+
+  install_element(ENABLE_NODE, &debug_igmp_cmd);
+  install_element(ENABLE_NODE, &no_debug_igmp_cmd);
+  install_element(ENABLE_NODE, &undebug_igmp_cmd);
+  install_element(ENABLE_NODE, &debug_igmp_events_cmd);
+  install_element(ENABLE_NODE, &no_debug_igmp_events_cmd);
+  install_element(ENABLE_NODE, &undebug_igmp_events_cmd);
+  install_element(ENABLE_NODE, &debug_igmp_packets_cmd);
+  install_element(ENABLE_NODE, &no_debug_igmp_packets_cmd);
+  install_element(ENABLE_NODE, &undebug_igmp_packets_cmd);
+  install_element(ENABLE_NODE, &debug_igmp_trace_cmd);
+  install_element(ENABLE_NODE, &no_debug_igmp_trace_cmd);
+  install_element(ENABLE_NODE, &undebug_igmp_trace_cmd);
+  install_element(ENABLE_NODE, &debug_pim_cmd);
+  install_element(ENABLE_NODE, &no_debug_pim_cmd);
+  install_element(ENABLE_NODE, &undebug_pim_cmd);
+  install_element(ENABLE_NODE, &debug_pim_events_cmd);
+  install_element(ENABLE_NODE, &no_debug_pim_events_cmd);
+  install_element(ENABLE_NODE, &undebug_pim_events_cmd);
+  install_element(ENABLE_NODE, &debug_pim_packets_cmd);
+  install_element(ENABLE_NODE, &no_debug_pim_packets_cmd);
+  install_element(ENABLE_NODE, &undebug_pim_packets_cmd);
+  install_element(ENABLE_NODE, &debug_pim_trace_cmd);
+  install_element(ENABLE_NODE, &no_debug_pim_trace_cmd);
+  install_element(ENABLE_NODE, &undebug_pim_trace_cmd);
+  install_element(ENABLE_NODE, &debug_pim_zebra_cmd);
+  install_element(ENABLE_NODE, &no_debug_pim_zebra_cmd);
+  install_element(ENABLE_NODE, &undebug_pim_zebra_cmd);
+
+  install_element(CONFIG_NODE, &debug_igmp_cmd);
+  install_element(CONFIG_NODE, &no_debug_igmp_cmd);
+  install_element(CONFIG_NODE, &undebug_igmp_cmd);
+  install_element(CONFIG_NODE, &debug_igmp_events_cmd);
+  install_element(CONFIG_NODE, &no_debug_igmp_events_cmd);
+  install_element(CONFIG_NODE, &undebug_igmp_events_cmd);
+  install_element(CONFIG_NODE, &debug_igmp_packets_cmd);
+  install_element(CONFIG_NODE, &no_debug_igmp_packets_cmd);
+  install_element(CONFIG_NODE, &undebug_igmp_packets_cmd);
+  install_element(CONFIG_NODE, &debug_igmp_trace_cmd);
+  install_element(CONFIG_NODE, &no_debug_igmp_trace_cmd);
+  install_element(CONFIG_NODE, &undebug_igmp_trace_cmd);
+  install_element(CONFIG_NODE, &debug_pim_cmd);
+  install_element(CONFIG_NODE, &no_debug_pim_cmd);
+  install_element(CONFIG_NODE, &undebug_pim_cmd);
+  install_element(CONFIG_NODE, &debug_pim_events_cmd);
+  install_element(CONFIG_NODE, &no_debug_pim_events_cmd);
+  install_element(CONFIG_NODE, &undebug_pim_events_cmd);
+  install_element(CONFIG_NODE, &debug_pim_packets_cmd);
+  install_element(CONFIG_NODE, &no_debug_pim_packets_cmd);
+  install_element(CONFIG_NODE, &undebug_pim_packets_cmd);
+  install_element(CONFIG_NODE, &debug_pim_trace_cmd);
+  install_element(CONFIG_NODE, &no_debug_pim_trace_cmd);
+  install_element(CONFIG_NODE, &undebug_pim_trace_cmd);
+  install_element(CONFIG_NODE, &debug_pim_zebra_cmd);
+  install_element(CONFIG_NODE, &no_debug_pim_zebra_cmd);
+  install_element(CONFIG_NODE, &undebug_pim_zebra_cmd);
+}
diff --git a/pimd/pim_cmd.h b/pimd/pim_cmd.h
new file mode 100644
index 0000000..c2bb61b
--- /dev/null
+++ b/pimd/pim_cmd.h
@@ -0,0 +1,56 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_CMD_H
+#define PIM_CMD_H
+
+#define PIM_STR                                "PIM information\n"
+#define IGMP_STR                               "IGMP information\n"
+#define IGMP_GROUP_STR                         "IGMP groups information\n"
+#define IGMP_SOURCE_STR                        "IGMP sources information\n"
+#define IFACE_PIM_STR                          "Enable PIM SSM operation\n"
+#define IFACE_IGMP_STR                         "Enable IGMP operation\n"
+#define IFACE_IGMP_QUERY_INTERVAL_STR          "IGMP host query interval\n"
+#define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR      "IGMP max query response value (seconds)\n"
+#define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR "IGMP max query response value (deciseconds)\n"
+#define DEBUG_IGMP_STR                              "IGMP protocol activity\n"
+#define DEBUG_IGMP_EVENTS_STR                       "IGMP protocol events\n"
+#define DEBUG_IGMP_PACKETS_STR                      "IGMP protocol packets\n"
+#define DEBUG_IGMP_TRACE_STR                        "IGMP internal daemon activity\n"
+#define DEBUG_PIM_STR                               "PIM protocol activity\n"
+#define DEBUG_PIM_EVENTS_STR                        "PIM protocol events\n"
+#define DEBUG_PIM_PACKETS_STR                       "PIM protocol packets\n"
+#define DEBUG_PIM_TRACE_STR                         "PIM internal daemon activity\n"
+#define DEBUG_PIM_ZEBRA_STR                         "ZEBRA protocol activity\n"
+#define CLEAR_IP_IGMP_STR                           "IGMP clear commands\n"
+#define CLEAR_IP_PIM_STR                            "PIM clear commands\n"
+#define MROUTE_STR                                  "IP multicast routing table\n"
+
+#define PIM_CMD_NO                                   "no"
+#define PIM_CMD_IP_MULTICAST_ROUTING                 "ip multicast-routing"
+#define PIM_CMD_IP_IGMP_QUERY_INTERVAL               "ip igmp query-interval"
+#define PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME      "ip igmp query-max-response-time"
+#define PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC "ip igmp query-max-response-time-dsec"
+
+void pim_cmd_init(void);
+
+#endif /* PIM_CMD_H */
diff --git a/pimd/pim_hello.c b/pimd/pim_hello.c
new file mode 100644
index 0000000..8b710b8
--- /dev/null
+++ b/pimd/pim_hello.c
@@ -0,0 +1,529 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_tlv.h"
+#include "pim_util.h"
+#include "pim_hello.h"
+#include "pim_iface.h"
+#include "pim_neighbor.h"
+#include "pim_upstream.h"
+
+static void on_trace(const char *label,
+		     struct interface *ifp, struct in_addr src)
+{
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src, src_str, sizeof(src_str));
+    zlog_debug("%s: from %s on %s",
+	       label, src_str, ifp->name);
+  }
+}
+
+static void tlv_trace_bool(const char *label, const char *tlv_name,
+			   const char *ifname, struct in_addr src_addr,
+			   int isset, int value)
+{
+  if (isset) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_debug("%s: PIM hello option from %s on interface %s: %s=%d",
+	       label, 
+	       src_str, ifname,
+	       tlv_name, value);
+  }
+}
+
+static void tlv_trace_uint16(const char *label, const char *tlv_name,
+			     const char *ifname, struct in_addr src_addr,
+			     int isset, uint16_t value)
+{
+  if (isset) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_debug("%s: PIM hello option from %s on interface %s: %s=%u",
+	       label, 
+	       src_str, ifname,
+	       tlv_name, value);
+  }
+}
+
+static void tlv_trace_uint32(const char *label, const char *tlv_name,
+			     const char *ifname, struct in_addr src_addr,
+			     int isset, uint32_t value)
+{
+  if (isset) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_debug("%s: PIM hello option from %s on interface %s: %s=%u",
+	       label, 
+	       src_str, ifname,
+	       tlv_name, value);
+  }
+}
+
+static void tlv_trace_uint32_hex(const char *label, const char *tlv_name,
+				 const char *ifname, struct in_addr src_addr,
+				 int isset, uint32_t value)
+{
+  if (isset) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_debug("%s: PIM hello option from %s on interface %s: %s=%08x",
+	       label, 
+	       src_str, ifname,
+	       tlv_name, value);
+  }
+}
+
+#if 0
+static void tlv_trace(const char *label, const char *tlv_name,
+		      const char *ifname, struct in_addr src_addr,
+		      int isset)
+{
+  if (isset) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_debug("%s: PIM hello option from %s on interface %s: %s",
+	       label, 
+	       src_str, ifname,
+	       tlv_name);
+  }
+}
+#endif
+
+static void tlv_trace_list(const char *label, const char *tlv_name,
+			   const char *ifname, struct in_addr src_addr,
+			   int isset, struct list *addr_list)
+{
+  if (isset) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_debug("%s: PIM hello option from %s on interface %s: %s size=%d list=%x",
+	       label, 
+	       src_str, ifname,
+	       tlv_name,
+	       addr_list ? ((int) listcount(addr_list)) : -1,
+	       (unsigned) addr_list);
+  }
+}
+
+#define FREE_ADDR_LIST \
+  if (hello_option_addr_list) { \
+    list_delete(hello_option_addr_list); \
+  }
+
+#define FREE_ADDR_LIST_THEN_RETURN(code) \
+{ \
+  FREE_ADDR_LIST \
+  return (code); \
+}
+
+int pim_hello_recv(struct interface *ifp,
+		   struct in_addr src_addr,
+		   char *tlv_buf, int tlv_buf_size)
+{
+  struct pim_interface *pim_ifp;
+  struct pim_neighbor *neigh;
+  char *tlv_curr;
+  char *tlv_pastend;
+  pim_hello_options hello_options = 0; /* bit array recording options found */
+  uint16_t hello_option_holdtime = 0;
+  uint16_t hello_option_propagation_delay = 0;
+  uint16_t hello_option_override_interval = 0;
+  uint32_t hello_option_dr_priority = 0;
+  uint32_t hello_option_generation_id = 0;
+  struct list *hello_option_addr_list = 0;
+
+  on_trace(__PRETTY_FUNCTION__, ifp, src_addr);
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  ++pim_ifp->pim_ifstat_hello_recv;
+
+  /*
+    Parse PIM hello TLVs
+   */
+  zassert(tlv_buf_size >= 0);
+  tlv_curr = tlv_buf;
+  tlv_pastend = tlv_buf + tlv_buf_size;
+
+  while (tlv_curr < tlv_pastend) {
+    uint16_t option_type; 
+    uint16_t option_len;
+    int remain = tlv_pastend - tlv_curr;
+
+    if (remain < PIM_TLV_MIN_SIZE) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: short PIM hello TLV size=%d < min=%d from %s on interface %s",
+		__PRETTY_FUNCTION__,
+		remain, PIM_TLV_MIN_SIZE,
+		src_str, ifp->name);
+      FREE_ADDR_LIST_THEN_RETURN(-1);
+    }
+
+    option_type = PIM_TLV_GET_TYPE(tlv_curr);
+    tlv_curr += PIM_TLV_TYPE_SIZE;
+    option_len = PIM_TLV_GET_LENGTH(tlv_curr);
+    tlv_curr += PIM_TLV_LENGTH_SIZE;
+
+    if ((tlv_curr + option_len) > tlv_pastend) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: long PIM hello TLV type=%d length=%d > max=%d from %s on interface %s",
+		__PRETTY_FUNCTION__,
+		option_type, option_len, tlv_pastend - tlv_curr,
+		src_str, ifp->name);
+      FREE_ADDR_LIST_THEN_RETURN(-2);
+    }
+
+    if (PIM_DEBUG_PIM_TRACE) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_debug("%s: parse left_size=%d: PIM hello TLV type=%d length=%d from %s on %s",
+		 __PRETTY_FUNCTION__,
+		 remain,
+		 option_type, option_len,
+		 src_str, ifp->name);
+    }
+
+    switch (option_type) {
+    case PIM_MSG_OPTION_TYPE_HOLDTIME:
+      if (pim_tlv_parse_holdtime(ifp->name, src_addr,
+				 &hello_options,
+				 &hello_option_holdtime,
+				 option_len,
+				 tlv_curr)) {
+	FREE_ADDR_LIST_THEN_RETURN(-3);
+      }
+      break;
+    case PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY:
+      if (pim_tlv_parse_lan_prune_delay(ifp->name, src_addr,
+					&hello_options,
+					&hello_option_propagation_delay,
+					&hello_option_override_interval,
+					option_len,
+					tlv_curr)) {
+	FREE_ADDR_LIST_THEN_RETURN(-4);
+      }
+      break;
+    case PIM_MSG_OPTION_TYPE_DR_PRIORITY:
+      if (pim_tlv_parse_dr_priority(ifp->name, src_addr,
+				    &hello_options,
+				    &hello_option_dr_priority,
+				    option_len,
+				    tlv_curr)) {
+	FREE_ADDR_LIST_THEN_RETURN(-5);
+      }
+      break;
+    case PIM_MSG_OPTION_TYPE_GENERATION_ID:
+      if (pim_tlv_parse_generation_id(ifp->name, src_addr,
+				      &hello_options,
+				      &hello_option_generation_id,
+				      option_len,
+				      tlv_curr)) {
+	FREE_ADDR_LIST_THEN_RETURN(-6);
+      }
+      break;
+    case PIM_MSG_OPTION_TYPE_ADDRESS_LIST:
+      if (pim_tlv_parse_addr_list(ifp->name, src_addr,
+				  &hello_options,
+				  &hello_option_addr_list,
+				  option_len,
+				  tlv_curr)) {
+	return -7;
+      }
+      break;
+    case PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH:
+      if (PIM_DEBUG_PIM_TRACE) {
+	char src_str[100];
+	pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+	zlog_debug("%s: ignoring PIM hello dense-mode state refresh TLV option type=%d length=%d from %s on interface %s",
+		   __PRETTY_FUNCTION__,
+		   option_type, option_len,
+		   src_str, ifp->name);
+      }
+      break;
+    default:
+      {
+	char src_str[100];
+	pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+	zlog_warn("%s: ignoring unknown PIM hello TLV type=%d length=%d from %s on interface %s",
+		  __PRETTY_FUNCTION__,
+		  option_type, option_len,
+		  src_str, ifp->name);
+      }
+    }
+
+    tlv_curr += option_len;
+  }
+
+  /*
+    Check received PIM hello options
+  */
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    tlv_trace_uint16(__PRETTY_FUNCTION__, "holdtime",
+		     ifp->name, src_addr,
+		     PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME),
+		     hello_option_holdtime);
+    tlv_trace_uint16(__PRETTY_FUNCTION__, "propagation_delay",
+		     ifp->name, src_addr,
+		     PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY),
+		     hello_option_propagation_delay);
+    tlv_trace_uint16(__PRETTY_FUNCTION__, "override_interval",
+		     ifp->name, src_addr,
+		     PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY),
+		     hello_option_override_interval);
+    tlv_trace_bool(__PRETTY_FUNCTION__, "can_disable_join_suppression",
+		   ifp->name, src_addr,
+		   PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY),
+		   PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION));
+    tlv_trace_uint32(__PRETTY_FUNCTION__, "dr_priority",
+		     ifp->name, src_addr,
+		     PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_DR_PRIORITY),
+		     hello_option_dr_priority);
+    tlv_trace_uint32_hex(__PRETTY_FUNCTION__, "generation_id",
+			 ifp->name, src_addr,
+			 PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_GENERATION_ID),
+			 hello_option_generation_id);
+    tlv_trace_list(__PRETTY_FUNCTION__, "address_list",
+		   ifp->name, src_addr,
+		   PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_ADDRESS_LIST),
+		   hello_option_addr_list);
+  }
+
+  if (!PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: PIM hello missing holdtime from %s on interface %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, ifp->name);
+  }
+
+  /*
+    New neighbor?
+  */
+
+  neigh = pim_neighbor_find(ifp, src_addr);
+  if (!neigh) {
+    /* Add as new neighbor */
+    
+    neigh = pim_neighbor_add(ifp, src_addr,
+			     hello_options,
+			     hello_option_holdtime,
+			     hello_option_propagation_delay,
+			     hello_option_override_interval,
+			     hello_option_dr_priority,
+			     hello_option_generation_id,
+			     hello_option_addr_list);
+    if (!neigh) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: failure creating PIM neighbor %s on interface %s",
+		__PRETTY_FUNCTION__,
+		src_str, ifp->name);
+      FREE_ADDR_LIST_THEN_RETURN(-8);
+    }
+
+    /* actual addr list has been saved under neighbor */
+    return 0;
+  }
+
+  /*
+    Received generation ID ?
+  */
+  
+  if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_GENERATION_ID)) {
+    /* GenID mismatch ? */
+    if (!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_GENERATION_ID) ||
+	(hello_option_generation_id != neigh->generation_id)) {
+
+      /* GenID changed */
+
+      pim_upstream_rpf_genid_changed(neigh->source_addr);
+
+      /* GenID mismatch, then replace neighbor */
+      
+      if (PIM_DEBUG_PIM_TRACE) {
+	char src_str[100];
+	pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+	zlog_debug("%s: GenId mismatch new=%08x old=%08x: replacing neighbor %s on %s",
+		   __PRETTY_FUNCTION__,
+		   hello_option_generation_id,
+		   neigh->generation_id,
+		   src_str, ifp->name);
+      }
+
+      pim_upstream_rpf_genid_changed(neigh->source_addr);
+      
+      pim_neighbor_delete(ifp, neigh, "GenID mismatch");
+      neigh = pim_neighbor_add(ifp, src_addr,
+			       hello_options,
+			       hello_option_holdtime,
+			       hello_option_propagation_delay,
+			       hello_option_override_interval,
+			       hello_option_dr_priority,
+			       hello_option_generation_id,
+			       hello_option_addr_list);
+      if (!neigh) {
+	char src_str[100];
+	pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+	zlog_warn("%s: failure re-creating PIM neighbor %s on interface %s",
+		  __PRETTY_FUNCTION__,
+		  src_str, ifp->name);
+	FREE_ADDR_LIST_THEN_RETURN(-9);
+      }
+      /* actual addr list is saved under neighbor */
+      return 0;
+
+    } /* GenId mismatch: replace neighbor */
+    
+  } /* GenId received */
+
+  /*
+    Update existing neighbor
+  */
+
+  pim_neighbor_update(neigh,
+		      hello_options,
+		      hello_option_holdtime,
+		      hello_option_dr_priority,
+		      hello_option_addr_list);
+  /* actual addr list is saved under neighbor */
+  return 0;
+}
+
+int pim_hello_build_tlv(const char *ifname,
+			char *tlv_buf, int tlv_buf_size,
+			uint16_t holdtime,
+			uint32_t dr_priority,
+			uint32_t generation_id,
+			uint16_t propagation_delay,
+			uint16_t override_interval,
+			int can_disable_join_suppression,
+			struct list *ifconnected)
+{
+  char *curr = tlv_buf;
+  char *pastend = tlv_buf + tlv_buf_size;
+  char *tmp;
+
+  /*
+   * Append options
+   */
+
+  /* Holdtime */
+  curr = pim_tlv_append_uint16(curr,
+			       pastend,
+			       PIM_MSG_OPTION_TYPE_HOLDTIME,
+			       holdtime);
+  if (!curr) {
+    zlog_warn("%s: could not set PIM hello Holdtime option for interface %s",
+	      __PRETTY_FUNCTION__, ifname);
+    return -1;
+  }
+
+  /* LAN Prune Delay */
+  tmp = pim_tlv_append_2uint16(curr,
+			       pastend,
+			       PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY,
+			       propagation_delay,
+			       override_interval);
+  if (!tmp) {
+    zlog_warn("%s: could not set PIM LAN Prune Delay option for interface %s",
+	      __PRETTY_FUNCTION__, ifname);
+    return -1;
+  }
+  if (can_disable_join_suppression) {
+    *((uint8_t*)(curr) + 4) |= 0x80; /* enable T bit */
+  }
+  curr = tmp;
+
+  /* DR Priority */
+  curr = pim_tlv_append_uint32(curr,
+			       pastend,
+			       PIM_MSG_OPTION_TYPE_DR_PRIORITY,
+			       dr_priority);
+  if (!curr) {
+    zlog_warn("%s: could not set PIM hello DR Priority option for interface %s",
+	      __PRETTY_FUNCTION__, ifname);
+    return -2;
+  }
+
+  /* Generation ID */
+  curr = pim_tlv_append_uint32(curr,
+			       pastend,
+			       PIM_MSG_OPTION_TYPE_GENERATION_ID,
+			       generation_id);
+  if (!curr) {
+    zlog_warn("%s: could not set PIM hello Generation ID option for interface %s",
+	      __PRETTY_FUNCTION__, ifname);
+    return -3;
+  }
+
+  /* Secondary Address List */
+  if (ifconnected) {
+    curr = pim_tlv_append_addrlist_ucast(curr,
+					 pastend,
+					 ifconnected);
+    if (!curr) {
+      zlog_warn("%s: could not set PIM hello Secondary Address List option for interface %s",
+		__PRETTY_FUNCTION__, ifname);
+      return -4;
+    }
+  }
+
+  return curr - tlv_buf;
+}
+
+/*
+  RFC 4601: 4.3.1.  Sending Hello Messages
+
+  Thus, if a router needs to send a Join/Prune or Assert message on an
+  interface on which it has not yet sent a Hello message with the
+  currently configured IP address, then it MUST immediately send the
+  relevant Hello message without waiting for the Hello Timer to
+  expire, followed by the Join/Prune or Assert message.
+*/
+void pim_hello_require(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+
+  pim_ifp = ifp->info;
+
+  zassert(pim_ifp);
+
+  if (pim_ifp->pim_ifstat_hello_sent)
+    return;
+
+  pim_hello_restart_now(ifp); /* Send hello and restart timer */
+}
diff --git a/pimd/pim_hello.h b/pimd/pim_hello.h
new file mode 100644
index 0000000..90a11ba
--- /dev/null
+++ b/pimd/pim_hello.h
@@ -0,0 +1,46 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_HELLO_H
+#define PIM_HELLO_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+int pim_hello_recv(struct interface *ifp,
+		   struct in_addr src_addr,
+		   char *tlv_buf, int tlv_buf_size);
+
+int pim_hello_build_tlv(const char *ifname,
+			char *tlv_buf, int tlv_buf_size,
+			uint16_t holdtime,
+			uint32_t dr_priority,
+			uint32_t generation_id,
+			uint16_t propagation_delay,
+			uint16_t override_interval,
+			int can_disable_join_suppression,
+			struct list *ifconnected);
+
+void pim_hello_require(struct interface *ifp);
+
+#endif /* PIM_HELLO_H */
diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c
new file mode 100644
index 0000000..9b5959c
--- /dev/null
+++ b/pimd/pim_iface.c
@@ -0,0 +1,768 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "if.h"
+#include "log.h"
+#include "vty.h"
+#include "memory.h"
+#include "prefix.h"
+
+#include "pimd.h"
+#include "pim_iface.h"
+#include "pim_igmp.h"
+#include "pim_mroute.h"
+#include "pim_oil.h"
+#include "pim_str.h"
+#include "pim_pim.h"
+#include "pim_neighbor.h"
+#include "pim_ifchannel.h"
+#include "pim_rand.h"
+
+void pim_if_init()
+{
+  if_init();
+}
+
+static void *if_list_clean(struct pim_interface *pim_ifp)
+{
+  if (pim_ifp->igmp_socket_list) {
+    list_delete(pim_ifp->igmp_socket_list);
+  }
+
+  if (pim_ifp->pim_neighbor_list) {
+    list_delete(pim_ifp->pim_neighbor_list);
+  }
+
+  if (pim_ifp->pim_ifchannel_list) {
+    list_delete(pim_ifp->pim_ifchannel_list);
+  }
+
+  XFREE(MTYPE_PIM_INTERFACE, pim_ifp);
+
+  return 0;
+}
+
+struct pim_interface *pim_if_new(struct interface *ifp, int igmp, int pim)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+  zassert(!ifp->info);
+
+  pim_ifp = XMALLOC(MTYPE_PIM_INTERFACE, sizeof(*pim_ifp));
+  if (!pim_ifp) {
+    zlog_err("PIM XMALLOC(%d) failure", sizeof(*pim_ifp));
+    return 0;
+  }
+
+  pim_ifp->options                           = 0;
+  pim_ifp->mroute_vif_index                  = -1;
+
+  pim_ifp->igmp_default_robustness_variable  = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
+  pim_ifp->igmp_default_query_interval       = IGMP_GENERAL_QUERY_INTERVAL;
+  pim_ifp->igmp_query_max_response_time_dsec = IGMP_QUERY_MAX_RESPONSE_TIME_DSEC;
+
+  /*
+    RFC 3376: 8.3. Query Response Interval
+    The number of seconds represented by the [Query Response Interval]
+    must be less than the [Query Interval].
+   */
+  zassert(pim_ifp->igmp_query_max_response_time_dsec < pim_ifp->igmp_default_query_interval);
+
+  if (pim)
+    PIM_IF_DO_PIM(pim_ifp->options);
+  if (igmp)
+    PIM_IF_DO_IGMP(pim_ifp->options);
+
+#if 0
+  /* FIXME: Should join? */
+  PIM_IF_DO_IGMP_LISTEN_ALLROUTERS(pim_ifp->options);
+#endif
+
+  pim_ifp->igmp_socket_list = 0;
+  pim_ifp->pim_neighbor_list = 0;
+  pim_ifp->pim_ifchannel_list = 0;
+
+  /* list of struct igmp_sock */
+  pim_ifp->igmp_socket_list = list_new();
+  if (!pim_ifp->igmp_socket_list) {
+    zlog_err("%s %s: failure: igmp_socket_list=list_new()",
+	     __FILE__, __PRETTY_FUNCTION__);
+    return if_list_clean(pim_ifp);
+  }
+  pim_ifp->igmp_socket_list->del = (void (*)(void *)) igmp_sock_free;
+
+  /* list of struct pim_neighbor */
+  pim_ifp->pim_neighbor_list = list_new();
+  if (!pim_ifp->pim_neighbor_list) {
+    zlog_err("%s %s: failure: pim_neighbor_list=list_new()",
+	     __FILE__, __PRETTY_FUNCTION__);
+    return if_list_clean(pim_ifp);
+  }
+  pim_ifp->pim_neighbor_list->del = (void (*)(void *)) pim_neighbor_free;
+
+  /* list of struct pim_ifchannel */
+  pim_ifp->pim_ifchannel_list = list_new();
+  if (!pim_ifp->pim_ifchannel_list) {
+    zlog_err("%s %s: failure: pim_ifchannel_list=list_new()",
+	     __FILE__, __PRETTY_FUNCTION__);
+    return if_list_clean(pim_ifp);
+  }
+  pim_ifp->pim_ifchannel_list->del = (void (*)(void *)) pim_ifchannel_free;
+
+  ifp->info = pim_ifp;
+
+  pim_sock_reset(ifp);
+
+  zassert(PIM_IF_TEST_PIM(pim_ifp->options) || PIM_IF_TEST_IGMP(pim_ifp->options));
+
+  if (PIM_MROUTE_IS_ENABLED) {
+    pim_if_add_vif(ifp);
+  }
+
+  return pim_ifp;
+}
+
+void pim_if_delete(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+  pim_ifp = ifp->info;
+
+  zassert(pim_ifp);
+
+  zassert(pim_ifp->igmp_socket_list);
+  zassert(!listcount(pim_ifp->igmp_socket_list));
+
+  zassert(pim_ifp->pim_neighbor_list);
+  zassert(!listcount(pim_ifp->pim_neighbor_list));
+
+  zassert(pim_ifp->pim_ifchannel_list);
+  zassert(!listcount(pim_ifp->pim_ifchannel_list));
+
+  if (PIM_MROUTE_IS_ENABLED) {
+    pim_if_del_vif(ifp);
+  }
+
+  list_delete(pim_ifp->igmp_socket_list);
+  list_delete(pim_ifp->pim_neighbor_list);
+  list_delete(pim_ifp->pim_ifchannel_list);
+
+  XFREE(MTYPE_PIM_INTERFACE, pim_ifp);
+
+  ifp->info = 0;
+}
+
+static void pim_addr_change(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  pim_if_dr_election(ifp); /* Done TODO T30 */
+
+  /*
+    RFC 4601: 4.3.1.  Sending Hello Messages
+
+    1) Before an interface goes down or changes primary IP address, a
+    Hello message with a zero HoldTime should be sent immediately
+    (with the old IP address if the IP address changed).
+    -- FIXME See CAVEAT C13
+
+    2) After an interface has changed its IP address, it MUST send a
+    Hello message with its new IP address.
+    -- DONE below
+
+    3) If an interface changes one of its secondary IP addresses, a
+    Hello message with an updated Address_List option and a non-zero
+    HoldTime should be sent immediately.
+    -- FIXME See TODO T31
+   */
+  pim_ifp->pim_ifstat_hello_sent = 0; /* reset hello counter */
+  if (pim_ifp->pim_sock_fd < 0)
+    return;
+  pim_hello_restart_now(ifp);         /* send hello and restart timer */
+}
+
+static void on_primary_address_change(struct interface *ifp,
+				      const char *caller,
+				      struct in_addr old_addr,
+				      struct in_addr new_addr)
+{
+  struct pim_interface *pim_ifp;
+
+  {
+    char old_str[100];
+    char new_str[100];
+    pim_inet4_dump("<old?>", old_addr, old_str, sizeof(old_str));
+    pim_inet4_dump("<new?>", new_addr, new_str, sizeof(new_str));
+    zlog_info("%s: %s: primary address changed from %s to %s on interface %s",
+	      __PRETTY_FUNCTION__, caller,
+	      old_str, new_str, ifp->name);
+  }
+
+  pim_ifp = ifp->info;
+
+  if (pim_ifp) {
+    if (PIM_IF_TEST_PIM(pim_ifp->options)) {
+      pim_addr_change(ifp);
+    }
+  }
+}
+
+static void detect_primary_address_change(struct interface *ifp,
+					  const char *caller)
+{
+  struct pim_interface *pim_ifp;
+  struct in_addr first_addr;
+
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return;
+
+  first_addr = pim_find_first_addr(ifp);
+
+  if (first_addr.s_addr != pim_ifp->primary_address.s_addr) {
+    struct in_addr old = pim_ifp->primary_address;
+    pim_ifp->primary_address = first_addr;
+
+    on_primary_address_change(ifp, caller, old, first_addr);
+  }
+}
+
+void pim_if_addr_add(struct connected *ifc)
+{
+  struct pim_interface *pim_ifp;
+  struct interface *ifp;
+  struct in_addr ifaddr;
+
+  zassert(ifc);
+
+  ifp = ifc->ifp;
+  zassert(ifp);
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return;
+
+  if (!if_is_operative(ifp))
+    return;
+
+  ifaddr = ifc->address->u.prefix4;
+
+  detect_primary_address_change(ifp, __PRETTY_FUNCTION__);
+
+  if (PIM_IF_TEST_IGMP(pim_ifp->options)) {
+    struct igmp_sock *igmp;
+
+    /* lookup IGMP socket */
+    igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list,
+				       ifaddr);
+    if (!igmp) {
+      /* if addr new, add IGMP socket */
+      pim_igmp_sock_add(pim_ifp->igmp_socket_list, ifaddr, ifp);
+    }
+  } /* igmp */
+
+  if (PIM_IF_TEST_PIM(pim_ifp->options)) {
+
+    /* Interface has a valid primary address ? */
+    if (PIM_INADDR_ISNOT_ANY(pim_ifp->primary_address)) {
+
+      /* Interface has a valid socket ? */
+      if (pim_ifp->pim_sock_fd < 0) {
+	if (pim_sock_add(ifp)) {
+	  zlog_warn("Failure creating PIM socket for interface %s",
+		    ifp->name);
+	}
+      }
+
+    }
+  } /* pim */
+
+  if (PIM_MROUTE_IS_ENABLED) {
+    /*
+      PIM or IGMP is enabled on interface, and there is at least one
+      address assigned, then try to create a vif_index.
+    */
+    if (pim_ifp->mroute_vif_index < 0) {
+      pim_if_add_vif(ifp);
+    }
+  }
+}
+
+static void pim_if_addr_del_igmp(struct connected *ifc)
+{
+  struct pim_interface *pim_ifp = ifc->ifp->info;
+  struct igmp_sock *igmp;
+  struct in_addr ifaddr;
+
+  if (ifc->address->family != AF_INET) {
+    /* non-IPv4 address */
+    return;
+  }
+
+  if (!pim_ifp) {
+    /* IGMP not enabled on interface */
+    return;
+  }
+
+  ifaddr = ifc->address->u.prefix4;
+
+  /* lookup IGMP socket */
+  igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list,
+				     ifaddr);
+  if (igmp) {
+    /* if addr found, del IGMP socket */
+    igmp_sock_delete(igmp);
+  }
+}
+
+static void pim_if_addr_del_pim(struct connected *ifc)
+{
+  struct pim_interface *pim_ifp = ifc->ifp->info;
+
+  if (ifc->address->family != AF_INET) {
+    /* non-IPv4 address */
+    return;
+  }
+
+  if (!pim_ifp) {
+    /* PIM not enabled on interface */
+    return;
+  }
+
+  if (PIM_INADDR_ISNOT_ANY(pim_ifp->primary_address)) {
+    /* Interface keeps a valid primary address */
+    return;
+  }
+
+  if (pim_ifp->pim_sock_fd < 0) {
+    /* Interface does not hold a valid socket any longer */
+    return;
+  }
+
+  /*
+    pim_sock_delete() closes the socket, stops read and timer threads,
+    and kills all neighbors.
+   */
+  pim_sock_delete(ifc->ifp, "last address has been removed from interface");
+}
+
+void pim_if_addr_del(struct connected *ifc)
+{
+  struct interface *ifp;
+
+  ifp = ifc->ifp;
+  zassert(ifp);
+
+  pim_if_addr_del_igmp(ifc);
+  pim_if_addr_del_pim(ifc);
+  detect_primary_address_change(ifp, __PRETTY_FUNCTION__);
+}
+
+void pim_if_addr_add_all(struct interface *ifp)
+{
+  struct connected *ifc;
+  struct listnode *node;
+  struct listnode *nextnode;
+
+  /* PIM/IGMP enabled ? */
+  if (!ifp->info)
+    return;
+
+  for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+    struct prefix *p = ifc->address;
+    
+    if (p->family != AF_INET)
+      continue;
+
+    pim_if_addr_add(ifc);
+  }
+}
+
+void pim_if_addr_del_all(struct interface *ifp)
+{
+  struct connected *ifc;
+  struct listnode *node;
+  struct listnode *nextnode;
+
+  /* PIM/IGMP enabled ? */
+  if (!ifp->info)
+    return;
+
+  for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+    struct prefix *p = ifc->address;
+    
+    if (p->family != AF_INET)
+      continue;
+
+    pim_if_addr_del(ifc);
+  }
+}
+
+void pim_if_addr_del_all_igmp(struct interface *ifp)
+{
+  struct connected *ifc;
+  struct listnode *node;
+  struct listnode *nextnode;
+
+  /* PIM/IGMP enabled ? */
+  if (!ifp->info)
+    return;
+
+  for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+    struct prefix *p = ifc->address;
+    
+    if (p->family != AF_INET)
+      continue;
+
+    pim_if_addr_del_igmp(ifc);
+  }
+}
+
+void pim_if_addr_del_all_pim(struct interface *ifp)
+{
+  struct connected *ifc;
+  struct listnode *node;
+  struct listnode *nextnode;
+
+  /* PIM/IGMP enabled ? */
+  if (!ifp->info)
+    return;
+
+  for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+    struct prefix *p = ifc->address;
+    
+    if (p->family != AF_INET)
+      continue;
+
+    pim_if_addr_del_pim(ifc);
+  }
+}
+
+struct in_addr pim_find_first_addr(struct interface *ifp)
+{
+  struct connected *ifc;
+  struct listnode *node;
+  struct in_addr addr;
+
+  for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) {
+    struct prefix *p = ifc->address;
+    
+    if (p->family != AF_INET)
+      continue;
+
+    return p->u.prefix4;
+  }
+
+  addr.s_addr = PIM_NET_INADDR_ANY;
+
+  return addr;
+}
+
+/*
+  pim_if_add_vif() uses ifindex as vif_index
+
+  see also pim_if_find_vifindex_by_ifindex()
+ */
+int pim_if_add_vif(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp = ifp->info;
+  struct in_addr ifaddr;
+
+  zassert(pim_ifp);
+
+  if (pim_ifp->mroute_vif_index > 0) {
+    zlog_warn("%s: vif_index=%d > 0 on interface %s ifindex=%d",
+	      __PRETTY_FUNCTION__,
+	      pim_ifp->mroute_vif_index, ifp->name, ifp->ifindex);
+    return -1;
+  }
+
+  if (ifp->ifindex < 1) {
+    zlog_warn("%s: ifindex=%d < 1 on interface %s",
+	      __PRETTY_FUNCTION__,
+	      ifp->ifindex, ifp->name);
+    return -2;
+  }
+
+  if (ifp->ifindex >= MAXVIFS) {
+    zlog_warn("%s: ifindex=%d >= MAXVIFS=%d on interface %s",
+	      __PRETTY_FUNCTION__,
+	      ifp->ifindex, MAXVIFS, ifp->name);
+    return -3;
+  }
+
+  ifaddr = pim_ifp->primary_address;
+  if (PIM_INADDR_IS_ANY(ifaddr)) {
+    zlog_warn("%s: could not get address for interface %s ifindex=%d",
+	      __PRETTY_FUNCTION__,
+	      ifp->name, ifp->ifindex);
+    return -4;
+  }
+
+  if (pim_mroute_add_vif(ifp->ifindex, ifaddr)) {
+    /* pim_mroute_add_vif reported error */
+    return -5;
+  }
+
+  pim_ifp->mroute_vif_index = ifp->ifindex;
+
+  /*
+    Update highest vif_index
+   */
+  if (pim_ifp->mroute_vif_index > qpim_mroute_oif_highest_vif_index) {
+    qpim_mroute_oif_highest_vif_index = pim_ifp->mroute_vif_index;
+  }
+
+  return 0;
+}
+
+static int iflist_find_highest_vif_index()
+{
+  struct listnode      *ifnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  int                   highest_vif_index = -1;
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    if (pim_ifp->mroute_vif_index > highest_vif_index) {
+      highest_vif_index = pim_ifp->mroute_vif_index;
+    }
+  }
+
+  return highest_vif_index;
+}
+
+int pim_if_del_vif(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp = ifp->info;
+  int old_vif_index;
+
+  if (pim_ifp->mroute_vif_index < 1) {
+    zlog_warn("%s: vif_index=%d < 1 on interface %s ifindex=%d",
+	      __PRETTY_FUNCTION__,
+	      pim_ifp->mroute_vif_index, ifp->name, ifp->ifindex);
+    return -1;
+  }
+
+  if (pim_mroute_del_vif(pim_ifp->mroute_vif_index)) {
+    /* pim_mroute_del_vif reported error */
+    return -2;
+  }
+
+  /*
+    Update highest vif_index
+   */
+
+  /* save old vif_index in order to compare with highest below */
+  old_vif_index = pim_ifp->mroute_vif_index;
+
+  pim_ifp->mroute_vif_index = -1;
+
+  if (old_vif_index == qpim_mroute_oif_highest_vif_index) {
+    qpim_mroute_oif_highest_vif_index = iflist_find_highest_vif_index();
+  }
+
+  return 0;
+}
+
+void pim_if_add_vif_all()
+{
+  struct listnode  *ifnode;
+  struct listnode  *ifnextnode;
+  struct interface *ifp;
+
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    if (!ifp->info)
+      continue;
+
+    pim_if_add_vif(ifp);
+  }
+}
+
+void pim_if_del_vif_all()
+{
+  struct listnode  *ifnode;
+  struct listnode  *ifnextnode;
+  struct interface *ifp;
+
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    if (!ifp->info)
+      continue;
+
+    pim_if_del_vif(ifp);
+  }
+}
+
+struct interface *pim_if_find_by_vif_index(int vif_index)
+{
+  struct listnode  *ifnode;
+  struct interface *ifp;
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    if (ifp->info) {
+      struct pim_interface *pim_ifp;
+      pim_ifp = ifp->info;
+      if (vif_index == pim_ifp->mroute_vif_index)
+	return ifp;
+    }
+  }
+
+  return 0;
+}
+
+/*
+  pim_if_add_vif() uses ifindex as vif_index
+ */
+int pim_if_find_vifindex_by_ifindex(int ifindex)
+{
+  return ifindex;
+}
+
+int pim_if_lan_delay_enabled(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+  zassert(pim_ifp->pim_number_of_nonlandelay_neighbors >= 0);
+
+  return pim_ifp->pim_number_of_nonlandelay_neighbors == 0;
+}
+
+uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp)
+{
+  if (pim_if_lan_delay_enabled(ifp)) {
+    struct pim_interface *pim_ifp;
+    pim_ifp = ifp->info;
+    return pim_ifp->pim_neighbors_highest_propagation_delay_msec;
+  }
+  else {
+    return PIM_DEFAULT_PROPAGATION_DELAY_MSEC;
+  }
+}
+
+uint16_t pim_if_effective_override_interval_msec(struct interface *ifp)
+{
+  if (pim_if_lan_delay_enabled(ifp)) {
+    struct pim_interface *pim_ifp;
+    pim_ifp = ifp->info;
+    return pim_ifp->pim_neighbors_highest_override_interval_msec;
+  }
+  else {
+    return PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC;
+  }
+}
+
+int pim_if_t_override_msec(struct interface *ifp)
+{
+  int effective_override_interval_msec;
+  int t_override_msec;
+
+  effective_override_interval_msec =
+    pim_if_effective_override_interval_msec(ifp);
+
+  t_override_msec = pim_rand_next(0, effective_override_interval_msec);
+
+  return t_override_msec;
+}
+
+uint16_t pim_if_jp_override_interval_msec(struct interface *ifp)
+{
+  return pim_if_effective_propagation_delay_msec(ifp) +
+    pim_if_effective_override_interval_msec(ifp);
+}
+
+/*
+  RFC 4601: 4.1.6.  State Summarization Macros
+
+  The function NBR( I, A ) uses information gathered through PIM Hello
+  messages to map the IP address A of a directly connected PIM
+  neighbor router on interface I to the primary IP address of the same
+  router (Section 4.3.4).  The primary IP address of a neighbor is the
+  address that it uses as the source of its PIM Hello messages.
+*/
+struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp,
+					  struct in_addr addr)
+{
+  struct listnode *neighnode;
+  struct pim_neighbor *neigh;
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+
+  pim_ifp = ifp->info;
+  if (!pim_ifp) {
+    zlog_warn("%s: multicast not enabled on interface %s",
+	      __PRETTY_FUNCTION__,
+	      ifp->name);
+    return 0;
+  }
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+
+    /* primary address ? */
+    if (neigh->source_addr.s_addr == addr.s_addr)
+      return neigh;
+
+    /* secondary address ? */
+    if (pim_neighbor_find_secondary(neigh, addr))
+	return neigh;
+  }
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_debug("%s: neighbor not found for address %s on interface %s",
+	       __PRETTY_FUNCTION__, 
+	       addr_str, ifp->name);
+  }
+
+  return 0;
+}
+
+long pim_if_t_suppressed_msec(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  long t_suppressed_msec;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  /* join suppression disabled ? */
+  if (PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options))
+    return 0;
+
+  /* t_suppressed = t_periodic * rand(1.1, 1.4) */
+
+  t_suppressed_msec = qpim_t_periodic * pim_rand_next(1100, 1400);
+
+  return t_suppressed_msec;
+}
diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h
new file mode 100644
index 0000000..4136a43
--- /dev/null
+++ b/pimd/pim_iface.h
@@ -0,0 +1,140 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_IFACE_H
+#define PIM_IFACE_H
+
+#include <zebra.h>
+
+#include "if.h"
+#include "vty.h"
+
+#include "pim_igmp.h"
+
+#define PIM_IF_MASK_PIM                             (1 << 0)
+#define PIM_IF_MASK_IGMP                            (1 << 1)
+#define PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS          (1 << 2)
+#define PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION (1 << 3)
+
+#define PIM_IF_IS_DELETED(ifp) ((ifp)->ifindex == IFINDEX_INTERNAL)
+
+#define PIM_IF_TEST_PIM(options) (PIM_IF_MASK_PIM & (options))
+#define PIM_IF_TEST_IGMP(options) (PIM_IF_MASK_IGMP & (options))
+#define PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(options) (PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS & (options))
+#define PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) (PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION & (options))
+
+#define PIM_IF_DO_PIM(options) ((options) |= PIM_IF_MASK_PIM)
+#define PIM_IF_DO_IGMP(options) ((options) |= PIM_IF_MASK_IGMP)
+#define PIM_IF_DO_IGMP_LISTEN_ALLROUTERS(options) ((options) |= PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS)
+#define PIM_IF_DO_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) ((options) |= PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION)
+
+#define PIM_IF_DONT_PIM(options) ((options) &= ~PIM_IF_MASK_PIM)
+#define PIM_IF_DONT_IGMP(options) ((options) &= ~PIM_IF_MASK_IGMP)
+#define PIM_IF_DONT_IGMP_LISTEN_ALLROUTERS(options) ((options) &= ~PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS)
+#define PIM_IF_DONT_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) ((options) &= ~PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION)
+
+struct pim_interface {
+  uint32_t       options;                            /* bit vector */
+  int            mroute_vif_index;
+  struct in_addr primary_address; /* remember addr to detect change */
+
+  int          igmp_default_robustness_variable;   /* IGMPv3 QRV */
+  int          igmp_default_query_interval;        /* IGMPv3 secs between general queries */
+  int          igmp_query_max_response_time_dsec;  /* IGMPv3 Max Response Time in dsecs */
+  struct list *igmp_socket_list;                   /* list of struct igmp_sock */
+
+  int            pim_sock_fd;       /* PIM socket file descriptor */
+  struct thread *t_pim_sock_read;   /* thread for reading PIM socket */
+  int64_t        pim_sock_creation; /* timestamp of PIM socket creation */
+
+  struct thread *t_pim_hello_timer;
+  int            pim_hello_period;
+  int            pim_default_holdtime;
+  int            pim_triggered_hello_delay;
+  uint32_t       pim_generation_id;
+  uint16_t       pim_propagation_delay_msec; /* config */
+  uint16_t       pim_override_interval_msec; /* config */
+  struct list   *pim_neighbor_list; /* list of struct pim_neighbor */
+  struct list   *pim_ifchannel_list; /* list of struct pim_ifchannel */
+
+  /* neighbors without lan_delay */
+  int            pim_number_of_nonlandelay_neighbors;
+  uint16_t       pim_neighbors_highest_propagation_delay_msec;
+  uint16_t       pim_neighbors_highest_override_interval_msec;
+
+  /* DR Election */
+  int64_t        pim_dr_election_last; /* timestamp */
+  int            pim_dr_election_count;
+  struct in_addr pim_dr_addr;
+  uint32_t       pim_dr_priority;            /* config */
+  int            pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */
+
+  int64_t        pim_ifstat_start; /* start timestamp for stats */
+  uint32_t       pim_ifstat_hello_sent;
+  uint32_t       pim_ifstat_hello_sendfail;
+  uint32_t       pim_ifstat_hello_recv;
+  uint32_t       pim_ifstat_hello_recvfail;
+};
+
+/*
+  if default_holdtime is set (>= 0), use it;
+  otherwise default_holdtime is 3.5 * hello_period
+ */
+#define PIM_IF_DEFAULT_HOLDTIME(pim_ifp) \
+  (((pim_ifp)->pim_default_holdtime < 0) ? \
+  ((pim_ifp)->pim_hello_period * 7 / 2) : \
+  ((pim_ifp)->pim_default_holdtime))
+
+void pim_if_init(void);
+
+struct pim_interface *pim_if_new(struct interface *ifp, int igmp, int pim);
+void                  pim_if_delete(struct interface *ifp);
+int                   pim_if_igmp_listen(struct vty *vty,
+					 struct interface *ifp);
+void pim_if_addr_add(struct connected *ifc);
+void pim_if_addr_del(struct connected *ifc);
+void pim_if_addr_add_all(struct interface *ifp);
+void pim_if_addr_del_all(struct interface *ifp);
+void pim_if_addr_del_all_igmp(struct interface *ifp);
+void pim_if_addr_del_all_pim(struct interface *ifp);
+
+int pim_if_add_vif(struct interface *ifp);
+int pim_if_del_vif(struct interface *ifp);
+void pim_if_add_vif_all(void);
+void pim_if_del_vif_all(void);
+
+struct interface *pim_if_find_by_vif_index(int vif_index);
+int pim_if_find_vifindex_by_ifindex(int ifindex);
+
+int pim_if_lan_delay_enabled(struct interface *ifp);
+uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp);
+uint16_t pim_if_effective_override_interval_msec(struct interface *ifp);
+uint16_t pim_if_jp_override_interval_msec(struct interface *ifp);
+struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp,
+					  struct in_addr addr);
+
+long pim_if_t_suppressed_msec(struct interface *ifp);
+int pim_if_t_override_msec(struct interface *ifp);
+
+struct in_addr pim_find_first_addr(struct interface *ifp);
+
+#endif /* PIM_IFACE_H */
diff --git a/pimd/pim_ifchannel.c b/pimd/pim_ifchannel.c
new file mode 100644
index 0000000..8c61c40
--- /dev/null
+++ b/pimd/pim_ifchannel.c
@@ -0,0 +1,935 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "linklist.h"
+#include "thread.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+#include "pim_ifchannel.h"
+#include "pim_zebra.h"
+#include "pim_time.h"
+#include "pim_msg.h"
+#include "pim_pim.h"
+#include "pim_join.h"
+#include "pim_rpf.h"
+
+void pim_ifchannel_free(struct pim_ifchannel *ch)
+{
+  zassert(!ch->t_ifjoin_expiry_timer);
+  zassert(!ch->t_ifjoin_prune_pending_timer);
+  zassert(!ch->t_ifassert_timer);
+
+  XFREE(MTYPE_PIM_IFCHANNEL, ch);
+}
+
+void pim_ifchannel_delete(struct pim_ifchannel *ch)
+{
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = ch->interface->info;
+  zassert(pim_ifp);
+
+  if (ch->ifjoin_state != PIM_IFJOIN_NOINFO) {
+    pim_upstream_update_join_desired(ch->upstream);
+  }
+
+  pim_upstream_del(ch->upstream);
+
+  THREAD_OFF(ch->t_ifjoin_expiry_timer);
+  THREAD_OFF(ch->t_ifjoin_prune_pending_timer);
+  THREAD_OFF(ch->t_ifassert_timer);
+
+  /*
+    notice that listnode_delete() can't be moved
+    into pim_ifchannel_free() because the later is
+    called by list_delete_all_node()
+  */
+  listnode_delete(pim_ifp->pim_ifchannel_list, ch);
+
+  pim_ifchannel_free(ch);
+}
+
+#define IFCHANNEL_NOINFO(ch)					\
+  (								\
+   ((ch)->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO)	\
+   &&								\
+   ((ch)->ifjoin_state == PIM_IFJOIN_NOINFO)			\
+   &&								\
+   ((ch)->ifassert_state == PIM_IFASSERT_NOINFO)		\
+   )
+   
+static void delete_on_noinfo(struct pim_ifchannel *ch)
+{
+  if (IFCHANNEL_NOINFO(ch)) {
+
+    /* In NOINFO state, timers should have been cleared */
+    zassert(!ch->t_ifjoin_expiry_timer);
+    zassert(!ch->t_ifjoin_prune_pending_timer);
+    zassert(!ch->t_ifassert_timer);
+    
+    pim_ifchannel_delete(ch);
+  }
+}
+
+void pim_ifchannel_ifjoin_switch(const char *caller,
+				 struct pim_ifchannel *ch,
+				 enum pim_ifjoin_state new_state)
+{
+  enum pim_ifjoin_state old_state = ch->ifjoin_state;
+
+  if (old_state == new_state) {
+    zlog_debug("%s calledby %s: non-transition on state %d (%s)",
+	       __PRETTY_FUNCTION__, caller, new_state,
+	       pim_ifchannel_ifjoin_name(new_state));
+    return;
+  }
+
+  zassert(old_state != new_state);
+
+  ch->ifjoin_state = new_state;
+
+  /* Transition to/from NOINFO ? */
+  if (
+      (old_state == PIM_IFJOIN_NOINFO)
+      ||
+      (new_state == PIM_IFJOIN_NOINFO)
+      ) {
+
+    if (PIM_DEBUG_PIM_EVENTS) {
+      char src_str[100];
+      char grp_str[100];
+      pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+      zlog_debug("PIM_IFCHANNEL_%s: (S,G)=(%s,%s) on interface %s",
+		 ((new_state == PIM_IFJOIN_NOINFO) ? "DOWN" : "UP"),
+		 src_str, grp_str, ch->interface->name);
+    }
+
+    /*
+      Record uptime of state transition to/from NOINFO
+    */
+    ch->ifjoin_creation = pim_time_monotonic_sec();
+
+    pim_upstream_update_join_desired(ch->upstream);
+  }
+}
+
+const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state)
+{
+  switch (ifjoin_state) {
+  case PIM_IFJOIN_NOINFO:        return "NOINFO";
+  case PIM_IFJOIN_JOIN:          return "JOIN";
+  case PIM_IFJOIN_PRUNE_PENDING: return "PRUNEP";
+  }
+
+  return "ifjoin_bad_state";
+}
+
+const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state)
+{
+  switch (ifassert_state) {
+  case PIM_IFASSERT_NOINFO:      return "NOINFO";
+  case PIM_IFASSERT_I_AM_WINNER: return "WINNER";
+  case PIM_IFASSERT_I_AM_LOSER:  return "LOSER";
+  }
+
+  return "ifassert_bad_state";
+}
+
+static struct pim_ifchannel *pim_ifchannel_new(struct interface *ifp,
+					       struct in_addr source_addr,
+					       struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  struct pim_interface *pim_ifp;
+  struct pim_upstream  *up;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  up = pim_upstream_add(source_addr, group_addr);
+  if (!up) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_err("%s: could not attach upstream (S,G)=(%s,%s) on interface %s",
+	     __PRETTY_FUNCTION__,
+	     src_str, grp_str, ifp->name);
+    return 0;
+  }
+
+  ch = XMALLOC(MTYPE_PIM_IFCHANNEL, sizeof(*ch));
+  if (!ch) {
+    zlog_err("%s: PIM XMALLOC(%d) failure",
+	     __PRETTY_FUNCTION__, sizeof(*ch));
+    return 0;
+  }
+  
+  ch->upstream                     = up;
+  ch->interface                    = ifp;
+  ch->source_addr                  = source_addr;
+  ch->group_addr                   = group_addr;
+  ch->local_ifmembership           = PIM_IFMEMBERSHIP_NOINFO;
+
+  ch->ifjoin_state                 = PIM_IFJOIN_NOINFO;
+  ch->t_ifjoin_expiry_timer        = 0;
+  ch->t_ifjoin_prune_pending_timer = 0;
+  ch->ifjoin_creation              = 0;
+
+  /*
+    RFC 4601: 4.6.5.  Assert State Macros
+
+    AssertWinner(S,G,I) is the IP source address of the Assert(S,G)
+    packet that won an Assert.
+
+    AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I)
+    defaults to Infinity when in the NoInfo state.
+  */
+  ch->ifassert_state               = PIM_IFASSERT_NOINFO;
+  ch->t_ifassert_timer             = 0;
+  ch->ifassert_winner.s_addr       = PIM_NET_INADDR_ANY;
+  ch->ifassert_winner_metric       = qpim_infinite_assert_metric;
+  ch->ifassert_creation            = pim_time_monotonic_sec();
+
+  listnode_add(pim_ifp->pim_ifchannel_list, ch);
+
+  zassert(IFCHANNEL_NOINFO(ch));
+
+  return ch;
+}
+
+struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp,
+					 struct in_addr source_addr,
+					 struct in_addr group_addr)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *ch_node;
+  struct pim_ifchannel *ch;
+
+  zassert(ifp);
+
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s: (S,G)=(%s,%s): multicast not enabled on interface %s",
+	      __FILE__,
+	      src_str, grp_str,
+	      ifp->name);
+    return 0;
+  }
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+    if (
+	(source_addr.s_addr == ch->source_addr.s_addr) &&
+	(group_addr.s_addr == ch->group_addr.s_addr)
+	) {
+      return ch;
+    }
+  }
+
+  return 0;
+}
+
+void pim_ifchannel_membership_clear(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *ch_node;
+  struct pim_ifchannel *ch;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+    ch->local_ifmembership = PIM_IFMEMBERSHIP_NOINFO;
+  }
+}
+
+void pim_ifchannel_delete_on_noinfo(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *node;
+  struct listnode      *next_node;
+  struct pim_ifchannel *ch;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, node, next_node, ch)) {
+    delete_on_noinfo(ch);
+  }
+}
+
+static struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp,
+					       struct in_addr source_addr,
+					       struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  char src_str[100];
+  char grp_str[100];
+
+  ch = pim_ifchannel_find(ifp, source_addr, group_addr);
+  if (ch)
+    return ch;
+
+  ch = pim_ifchannel_new(ifp, source_addr, group_addr);
+  if (ch)
+    return ch;
+    
+  pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+  pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+  zlog_warn("%s: pim_ifchannel_new() failure for (S,G)=(%s,%s) on interface %s",
+	    __PRETTY_FUNCTION__,
+	    src_str, grp_str, ifp->name);
+
+  return 0;
+}
+
+static void ifjoin_to_noinfo(struct pim_ifchannel *ch)
+{
+  pim_forward_stop(ch);
+  pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_NOINFO);
+  delete_on_noinfo(ch);
+}
+
+static int on_ifjoin_expiry_timer(struct thread *t)
+{
+  struct pim_ifchannel *ch;
+
+  zassert(t);
+  ch = THREAD_ARG(t);
+  zassert(ch);
+
+  ch->t_ifjoin_expiry_timer = 0;
+
+  zassert(ch->ifjoin_state == PIM_IFJOIN_JOIN);
+
+  ifjoin_to_noinfo(ch);
+  /* ch may have been deleted */
+
+  return 0;
+}
+
+static void prune_echo(struct interface *ifp,
+		       struct in_addr source_addr,
+		       struct in_addr group_addr)
+{
+  struct pim_interface *pim_ifp;
+  struct in_addr neigh_dst_addr;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  neigh_dst_addr = pim_ifp->primary_address;
+
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char source_str[100];
+    char group_str[100];
+    char neigh_dst_str[100];
+    pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<neigh?>", neigh_dst_addr, neigh_dst_str, sizeof(neigh_dst_str));
+    zlog_debug("%s: sending PruneEcho(S,G)=(%s,%s) to upstream=%s on interface %s",
+	       __PRETTY_FUNCTION__, source_str, group_str, neigh_dst_str, ifp->name);
+  }
+
+  pim_joinprune_send(ifp, neigh_dst_addr, source_addr, group_addr,
+		     0 /* boolean: send_join=false (prune) */);
+}
+
+static int on_ifjoin_prune_pending_timer(struct thread *t)
+{
+  struct pim_ifchannel *ch;
+  int send_prune_echo; /* boolean */
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  struct in_addr ch_source;
+  struct in_addr ch_group;
+
+  zassert(t);
+  ch = THREAD_ARG(t);
+  zassert(ch);
+
+  ch->t_ifjoin_prune_pending_timer = 0;
+
+  zassert(ch->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING);
+
+  /* Send PruneEcho(S,G) ? */
+  ifp = ch->interface;
+  pim_ifp = ifp->info;
+  send_prune_echo = (listcount(pim_ifp->pim_neighbor_list) > 1);
+  if (send_prune_echo) {
+    ch_source = ch->source_addr;
+    ch_group = ch->group_addr;
+  }
+
+  ifjoin_to_noinfo(ch);
+  /* from here ch may have been deleted */
+
+  if (send_prune_echo)
+    prune_echo(ifp, ch_source, ch_group);
+
+  return 0;
+}
+
+static void check_recv_upstream(int is_join,
+				struct interface *recv_ifp,
+				struct in_addr upstream,
+				struct in_addr source_addr,
+				struct in_addr group_addr,
+				uint8_t source_flags,
+				int holdtime)
+{
+  struct pim_upstream *up;
+
+  /* Upstream (S,G) in Joined state ? */
+  up = pim_upstream_find(source_addr, group_addr);
+  if (!up)
+    return;
+  if (up->join_state != PIM_UPSTREAM_JOINED)
+    return;
+
+  /* Upstream (S,G) in Joined state */
+
+  if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) {
+    /* RPF'(S,G) not found */
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s %s: RPF'(%s,%s) not found",
+	      __FILE__, __PRETTY_FUNCTION__, 
+	      src_str, grp_str);
+    return;
+  }
+
+  /* upstream directed to RPF'(S,G) ? */
+  if (upstream.s_addr != up->rpf.rpf_addr.s_addr) {
+    char src_str[100];
+    char grp_str[100];
+    char up_str[100];
+    char rpf_str[100];
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<up?>", upstream, up_str, sizeof(up_str));
+    pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str));
+    zlog_warn("%s %s: (S,G)=(%s,%s) upstream=%s not directed to RPF'(S,G)=%s on interface %s",
+	      __FILE__, __PRETTY_FUNCTION__, 
+	      src_str, grp_str,
+	      up_str, rpf_str, recv_ifp->name);
+    return;
+  }
+  /* upstream directed to RPF'(S,G) */
+
+  if (is_join) {
+    /* Join(S,G) to RPF'(S,G) */
+    pim_upstream_join_suppress(up, up->rpf.rpf_addr, holdtime);
+    return;
+  }
+
+  /* Prune to RPF'(S,G) */
+
+  if (source_flags & PIM_RPT_BIT_MASK) {
+    if (source_flags & PIM_WILDCARD_BIT_MASK) {
+      /* Prune(*,G) to RPF'(S,G) */
+      pim_upstream_join_timer_decrease_to_t_override("Prune(*,G)",
+						     up, up->rpf.rpf_addr);
+      return;
+    }
+
+    /* Prune(S,G,rpt) to RPF'(S,G) */
+    pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)",
+						   up, up->rpf.rpf_addr);
+    return;
+  }
+
+  /* Prune(S,G) to RPF'(S,G) */
+  pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up,
+						 up->rpf.rpf_addr);
+}
+
+static int nonlocal_upstream(int is_join,
+			     struct interface *recv_ifp,
+			     struct in_addr upstream,
+			     struct in_addr source_addr,
+			     struct in_addr group_addr,
+			     uint8_t source_flags,
+			     uint16_t holdtime)
+{
+  struct pim_interface *recv_pim_ifp;
+  struct in_addr ifaddr;
+  int local; /* boolean */
+
+  recv_pim_ifp = recv_ifp->info;
+  zassert(recv_pim_ifp);
+
+  ifaddr = recv_pim_ifp->primary_address;
+
+  local = upstream.s_addr == ifaddr.s_addr;
+  
+  if (PIM_DEBUG_PIM_TRACE) {
+    char up_str[100];
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<upstream?>", upstream, up_str, sizeof(up_str));
+    pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s: recv %s (S,G)=(%s,%s) to %s upstream=%s on %s",
+	      __PRETTY_FUNCTION__,
+	      is_join ? "join" : "prune",
+	      src_str, grp_str,
+	      local ? "local" : "non-local",
+	      up_str, recv_ifp->name);
+  }
+
+  if (local)
+    return 0;
+
+  /*
+    Since recv upstream addr was not directed to our primary
+    address, check if we should react to it in any way.
+  */
+  check_recv_upstream(is_join, recv_ifp, upstream, source_addr, group_addr,
+		      source_flags, holdtime);
+
+  return 1;
+}
+
+void pim_ifchannel_join_add(struct interface *ifp,
+			    struct in_addr upstream,
+			    struct in_addr source_addr,
+			    struct in_addr group_addr,
+			    uint8_t source_flags,
+			    uint16_t holdtime)
+{
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  if (nonlocal_upstream(1 /* join */, ifp, upstream,
+			source_addr, group_addr, source_flags, holdtime)) {
+    return;
+  }
+
+  ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+  if (!ch)
+    return;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  switch (ch->ifjoin_state) {
+  case PIM_IFJOIN_NOINFO:
+    pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN);
+    if (pim_ifchannel_isin_oiflist(ch, pim_ifp->primary_address)) {
+      pim_forward_start(ch);
+    }
+    break;
+  case PIM_IFJOIN_JOIN:
+    zassert(!ch->t_ifjoin_prune_pending_timer);
+
+    /*
+      In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to a
+      previously received join message with holdtime=0xFFFF.
+     */
+    if (ch->t_ifjoin_expiry_timer) {
+      unsigned long remain =
+	thread_timer_remain_second(ch->t_ifjoin_expiry_timer);
+      if (remain > holdtime) {
+	/*
+	  RFC 4601: 4.5.3.  Receiving (S,G) Join/Prune Messages
+
+	  Transitions from Join State
+
+          The (S,G) downstream state machine on interface I remains in
+          Join state, and the Expiry Timer (ET) is restarted, set to
+          maximum of its current value and the HoldTime from the
+          triggering Join/Prune message.
+
+	  Conclusion: Do not change the ET if the current value is
+	  higher than the received join holdtime.
+	 */
+	return;
+      }
+    }
+    THREAD_OFF(ch->t_ifjoin_expiry_timer);
+    break;
+  case PIM_IFJOIN_PRUNE_PENDING:
+    zassert(!ch->t_ifjoin_expiry_timer);
+    zassert(ch->t_ifjoin_prune_pending_timer);
+    THREAD_OFF(ch->t_ifjoin_prune_pending_timer);
+    pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN);
+    break;
+  }
+
+  zassert(!IFCHANNEL_NOINFO(ch));
+
+  if (holdtime != 0xFFFF) {
+    THREAD_TIMER_ON(master, ch->t_ifjoin_expiry_timer,
+		    on_ifjoin_expiry_timer,
+		    ch, holdtime);
+  }
+}
+
+void pim_ifchannel_prune(struct interface *ifp,
+			 struct in_addr upstream,
+			 struct in_addr source_addr,
+			 struct in_addr group_addr,
+			 uint8_t source_flags,
+			 uint16_t holdtime)
+{
+  struct pim_ifchannel *ch;
+  int jp_override_interval_msec;
+
+  if (nonlocal_upstream(0 /* prune */, ifp, upstream,
+			source_addr, group_addr, source_flags, holdtime)) {
+    return;
+  }
+
+  ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+  if (!ch)
+    return;
+
+  switch (ch->ifjoin_state) {
+  case PIM_IFJOIN_NOINFO:
+  case PIM_IFJOIN_PRUNE_PENDING:
+    /* nothing to do */
+    break;
+  case PIM_IFJOIN_JOIN:
+    {
+      struct pim_interface *pim_ifp;
+
+      pim_ifp = ifp->info;
+
+      zassert(ch->t_ifjoin_expiry_timer);
+      zassert(!ch->t_ifjoin_prune_pending_timer);
+
+      THREAD_OFF(ch->t_ifjoin_expiry_timer);
+      
+      pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_PRUNE_PENDING);
+      
+      if (listcount(pim_ifp->pim_neighbor_list) > 1) {
+	jp_override_interval_msec = pim_if_jp_override_interval_msec(ifp);
+      }
+      else {
+	jp_override_interval_msec = 0; /* schedule to expire immediately */
+	/* If we called ifjoin_prune() directly instead, care should
+	   be taken not to use "ch" afterwards since it would be
+	   deleted. */
+      }
+      
+      THREAD_TIMER_MSEC_ON(master, ch->t_ifjoin_prune_pending_timer,
+			   on_ifjoin_prune_pending_timer,
+			   ch, jp_override_interval_msec);
+      
+      zassert(!ch->t_ifjoin_expiry_timer);
+      zassert(ch->t_ifjoin_prune_pending_timer);
+    }
+    break;
+  }
+
+}
+
+void pim_ifchannel_local_membership_add(struct interface *ifp,
+					struct in_addr source_addr,
+					struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  struct pim_interface *pim_ifp;
+
+  /* PIM enabled on interface? */
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return;
+  if (!PIM_IF_TEST_PIM(pim_ifp->options))
+    return;
+
+  ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+  if (!ch) {
+    return;
+  }
+
+  ch->local_ifmembership = PIM_IFMEMBERSHIP_INCLUDE;
+
+  pim_upstream_update_join_desired(ch->upstream);
+
+  zassert(!IFCHANNEL_NOINFO(ch));
+}
+
+void pim_ifchannel_local_membership_del(struct interface *ifp,
+					struct in_addr source_addr,
+					struct in_addr group_addr)
+{
+  struct pim_ifchannel *ch;
+  struct pim_interface *pim_ifp;
+
+  /* PIM enabled on interface? */
+  pim_ifp = ifp->info;
+  if (!pim_ifp)
+    return;
+  if (!PIM_IF_TEST_PIM(pim_ifp->options))
+    return;
+
+  ch = pim_ifchannel_find(ifp, source_addr, group_addr);
+  if (!ch)
+    return;
+
+  ch->local_ifmembership = PIM_IFMEMBERSHIP_NOINFO;
+
+  pim_upstream_update_join_desired(ch->upstream);
+
+  delete_on_noinfo(ch);
+}
+
+/*
+  RFC 4601: 4.1.6.  State Summarization Macros
+
+   The set "joins(S,G)" is the set of all interfaces on which the
+   router has received (S,G) Joins:
+
+   joins(S,G) =
+       { all interfaces I such that
+         DownstreamJPState(S,G,I) is either Join or Prune-Pending }
+ */
+int pim_ifchannel_isin_joins(struct pim_ifchannel *ch)
+{
+  return (ch->ifjoin_state != PIM_IFJOIN_NOINFO);
+}
+
+/*
+  RFC 4601: 4.1.6.  State Summarization Macros
+
+   pim_include(S,G) =
+       { all interfaces I such that:
+         ( (I_am_DR( I ) AND lost_assert(S,G,I) == FALSE )
+           OR AssertWinner(S,G,I) == me )
+          AND  local_receiver_include(S,G,I) }
+
+   AssertWinner(S,G,I) is the IP source address of the Assert(S,G)
+   packet that won an Assert.
+
+   The clause "local_receiver_include(S,G,I)" is true if the IGMP/MLD
+   module or other local membership mechanism has determined that
+   local members on interface I desire to receive traffic sent
+   specifically by S to G.
+*/
+int pim_ifchannel_isin_pim_include(struct pim_ifchannel *ch,
+				   struct in_addr me_ifaddr)
+{
+  struct pim_interface *pim_iface;
+
+  /* local_receiver_include(S,G,I) ? */
+  if (ch->local_ifmembership != PIM_IFMEMBERSHIP_INCLUDE)
+    return 0; /* false */
+
+  /* AssertWinner(S,G,I) == me ? */
+  if (ch->ifassert_winner.s_addr == me_ifaddr.s_addr)
+    return 1; /* true */
+
+  pim_iface = ch->interface->info;
+
+  return (
+	  /* I_am_DR( I ) */
+	  (pim_iface->pim_dr_addr.s_addr == me_ifaddr.s_addr)
+	  &&
+	  /* lost_assert(S,G,I) == FALSE */
+	  (!pim_ifchannel_isin_lost_assert(ch, me_ifaddr))
+	  );
+}
+
+/*
+  RFC 4601: 4.6.1.  (S,G) Assert Message State Machine
+
+  CouldAssert(S,G,I) =
+  SPTbit(S,G)==TRUE
+  AND (RPF_interface(S) != I)
+  AND (I in ( ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) )
+                 (+) ( pim_include(*,G) (-) pim_exclude(S,G) )
+                 (-) lost_assert(*,G)
+                 (+) joins(S,G) (+) pim_include(S,G) ) )
+
+  CouldAssert(S,G,I) is true for downstream interfaces that would be in
+  the inherited_olist(S,G) if (S,G) assert information was not taken
+  into account.
+*/
+static int could_assert(struct pim_ifchannel *ch, struct in_addr me_ifaddr)
+{
+  /* SPTbit(S,G) is always true for PIM-SSM-Only Routers */
+
+  if (ch->upstream->rpf.source_nexthop.interface == ch->interface)
+    return 0; /* false */
+
+  /* I in joins(S,G) ? */
+  if (pim_ifchannel_isin_joins(ch))
+    return 1; /* true */
+
+  /* I in pim_include(S,G) ? */
+  return pim_ifchannel_isin_pim_include(ch, me_ifaddr);
+}
+
+/*
+  RFC 4601: 4.6.3.  Assert Metrics
+
+   spt_assert_metric(S,I) gives the assert metric we use if we're
+   sending an assert based on active (S,G) forwarding state:
+
+    assert_metric
+    spt_assert_metric(S,I) {
+      return {0,MRIB.pref(S),MRIB.metric(S),my_ip_address(I)}
+    }
+*/
+static struct pim_assert_metric *spt_assert_metric(struct pim_ifchannel *ch,
+						   struct in_addr me_ifaddr)
+{
+  ch->ifassert_spt_assert_metric.rpt_bit_flag      = 0;
+  ch->ifassert_spt_assert_metric.metric_preference = ch->upstream->rpf.source_nexthop.mrib_metric_preference;
+  ch->ifassert_spt_assert_metric.route_metric      = ch->upstream->rpf.source_nexthop.mrib_route_metric;
+  ch->ifassert_spt_assert_metric.ip_address        = me_ifaddr;
+
+  return &ch->ifassert_spt_assert_metric;
+}
+
+/*
+  RFC 4601: 4.6.3.  Assert Metrics
+
+   An assert metric for (S,G) to include in (or compare against) an
+   Assert message sent on interface I should be computed using the
+   following pseudocode:
+
+  assert_metric  my_assert_metric(S,G,I) {
+    if( CouldAssert(S,G,I) == TRUE ) {
+      return spt_assert_metric(S,I)
+    } else if( CouldAssert(*,G,I) == TRUE ) {
+      return rpt_assert_metric(G,I)
+    } else {
+      return infinite_assert_metric()
+    }
+  }
+*/
+struct pim_assert_metric *my_assert_metric(struct pim_ifchannel *ch)
+{
+  struct pim_ifp *pim_ifp;
+  struct in_addr me_ifaddr;
+
+  pim_ifp = ch->interface->info;
+
+  if (pim_ifp) {
+    me_ifaddr = me_ifaddr;
+    if (could_assert(ch, me_ifaddr)) {
+      return spt_assert_metric(ch, me_ifaddr);
+    }
+  }
+
+  return &qpim_infinite_assert_metric;
+}
+
+/*
+  RFC 4601: 4.6.5.  Assert State Macros
+
+     bool lost_assert(S,G,I) {
+       if ( RPF_interface(S) == I ) {
+          return FALSE
+       } else {
+          return ( AssertWinner(S,G,I) != NULL AND
+                   AssertWinner(S,G,I) != me  AND
+                   (AssertWinnerMetric(S,G,I) is better
+                      than spt_assert_metric(S,I) )
+       }
+     }
+
+  AssertWinner(S,G,I) is the IP source address of the Assert(S,G)
+  packet that won an Assert.
+*/
+int pim_ifchannel_isin_lost_assert(struct pim_ifchannel *ch,
+				   struct in_addr me_ifaddr)
+{
+  /* RPF_interface(S) == I ? */
+  if (ch->upstream->rpf.source_nexthop.interface == ch->interface)
+    return 0; /* false */
+
+  return (
+	  (PIM_INADDR_ISNOT_ANY(ch->ifassert_winner))
+	  &&
+	  (ch->ifassert_winner.s_addr != me_ifaddr.s_addr)
+	  &&
+	  pim_assert_metric_better(&ch->ifassert_winner_metric,
+				   spt_assert_metric(ch, me_ifaddr))
+	  );
+}
+
+/*
+  RFC 4601 4.2.  Data Packet Forwarding Rules
+  RFC 4601 4.8.2.  PIM-SSM-Only Routers
+
+  Additionally, the Packet forwarding rules of Section 4.2 can be
+  simplified in a PIM-SSM-only router:
+  
+  iif is the incoming interface of the packet.
+  oiflist = NULL
+  if (iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined) {
+    oiflist = inherited_olist(S,G)
+  } else if (iif is in inherited_olist(S,G)) {
+    send Assert(S,G) on iif
+  }
+  oiflist = oiflist (-) iif
+  forward packet on all interfaces in oiflist
+  
+  Macro:
+  inherited_olist(S,G) =
+    joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+
+  Note:
+  - The following test is performed as response to WRONGVIF kernel
+    upcall:
+    if (iif is in inherited_olist(S,G)) {
+      send Assert(S,G) on iif
+    }
+    See pim_mroute.c mroute_msg().
+*/
+int pim_ifchannel_isin_oiflist(struct pim_ifchannel *ch,
+			       struct in_addr me_ifaddr)
+{
+  if (ch->upstream->join_state != PIM_UPSTREAM_JOINED) {
+    /* oiflist is NULL */
+    return 0;
+  }
+
+  /* oiflist = oiflist (-) iif */
+  if (ch->interface == ch->upstream->rpf.source_nexthop.interface)
+    return 0;
+
+  if (pim_ifchannel_isin_lost_assert(ch, me_ifaddr))
+    return 0;
+
+  if (pim_ifchannel_isin_joins(ch))
+    return 1;
+
+  return pim_ifchannel_isin_pim_include(ch, me_ifaddr);
+}
diff --git a/pimd/pim_ifchannel.h b/pimd/pim_ifchannel.h
new file mode 100644
index 0000000..3939372
--- /dev/null
+++ b/pimd/pim_ifchannel.h
@@ -0,0 +1,128 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_IFCHANNEL_H
+#define PIM_IFCHANNEL_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_upstream.h"
+#include "pim_assert.h"
+
+enum pim_ifmembership {
+  PIM_IFMEMBERSHIP_NOINFO,
+  PIM_IFMEMBERSHIP_INCLUDE
+};
+
+enum pim_ifjoin_state {
+  PIM_IFJOIN_NOINFO,
+  PIM_IFJOIN_JOIN,
+  PIM_IFJOIN_PRUNE_PENDING
+};
+
+enum pim_ifassert_state {
+  PIM_IFASSERT_NOINFO,
+  PIM_IFASSERT_I_AM_WINNER,
+  PIM_IFASSERT_I_AM_LOSER
+};
+
+struct pim_assert_metric {
+  uint32_t       rpt_bit_flag;
+  uint32_t       metric_preference;
+  uint32_t       route_metric;
+  struct in_addr ip_address; /* neighbor router that sourced the Assert message */
+};
+
+/*
+  Per-interface (S,G) state
+ */
+struct pim_ifchannel {
+  struct in_addr            source_addr; /* (S,G) source key */
+  struct in_addr            group_addr;  /* (S,G) group key */
+  struct interface         *interface;   /* backpointer to interface */
+
+  /* IGMPv3 determined interface has local members for (S,G) ? */
+  enum pim_ifmembership     local_ifmembership;
+
+  /* Per-interface (S,G) Join/Prune State (Section 4.1.4 of RFC4601) */
+  enum pim_ifjoin_state     ifjoin_state;
+  struct thread            *t_ifjoin_expiry_timer;
+  struct thread            *t_ifjoin_prune_pending_timer;
+  int64_t                   ifjoin_creation; /* Record uptime of ifjoin state */
+
+  /* Per-interface (S,G) Assert State (Section 4.6.1 of RFC4601) */
+  enum pim_ifassert_state   ifassert_state;
+  struct thread            *t_ifassert_timer;
+  struct in_addr            ifassert_winner;
+  struct pim_assert_metric  ifassert_winner_metric;
+  struct pim_assert_metric  ifassert_spt_assert_metric;
+  int64_t                   ifassert_creation; /* Record uptime of ifassert state */
+
+  /* Upstream (S,G) state */
+  struct pim_upstream      *upstream;
+};
+
+void pim_ifchannel_free(struct pim_ifchannel *ch);
+void pim_ifchannel_delete(struct pim_ifchannel *ch);
+void pim_ifchannel_membership_clear(struct interface *ifp);
+void pim_ifchannel_delete_on_noinfo(struct interface *ifp);
+struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp,
+					 struct in_addr source_addr,
+					 struct in_addr group_addr);
+void pim_ifchannel_join_add(struct interface *ifp,
+			    struct in_addr upstream,
+			    struct in_addr source_addr,
+			    struct in_addr group_addr,
+			    uint8_t source_flags,
+			    uint16_t holdtime);
+void pim_ifchannel_prune(struct interface *ifp,
+			 struct in_addr upstream,
+			 struct in_addr source_addr,
+			 struct in_addr group_addr,
+			 uint8_t source_flags,
+			 uint16_t holdtime);
+void pim_ifchannel_local_membership_add(struct interface *ifp,
+					struct in_addr source_addr,
+					struct in_addr group_addr);
+void pim_ifchannel_local_membership_del(struct interface *ifp,
+					struct in_addr source_addr,
+					struct in_addr group_addr);
+
+void pim_ifchannel_ifjoin_switch(const char *caller,
+				 struct pim_ifchannel *ch,
+				 enum pim_ifjoin_state new_state);
+const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state);
+const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state);
+
+int pim_ifchannel_isin_joins(struct pim_ifchannel *ch);
+int pim_ifchannel_isin_pim_include(struct pim_ifchannel *ch,
+				   struct in_addr me_ifaddr);
+int pim_ifchannel_isin_lost_assert(struct pim_ifchannel *ch,
+				   struct in_addr me_ifaddr);
+int pim_ifchannel_isin_oiflist(struct pim_ifchannel *ch,
+			       struct in_addr me_ifaddr);
+
+struct pim_assert_metric *my_assert_metric(struct pim_ifchannel *ch);
+
+#endif /* PIM_IFCHANNEL_H */
diff --git a/pimd/pim_igmp.c b/pimd/pim_igmp.c
new file mode 100644
index 0000000..1e02f38
--- /dev/null
+++ b/pimd/pim_igmp.c
@@ -0,0 +1,1402 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_igmp.h"
+#include "pim_igmpv3.h"
+#include "pim_iface.h"
+#include "pim_sock.h"
+#include "pim_mroute.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#include "pim_time.h"
+#include "pim_zebra.h"
+
+#define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE        (1)
+#define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE        (2)
+#define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3)
+#define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4)
+#define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES      (5)
+#define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES      (6)
+
+static void group_timer_off(struct igmp_group *group);
+
+static struct igmp_group *find_group_by_addr(struct igmp_sock *igmp,
+					     struct in_addr group_addr);
+
+static int igmp_sock_open(struct in_addr ifaddr, int ifindex, uint32_t pim_options)
+{
+  int fd;
+  int join = 0;
+  struct in_addr group;
+
+  fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, 1 /* loop=true */);
+  if (fd < 0)
+    return -1;
+
+  if (PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(pim_options)) {
+    if (inet_aton(PIM_ALL_ROUTERS, &group)) {
+      if (!pim_socket_join(fd, group, ifaddr, ifindex))
+	++join;
+    }
+    else {
+      zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
+		__FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
+		PIM_ALL_ROUTERS, errno, strerror(errno));
+    }
+  }
+
+  /*
+    IGMP routers periodically send IGMP general queries to AllSystems=224.0.0.1
+    IGMP routers must receive general queries for querier election.
+  */
+  if (inet_aton(PIM_ALL_SYSTEMS, &group)) {
+    if (!pim_socket_join(fd, group, ifaddr, ifindex))
+      ++join;
+  }
+  else {
+    zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
+	      __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
+	      PIM_ALL_SYSTEMS, errno, strerror(errno));
+  }
+
+  if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) {
+    if (!pim_socket_join(fd, group, ifaddr, ifindex)) {
+      ++join;
+    }
+  }
+  else {
+      zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s",
+		__FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr),
+		PIM_ALL_IGMP_ROUTERS, errno, strerror(errno));
+  }    
+
+  if (!join) {
+    zlog_err("IGMP socket fd=%d could not join any group on interface address %s",
+	     fd, inet_ntoa(ifaddr));
+    close(fd);
+    fd = -1;
+  }
+
+  return fd;
+}
+
+#undef IGMP_SOCK_DUMP
+
+#ifdef IGMP_SOCK_DUMP
+static void igmp_sock_dump(array_t *igmp_sock_array)
+{
+  int size = array_size(igmp_sock_array);
+  for (int i = 0; i < size; ++i) {
+    
+    struct igmp_sock *igmp = array_get(igmp_sock_array, i);
+    
+    zlog_debug("%s %s: [%d/%d] igmp_addr=%s fd=%d",
+	       __FILE__, __PRETTY_FUNCTION__,
+	       i, size,
+	       inet_ntoa(igmp->ifaddr),
+	       igmp->fd);
+  }
+}
+#endif
+
+struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
+					      struct in_addr ifaddr)
+{
+  struct listnode  *sock_node;
+  struct igmp_sock *igmp;
+
+#ifdef IGMP_SOCK_DUMP
+  igmp_sock_dump(igmp_sock_list);
+#endif
+
+  for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
+    if (ifaddr.s_addr == igmp->ifaddr.s_addr)
+      return igmp;
+
+  return 0;
+}
+
+struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list,
+					 int fd)
+{
+  struct listnode  *sock_node;
+  struct igmp_sock *igmp;
+
+  for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
+    if (fd == igmp->fd)
+      return igmp;
+
+  return 0;
+}
+
+static int pim_igmp_other_querier_expire(struct thread *t)
+{
+  struct igmp_sock *igmp;
+
+  zassert(t);
+  igmp = THREAD_ARG(t);
+  zassert(igmp);
+
+  zassert(!igmp->t_other_querier_timer);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char ifaddr_str[100];
+    pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+    zlog_debug("%s: Querier %s resuming",
+	       __PRETTY_FUNCTION__,
+	       ifaddr_str);
+  }
+
+  /*
+    We are the current querier, then
+    re-start sending general queries.
+  */
+  pim_igmp_general_query_on(igmp);
+
+  return 0;
+}
+
+void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp)
+{
+  long other_querier_present_interval_msec;
+  struct pim_interface *pim_ifp;
+
+  zassert(igmp);
+  zassert(igmp->interface);
+  zassert(igmp->interface->info);
+
+  pim_ifp = igmp->interface->info;
+
+  if (igmp->t_other_querier_timer) {
+    /*
+      There is other querier present already,
+      then reset the other-querier-present timer.
+    */
+
+    if (PIM_DEBUG_IGMP_TRACE) {
+      char ifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+      zlog_debug("Querier %s resetting TIMER event for Other-Querier-Present",
+		 ifaddr_str);
+    }
+
+    THREAD_OFF(igmp->t_other_querier_timer);
+    zassert(!igmp->t_other_querier_timer);
+  }
+  else {
+    /*
+      We are the current querier, then stop sending general queries:
+      igmp->t_igmp_query_timer = 0;
+    */
+    pim_igmp_general_query_off(igmp);
+  }
+
+  /*
+    Since this socket is starting the other-querier-present timer,
+    there should not be periodic query timer for this socket.
+   */
+  zassert(!igmp->t_igmp_query_timer);
+
+  /*
+    RFC 3376: 8.5. Other Querier Present Interval
+
+    The Other Querier Present Interval is the length of time that must
+    pass before a multicast router decides that there is no longer
+    another multicast router which should be the querier.  This value
+    MUST be ((the Robustness Variable) times (the Query Interval)) plus
+    (one half of one Query Response Interval).
+
+    other_querier_present_interval_msec = \
+      igmp->querier_robustness_variable * \
+      1000 * igmp->querier_query_interval + \
+      100 * (pim_ifp->query_max_response_time_dsec >> 1);
+  */
+  other_querier_present_interval_msec =
+    PIM_IGMP_OQPI_MSEC(igmp->querier_robustness_variable,
+		       igmp->querier_query_interval,
+		       pim_ifp->igmp_query_max_response_time_dsec);
+  
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char ifaddr_str[100];
+    pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+    zlog_debug("Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
+	       ifaddr_str,
+	       other_querier_present_interval_msec / 1000,
+	       other_querier_present_interval_msec % 1000);
+  }
+  
+  THREAD_TIMER_MSEC_ON(master, igmp->t_other_querier_timer,
+		       pim_igmp_other_querier_expire,
+		       igmp, other_querier_present_interval_msec);
+}
+
+void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp)
+{
+  zassert(igmp);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    if (igmp->t_other_querier_timer) {
+      char ifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+      zlog_debug("IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
+		 ifaddr_str, igmp->fd, igmp->interface->name);
+    }
+  }
+  THREAD_OFF(igmp->t_other_querier_timer);
+  zassert(!igmp->t_other_querier_timer);
+}
+
+static int recv_igmp_query(struct igmp_sock *igmp, int query_version,
+			   int max_resp_code,
+			   struct in_addr from, const char *from_str,
+			   char *igmp_msg, int igmp_msg_len)
+{
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  uint8_t               resv_s_qrv;
+  uint8_t               s_flag;
+  uint8_t               qrv;
+  struct in_addr        group_addr;
+  uint16_t              recv_checksum;
+  uint16_t              checksum;
+
+  group_addr = *(struct in_addr *)(igmp_msg + 4);
+
+  ifp = igmp->interface;
+  pim_ifp = ifp->info;
+
+  recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
+
+  /* for computing checksum */
+  *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
+
+  checksum = pim_inet_checksum(igmp_msg, igmp_msg_len);
+  if (checksum != recv_checksum) {
+    zlog_warn("Recv IGMP query v%d from %s on %s: checksum mismatch: received=%x computed=%x",
+	      query_version, from_str, ifp->name, recv_checksum, checksum);
+    return -1;
+  }
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    zlog_debug("Recv IGMP query v%d from %s on %s: size=%d checksum=%x group=%s",
+	       query_version, from_str, ifp->name,
+	       igmp_msg_len, checksum, group_str);
+  }
+
+  /*
+    RFC 3376: 6.6.2. Querier Election
+
+    When a router receives a query with a lower IP address, it sets
+    the Other-Querier-Present timer to Other Querier Present Interval
+    and ceases to send queries on the network if it was the previously
+    elected querier.
+   */
+  if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) {
+    
+    if (PIM_DEBUG_IGMP_TRACE) {
+      char ifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+      zlog_debug("%s: local address %s (%u) lost querier election to %s (%u)",
+		 ifp->name,
+		 ifaddr_str, ntohl(igmp->ifaddr.s_addr),
+		 from_str, ntohl(from.s_addr));
+    }
+
+    pim_igmp_other_querier_timer_on(igmp);
+  }
+
+  /*
+    RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
+
+    Routers adopt the QRV value from the most recently received Query
+    as their own [Robustness Variable] value, unless that most
+    recently received QRV was zero, in which case the receivers use
+    the default [Robustness Variable] value specified in section 8.1
+    or a statically configured value.
+  */
+  resv_s_qrv = igmp_msg[8];
+  qrv = 7 & resv_s_qrv;
+  igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable;
+
+  /*
+    RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
+
+    Multicast routers that are not the current querier adopt the QQI
+    value from the most recently received Query as their own [Query
+    Interval] value, unless that most recently received QQI was zero,
+    in which case the receiving routers use the default.
+  */
+  if (igmp->t_other_querier_timer) {
+    /* other querier present */
+    uint8_t  qqic;
+    uint16_t qqi;
+    qqic = igmp_msg[9];
+    qqi = igmp_msg_decode8to16(qqic);
+    igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval;
+
+    if (PIM_DEBUG_IGMP_TRACE) {
+      char ifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+      zlog_debug("Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)",
+		 ifaddr_str,
+		 qqi ? "recv-non-default" : "default",
+		 igmp->querier_query_interval,
+		 qqic,
+		 from_str);
+    }
+  }
+
+  /*
+    RFC 3376: 6.6.1. Timer Updates
+
+    When a router sends or receives a query with a clear Suppress
+    Router-Side Processing flag, it must update its timers to reflect
+    the correct timeout values for the group or sources being queried.
+
+    General queries don't trigger timer update.
+  */
+  s_flag = (1 << 3) & resv_s_qrv;
+  if (!s_flag) {
+    /* s_flag is clear */
+
+    if (PIM_INADDR_IS_ANY(group_addr)) {
+      /* this is a general query */
+
+      /* log that general query should have the s_flag set */
+      zlog_warn("General IGMP query v%d from %s on %s: Route-Side Processing flag is clear",
+		query_version, from_str, ifp->name);
+    }
+    else {
+      struct igmp_group *group;
+
+      /* this is a non-general query: perform timer updates */
+
+      group = find_group_by_addr(igmp, group_addr);
+      if (group) {
+	int recv_num_sources = ntohs(*(uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET));
+
+	/*
+	  RFC 3376: 6.6.1. Timer Updates
+	  Query Q(G,A): Source Timer for sources in A are lowered to LMQT
+	  Query Q(G): Group Timer is lowered to LMQT
+	*/
+	if (recv_num_sources < 1) {
+	  /* Query Q(G): Group Timer is lowered to LMQT */
+
+	  igmp_group_timer_lower_to_lmqt(group);
+	}
+	else {
+	  /* Query Q(G,A): Source Timer for sources in A are lowered to LMQT */
+
+	  /* Scan sources in query and lower their timers to LMQT */
+	  struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET);
+	  for (int i = 0; i < recv_num_sources; ++i) {
+	    struct in_addr src_addr = sources[i];
+	    struct igmp_source *src = igmp_find_source_by_addr(group, src_addr);
+	    if (src) {
+	      igmp_source_timer_lower_to_lmqt(src);
+	    }
+	  }
+	}
+
+      }
+      else {
+	char group_str[100];
+	pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+	zlog_warn("IGMP query v%d from %s on %s: could not find group %s for timer update",
+		  query_version, from_str, ifp->name, group_str);
+      }
+    }
+  } /* s_flag is clear: timer updates */
+  
+  return 0;
+}
+
+static int igmp_v3_report(struct igmp_sock *igmp,
+			  struct in_addr from, const char *from_str,
+			  char *igmp_msg, int igmp_msg_len)
+{
+  uint16_t          recv_checksum;
+  uint16_t          checksum;
+  int               num_groups;
+  uint8_t          *group_record;
+  uint8_t          *report_pastend = (uint8_t *) igmp_msg + igmp_msg_len;
+  struct interface *ifp = igmp->interface;
+
+  if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) {
+    zlog_warn("Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d",
+	      from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE);
+    return -1;
+  }
+
+  recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET);
+
+  /* for computing checksum */
+  *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0;
+
+  checksum = pim_inet_checksum(igmp_msg, igmp_msg_len);
+  if (checksum != recv_checksum) {
+    zlog_warn("Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x",
+	      from_str, ifp->name, recv_checksum, checksum);
+    return -1;
+  }
+
+  num_groups = ntohs(*(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET));
+  if (num_groups < 1) {
+    zlog_warn("Recv IGMP report v3 from %s on %s: missing group records",
+	      from_str, ifp->name);
+    return -1;
+  }
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    zlog_debug("Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d",
+	       from_str, ifp->name, igmp_msg_len, checksum, num_groups);
+  }
+
+  group_record = (uint8_t *) igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET;
+
+  /* Scan groups */
+  for (int i = 0; i < num_groups; ++i) {
+    struct in_addr  rec_group;
+    uint8_t        *sources;
+    uint8_t        *src;
+    int             rec_type;
+    int             rec_auxdatalen;
+    int             rec_num_sources;
+    int             j;
+
+    if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) {
+      zlog_warn("Recv IGMP report v3 from %s on %s: group record beyond report end",
+		from_str, ifp->name);
+      return -1;
+    }
+
+    rec_type        = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET];
+    rec_auxdatalen  = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET];
+    rec_num_sources = ntohs(* (uint16_t *) (group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET));
+
+    rec_group = *(struct in_addr *)(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET);
+
+    if (PIM_DEBUG_IGMP_PACKETS) {
+      zlog_debug("Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s",
+		 from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group));
+    }
+
+    /* Scan sources */
+    
+    sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET;
+
+    for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) {
+
+      if ((src + 4) > report_pastend) {
+	zlog_warn("Recv IGMP report v3 from %s on %s: group source beyond report end",
+		  from_str, ifp->name);
+	return -1;
+      }
+
+      if (PIM_DEBUG_IGMP_PACKETS) {
+	char src_str[200];
+
+	if (!inet_ntop(AF_INET, src, src_str , sizeof(src_str)))
+	  sprintf(src_str, "<source?>");
+	
+	zlog_debug("Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s",
+		   from_str, ifp->name, i, inet_ntoa(rec_group), src_str);
+      }
+    } /* for (sources) */
+
+    switch (rec_type) {
+    case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE:
+      igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+      break;
+    case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE:
+      igmpv3_report_isex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+      break;
+    case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE:
+      igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+      break;
+    case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE:
+      igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+      break;
+    case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES:
+      igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+      break;
+    case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES:
+      igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources);
+      break;
+    default:
+      zlog_warn("Recv IGMP report v3 from %s on %s: unknown record type: type=%d",
+		from_str, ifp->name, rec_type);
+    }
+
+    group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2);
+
+  } /* for (group records) */
+
+  return 0;
+}
+
+static void on_trace(const char *label,
+		     struct interface *ifp, struct in_addr from)
+{
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char from_str[100];
+    pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
+    zlog_debug("%s: from %s on %s",
+	       label, from_str, ifp->name);
+  }
+}
+
+static int igmp_v2_report(struct igmp_sock *igmp,
+			  struct in_addr from, const char *from_str,
+			  char *igmp_msg, int igmp_msg_len)
+{
+  struct interface *ifp = igmp->interface;
+  struct igmp_group *group;
+  struct in_addr group_addr;
+
+  on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
+
+  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+    zlog_warn("Recv IGMP report v2 from %s on %s: size=%d other than correct=%d",
+	      from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
+    return -1;
+  }
+
+  zlog_warn("%s %s: FIXME WRITEME",
+	    __FILE__, __PRETTY_FUNCTION__);
+
+  group_addr = *(struct in_addr *)(igmp_msg + 4);
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return -1;
+  }
+
+  group->last_igmp_v2_report_dsec = pim_time_monotonic_dsec();
+
+  return 0;
+}
+
+static int igmp_v2_leave(struct igmp_sock *igmp,
+			 struct in_addr from, const char *from_str,
+			 char *igmp_msg, int igmp_msg_len)
+{
+  struct interface *ifp = igmp->interface;
+
+  on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
+
+  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+    zlog_warn("Recv IGMP leave v2 from %s on %s: size=%d other than correct=%d",
+	      from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
+    return -1;
+  }
+
+  zlog_warn("%s %s: FIXME WRITEME",
+	    __FILE__, __PRETTY_FUNCTION__);
+
+  return 0;
+}
+
+static int igmp_v1_report(struct igmp_sock *igmp,
+			  struct in_addr from, const char *from_str,
+			  char *igmp_msg, int igmp_msg_len)
+{
+  struct interface *ifp = igmp->interface;
+  struct igmp_group *group;
+  struct in_addr group_addr;
+
+  on_trace(__PRETTY_FUNCTION__, igmp->interface, from);
+
+  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
+    zlog_warn("Recv IGMP report v1 from %s on %s: size=%d other than correct=%d",
+	      from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE);
+    return -1;
+  }
+
+  zlog_warn("%s %s: FIXME WRITEME",
+	    __FILE__, __PRETTY_FUNCTION__);
+
+  group_addr = *(struct in_addr *)(igmp_msg + 4);
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return -1;
+  }
+
+  group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec();
+
+  return 0;
+}
+
+int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len)
+{
+  struct ip *ip_hdr;
+  size_t ip_hlen; /* ip header length in bytes */
+  char *igmp_msg;
+  int igmp_msg_len;
+  int msg_type;
+  char from_str[100];
+  char to_str[100];
+    
+  if (len < sizeof(*ip_hdr)) {
+    zlog_warn("IGMP packet size=%d shorter than minimum=%d",
+	      len, sizeof(*ip_hdr));
+    return -1;
+  }
+
+  ip_hdr = (struct ip *) buf;
+
+  pim_inet4_dump("<src?>", ip_hdr->ip_src, from_str , sizeof(from_str));
+  pim_inet4_dump("<dst?>", ip_hdr->ip_dst, to_str , sizeof(to_str));
+
+  ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    zlog_debug("Recv IP packet from %s to %s on %s: size=%d ip_header_size=%d ip_proto=%d",
+	       from_str, to_str, igmp->interface->name, len, ip_hlen, ip_hdr->ip_p);
+  }
+
+  if (ip_hdr->ip_p != PIM_IP_PROTO_IGMP) {
+    zlog_warn("IP packet protocol=%d is not IGMP=%d",
+	      ip_hdr->ip_p, PIM_IP_PROTO_IGMP);
+    return -1;
+  }
+
+  if (ip_hlen < PIM_IP_HEADER_MIN_LEN) {
+    zlog_warn("IP packet header size=%d shorter than minimum=%d",
+	      ip_hlen, PIM_IP_HEADER_MIN_LEN);
+    return -1;
+  }
+  if (ip_hlen > PIM_IP_HEADER_MAX_LEN) {
+    zlog_warn("IP packet header size=%d greater than maximum=%d",
+	      ip_hlen, PIM_IP_HEADER_MAX_LEN);
+    return -1;
+  }
+
+  igmp_msg = buf + ip_hlen;
+  msg_type = *igmp_msg;
+  igmp_msg_len = len - ip_hlen;
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    zlog_debug("Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d",
+	       from_str, to_str, igmp->interface->name, ip_hdr->ip_ttl, msg_type,
+	       igmp_msg_len);
+  }
+
+  if (igmp_msg_len < PIM_IGMP_MIN_LEN) {
+    zlog_warn("IGMP message size=%d shorter than minimum=%d",
+	      igmp_msg_len, PIM_IGMP_MIN_LEN);
+    return -1;
+  }
+
+  switch (msg_type) {
+  case PIM_IGMP_MEMBERSHIP_QUERY:
+    {
+      int max_resp_code = igmp_msg[1];
+      int query_version;
+
+      /*
+	RFC 3376: 7.1. Query Version Distinctions
+	IGMPv1 Query: length = 8 octets AND Max Resp Code field is zero
+	IGMPv2 Query: length = 8 octets AND Max Resp Code field is non-zero
+	IGMPv3 Query: length >= 12 octets
+      */
+
+      if (igmp_msg_len == 8) {
+	query_version = max_resp_code ? 2 : 1;
+      }
+      else if (igmp_msg_len >= 12) {
+	query_version = 3;
+      }
+      else {
+	zlog_warn("Unknown IGMP query version");
+	return -1;
+      }
+
+      return recv_igmp_query(igmp, query_version, max_resp_code,
+			     ip_hdr->ip_src, from_str,
+			     igmp_msg, igmp_msg_len);
+    }
+
+  case PIM_IGMP_V3_MEMBERSHIP_REPORT:
+    return igmp_v3_report(igmp, ip_hdr->ip_src, from_str,
+			  igmp_msg, igmp_msg_len);
+
+  case PIM_IGMP_V2_MEMBERSHIP_REPORT:
+    return igmp_v2_report(igmp, ip_hdr->ip_src, from_str,
+			  igmp_msg, igmp_msg_len);
+
+  case PIM_IGMP_V1_MEMBERSHIP_REPORT:
+    return igmp_v1_report(igmp, ip_hdr->ip_src, from_str,
+			  igmp_msg, igmp_msg_len);
+
+  case PIM_IGMP_V2_LEAVE_GROUP:
+    return igmp_v2_leave(igmp, ip_hdr->ip_src, from_str,
+			 igmp_msg, igmp_msg_len);
+  }
+
+  zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type);
+
+  return -1;
+}
+
+static int pim_igmp_general_query(struct thread *t);
+
+void pim_igmp_general_query_on(struct igmp_sock *igmp)
+{
+  struct pim_interface *pim_ifp;
+  int startup_mode;
+  int query_interval;
+
+  zassert(igmp);
+  zassert(igmp->interface);
+
+  /*
+    Since this socket is starting as querier,
+    there should not be timer for other-querier-present.
+   */
+  zassert(!igmp->t_other_querier_timer);
+  pim_ifp = igmp->interface->info;
+  zassert(pim_ifp);
+
+  /*
+    RFC 3376: 8.6. Startup Query Interval
+
+    The Startup Query Interval is the interval between General Queries
+    sent by a Querier on startup.  Default: 1/4 the Query Interval.
+  */
+  startup_mode = igmp->startup_query_count > 0;
+  if (startup_mode) {
+    --igmp->startup_query_count;
+
+    /* query_interval = pim_ifp->igmp_default_query_interval >> 2; */
+    query_interval = PIM_IGMP_SQI(pim_ifp->igmp_default_query_interval);
+  }
+  else {
+    query_interval = igmp->querier_query_interval;
+  }
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char ifaddr_str[100];
+    pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+    zlog_debug("Querier %s scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
+	       ifaddr_str,
+	       query_interval,
+	       startup_mode ? "startup" : "non-startup",
+	       igmp->fd);
+  }
+  igmp->t_igmp_query_timer = 0;
+  zassert(!igmp->t_igmp_query_timer);
+  THREAD_TIMER_ON(master, igmp->t_igmp_query_timer,
+		  pim_igmp_general_query,
+		  igmp, query_interval);
+}
+
+void pim_igmp_general_query_off(struct igmp_sock *igmp)
+{
+  zassert(igmp);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    if (igmp->t_igmp_query_timer) {
+      char ifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+      zlog_debug("IGMP querier %s fd=%d cancelling query TIMER event on %s",
+		 ifaddr_str, igmp->fd, igmp->interface->name);
+    }
+  }
+  THREAD_OFF(igmp->t_igmp_query_timer);
+  zassert(!igmp->t_igmp_query_timer);
+}
+
+/* Issue IGMP general query */
+static int pim_igmp_general_query(struct thread *t)
+{
+  char   query_buf[PIM_IGMP_BUFSIZE_WRITE];
+  struct igmp_sock *igmp;
+  struct in_addr dst_addr;
+  struct in_addr group_addr;
+  struct pim_interface *pim_ifp;
+
+  zassert(t);
+
+  igmp = THREAD_ARG(t);
+
+  zassert(igmp);
+  zassert(igmp->interface);
+  zassert(igmp->interface->info);
+
+  pim_ifp = igmp->interface->info;
+
+  /*
+    RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+    In IGMPv3, General Queries are sent with an IP destination address
+    of 224.0.0.1, the all-systems multicast address.  Group-Specific
+    and Group-and-Source-Specific Queries are sent with an IP
+    destination address equal to the multicast address of interest.
+  */
+
+  dst_addr.s_addr   = htonl(INADDR_ALLHOSTS_GROUP);
+  group_addr.s_addr = PIM_NET_INADDR_ANY;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char querier_str[100];
+    char dst_str[100];
+    pim_inet4_dump("<querier?>", igmp->ifaddr, querier_str,
+		   sizeof(querier_str));
+    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+    zlog_debug("Querier %s issuing IGMP general query to %s on %s",
+	       querier_str, dst_str, igmp->interface->name);
+  }
+
+  pim_igmp_send_membership_query(0 /* igmp_group */,
+				 igmp->fd,
+				 igmp->interface->name,
+				 query_buf,
+				 sizeof(query_buf),
+				 0 /* num_sources */,
+				 dst_addr,
+				 group_addr,
+				 pim_ifp->igmp_query_max_response_time_dsec,
+				 1 /* s_flag: always set for general queries */,
+				 igmp->querier_robustness_variable,
+				 igmp->querier_query_interval);
+
+  pim_igmp_general_query_on(igmp);
+
+  return 0;
+}
+
+static int pim_igmp_read(struct thread *t);
+
+static void igmp_read_on(struct igmp_sock *igmp)
+{
+  zassert(igmp);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    zlog_debug("Scheduling READ event on IGMP socket fd=%d",
+	       igmp->fd);
+  }
+  igmp->t_igmp_read = 0;
+  zassert(!igmp->t_igmp_read);
+  THREAD_READ_ON(master, igmp->t_igmp_read, pim_igmp_read, igmp, igmp->fd);
+}
+
+static int pim_igmp_read(struct thread *t)
+{
+  struct igmp_sock *igmp;
+  int fd;
+  struct sockaddr_in from;
+  struct sockaddr_in to;
+  socklen_t fromlen = sizeof(from);
+  socklen_t tolen = sizeof(to);
+  char buf[PIM_IGMP_BUFSIZE_READ];
+  int len;
+  int ifindex = -1;
+  int result = -1; /* defaults to bad */
+
+  zassert(t);
+
+  igmp = THREAD_ARG(t);
+
+  zassert(igmp);
+
+  fd = THREAD_FD(t);
+
+  zassert(fd == igmp->fd);
+
+  len = pim_socket_recvfromto(fd, buf, sizeof(buf),
+			      &from, &fromlen,
+			      &to, &tolen,
+			      &ifindex);
+  if (len < 0) {
+    zlog_warn("Failure receiving IP IGMP packet on fd=%d: errno=%d: %s",
+	      fd, errno, strerror(errno));
+    goto done;
+  }
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    char from_str[100];
+    char to_str[100];
+    
+    if (!inet_ntop(AF_INET, &from.sin_addr, from_str, sizeof(from_str)))
+      sprintf(from_str, "<from?>");
+    if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str)))
+      sprintf(to_str, "<to?>");
+    
+    zlog_debug("Recv IP IGMP pkt size=%d from %s to %s on fd=%d on ifindex=%d (sock_ifindex=%d)",
+	       len, from_str, to_str, fd, ifindex, igmp->interface->ifindex);
+  }
+
+#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
+  /* ifindex sanity check */
+  if (ifindex != (int) igmp->interface->ifindex) {
+    char from_str[100];
+    char to_str[100];
+    struct interface *ifp;
+
+    if (!inet_ntop(AF_INET, &from.sin_addr, from_str , sizeof(from_str)))
+      sprintf(from_str, "<from?>");
+    if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str)))
+      sprintf(to_str, "<to?>");
+
+    ifp = if_lookup_by_index(ifindex);
+    if (ifp) {
+      zassert(ifindex == (int) ifp->ifindex);
+    }
+
+#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
+    zlog_warn("Interface mismatch: recv IGMP pkt from %s to %s on fd=%d: recv_ifindex=%d (%s) sock_ifindex=%d (%s)",
+	      from_str, to_str, fd,
+	      ifindex, ifp ? ifp->name : "<if-notfound>",
+	      igmp->interface->ifindex, igmp->interface->name);
+#endif
+    goto done;
+  }
+#endif
+
+  if (pim_igmp_packet(igmp, buf, len)) {
+    goto done;
+  }
+
+  result = 0; /* good */
+
+ done:
+  igmp_read_on(igmp);
+
+  return result;
+}
+
+static void sock_close(struct igmp_sock *igmp)
+{
+  pim_igmp_other_querier_timer_off(igmp);
+  pim_igmp_general_query_off(igmp);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    if (igmp->t_igmp_read) {
+      zlog_debug("Cancelling READ event on IGMP socket %s fd=%d on interface %s",
+		 inet_ntoa(igmp->ifaddr), igmp->fd,
+		 igmp->interface->name);
+    }
+  }
+  THREAD_OFF(igmp->t_igmp_read);
+  zassert(!igmp->t_igmp_read);
+  
+  if (close(igmp->fd)) {
+    zlog_err("Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
+	     inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name,
+	     errno, strerror(errno));
+  }
+  
+  if (PIM_DEBUG_IGMP_TRACE) {
+    zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
+	       inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name);
+  }
+}
+
+void igmp_startup_mode_on(struct igmp_sock *igmp)
+{
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = igmp->interface->info;
+
+  /*
+    RFC 3376: 8.7. Startup Query Count
+
+    The Startup Query Count is the number of Queries sent out on
+    startup, separated by the Startup Query Interval.  Default: the
+    Robustness Variable.
+  */
+  igmp->startup_query_count = igmp->querier_robustness_variable;
+
+  /*
+    Since we're (re)starting, reset QQI to default Query Interval
+  */
+  igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
+}
+
+static void igmp_group_free(struct igmp_group *group)
+{
+  zassert(!group->t_group_query_retransmit_timer);
+  zassert(!group->t_group_timer);
+  zassert(group->group_source_list);
+  zassert(!listcount(group->group_source_list));
+
+  list_free(group->group_source_list);
+
+  XFREE(MTYPE_PIM_IGMP_GROUP, group);
+}
+
+static void igmp_group_delete(struct igmp_group *group)
+{
+  struct listnode *src_node;
+  struct listnode *src_nextnode;
+  struct igmp_source *src;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Deleting IGMP group %s from socket %d interface %s",
+	       group_str,
+	       group->group_igmp_sock->fd,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode, src)) {
+    igmp_source_delete(src);
+  }
+
+  if (group->t_group_query_retransmit_timer) {
+    THREAD_OFF(group->t_group_query_retransmit_timer);
+    zassert(!group->t_group_query_retransmit_timer);
+  }
+
+  group_timer_off(group);
+  listnode_delete(group->group_igmp_sock->igmp_group_list, group);
+  igmp_group_free(group);
+}
+
+void igmp_group_delete_empty_include(struct igmp_group *group)
+{
+  zassert(!group->group_filtermode_isexcl);
+  zassert(!listcount(group->group_source_list));
+
+  igmp_group_delete(group);
+}
+
+void igmp_sock_free(struct igmp_sock *igmp)
+{
+  zassert(!igmp->t_igmp_read);
+  zassert(!igmp->t_igmp_query_timer);
+  zassert(!igmp->t_other_querier_timer);
+  zassert(igmp->igmp_group_list);
+  zassert(!listcount(igmp->igmp_group_list));
+
+  list_free(igmp->igmp_group_list);
+
+  XFREE(MTYPE_PIM_IGMP_SOCKET, igmp);
+}
+
+void igmp_sock_delete(struct igmp_sock *igmp)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *grp_node;
+  struct listnode      *grp_nextnode;
+  struct igmp_group    *grp;
+
+  for (ALL_LIST_ELEMENTS(igmp->igmp_group_list, grp_node, grp_nextnode, grp)) {
+    igmp_group_delete(grp);
+  }
+
+  sock_close(igmp);
+
+  pim_ifp = igmp->interface->info;
+
+  listnode_delete(pim_ifp->igmp_socket_list, igmp);
+
+  igmp_sock_free(igmp);
+}
+
+static struct igmp_sock *igmp_sock_new(int fd,
+				       struct in_addr ifaddr,
+				       struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct igmp_sock *igmp;
+
+  pim_ifp = ifp->info;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    zlog_debug("Creating IGMP socket fd=%d for address %s on interface %s",
+	       fd, inet_ntoa(ifaddr), ifp->name);
+  }
+
+  igmp = XMALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp));
+  if (!igmp) {
+    zlog_warn("%s %s: XMALLOC() failure",
+              __FILE__, __PRETTY_FUNCTION__);
+    return 0;
+  }
+
+  igmp->igmp_group_list = list_new();
+  if (!igmp->igmp_group_list) {
+    zlog_err("%s %s: failure: igmp_group_list = list_new()",
+	     __FILE__, __PRETTY_FUNCTION__);
+    return 0;
+  }
+  igmp->igmp_group_list->del = (void (*)(void *)) igmp_group_free;
+
+  igmp->fd                          = fd;
+  igmp->interface                   = ifp;
+  igmp->ifaddr                      = ifaddr;
+  igmp->t_igmp_read                 = 0;
+  igmp->t_igmp_query_timer          = 0;
+  igmp->t_other_querier_timer       = 0; /* no other querier present */
+  igmp->querier_robustness_variable = pim_ifp->igmp_default_robustness_variable;
+  igmp->sock_creation               = pim_time_monotonic_sec();
+
+  /*
+    igmp_startup_mode_on() will reset QQI:
+
+    igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
+  */
+  igmp_startup_mode_on(igmp);
+
+  igmp_read_on(igmp);
+  pim_igmp_general_query_on(igmp);
+
+  return igmp;
+}
+
+struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
+				    struct in_addr ifaddr,
+				    struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct igmp_sock *igmp;
+  int fd;
+
+  pim_ifp = ifp->info;
+
+  fd = igmp_sock_open(ifaddr, ifp->ifindex, pim_ifp->options);
+  if (fd < 0) {
+    zlog_warn("Could not open IGMP socket for %s on %s",
+	      inet_ntoa(ifaddr), ifp->name);
+    return 0;
+  }
+
+  igmp = igmp_sock_new(fd, ifaddr, ifp);
+  if (!igmp) {
+    zlog_err("%s %s: igmp_sock_new() failure",
+	     __FILE__, __PRETTY_FUNCTION__);
+    close(fd);
+    return 0;
+  }
+
+  listnode_add(igmp_sock_list, igmp);
+
+#ifdef IGMP_SOCK_DUMP
+  igmp_sock_dump(igmp_sock_array);
+#endif
+
+  return igmp;
+}
+
+/*
+  RFC 3376: 6.5. Switching Router Filter-Modes
+
+  When a router's filter-mode for a group is EXCLUDE and the group
+  timer expires, the router filter-mode for the group transitions to
+  INCLUDE.
+
+  A router uses source records with running source timers as its state
+  for the switch to a filter-mode of INCLUDE.  If there are any source
+  records with source timers greater than zero (i.e., requested to be
+  forwarded), a router switches to filter-mode of INCLUDE using those
+  source records.  Source records whose timers are zero (from the
+  previous EXCLUDE mode) are deleted.
+ */
+static int igmp_group_timer(struct thread *t)
+{
+  struct igmp_group *group;
+
+  zassert(t);
+  group = THREAD_ARG(t);
+  zassert(group);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: Timer for group %s on interface %s",
+	       __PRETTY_FUNCTION__,
+	       group_str, group->group_igmp_sock->interface->name);
+  }
+
+  zassert(group->group_filtermode_isexcl);
+
+  group->t_group_timer = 0;
+  group->group_filtermode_isexcl = 0;
+
+  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+  igmp_anysource_forward_stop(group);
+
+  igmp_source_delete_expired(group->group_source_list);
+
+  zassert(!group->t_group_timer);
+  zassert(!group->group_filtermode_isexcl);
+
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+
+    If there are no more source records for the group, delete group
+    record.
+  */
+  if (listcount(group->group_source_list) < 1) {
+    igmp_group_delete_empty_include(group);
+  }
+
+  return 0;
+}
+
+static void group_timer_off(struct igmp_group *group)
+{
+  if (!group->t_group_timer)
+    return;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Cancelling TIMER event for group %s on %s",
+	       group_str, group->group_igmp_sock->interface->name);
+  }
+    
+  THREAD_OFF(group->t_group_timer);
+  zassert(!group->t_group_timer);
+}
+
+void igmp_group_timer_on(struct igmp_group *group,
+			 long interval_msec, const char *ifname)
+{
+  group_timer_off(group);
+
+  if (PIM_DEBUG_IGMP_EVENTS) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s on %s",
+	       interval_msec / 1000,
+	       interval_msec % 1000,
+	       group_str, ifname);
+  }
+
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+
+    The group timer is only used when a group is in EXCLUDE mode and
+    it represents the time for the *filter-mode* of the group to
+    expire and switch to INCLUDE mode.
+  */
+  zassert(group->group_filtermode_isexcl);
+
+  THREAD_TIMER_MSEC_ON(master, group->t_group_timer,
+		       igmp_group_timer,
+		       group, interval_msec);
+}
+
+static struct igmp_group *find_group_by_addr(struct igmp_sock *igmp,
+					     struct in_addr group_addr)
+{
+  struct igmp_group *group;
+  struct listnode   *node;
+
+  for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, node, group))
+    if (group_addr.s_addr == group->group_addr.s_addr)
+      return group;
+
+  return 0;
+}
+
+struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
+					  struct in_addr group_addr,
+					  const char *ifname)
+{
+  struct igmp_group *group;
+
+  group = find_group_by_addr(igmp, group_addr);
+  if (group) {
+    return group;
+  }
+
+  /*
+    Non-existant group is created as INCLUDE {empty}:
+
+    RFC 3376 - 5.1. Action on Change of Interface State
+
+    If no interface state existed for that multicast address before
+    the change (i.e., the change consisted of creating a new
+    per-interface record), or if no state exists after the change
+    (i.e., the change consisted of deleting a per-interface record),
+    then the "non-existent" state is considered to have a filter mode
+    of INCLUDE and an empty source list.
+  */
+
+  group = XMALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group));
+  if (!group) {
+    zlog_warn("%s %s: XMALLOC() failure",
+	      __FILE__, __PRETTY_FUNCTION__);
+    return 0; /* error, not found, could not create */
+  }
+
+  group->group_source_list = list_new();
+  if (!group->group_source_list) {
+    zlog_warn("%s %s: list_new() failure",
+	      __FILE__, __PRETTY_FUNCTION__);
+    XFREE(MTYPE_PIM_IGMP_GROUP, group); /* discard group */
+    return 0; /* error, not found, could not initialize */
+  }
+  group->group_source_list->del = (void (*)(void *)) igmp_source_free;
+
+  group->t_group_timer                         = 0;
+  group->t_group_query_retransmit_timer        = 0;
+  group->group_specific_query_retransmit_count = 0;
+  group->group_addr                            = group_addr;
+  group->group_igmp_sock                       = igmp;
+  group->last_igmp_v1_report_dsec              = -1;
+  group->last_igmp_v2_report_dsec              = -1;
+  group->group_creation                        = pim_time_monotonic_sec();
+
+  /* initialize new group as INCLUDE {empty} */
+  group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */
+
+  listnode_add(igmp->igmp_group_list, group);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Creating new IGMP group %s on socket %d interface %s",
+	       group_str, group->group_igmp_sock->fd, ifname);
+  }
+
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+
+    The group timer is only used when a group is in EXCLUDE mode and
+    it represents the time for the *filter-mode* of the group to
+    expire and switch to INCLUDE mode.
+  */
+  zassert(!group->group_filtermode_isexcl); /* INCLUDE mode */
+  zassert(!group->t_group_timer); /* group timer == 0 */
+
+  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+  igmp_anysource_forward_stop(group);
+
+  return group;
+}
diff --git a/pimd/pim_igmp.h b/pimd/pim_igmp.h
new file mode 100644
index 0000000..78a0195
--- /dev/null
+++ b/pimd/pim_igmp.h
@@ -0,0 +1,166 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_IGMP_H
+#define PIM_IGMP_H
+
+#include <netinet/in.h>
+
+#include <zebra.h>
+#include "vty.h"
+#include "linklist.h"
+
+/*
+  The following sizes are likely to support
+  any message sent within local MTU.
+*/
+#define PIM_IGMP_BUFSIZE_READ         (20000)
+#define PIM_IGMP_BUFSIZE_WRITE        (20000)
+
+#define PIM_IGMP_MEMBERSHIP_QUERY     (0x11)
+#define PIM_IGMP_V1_MEMBERSHIP_REPORT (0x12)
+#define PIM_IGMP_V2_MEMBERSHIP_REPORT (0x16)
+#define PIM_IGMP_V2_LEAVE_GROUP       (0x17)
+#define PIM_IGMP_V3_MEMBERSHIP_REPORT (0x22)
+
+#define IGMP_V3_REPORT_HEADER_SIZE    (8)
+#define IGMP_V3_GROUP_RECORD_MIN_SIZE (8)
+#define IGMP_V3_MSG_MIN_SIZE          (IGMP_V3_REPORT_HEADER_SIZE + \
+				       IGMP_V3_GROUP_RECORD_MIN_SIZE)
+#define IGMP_V12_MSG_SIZE             (8)
+
+#define IGMP_V3_GROUP_RECORD_TYPE_OFFSET       (0)
+#define IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET (1)
+#define IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET (2)
+#define IGMP_V3_GROUP_RECORD_GROUP_OFFSET      (4)
+#define IGMP_V3_GROUP_RECORD_SOURCE_OFFSET     (8)
+
+/* RFC 3376: 8.1. Robustness Variable - Default: 2 */
+#define IGMP_DEFAULT_ROBUSTNESS_VARIABLE  (2)
+
+/* RFC 3376: 8.2. Query Interval - Default: 125 seconds */
+#define IGMP_GENERAL_QUERY_INTERVAL       (125)
+
+/* RFC 3376: 8.3. Query Response Interval - Default: 100 deciseconds */
+#define IGMP_QUERY_MAX_RESPONSE_TIME_DSEC (100)
+
+struct igmp_sock {
+  int               fd;
+  struct interface *interface;
+  struct in_addr    ifaddr;
+  time_t            sock_creation;
+
+  struct thread    *t_igmp_read;                 /* read: IGMP sockets */
+  struct thread    *t_igmp_query_timer; /* timer: issue IGMP general queries */
+  struct thread    *t_other_querier_timer;   /* timer: other querier present */
+
+  int               querier_query_interval;      /* QQI */
+  int               querier_robustness_variable; /* QRV */
+  int               startup_query_count;
+
+  struct list      *igmp_group_list; /* list of struct igmp_group */
+};
+
+struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
+					      struct in_addr ifaddr);
+struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list,
+					 int fd);
+struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
+				    struct in_addr ifaddr,
+				    struct interface *ifp);
+void igmp_sock_delete(struct igmp_sock *igmp);
+void igmp_sock_free(struct igmp_sock *igmp);
+
+int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len);
+
+void pim_igmp_general_query_on(struct igmp_sock *igmp);
+void pim_igmp_general_query_off(struct igmp_sock *igmp);
+void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp);
+void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp);
+
+#define IGMP_SOURCE_MASK_FORWARDING        (1 << 0)
+#define IGMP_SOURCE_MASK_DELETE            (1 << 1)
+#define IGMP_SOURCE_MASK_SEND              (1 << 2)
+#define IGMP_SOURCE_TEST_FORWARDING(flags) ((flags) & IGMP_SOURCE_MASK_FORWARDING)
+#define IGMP_SOURCE_TEST_DELETE(flags)     ((flags) & IGMP_SOURCE_MASK_DELETE)
+#define IGMP_SOURCE_TEST_SEND(flags)       ((flags) & IGMP_SOURCE_MASK_SEND)
+#define IGMP_SOURCE_DO_FORWARDING(flags)   ((flags) |= IGMP_SOURCE_MASK_FORWARDING)
+#define IGMP_SOURCE_DO_DELETE(flags)       ((flags) |= IGMP_SOURCE_MASK_DELETE)
+#define IGMP_SOURCE_DO_SEND(flags)         ((flags) |= IGMP_SOURCE_MASK_SEND)
+#define IGMP_SOURCE_DONT_FORWARDING(flags) ((flags) &= ~IGMP_SOURCE_MASK_FORWARDING)
+#define IGMP_SOURCE_DONT_DELETE(flags)     ((flags) &= ~IGMP_SOURCE_MASK_DELETE)
+#define IGMP_SOURCE_DONT_SEND(flags)       ((flags) &= ~IGMP_SOURCE_MASK_SEND)
+
+struct igmp_source {
+  struct in_addr      source_addr;
+  struct thread      *t_source_timer;
+  struct igmp_group  *source_group;    /* back pointer */
+  time_t              source_creation;
+  uint32_t            source_flags;
+  struct channel_oil *source_channel_oil;
+
+  /*
+    RFC 3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+  */
+  int                source_query_retransmit_count;
+};
+
+struct igmp_group {
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+
+    The group timer is only used when a group is in EXCLUDE mode and it
+    represents the time for the *filter-mode* of the group to expire and
+    switch to INCLUDE mode.
+  */
+  struct thread    *t_group_timer;
+
+  /* Shared between group-specific and
+     group-and-source-specific retransmissions */
+  struct thread    *t_group_query_retransmit_timer;
+
+  /* Counter exclusive for group-specific retransmissions
+     (not used by group-and-source-specific retransmissions,
+     since sources have their counters) */
+  int               group_specific_query_retransmit_count;
+
+  struct in_addr    group_addr;
+  int               group_filtermode_isexcl;  /* 0=INCLUDE, 1=EXCLUDE */
+  struct list      *group_source_list;        /* list of struct igmp_source */
+  time_t            group_creation;
+  struct igmp_sock *group_igmp_sock;          /* back pointer */
+  int64_t           last_igmp_v1_report_dsec;
+  int64_t           last_igmp_v2_report_dsec;
+};
+
+struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
+					  struct in_addr group_addr,
+					  const char *ifname);
+
+void igmp_group_delete_empty_include(struct igmp_group *group);
+
+void igmp_startup_mode_on(struct igmp_sock *igmp);
+
+void igmp_group_timer_on(struct igmp_group *group,
+			 long interval_msec, const char *ifname);
+
+#endif /* PIM_IGMP_H */
diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c
new file mode 100644
index 0000000..62d6e40
--- /dev/null
+++ b/pimd/pim_igmpv3.c
@@ -0,0 +1,1717 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+#include "log.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_iface.h"
+#include "pim_igmp.h"
+#include "pim_igmpv3.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#include "pim_time.h"
+#include "pim_zebra.h"
+#include "pim_oil.h"
+
+static void group_retransmit_timer_on(struct igmp_group *group);
+static long igmp_group_timer_remain_msec(struct igmp_group *group);
+static long igmp_source_timer_remain_msec(struct igmp_source *source);
+static void group_query_send(struct igmp_group *group);
+static void source_query_send_by_flag(struct igmp_group *group,
+				      int num_sources_tosend);
+
+static void on_trace(const char *label,
+		     struct interface *ifp, struct in_addr from,
+		     struct in_addr group_addr,
+		     int num_sources, struct in_addr *sources)
+{
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char from_str[100];
+    char group_str[100];
+
+    pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+
+    zlog_debug("%s: from %s on %s: group=%s sources=%d",
+	       label, from_str, ifp->name, group_str, num_sources);
+  }
+}
+
+int igmp_group_compat_mode(const struct igmp_sock *igmp,
+			   const struct igmp_group *group)
+{
+  struct pim_interface *pim_ifp;
+  int64_t               now_dsec;
+  long                  older_host_present_interval_dsec;
+
+  zassert(igmp);
+  zassert(igmp->interface);
+  zassert(igmp->interface->info);
+
+  pim_ifp = igmp->interface->info;
+
+  /*
+    RFC 3376: 8.13. Older Host Present Interval
+
+    This value MUST be ((the Robustness Variable) times (the Query
+    Interval)) plus (one Query Response Interval).
+
+    older_host_present_interval_dsec = \
+      igmp->querier_robustness_variable * \
+      10 * igmp->querier_query_interval + \
+      pim_ifp->query_max_response_time_dsec;
+  */
+  older_host_present_interval_dsec =
+    PIM_IGMP_OHPI_DSEC(igmp->querier_robustness_variable,
+		       igmp->querier_query_interval,
+		       pim_ifp->igmp_query_max_response_time_dsec);
+
+  now_dsec = pim_time_monotonic_dsec();
+  if (now_dsec < 1) {
+    /* broken timer logged by pim_time_monotonic_dsec() */
+    return 3;
+  }
+
+  if ((now_dsec - group->last_igmp_v1_report_dsec) < older_host_present_interval_dsec)
+    return 1; /* IGMPv1 */
+
+  if ((now_dsec - group->last_igmp_v2_report_dsec) < older_host_present_interval_dsec)
+    return 2; /* IGMPv2 */
+
+  return 3; /* IGMPv3 */
+}
+
+void igmp_group_reset_gmi(struct igmp_group *group)
+{
+  long group_membership_interval_msec;
+  struct pim_interface *pim_ifp;
+  struct igmp_sock *igmp;
+  struct interface *ifp;
+
+  igmp = group->group_igmp_sock;
+  ifp = igmp->interface;
+  pim_ifp = ifp->info;
+
+  /*
+    RFC 3376: 8.4. Group Membership Interval
+
+    The Group Membership Interval is the amount of time that must pass
+    before a multicast router decides there are no more members of a
+    group or a particular source on a network.
+
+    This value MUST be ((the Robustness Variable) times (the Query
+    Interval)) plus (one Query Response Interval).
+
+    group_membership_interval_msec = querier_robustness_variable *
+                                     (1000 * querier_query_interval) +
+                                     100 * query_response_interval_dsec;
+  */
+  group_membership_interval_msec =
+    PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+		      igmp->querier_query_interval,
+		      pim_ifp->igmp_query_max_response_time_dsec);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Resetting group %s timer to GMI=%ld.%03ld sec on %s",
+	       group_str,
+	       group_membership_interval_msec / 1000,
+	       group_membership_interval_msec % 1000,
+	       ifp->name);
+  }
+
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+
+    The group timer is only used when a group is in EXCLUDE mode and
+    it represents the time for the *filter-mode* of the group to
+    expire and switch to INCLUDE mode.
+  */
+  zassert(group->group_filtermode_isexcl);
+
+  igmp_group_timer_on(group, group_membership_interval_msec, ifp->name);
+}
+
+static int igmp_source_timer(struct thread *t)
+{
+  struct igmp_source *source;
+  struct igmp_group *group;
+
+  zassert(t);
+  source = THREAD_ARG(t);
+  zassert(source);
+
+  group = source->source_group;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("%s: Source timer expired for group %s source %s on %s",
+	       __PRETTY_FUNCTION__,
+	       group_str, source_str,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  zassert(source->t_source_timer);
+  source->t_source_timer = 0;
+
+  /*
+    RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules
+
+    Group
+    Filter-Mode    Source Timer Value    Action
+    -----------    ------------------    ------
+    INCLUDE        TIMER == 0            Suggest to stop forwarding
+                                         traffic from source and
+                                         remove source record.  If
+                                         there are no more source
+                                         records for the group, delete
+                                         group record.
+
+    EXCLUDE        TIMER == 0            Suggest to not forward
+                                         traffic from source
+                                         (DO NOT remove record)
+
+    Source timer switched from (T > 0) to (T == 0): disable forwarding.
+   */
+
+  zassert(!source->t_source_timer);
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+
+    igmp_source_forward_stop(source);
+  }
+  else {
+    /* INCLUDE mode */
+
+    /* igmp_source_delete() will stop forwarding source */
+    igmp_source_delete(source);
+
+    /*
+      If there are no more source records for the group, delete group
+      record.
+    */
+    if (!listcount(group->group_source_list)) {
+      igmp_group_delete_empty_include(group);
+    }
+  }
+
+  return 0;
+}
+
+static void source_timer_off(struct igmp_group *group,
+			     struct igmp_source *source)
+{
+  if (!source->t_source_timer)
+    return;
+  
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("Cancelling TIMER event for group %s source %s on %s",
+	       group_str, source_str,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  THREAD_OFF(source->t_source_timer);
+  zassert(!source->t_source_timer);
+}
+
+static void igmp_source_timer_on(struct igmp_group *group,
+				 struct igmp_source *source,
+				 long interval_msec)
+{
+  source_timer_off(group, source);
+
+  if (PIM_DEBUG_IGMP_EVENTS) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s source %s on %s",
+	       interval_msec / 1000,
+	       interval_msec % 1000,
+	       group_str, source_str,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  THREAD_TIMER_MSEC_ON(master, source->t_source_timer,
+		       igmp_source_timer,
+		       source, interval_msec);
+  zassert(source->t_source_timer);
+
+  /*
+    RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules
+    
+    Source timer switched from (T == 0) to (T > 0): enable forwarding.
+  */
+  igmp_source_forward_start(source);
+}
+
+void igmp_source_reset_gmi(struct igmp_sock *igmp,
+			   struct igmp_group *group,
+			   struct igmp_source *source)
+{
+  long group_membership_interval_msec;
+  struct pim_interface *pim_ifp;
+  struct interface *ifp;
+
+  ifp = igmp->interface;
+  pim_ifp = ifp->info;
+
+  group_membership_interval_msec =
+    PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+		      igmp->querier_query_interval,
+		      pim_ifp->igmp_query_max_response_time_dsec);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+
+    zlog_debug("Resetting source %s timer to GMI=%ld.%03ld sec for group %s on %s",
+	       source_str,
+	       group_membership_interval_msec / 1000,
+	       group_membership_interval_msec % 1000,
+	       group_str,
+	       ifp->name);
+  }
+
+  igmp_source_timer_on(group, source,
+		       group_membership_interval_msec);
+}
+
+static void source_mark_delete_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    IGMP_SOURCE_DO_DELETE(src->source_flags);
+  }
+}
+
+static void source_mark_send_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    IGMP_SOURCE_DO_SEND(src->source_flags);
+  }
+}
+
+static int source_mark_send_flag_by_timer(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+  int                 num_marked_sources = 0;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    /* Is source timer running? */
+    if (src->t_source_timer) {
+      IGMP_SOURCE_DO_SEND(src->source_flags);
+      ++num_marked_sources;
+    }
+    else {
+      IGMP_SOURCE_DONT_SEND(src->source_flags);
+    }
+  }
+
+  return num_marked_sources;
+}
+
+static void source_clear_send_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+    IGMP_SOURCE_DONT_SEND(src->source_flags);
+  }
+}
+
+/*
+  Any source (*,G) is forwarded only if mode is EXCLUDE {empty}
+*/
+static void group_exclude_fwd_anysrc_ifempty(struct igmp_group *group)
+{
+  zassert(group->group_filtermode_isexcl);
+
+  if (listcount(group->group_source_list) < 1) {
+    igmp_anysource_forward_start(group);
+  }
+}
+
+void igmp_source_free(struct igmp_source *source)
+{
+  /* make sure there is no source timer running */
+  zassert(!source->t_source_timer);
+
+  XFREE(MTYPE_PIM_IGMP_GROUP_SOURCE, source);
+}
+
+static void source_channel_oil_detach(struct igmp_source *source)
+{
+  if (source->source_channel_oil) {
+    pim_channel_oil_del(source->source_channel_oil);
+    source->source_channel_oil = 0;
+  }
+}
+
+void igmp_source_delete(struct igmp_source *source)
+{
+  struct igmp_group *group;
+
+  group = source->source_group;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("Deleting IGMP source %s for group %s from socket %d interface %s",
+	       source_str, group_str,
+	       group->group_igmp_sock->fd,
+	       group->group_igmp_sock->interface->name);
+  }
+
+  source_timer_off(group, source);
+  igmp_source_forward_stop(source);
+
+  /* make sure forwarding is disabled */
+  zassert(!IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+
+  source_channel_oil_detach(source);
+
+  /*
+    notice that listnode_delete() can't be moved
+    into igmp_source_free() because the later is
+    called by list_delete_all_node()
+  */
+  listnode_delete(group->group_source_list, source);
+
+  igmp_source_free(source);
+
+  if (group->group_filtermode_isexcl) {
+    group_exclude_fwd_anysrc_ifempty(group);
+  }
+}
+
+static void source_delete_by_flag(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct listnode    *src_nextnode;
+  struct igmp_source *src;
+  
+  for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src))
+    if (IGMP_SOURCE_TEST_DELETE(src->source_flags))
+      igmp_source_delete(src);
+}
+
+void igmp_source_delete_expired(struct list *source_list)
+{
+  struct listnode    *src_node;
+  struct listnode    *src_nextnode;
+  struct igmp_source *src;
+  
+  for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src))
+    if (!src->t_source_timer)
+      igmp_source_delete(src);
+}
+
+struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group,
+					     struct in_addr src_addr)
+{
+  struct listnode    *src_node;
+  struct igmp_source *src;
+
+  for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src))
+    if (src_addr.s_addr == src->source_addr.s_addr)
+      return src;
+
+  return 0;
+}
+
+static struct igmp_source *source_new(struct igmp_group *group,
+				      struct in_addr src_addr,
+				      const char *ifname)
+{
+  struct igmp_source *src;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", src_addr, source_str, sizeof(source_str));
+    zlog_debug("Creating new IGMP source %s for group %s on socket %d interface %s",
+	       source_str, group_str,
+	       group->group_igmp_sock->fd,
+	       ifname);
+  }
+
+  src = XMALLOC(MTYPE_PIM_IGMP_GROUP_SOURCE, sizeof(*src));
+  if (!src) {
+    zlog_warn("%s %s: XMALLOC() failure",
+	      __FILE__, __PRETTY_FUNCTION__);
+    return 0; /* error, not found, could not create */
+  }
+  
+  src->t_source_timer                = 0;
+  src->source_group                  = group; /* back pointer */
+  src->source_addr                   = src_addr;
+  src->source_creation               = pim_time_monotonic_sec();
+  src->source_flags                  = 0;
+  src->source_query_retransmit_count = 0;
+  src->source_channel_oil            = 0;
+
+  listnode_add(group->group_source_list, src);
+
+  zassert(!src->t_source_timer); /* source timer == 0 */
+
+  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+  igmp_anysource_forward_stop(group);
+
+  return src;
+}
+
+static struct igmp_source *add_source_by_addr(struct igmp_sock *igmp,
+					      struct igmp_group *group,
+					      struct in_addr src_addr,
+					      const char *ifname)
+{
+  struct igmp_source *src;
+
+  src = igmp_find_source_by_addr(group, src_addr);
+  if (src) {
+    return src;
+  }
+
+  src = source_new(group, src_addr, ifname);
+  if (!src) {
+    return 0;
+  }
+
+  return src;
+}
+
+static void allow(struct igmp_sock *igmp, struct in_addr from,
+		  struct in_addr group_addr,
+		  int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  /* scan received sources */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    source = add_source_by_addr(igmp, group, *src_addr,	ifp->name);
+    if (!source) {
+      continue;
+    }
+
+    /*
+      RFC 3376: 6.4.1. Reception of Current-State Records
+
+      When receiving IS_IN reports for groups in EXCLUDE mode is
+      sources should be moved from set with (timers = 0) to set with
+      (timers > 0).
+
+      igmp_source_reset_gmi() below, resetting the source timers to
+      GMI, accomplishes this.
+    */
+    igmp_source_reset_gmi(igmp, group, source);
+
+  } /* scan received sources */
+}
+
+void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  on_trace(__PRETTY_FUNCTION__,
+	   igmp->interface, from, group_addr, num_sources, sources);
+
+  allow(igmp, from, group_addr, num_sources, sources);
+}
+
+static void isex_excl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  /* EXCLUDE mode */
+  zassert(group->group_filtermode_isexcl);
+  
+  /* E.1: set deletion flag for known sources (X,Y) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* E.2: lookup reported source from (A) in (X,Y) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* E.3: if found, clear deletion flag: (X*A) or (Y*A) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+    }
+    else {
+      /* E.4: if not found, create source with timer=GMI: (A-X-Y) */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+      zassert(!source->t_source_timer); /* timer == 0 */
+      igmp_source_reset_gmi(group->group_igmp_sock, group, source);
+      zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+    }
+
+  } /* scan received sources */
+
+  /* E.5: delete all sources marked with deletion flag: (X-A) and (Y-A) */
+  source_delete_by_flag(group->group_source_list);
+}
+
+static void isex_incl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  /* INCLUDE mode */
+  zassert(!group->group_filtermode_isexcl);
+  
+  /* I.1: set deletion flag for known sources (A) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* scan received sources (B) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* I.2: lookup reported source (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* I.3: if found, clear deletion flag (A*B) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+    }
+    else {
+      /* I.4: if not found, create source with timer=0 (B-A) */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+      zassert(!source->t_source_timer); /* (B-A) timer=0 */
+    }
+
+  } /* scan received sources */
+
+  /* I.5: delete all sources marked with deletion flag (A-B) */
+  source_delete_by_flag(group->group_source_list);
+
+  group->group_filtermode_isexcl = 1; /* boolean=true */
+
+  zassert(group->group_filtermode_isexcl);
+
+  group_exclude_fwd_anysrc_ifempty(group);
+}
+
+void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    isex_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    isex_incl(group, num_sources, sources);
+    zassert(group->group_filtermode_isexcl);
+  }
+
+  zassert(group->group_filtermode_isexcl);
+
+  igmp_group_reset_gmi(group);
+}
+
+static void toin_incl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  struct igmp_sock *igmp = group->group_igmp_sock;
+  int num_sources_tosend = listcount(group->group_source_list);
+
+  /* Set SEND flag for all known sources (A) */
+  source_mark_send_flag(group->group_source_list);
+
+  /* Scan received sources (B) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* Lookup reported source (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* If found, clear SEND flag (A*B) */
+      IGMP_SOURCE_DONT_SEND(source->source_flags);
+      --num_sources_tosend;
+    }
+    else {
+      /* If not found, create new source */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+    }
+
+    /* (B)=GMI */
+    igmp_source_reset_gmi(igmp, group, source);
+  }
+
+  /* Send sources marked with SEND flag: Q(G,A-B) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+static void toin_excl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  struct igmp_sock *igmp = group->group_igmp_sock;
+  int num_sources_tosend;
+
+  /* Set SEND flag for X (sources with timer > 0) */
+  num_sources_tosend = source_mark_send_flag_by_timer(group->group_source_list);
+
+  /* Scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* Lookup reported source (A) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      if (source->t_source_timer) {
+	/* If found and timer running, clear SEND flag (X*A) */
+	IGMP_SOURCE_DONT_SEND(source->source_flags);
+	--num_sources_tosend;
+      }
+    }
+    else {
+      /* If not found, create new source */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+    }
+
+    /* (A)=GMI */
+    igmp_source_reset_gmi(igmp, group, source);
+  }
+
+  /* Send sources marked with SEND flag: Q(G,X-A) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+
+  /* Send Q(G) */
+  group_query_send(group);
+}
+
+void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    toin_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    toin_incl(group, num_sources, sources);
+  }
+}
+
+static void toex_incl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  zassert(!group->group_filtermode_isexcl);
+
+  /* Set DELETE flag for all known sources (A) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* Clear off SEND flag from all known sources (A) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* Scan received sources (B) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+
+    src_addr = sources + i;
+
+    /* Lookup reported source (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* If found, clear deletion flag: (A*B) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+      /* and set SEND flag (A*B) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+    else {
+      /* If source not found, create source with timer=0: (B-A)=0 */
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+      zassert(!source->t_source_timer); /* (B-A) timer=0 */
+    }
+
+  } /* Scan received sources (B) */
+
+  group->group_filtermode_isexcl = 1; /* boolean=true */
+
+  /* Delete all sources marked with DELETE flag (A-B) */
+  source_delete_by_flag(group->group_source_list);
+
+  /* Send sources marked with SEND flag: Q(G,A*B) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+
+  zassert(group->group_filtermode_isexcl);
+
+  group_exclude_fwd_anysrc_ifempty(group);
+}
+
+static void toex_excl(struct igmp_group *group,
+		      int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  /* set DELETE flag for all known sources (X,Y) */
+  source_mark_delete_flag(group->group_source_list);
+
+  /* clear off SEND flag from all known sources (X,Y) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+    
+    src_addr = sources + i;
+    
+    /* lookup reported source (A) in known sources (X,Y) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* if found, clear off DELETE flag from reported source (A) */
+      IGMP_SOURCE_DONT_DELETE(source->source_flags);
+    }
+    else {
+      /* if not found, create source with Group Timer: (A-X-Y)=Group Timer */
+      long group_timer_msec;
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+
+      zassert(!source->t_source_timer); /* timer == 0 */
+      group_timer_msec = igmp_group_timer_remain_msec(group);
+      igmp_source_timer_on(group, source, group_timer_msec);
+      zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+
+      /* make sure source is created with DELETE flag unset */
+      zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags));
+    }
+
+    /* make sure reported source has DELETE flag unset */
+    zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags));
+
+    if (source->t_source_timer) {
+      /* if source timer>0 mark SEND flag: Q(G,A-Y) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+
+  } /* scan received sources (A) */
+
+  /*
+    delete all sources marked with DELETE flag:
+    Delete (X-A)
+    Delete (Y-A)
+  */
+  source_delete_by_flag(group->group_source_list);
+ 
+  /* send sources marked with SEND flag: Q(G,A-Y) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+void igmpv3_report_toex(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    toex_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    toex_incl(group, num_sources, sources);
+    zassert(group->group_filtermode_isexcl);
+  }
+  zassert(group->group_filtermode_isexcl);
+
+  /* Group Timer=GMI */
+  igmp_group_reset_gmi(group);
+}
+
+void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from,
+			 struct in_addr group_addr,
+			 int num_sources, struct in_addr *sources)
+{
+  on_trace(__PRETTY_FUNCTION__,
+	   igmp->interface, from, group_addr, num_sources, sources);
+
+  allow(igmp, from, group_addr, num_sources, sources);
+}
+
+/*
+  RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+
+  When transmitting a group specific query, if the group timer is
+  larger than LMQT, the "Suppress Router-Side Processing" bit is set
+  in the query message.
+*/
+static void group_retransmit_group(struct igmp_group *group)
+{
+  char                  query_buf[PIM_IGMP_BUFSIZE_WRITE];
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  long                  lmqc;      /* Last Member Query Count */
+  long                  lmqi_msec; /* Last Member Query Interval */
+  long                  lmqt_msec; /* Last Member Query Time */
+  int                   s_flag;
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqc      = igmp->querier_robustness_variable;
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+  lmqt_msec = lmqc * lmqi_msec;
+
+  /*
+    RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+    
+    When transmitting a group specific query, if the group timer is
+    larger than LMQT, the "Suppress Router-Side Processing" bit is set
+    in the query message.
+  */
+  s_flag = igmp_group_timer_remain_msec(group) > lmqt_msec;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("retransmit_group_specific_query: group %s on %s: s_flag=%d count=%d",
+	       group_str, igmp->interface->name, s_flag,
+	       group->group_specific_query_retransmit_count);
+  }
+
+  /*
+    RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+    Group-Specific and Group-and-Source-Specific Queries are sent with
+    an IP destination address equal to the multicast address of
+    interest.
+  */
+
+  pim_igmp_send_membership_query(group,
+				 igmp->fd,
+				 igmp->interface->name,
+				 query_buf,
+				 sizeof(query_buf),
+				 0 /* num_sources_tosend */,
+				 group->group_addr /* dst_addr */,
+				 group->group_addr /* group_addr */,
+				 pim_ifp->igmp_query_max_response_time_dsec,
+				 s_flag,
+				 igmp->querier_robustness_variable,
+				 igmp->querier_query_interval);
+}
+
+/*
+  RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+
+  When building a group and source specific query for a group G, two
+  separate query messages are sent for the group.  The first one has
+  the "Suppress Router-Side Processing" bit set and contains all the
+  sources with retransmission state and timers greater than LMQT.  The
+  second has the "Suppress Router-Side Processing" bit clear and
+  contains all the sources with retransmission state and timers lower
+  or equal to LMQT.  If either of the two calculated messages does not
+  contain any sources, then its transmission is suppressed.
+ */
+static int group_retransmit_sources(struct igmp_group *group,
+				    int send_with_sflag_set)
+{
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  long                  lmqc;      /* Last Member Query Count */
+  long                  lmqi_msec; /* Last Member Query Interval */
+  long                  lmqt_msec; /* Last Member Query Time */
+  char                  query_buf1[PIM_IGMP_BUFSIZE_WRITE]; /* 1 = with s_flag set */
+  char                  query_buf2[PIM_IGMP_BUFSIZE_WRITE]; /* 2 = with s_flag clear */
+  int                   query_buf1_max_sources;
+  int                   query_buf2_max_sources;
+  struct in_addr       *source_addr1;
+  struct in_addr       *source_addr2;
+  int                   num_sources_tosend1;
+  int                   num_sources_tosend2;
+  struct listnode      *src_node;
+  struct igmp_source   *src;
+  int                   num_retransmit_sources_left = 0;
+  
+  query_buf1_max_sources = (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) >> 2;
+  query_buf2_max_sources = (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2;
+  
+  source_addr1 = (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET);
+  source_addr2 = (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET);
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqc      = igmp->querier_robustness_variable;
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+  lmqt_msec = lmqc * lmqi_msec;
+
+  /* Scan all group sources */
+  for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) {
+
+    /* Source has retransmission state? */
+    if (src->source_query_retransmit_count < 1)
+      continue;
+
+    if (--src->source_query_retransmit_count > 0) {
+      ++num_retransmit_sources_left;
+    }
+
+    /* Copy source address into appropriate query buffer */
+    if (igmp_source_timer_remain_msec(src) > lmqt_msec) {
+      *source_addr1 = src->source_addr;
+      ++source_addr1;
+    }
+    else {
+      *source_addr2 = src->source_addr;
+      ++source_addr2;
+    }
+
+  }
+  
+  num_sources_tosend1 = source_addr1 - (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET);
+  num_sources_tosend2 = source_addr2 - (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("retransmit_grp&src_specific_query: group %s on %s: srcs_with_sflag=%d srcs_wo_sflag=%d will_send_sflag=%d retransmit_src_left=%d",
+	       group_str, igmp->interface->name,
+	       num_sources_tosend1,
+	       num_sources_tosend2,
+	       send_with_sflag_set,
+	       num_retransmit_sources_left);
+  }
+
+  if (num_sources_tosend1 > 0) {
+    /*
+      Send group-and-source-specific query with s_flag set and all
+      sources with timers greater than LMQT.
+    */
+
+    if (send_with_sflag_set) {
+
+      query_buf1_max_sources = (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) >> 2;
+      if (num_sources_tosend1 > query_buf1_max_sources) {
+	char group_str[100];
+	pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+	zlog_warn("%s: group %s on %s: s_flag=1 unable to fit %d sources into buf_size=%d (max_sources=%d)",
+		  __PRETTY_FUNCTION__, group_str, igmp->interface->name,
+		  num_sources_tosend1, sizeof(query_buf1), query_buf1_max_sources);
+      }
+      else {
+	/*
+	  RFC3376: 4.1.12. IP Destination Addresses for Queries
+      
+	  Group-Specific and Group-and-Source-Specific Queries are sent with
+	  an IP destination address equal to the multicast address of
+	  interest.
+	*/
+    
+	pim_igmp_send_membership_query(group,
+				       igmp->fd,
+				       igmp->interface->name,
+				       query_buf1,
+				       sizeof(query_buf1),
+				       num_sources_tosend1,
+				       group->group_addr,
+				       group->group_addr,
+				       pim_ifp->igmp_query_max_response_time_dsec,
+				       1 /* s_flag */,
+				       igmp->querier_robustness_variable,
+				       igmp->querier_query_interval);
+    
+      }
+
+    } /* send_with_sflag_set */
+
+  }
+
+  if (num_sources_tosend2 > 0) {
+    /*
+      Send group-and-source-specific query with s_flag clear and all
+      sources with timers lower or equal to LMQT.
+    */
+  
+    query_buf2_max_sources = (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2;
+    if (num_sources_tosend2 > query_buf2_max_sources) {
+      char group_str[100];
+      pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+      zlog_warn("%s: group %s on %s: s_flag=0 unable to fit %d sources into buf_size=%d (max_sources=%d)",
+		__PRETTY_FUNCTION__, group_str, igmp->interface->name,
+		num_sources_tosend2, sizeof(query_buf2), query_buf2_max_sources);
+    }
+    else {
+      /*
+	RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+	Group-Specific and Group-and-Source-Specific Queries are sent with
+	an IP destination address equal to the multicast address of
+	interest.
+      */
+
+      pim_igmp_send_membership_query(group,
+				     igmp->fd,
+				     igmp->interface->name,
+				     query_buf2,
+				     sizeof(query_buf2),
+				     num_sources_tosend2,
+				     group->group_addr,
+				     group->group_addr,
+				     pim_ifp->igmp_query_max_response_time_dsec,
+				     0 /* s_flag */,
+				     igmp->querier_robustness_variable,
+				     igmp->querier_query_interval);
+
+    }
+  }
+
+  return num_retransmit_sources_left;
+}
+
+static int igmp_group_retransmit(struct thread *t)
+{
+  struct igmp_group *group;
+  int num_retransmit_sources_left;
+  int send_with_sflag_set; /* boolean */
+
+  zassert(t);
+  group = THREAD_ARG(t);
+  zassert(group);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("group_retransmit_timer: group %s on %s",
+	       group_str, group->group_igmp_sock->interface->name);
+  }
+
+  /* Retransmit group-specific queries? (RFC3376: 6.6.3.1) */
+  if (group->group_specific_query_retransmit_count > 0) {
+
+    /* Retransmit group-specific queries (RFC3376: 6.6.3.1) */
+    group_retransmit_group(group);
+    --group->group_specific_query_retransmit_count;
+
+    /*
+      RFC3376: 6.6.3.2
+      If a group specific query is scheduled to be transmitted at the
+      same time as a group and source specific query for the same group,
+      then transmission of the group and source specific message with the
+      "Suppress Router-Side Processing" bit set may be suppressed.
+    */
+    send_with_sflag_set = 0; /* boolean=false */
+  }
+  else {
+    send_with_sflag_set = 1; /* boolean=true */
+  }
+
+  /* Retransmit group-and-source-specific queries (RFC3376: 6.6.3.2) */
+  num_retransmit_sources_left = group_retransmit_sources(group,
+							 send_with_sflag_set);
+
+  group->t_group_query_retransmit_timer = 0;
+
+  /*
+    Keep group retransmit timer running if there is any retransmit
+    counter pending
+  */
+  if ((num_retransmit_sources_left > 0) ||
+      (group->group_specific_query_retransmit_count > 0)) {
+    group_retransmit_timer_on(group);
+  }
+
+  return 0;
+}
+
+/*
+  group_retransmit_timer_on:
+  if group retransmit timer isn't running, starts it;
+  otherwise, do nothing
+*/
+static void group_retransmit_timer_on(struct igmp_group *group)
+{
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  long                  lmqi_msec; /* Last Member Query Interval */
+
+  /* if group retransmit timer is running, do nothing */
+  if (group->t_group_query_retransmit_timer) {
+    return;
+  }
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("Scheduling %ld.%03ld sec retransmit timer for group %s on %s",
+	       lmqi_msec / 1000,
+	       lmqi_msec % 1000,
+	       group_str,
+	       igmp->interface->name);
+  }
+
+  THREAD_TIMER_MSEC_ON(master, group->t_group_query_retransmit_timer,
+		       igmp_group_retransmit,
+		       group, lmqi_msec);
+}
+
+static long igmp_group_timer_remain_msec(struct igmp_group *group)
+{
+  return pim_time_timer_remain_msec(group->t_group_timer);
+}
+
+static long igmp_source_timer_remain_msec(struct igmp_source *source)
+{
+  return pim_time_timer_remain_msec(source->t_source_timer);
+}
+
+/*
+  RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+*/
+static void group_query_send(struct igmp_group *group)
+{
+  long              lmqc;    /* Last Member Query Count */
+
+  lmqc = group->group_igmp_sock->querier_robustness_variable;
+
+  /* lower group timer to lmqt */
+  igmp_group_timer_lower_to_lmqt(group);
+
+  /* reset retransmission counter */
+  group->group_specific_query_retransmit_count = lmqc;
+
+  /* immediately send group specific query (decrease retransmit counter by 1)*/
+  group_retransmit_group(group);
+
+  /* make sure group retransmit timer is running */
+  group_retransmit_timer_on(group);
+}
+
+/*
+  RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+*/
+static void source_query_send_by_flag(struct igmp_group *group,
+				      int num_sources_tosend)
+{
+  struct igmp_sock     *igmp;
+  struct pim_interface *pim_ifp;
+  struct listnode      *src_node;
+  struct igmp_source   *src;
+  long                  lmqc;      /* Last Member Query Count */
+  long                  lmqi_msec; /* Last Member Query Interval */
+  long                  lmqt_msec; /* Last Member Query Time */
+
+  zassert(num_sources_tosend > 0);
+
+  igmp = group->group_igmp_sock;
+  pim_ifp = igmp->interface->info;
+
+  lmqc      = igmp->querier_robustness_variable;
+  lmqi_msec = 100 * pim_ifp->igmp_query_max_response_time_dsec;
+  lmqt_msec = lmqc * lmqi_msec;
+
+  /*
+    RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+
+    (...) for each of the sources in X of group G, with source timer larger
+    than LMQT:
+    o Set number of retransmissions for each source to [Last Member
+    Query Count].
+    o Lower source timer to LMQT.
+  */
+  for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) {
+    if (IGMP_SOURCE_TEST_SEND(src->source_flags)) {
+      /* source "src" in X of group G */
+      if (igmp_source_timer_remain_msec(src) > lmqt_msec) {
+	src->source_query_retransmit_count = lmqc;
+	igmp_source_timer_lower_to_lmqt(src);
+      }
+    }
+  }
+
+  /* send group-and-source specific queries */
+  group_retransmit_sources(group, 1 /* send_with_sflag_set=true */);
+
+  /* make sure group retransmit timer is running */
+  group_retransmit_timer_on(group);
+}
+
+static void block_excl(struct igmp_group *group,
+		       int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  /* 1. clear off SEND flag from all known sources (X,Y) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* 2. scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+    
+    src_addr = sources + i;
+    
+    /* lookup reported source (A) in known sources (X,Y) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (!source) {
+      /* 3: if not found, create source with Group Timer: (A-X-Y)=Group Timer */
+      long group_timer_msec;
+      source = source_new(group, *src_addr,
+			  group->group_igmp_sock->interface->name);
+      if (!source) {
+	/* ugh, internal malloc failure, skip source */
+	continue;
+      }
+
+      zassert(!source->t_source_timer); /* timer == 0 */
+      group_timer_msec = igmp_group_timer_remain_msec(group);
+      igmp_source_timer_on(group, source, group_timer_msec);
+      zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+    }
+
+    if (source->t_source_timer) {
+      /* 4. if source timer>0 mark SEND flag: Q(G,A-Y) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+  }
+ 
+  /* 5. send sources marked with SEND flag: Q(G,A-Y) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+static void block_incl(struct igmp_group *group,
+		       int num_sources, struct in_addr *sources)
+{
+  int num_sources_tosend = 0;
+
+  /* 1. clear off SEND flag from all known sources (B) */
+  source_clear_send_flag(group->group_source_list);
+
+  /* 2. scan received sources (A) */
+  for (int i = 0; i < num_sources; ++i) {
+    struct igmp_source *source;
+    struct in_addr     *src_addr;
+    
+    src_addr = sources + i;
+    
+    /* lookup reported source (A) in known sources (B) */
+    source = igmp_find_source_by_addr(group, *src_addr);
+    if (source) {
+      /* 3. if found (A*B), mark SEND flag: Q(G,A*B) */
+      IGMP_SOURCE_DO_SEND(source->source_flags);
+      ++num_sources_tosend;
+    }
+  } 
+ 
+  /* 4. send sources marked with SEND flag: Q(G,A*B) */
+  if (num_sources_tosend > 0) {
+    source_query_send_by_flag(group, num_sources_tosend);
+  }
+}
+
+void igmpv3_report_block(struct igmp_sock *igmp, struct in_addr from,
+			 struct in_addr group_addr,
+			 int num_sources, struct in_addr *sources)
+{
+  struct interface *ifp = igmp->interface;
+  struct pim_interface *pim_ifp;
+  struct igmp_group *group;
+
+  on_trace(__PRETTY_FUNCTION__,
+	   ifp, from, group_addr, num_sources, sources);
+
+  pim_ifp = ifp->info;
+
+  /* non-existant group is created as INCLUDE {empty} */
+  group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+  if (!group) {
+    return;
+  }
+
+  if (group->group_filtermode_isexcl) {
+    /* EXCLUDE mode */
+    block_excl(group, num_sources, sources);
+  }
+  else {
+    /* INCLUDE mode */
+    block_incl(group, num_sources, sources);
+  }
+}
+
+void igmp_group_timer_lower_to_lmqt(struct igmp_group *group)
+{
+  struct igmp_sock     *igmp;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  char                 *ifname;
+  int   lmqi_dsec; /* Last Member Query Interval */
+  int   lmqc;      /* Last Member Query Count */
+  int   lmqt_msec; /* Last Member Query Time */
+
+  /*
+    RFC 3376: 6.2.2. Definition of Group Timers
+    
+    The group timer is only used when a group is in EXCLUDE mode and
+    it represents the time for the *filter-mode* of the group to
+    expire and switch to INCLUDE mode.
+  */
+  if (!group->group_filtermode_isexcl) {
+    return;
+  }
+
+  igmp    = group->group_igmp_sock;
+  ifp     = igmp->interface;
+  pim_ifp = ifp->info;
+  ifname  = ifp->name;
+
+  lmqi_dsec = pim_ifp->igmp_query_max_response_time_dsec;
+  lmqc      = igmp->querier_robustness_variable;
+  lmqt_msec = PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: group %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec",
+	       __PRETTY_FUNCTION__,
+	       group_str, ifname,
+	       lmqc, lmqi_dsec, lmqt_msec);
+  }
+
+  zassert(group->group_filtermode_isexcl);
+
+  igmp_group_timer_on(group, lmqt_msec, ifname);
+}
+
+void igmp_source_timer_lower_to_lmqt(struct igmp_source *source)
+{
+  struct igmp_group    *group;
+  struct igmp_sock     *igmp;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  char                 *ifname;
+  int   lmqi_dsec; /* Last Member Query Interval */
+  int   lmqc;      /* Last Member Query Count */
+  int   lmqt_msec; /* Last Member Query Time */
+
+  group   = source->source_group;
+  igmp    = group->group_igmp_sock;
+  ifp     = igmp->interface;
+  pim_ifp = ifp->info;
+  ifname  = ifp->name;
+
+  lmqi_dsec = pim_ifp->igmp_query_max_response_time_dsec;
+  lmqc      = igmp->querier_robustness_variable;
+  lmqt_msec = PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char group_str[100];
+    char source_str[100];
+    pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    zlog_debug("%s: group %s source %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec",
+	       __PRETTY_FUNCTION__,
+	       group_str, source_str, ifname,
+	       lmqc, lmqi_dsec, lmqt_msec);
+  }
+
+  igmp_source_timer_on(group, source, lmqt_msec);
+}
+
+/*
+  Copy sources to message:
+    
+  struct in_addr *sources = (struct in_addr *)(query_buf + IGMP_V3_SOURCES_OFFSET);
+  if (num_sources > 0) {
+  struct listnode    *node;
+  struct igmp_source *src;
+  int                 i = 0;
+
+  for (ALL_LIST_ELEMENTS_RO(source_list, node, src)) {
+  sources[i++] = src->source_addr;
+  }
+  }
+*/
+void pim_igmp_send_membership_query(struct igmp_group *group,
+				    int fd,
+				    const char *ifname,
+				    char *query_buf,
+				    int query_buf_size,
+				    int num_sources,
+				    struct in_addr dst_addr,
+				    struct in_addr group_addr,
+				    int query_max_response_time_dsec,
+				    uint8_t s_flag,
+				    uint8_t querier_robustness_variable,
+				    uint16_t querier_query_interval)
+{
+  ssize_t             msg_size;
+  uint8_t             max_resp_code;
+  uint8_t             qqic;
+  ssize_t             sent;
+  struct sockaddr_in  to;
+  socklen_t           tolen;
+  uint16_t            checksum;
+
+  zassert(num_sources >= 0);
+
+  msg_size = IGMP_V3_SOURCES_OFFSET + (num_sources << 2);
+  if (msg_size > query_buf_size) {
+    zlog_err("%s %s: unable to send: msg_size=%d larger than query_buf_size=%d",
+	     __FILE__, __PRETTY_FUNCTION__,
+	     msg_size, query_buf_size);
+    return;
+  }
+
+  s_flag = !!s_flag; /* force boolean */
+  zassert((s_flag == 0) || (s_flag == 1));
+
+  max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec);
+  qqic          = igmp_msg_encode16to8(querier_query_interval);
+
+  /*
+    RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
+
+    If non-zero, the QRV field contains the [Robustness Variable]
+    value used by the querier, i.e., the sender of the Query.  If the
+    querier's [Robustness Variable] exceeds 7, the maximum value of
+    the QRV field, the QRV is set to zero.
+  */
+  if (querier_robustness_variable > 7) {
+    querier_robustness_variable = 0;
+  }
+
+  query_buf[0]                                         = PIM_IGMP_MEMBERSHIP_QUERY;
+  query_buf[1]                                         = max_resp_code;
+  *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET)   = 0; /* for computing checksum */
+  *(struct in_addr *)(query_buf + 4)                   = group_addr;
+  query_buf[8]                                         = (s_flag << 3) | querier_robustness_variable;
+  query_buf[9]                                         = qqic;
+  *(uint16_t *)(query_buf + IGMP_V3_NUMSOURCES_OFFSET) = htons(num_sources);
+
+  checksum = pim_inet_checksum(query_buf, msg_size);
+  *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET) = checksum;
+
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    char dst_str[100];
+    char group_str[100];
+    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: to %s on %s: group=%s sources=%d msg_size=%d s_flag=%x QRV=%u QQI=%u QQIC=%02x checksum=%x",
+	       __PRETTY_FUNCTION__,
+	       dst_str, ifname, group_str, num_sources,
+	       msg_size, s_flag, querier_robustness_variable,
+	       querier_query_interval, qqic, checksum);
+  }
+
+#if 0
+  memset(&to, 0, sizeof(to));
+#endif
+  to.sin_family = AF_INET;
+  to.sin_addr = dst_addr;
+#if 0
+  to.sin_port = htons(0);
+#endif
+  tolen = sizeof(to);
+
+  sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, &to, tolen);
+  if (sent != (ssize_t) msg_size) {
+    int e = errno;
+    char dst_str[100];
+    char group_str[100];
+    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    if (sent < 0) {
+      zlog_warn("%s: sendto() failure to %s on %s: group=%s msg_size=%d: errno=%d: %s",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, group_str, msg_size,
+		e, strerror(e));
+    }
+    else {
+      zlog_warn("%s: sendto() partial to %s on %s: group=%s msg_size=%d: sent=%d",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, group_str,
+		msg_size, sent);
+    }
+    return;
+  }
+
+  /*
+    s_flag sanity test: s_flag must be set for general queries
+
+    RFC 3376: 6.6.1. Timer Updates
+
+    When a router sends or receives a query with a clear Suppress
+    Router-Side Processing flag, it must update its timers to reflect
+    the correct timeout values for the group or sources being queried.
+
+    General queries don't trigger timer update.
+  */
+  if (!s_flag) {
+    /* general query? */
+    if (PIM_INADDR_IS_ANY(group_addr)) {
+      char dst_str[100];
+      char group_str[100];
+      pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+      pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+      zlog_warn("%s: to %s on %s: group=%s sources=%d: s_flag is clear for general query!",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, group_str, num_sources);
+    }
+  }
+
+}
diff --git a/pimd/pim_igmpv3.h b/pimd/pim_igmpv3.h
new file mode 100644
index 0000000..bb7e926
--- /dev/null
+++ b/pimd/pim_igmpv3.h
@@ -0,0 +1,100 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_IGMPV3_H
+#define PIM_IGMPV3_H
+
+#include <zebra.h>
+#include "if.h"
+
+#define IGMP_V3_CHECKSUM_OFFSET            (2)
+#define IGMP_V3_REPORT_NUMGROUPS_OFFSET    (6)
+#define IGMP_V3_REPORT_GROUPPRECORD_OFFSET (8)
+#define IGMP_V3_NUMSOURCES_OFFSET          (10)
+#define IGMP_V3_SOURCES_OFFSET             (12)
+
+/* GMI: Group Membership Interval */
+#define PIM_IGMP_GMI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * (qri_dsec))
+
+/* OQPI: Other Querier Present Interval */
+#define PIM_IGMP_OQPI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * ((qri_dsec) >> 1))
+
+/* SQI: Startup Query Interval */
+#define PIM_IGMP_SQI(qi) (((qi) < 4) ? 1 : ((qi) >> 2))
+
+/* LMQT: Last Member Query Time */
+#define PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc) ((lmqc) * (100 * (lmqi_dsec)))
+
+/* OHPI: Older Host Present Interval */
+#define PIM_IGMP_OHPI_DSEC(qrv,qqi,qri_dsec) ((qrv) * (10 * (qqi)) + (qri_dsec))
+
+void igmp_group_reset_gmi(struct igmp_group *group);
+void igmp_source_reset_gmi(struct igmp_sock *igmp,
+			   struct igmp_group *group,
+			   struct igmp_source *source);
+
+void igmp_source_free(struct igmp_source *source);
+void igmp_source_delete(struct igmp_source *source);
+void igmp_source_delete_expired(struct list *source_list);
+
+int igmp_group_compat_mode(const struct igmp_sock *igmp,
+			   const struct igmp_group *group);
+
+void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources);
+void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources);
+void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources);
+void igmpv3_report_toex(struct igmp_sock *igmp, struct in_addr from,
+			struct in_addr group_addr,
+			int num_sources, struct in_addr *sources);
+void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from,
+			 struct in_addr group_addr,
+			 int num_sources, struct in_addr *sources);
+void igmpv3_report_block(struct igmp_sock *igmp, struct in_addr from,
+			 struct in_addr group_addr,
+			 int num_sources, struct in_addr *sources);
+
+void igmp_group_timer_lower_to_lmqt(struct igmp_group *group);
+void igmp_source_timer_lower_to_lmqt(struct igmp_source *source);
+
+struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group,
+					     struct in_addr src_addr);
+
+void pim_igmp_send_membership_query(struct igmp_group *group,
+				    int fd,
+				    const char *ifname,
+				    char *query_buf,
+				    int query_buf_size,
+				    int num_sources,
+				    struct in_addr dst_addr,
+				    struct in_addr group_addr,
+				    int query_max_response_time_dsec,
+				    uint8_t s_flag,
+				    uint8_t querier_robustness_variable,
+				    uint16_t querier_query_interval);
+
+#endif /* PIM_IGMPV3_H */
diff --git a/pimd/pim_join.c b/pimd/pim_join.c
new file mode 100644
index 0000000..6dcb311
--- /dev/null
+++ b/pimd/pim_join.c
@@ -0,0 +1,428 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "prefix.h"
+
+#include "pimd.h"
+#include "pim_str.h"
+#include "pim_tlv.h"
+#include "pim_msg.h"
+#include "pim_pim.h"
+#include "pim_join.h"
+#include "pim_iface.h"
+#include "pim_hello.h"
+#include "pim_ifchannel.h"
+
+static void on_trace(const char *label,
+		     struct interface *ifp, struct in_addr src)
+{
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src, src_str, sizeof(src_str));
+    zlog_debug("%s: from %s on %s",
+	       label, src_str, ifp->name);
+  }
+}
+
+static void recv_join(struct interface *ifp,
+		      struct pim_neighbor *neigh,
+		      uint16_t holdtime,
+		      struct in_addr upstream,
+		      struct in_addr group,
+		      struct in_addr source,
+		      uint8_t source_flags)
+{
+  if (PIM_DEBUG_PIM_TRACE) {
+    char up_str[100];
+    char src_str[100];
+    char grp_str[100];
+    char neigh_str[100];
+    pim_inet4_dump("<upstream?>", upstream, up_str, sizeof(up_str));
+    pim_inet4_dump("<src?>", source, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<neigh?>", neigh->source_addr, neigh_str, sizeof(neigh_str));
+    zlog_warn("%s: join (S,G)=(%s,%s) rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str,
+	      source_flags & PIM_RPT_BIT_MASK,
+	      source_flags & PIM_WILDCARD_BIT_MASK,
+	      up_str, holdtime, neigh_str, ifp->name);
+  }
+  
+  /* Restart join expiry timer */
+  pim_ifchannel_join_add(ifp, upstream, source, group, source_flags, holdtime);
+}
+
+static void recv_prune(struct interface *ifp,
+		       struct pim_neighbor *neigh,
+		       uint16_t holdtime,
+		       struct in_addr upstream,
+		       struct in_addr group,
+		       struct in_addr source,
+		       uint8_t source_flags)
+{
+  if (PIM_DEBUG_PIM_TRACE) {
+    char up_str[100];
+    char src_str[100];
+    char grp_str[100];
+    char neigh_str[100];
+    pim_inet4_dump("<upstream?>", upstream, up_str, sizeof(up_str));
+    pim_inet4_dump("<src?>", source, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", group, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<neigh?>", neigh->source_addr, neigh_str, sizeof(neigh_str));
+    zlog_warn("%s: prune (S,G)=(%s,%s) rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str,
+	      source_flags & PIM_RPT_BIT_MASK,
+	      source_flags & PIM_WILDCARD_BIT_MASK,
+	      up_str, holdtime, neigh_str, ifp->name);
+  }
+  
+  pim_ifchannel_prune(ifp, upstream, source, group, source_flags, holdtime);
+}
+
+int pim_joinprune_recv(struct interface *ifp,
+		       struct pim_neighbor *neigh,
+		       struct in_addr src_addr,
+		       char *tlv_buf, int tlv_buf_size)
+{
+  struct prefix   msg_upstream_addr;
+  uint8_t         msg_num_groups;
+  uint16_t        msg_holdtime;
+  int             addr_offset;
+  char           *buf;
+  char           *pastend;
+  int             remain;
+  int             group;
+
+  on_trace(__PRETTY_FUNCTION__, ifp, src_addr);
+
+  buf     = tlv_buf;
+  pastend = tlv_buf + tlv_buf_size;
+
+  /*
+    Parse ucast addr
+  */
+  addr_offset = pim_parse_addr_ucast(ifp->name, src_addr,
+				     &msg_upstream_addr,
+				     buf, pastend - buf);
+  if (addr_offset < 1) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      src_str, ifp->name);
+    return -1;
+  }
+  buf += addr_offset;
+
+  /*
+    Check upstream address family
+   */
+  if (msg_upstream_addr.family != AF_INET) {
+    if (PIM_DEBUG_PIM_TRACE) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: ignoring join/prune directed to unexpected addr family=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		msg_upstream_addr.family, src_str, ifp->name);
+    }
+    return -2;
+  }
+
+  remain = pastend - buf;
+  if (remain < 4) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: short join/prune message buffer for group list: size=%d minimum=%d from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      remain, 4, src_str, ifp->name);
+    return -4;
+  }
+
+  ++buf; /* skip reserved byte */
+  msg_num_groups = *(const uint8_t *) buf;
+  ++buf;
+  msg_holdtime = ntohs(*(const uint16_t *) buf);
+  ++buf;
+  ++buf;
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    char upstream_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<addr?>", msg_upstream_addr.u.prefix4,
+		   upstream_str, sizeof(upstream_str));
+    zlog_warn("%s: join/prune upstream=%s groups=%d holdtime=%d from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      upstream_str, msg_num_groups, msg_holdtime,
+	      src_str, ifp->name);
+  }
+
+  /* Scan groups */
+  for (group = 0; group < msg_num_groups; ++group) {
+    struct prefix msg_group_addr;
+    struct prefix msg_source_addr;
+    uint8_t       msg_source_flags;
+    uint16_t      msg_num_joined_sources;
+    uint16_t      msg_num_pruned_sources;
+    int           source;
+
+    addr_offset = pim_parse_addr_group(ifp->name, src_addr,
+				       &msg_group_addr,
+				       buf, pastend - buf);
+    if (addr_offset < 1) {
+      return -5;
+    }
+    buf += addr_offset;
+
+    remain = pastend - buf;
+    if (remain < 4) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: short join/prune buffer for source list: size=%d minimum=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		remain, 4, src_str, ifp->name);
+      return -6;
+    }
+
+    msg_num_joined_sources = ntohs(*(const uint16_t *) buf);
+    buf += 2;
+    msg_num_pruned_sources = ntohs(*(const uint16_t *) buf);
+    buf += 2;
+
+    if (PIM_DEBUG_PIM_TRACE) {
+      char src_str[100];
+      char upstream_str[100];
+      char group_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<addr?>", msg_upstream_addr.u.prefix4,
+		     upstream_str, sizeof(upstream_str));
+      pim_inet4_dump("<grp?>", msg_group_addr.u.prefix4,
+		     group_str, sizeof(group_str));
+      zlog_warn("%s: join/prune upstream=%s group=%s/%d join_src=%d prune_src=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		upstream_str, group_str, msg_group_addr.prefixlen,
+		msg_num_joined_sources, msg_num_pruned_sources,
+		src_str, ifp->name);
+    }
+
+    /* Scan joined sources */
+    for (source = 0; source < msg_num_joined_sources; ++source) {
+      addr_offset = pim_parse_addr_source(ifp->name, src_addr,
+					  &msg_source_addr,
+					  &msg_source_flags,
+					  buf, pastend - buf);
+      if (addr_offset < 1) {
+	return -7;
+      }
+      buf += addr_offset;
+
+      recv_join(ifp, neigh, msg_holdtime,
+		msg_upstream_addr.u.prefix4,
+		msg_group_addr.u.prefix4,
+		msg_source_addr.u.prefix4,
+		msg_source_flags);
+    }
+
+    /* Scan pruned sources */
+    for (source = 0; source < msg_num_pruned_sources; ++source) {
+      addr_offset = pim_parse_addr_source(ifp->name, src_addr,
+					  &msg_source_addr,
+					  &msg_source_flags,
+					  buf, pastend - buf);
+      if (addr_offset < 1) {
+	return -8;
+      }
+      buf += addr_offset;
+
+      recv_prune(ifp, neigh, msg_holdtime,
+		 msg_upstream_addr.u.prefix4,
+		 msg_group_addr.u.prefix4,
+		 msg_source_addr.u.prefix4,
+		 msg_source_flags);
+    }
+
+  } /* scan groups */
+
+  return 0;
+}
+
+int pim_joinprune_send(struct interface *ifp,
+		       struct in_addr upstream_addr,
+		       struct in_addr source_addr,
+		       struct in_addr group_addr,
+		       int send_join)
+{
+  struct pim_interface *pim_ifp;
+  char pim_msg[1000];
+  const char *pastend = pim_msg + sizeof(pim_msg);
+  char *pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN; /* room for pim header */
+  int pim_msg_size;
+  int remain;
+
+  zassert(ifp);
+
+  pim_ifp = ifp->info;
+
+  if (!pim_ifp) {
+    zlog_warn("%s: multicast not enabled on interface %s",
+	      __PRETTY_FUNCTION__,
+	      ifp->name);
+    return -1;
+  }
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char source_str[100];
+    char group_str[100];
+    char dst_str[100];
+    pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<dst?>", upstream_addr, dst_str, sizeof(dst_str));
+    zlog_debug("%s: sending %s(S,G)=(%s,%s) to upstream=%s on interface %s",
+	       __PRETTY_FUNCTION__,
+	       send_join ? "Join" : "Prune",
+	       source_str, group_str, dst_str, ifp->name);
+  }
+
+  if (PIM_INADDR_IS_ANY(upstream_addr)) {
+    if (PIM_DEBUG_PIM_TRACE) {
+      char source_str[100];
+      char group_str[100];
+      char dst_str[100];
+      pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+      pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+      pim_inet4_dump("<dst?>", upstream_addr, dst_str, sizeof(dst_str));
+      zlog_debug("%s: %s(S,G)=(%s,%s): upstream=%s is myself on interface %s",
+		 __PRETTY_FUNCTION__,
+		 send_join ? "Join" : "Prune",
+		 source_str, group_str, dst_str, ifp->name);
+    }
+    return 0;
+  }
+
+  /*
+    RFC 4601: 4.3.1.  Sending Hello Messages
+
+    Thus, if a router needs to send a Join/Prune or Assert message on
+    an interface on which it has not yet sent a Hello message with the
+    currently configured IP address, then it MUST immediately send the
+    relevant Hello message without waiting for the Hello Timer to
+    expire, followed by the Join/Prune or Assert message.
+  */
+  pim_hello_require(ifp);
+
+  /*
+    Build PIM message
+  */
+
+  remain = pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr,
+						remain,
+						upstream_addr);
+  if (!pim_msg_curr) {
+    char dst_str[100];
+    pim_inet4_dump("<dst?>", upstream_addr, dst_str, sizeof(dst_str));
+    zlog_warn("%s: failure encoding destination address %s: space left=%d",
+	      __PRETTY_FUNCTION__, dst_str, remain);
+    return -3;
+  }
+
+  remain = pastend - pim_msg_curr;
+  if (remain < 4) {
+    zlog_warn("%s: group will not fit: space left=%d",
+	    __PRETTY_FUNCTION__, remain);
+    return -4;
+  }
+
+  *pim_msg_curr = 0; /* reserved */
+  ++pim_msg_curr;
+  *pim_msg_curr = 1; /* number of groups */
+  ++pim_msg_curr;
+  *((uint16_t *) pim_msg_curr) = htons(PIM_JP_HOLDTIME);
+  ++pim_msg_curr;
+  ++pim_msg_curr;
+
+  remain = pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_group(pim_msg_curr,
+						remain,
+						group_addr);
+  if (!pim_msg_curr) {
+    char group_str[100];
+    pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+    zlog_warn("%s: failure encoding group address %s: space left=%d",
+	      __PRETTY_FUNCTION__, group_str, remain);
+    return -5;
+  }
+
+  remain = pastend - pim_msg_curr;
+  if (remain < 4) {
+    zlog_warn("%s: sources will not fit: space left=%d",
+	      __PRETTY_FUNCTION__, remain);
+    return -6;
+  }
+
+  /* number of joined sources */
+  *((uint16_t *) pim_msg_curr) = htons(send_join ? 1 : 0);
+  ++pim_msg_curr;
+  ++pim_msg_curr;
+
+  /* number of pruned sources */
+  *((uint16_t *) pim_msg_curr) = htons(send_join ? 0 : 1);
+  ++pim_msg_curr;
+  ++pim_msg_curr;
+
+  remain = pastend - pim_msg_curr;
+  pim_msg_curr = pim_msg_addr_encode_ipv4_source(pim_msg_curr,
+						 remain,
+						 source_addr);
+  if (!pim_msg_curr) {
+    char source_str[100];
+    pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+    zlog_warn("%s: failure encoding source address %s: space left=%d",
+	      __PRETTY_FUNCTION__, source_str, remain);
+    return -7;
+  }
+
+  /* Add PIM header */
+
+  pim_msg_size = pim_msg_curr - pim_msg;
+
+  pim_msg_build_header(ifp->name,
+		       pim_msg, pim_msg_size,
+		       PIM_MSG_TYPE_JOIN_PRUNE);
+
+  if (pim_msg_send(pim_ifp->pim_sock_fd,
+		   qpim_all_pim_routers_addr,
+		   pim_msg,
+		   pim_msg_size,
+		   ifp->name)) {
+    zlog_warn("%s: could not send PIM message on interface %s",
+	      __PRETTY_FUNCTION__, ifp->name);
+    return -8;
+  }
+
+  return 0;
+}
diff --git a/pimd/pim_join.h b/pimd/pim_join.h
new file mode 100644
index 0000000..4d9407b
--- /dev/null
+++ b/pimd/pim_join.h
@@ -0,0 +1,43 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_JOIN_H
+#define PIM_JOIN_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_neighbor.h"
+
+int pim_joinprune_recv(struct interface *ifp,
+		       struct pim_neighbor *neigh,
+		       struct in_addr src_addr,
+		       char *tlv_buf, int tlv_buf_size);
+
+int pim_joinprune_send(struct interface *ifp,
+		       struct in_addr upstream_addr,
+		       struct in_addr source_addr,
+		       struct in_addr group_addr,
+		       int send_join);
+
+#endif /* PIM_JOIN_H */
diff --git a/pimd/pim_main.c b/pimd/pim_main.c
new file mode 100644
index 0000000..1206b55
--- /dev/null
+++ b/pimd/pim_main.c
@@ -0,0 +1,276 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "version.h"
+#include <getopt.h>
+#include "command.h"
+#include "thread.h"
+#include <signal.h>
+
+#include "memory.h"
+#include "filter.h"
+#include "vty.h"
+#include "sigevent.h"
+#include "version.h"
+
+#include "pimd.h"
+#include "pim_version.h"
+#include "pim_signals.h"
+#include "pim_zebra.h"
+
+#ifdef PIM_ZCLIENT_DEBUG
+extern int zclient_debug;
+#endif
+
+extern struct host host;
+extern const char *default_motd;
+
+char config_default[] = SYSCONFDIR PIMD_DEFAULT_CONFIG;
+
+struct option longopts[] = {
+  { "daemon",        no_argument,       NULL, 'd'},
+  { "config_file",   required_argument, NULL, 'f'},
+  { "pid_file",      required_argument, NULL, 'i'},
+  { "vty_addr",      required_argument, NULL, 'A'},
+  { "vty_port",      required_argument, NULL, 'P'},
+  { "version",       no_argument,       NULL, 'v'},
+  { "debug_zclient", no_argument,       NULL, 'Z'},
+  { "help",          no_argument,       NULL, 'h'},
+  { 0 }
+};
+
+char* progname;
+const char *pid_file = PATH_PIMD_PID;
+
+static void usage(int status)
+{
+  if (status != 0)
+    fprintf (stderr, "Try `%s --help' for more information.\n", progname);
+  else {    
+    printf ("Usage : %s [OPTION...]\n\
+Daemon which manages PIM.\n\n\
+-d, --daemon         Run in daemon mode\n\
+-f, --config_file    Set configuration file name\n\
+-i, --pid_file       Set process identifier file name\n\
+-A, --vty_addr       Set vty's bind address\n\
+-P, --vty_port       Set vty's port number\n\
+-v, --version        Print program version\n\
+"
+
+#ifdef PIM_ZCLIENT_DEBUG
+"\
+-Z, --debug_zclient  Enable zclient debugging\n\
+"
+#endif
+
+"\
+-h, --help           Display this help and exit\n\
+\n\
+Report bugs to %s\n", progname, PIMD_BUG_ADDRESS);
+  }
+
+  exit (status);
+}
+
+
+int main(int argc, char** argv, char** envp) {
+  char *p;
+  char *vty_addr = NULL;
+  int vty_port = -1;
+  int daemon_mode = 0;
+  char *config_file = NULL;
+  struct thread thread;
+          
+  umask(0027);
+ 
+  progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]);
+ 
+  zlog_default = openzlog(progname, ZLOG_PIM,
+			  LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON);
+     
+  /* this while just reads the options */                       
+  while (1) {
+    int opt;
+            
+    opt = getopt_long (argc, argv, "df:i:A:P:vZh", longopts, 0);
+                      
+    if (opt == EOF)
+      break;
+    
+    switch (opt) {
+    case 0:
+      break;
+    case 'd':
+      daemon_mode = 1;
+      break;
+    case 'f':
+      config_file = optarg;
+      break;
+    case 'i':
+      pid_file = optarg;
+      break;
+    case 'A':
+      vty_addr = optarg;
+      break;
+    case 'P':
+      vty_port = atoi (optarg);
+      break;
+    case 'v':
+      printf(PIMD_PROGNAME " version %s\n", PIMD_VERSION);
+      print_version(QUAGGA_PROGNAME);
+      exit (0);
+      break;
+#ifdef PIM_ZCLIENT_DEBUG
+    case 'Z':
+      zclient_debug = 1;
+      break;
+#endif
+    case 'h':
+      usage (0);
+      break;
+    default:
+      usage (1);
+      break;
+    }
+  }
+
+  master = thread_master_create();
+
+  /*
+   * Temporarily send zlog to stdout
+   */
+  zlog_default->maxlvl[ZLOG_DEST_STDOUT] = zlog_default->default_lvl;
+  zlog_notice("Boot logging temporarily directed to stdout - begin");
+
+  zlog_notice("Quagga %s " PIMD_PROGNAME " %s starting",
+	      QUAGGA_VERSION, PIMD_VERSION);
+
+  /* 
+   * Initializations
+   */
+  pim_signals_init();
+  cmd_init(1);
+  vty_init(master);
+  memory_init();
+  access_list_init();
+  pim_init();
+  sort_node();
+
+  /*
+   * reset zlog default, then will obey configuration file
+   */
+  zlog_notice("Boot logging temporarily directed to stdout - end");
+#if 0
+  /* this would disable logging to stdout, but config has not been
+     loaded yet to reconfig the logging output */
+  zlog_default->maxlvl[ZLOG_DEST_STDOUT] = ZLOG_DISABLED;
+#endif
+
+  zlog_notice("Loading configuration - begin");
+
+  /* Get configuration file. */
+  vty_read_config(config_file, config_default);
+
+  /*
+    Starting from here zlog_* functions will log according configuration
+   */
+
+  zlog_notice("Loading configuration - end");
+
+  /* Change to the daemon program. */
+  if (daemon_mode) {
+    if (daemon(0, 0)) {
+      zlog_warn("failed to daemonize");
+    }
+  }
+
+  /* Process ID file creation. */
+  pid_output(pid_file);
+
+  /* Create pimd VTY socket */
+  if (vty_port < 0)
+    vty_port = PIMD_VTY_PORT;
+  vty_serv_sock(vty_addr, vty_port, PIM_VTYSH_PATH);
+
+  zlog_notice("Quagga %s " PIMD_PROGNAME " %s starting, VTY interface at port TCP %d",
+	      QUAGGA_VERSION, PIMD_VERSION, vty_port);
+
+#ifdef PIM_MOTD_VERSION
+  /* Tweak default MOTD to include pimd version */
+  zlog_notice("PIM_MOTD_VERSION: adding pimd version to default MOTD");
+  if (host.motd == default_motd) {
+    host.motd =
+      "\r\n\
+Hello, this is " QUAGGA_PROGNAME " " QUAGGA_VERSION " " PIMD_PROGNAME " " PIMD_VERSION_STR "\r\n\
+" QUAGGA_COPYRIGHT "\r\n\
+\r\n";
+  }
+#endif
+
+#ifdef PIM_DEBUG_BYDEFAULT
+  zlog_notice("PIM_DEBUG_BYDEFAULT: Enabling all debug commands");
+  PIM_DO_DEBUG_PIM_EVENTS;
+  PIM_DO_DEBUG_PIM_PACKETS;
+  PIM_DO_DEBUG_PIM_TRACE;
+  PIM_DO_DEBUG_IGMP_EVENTS;
+  PIM_DO_DEBUG_IGMP_PACKETS;
+  PIM_DO_DEBUG_IGMP_TRACE;
+  PIM_DO_DEBUG_ZEBRA;
+#endif
+
+#ifdef PIM_ZCLIENT_DEBUG
+  zlog_notice("PIM_ZCLIENT_DEBUG: zclient debugging is supported, mode is %s (see option -Z)",
+	      zclient_debug ? "ON" : "OFF");
+#endif
+
+#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
+  zlog_notice("PIM_CHECK_RECV_IFINDEX_SANITY: will match sock/recv ifindex");
+#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
+  zlog_notice("PIM_REPORT_RECV_IFINDEX_MISMATCH: will report sock/recv ifindex mismatch");
+#endif
+#endif
+
+#ifdef PIM_USE_QUAGGA_INET_CHECKSUM
+  zlog_notice("PIM_USE_QUAGGA_INET_CHECKSUM: using Quagga's builtin checksum");
+#endif
+
+#ifdef PIM_UNEXPECTED_KERNEL_UPCALL
+  zlog_notice("PIM_UNEXPECTED_KERNEL_UPCALL: report unexpected kernel upcall");
+#endif
+
+  /*
+    Initialize zclient "update" and "lookup" sockets
+   */
+  pim_zebra_init();
+    
+  while (thread_fetch(master, &thread))
+    thread_call(&thread);
+
+  zlog_err("%s %s: thread_fetch() returned NULL, exiting",
+	   __FILE__, __PRETTY_FUNCTION__);
+
+  /* never reached */
+  return 0;
+}
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
new file mode 100644
index 0000000..da5f023
--- /dev/null
+++ b/pimd/pim_mroute.c
@@ -0,0 +1,394 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+#include "log.h"
+
+#include "pimd.h"
+#include "pim_mroute.h"
+#include "pim_str.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+
+static void mroute_read_on(void);
+
+static int pim_mroute_set(int fd, int enable)
+{
+  int err;
+  int opt = enable ? MRT_INIT : MRT_DONE;
+  socklen_t opt_len = sizeof(opt);
+
+  err = setsockopt(fd, IPPROTO_IP, opt, &opt, opt_len);
+  if (err) {
+    int e = errno;
+    zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,%s=%d): errno=%d: %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      fd, enable ? "MRT_INIT" : "MRT_DONE", opt, e, strerror(e));
+    errno = e;
+    return -1;
+  }
+
+#if 0
+  zlog_info("%s %s: setsockopt(fd=%d,IPPROTO_IP,MRT_INIT,opt=%d): ok",
+	    __FILE__, __PRETTY_FUNCTION__,
+	    fd, opt);
+#endif
+
+  return 0;
+}
+
+static int mroute_msg(int fd, const char *buf, int buf_size)
+{
+  struct interface     *ifp;
+  const struct ip      *ip_hdr;
+  const struct igmpmsg *msg;
+  const char *upcall;
+  char src_str[100];
+  char grp_str[100];
+
+  ip_hdr = (const struct ip *) buf;
+
+  /* kernel upcall must have protocol=0 */
+  if (ip_hdr->ip_p) {
+    /* this is not a kernel upcall */
+#ifdef PIM_UNEXPECTED_KERNEL_UPCALL
+    zlog_warn("%s: not a kernel upcall proto=%d msg_size=%d",
+	      __PRETTY_FUNCTION__, ip_hdr->ip_p, buf_size);
+#endif
+    return 0;
+  }
+
+  msg = (const struct igmpmsg *) buf;
+
+  switch (msg->im_msgtype) {
+  case IGMPMSG_NOCACHE:  upcall = "NOCACHE";  break;
+  case IGMPMSG_WRONGVIF: upcall = "WRONGVIF"; break;
+  case IGMPMSG_WHOLEPKT: upcall = "WHOLEPKT"; break;
+  default: upcall = "<unknown_upcall?>";
+  }
+  ifp = pim_if_find_by_vif_index(msg->im_vif);
+  pim_inet4_dump("<src?>", msg->im_src, src_str, sizeof(src_str));
+  pim_inet4_dump("<grp?>", msg->im_dst, grp_str, sizeof(grp_str));
+    
+  if (msg->im_msgtype == IGMPMSG_WRONGVIF) {
+    struct pim_ifchannel *ch;
+
+    /*
+      Send Assert(S,G) on iif as response to WRONGVIF kernel upcall.
+      
+      RFC 4601 4.8.2.  PIM-SSM-Only Routers
+      
+      iif is the incoming interface of the packet.
+      if (iif is in inherited_olist(S,G)) {
+      send Assert(S,G) on iif
+      }
+    */
+
+    if (PIM_DEBUG_PIM_TRACE) {
+      zlog_debug("%s: WRONGVIF from fd=%d for (S,G)=(%s,%s) on %s vifi=%d",
+		 __PRETTY_FUNCTION__,
+		 fd,
+		 src_str,
+		 grp_str,
+		 ifp ? ifp->name : "<ifname?>",
+		 msg->im_vif);
+    }
+
+    if (!ifp) {
+      zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) could not find input interface for input_vif_index=%d",
+		__PRETTY_FUNCTION__,
+		src_str, grp_str, msg->im_vif);
+      return -1;
+    }
+
+    ch = pim_ifchannel_find(ifp, msg->im_src, msg->im_dst);
+    if (!ch) {
+      zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) could not find channel on interface %s",
+		__PRETTY_FUNCTION__,
+		src_str, grp_str, ifp->name);
+      return -2;
+    }
+
+    if (pim_assert_send(ifp, ch)) {
+      zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) failure sending assert on interface %s",
+		__PRETTY_FUNCTION__,
+		src_str, grp_str, ifp->name);
+      return -3;
+    }
+
+    return 0;
+  } /* IGMPMSG_WRONGVIF */
+
+  zlog_warn("%s: kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%s,%s) on %s vifi=%d",
+	    __PRETTY_FUNCTION__,
+	    upcall,
+	    msg->im_msgtype,
+	    ip_hdr->ip_p,
+	    fd,
+	    src_str,
+	    grp_str,
+	    ifp ? ifp->name : "<ifname?>",
+	    msg->im_vif);
+
+  return 0;
+}
+
+static int mroute_read_msg(int fd)
+{
+  const int msg_min_size = MAX(sizeof(struct ip), sizeof(struct igmpmsg));
+  char buf[1000];
+  int rd;
+
+  if (((int) sizeof(buf)) < msg_min_size) {
+    zlog_err("%s: fd=%d: buf size=%d lower than msg_min=%d",
+	     __PRETTY_FUNCTION__, fd, sizeof(buf), msg_min_size);
+    return -1;
+  }
+
+  rd = read(fd, buf, sizeof(buf));
+  if (rd < 0) {
+    zlog_warn("%s: failure reading fd=%d: errno=%d: %s",
+	      __PRETTY_FUNCTION__, fd, errno, strerror(errno));
+    return -2;
+  }
+
+  if (rd < msg_min_size) {
+    zlog_warn("%s: short message reading fd=%d: read=%d msg_min=%d",
+	      __PRETTY_FUNCTION__, fd, rd, msg_min_size);
+    return -3;
+  }
+
+  return mroute_msg(fd, buf, rd);
+}
+
+static int mroute_read(struct thread *t)
+{
+  int fd;
+  int result;
+
+  zassert(t);
+  zassert(!THREAD_ARG(t));
+
+  fd = THREAD_FD(t);
+  zassert(fd == qpim_mroute_socket_fd);
+
+  result = mroute_read_msg(fd);
+
+  /* Keep reading */
+  qpim_mroute_socket_reader = 0;
+  mroute_read_on();
+
+  return result;
+}
+
+static void mroute_read_on()
+{
+  zassert(!qpim_mroute_socket_reader);
+  zassert(PIM_MROUTE_IS_ENABLED);
+
+  THREAD_READ_ON(master, qpim_mroute_socket_reader,
+		 mroute_read, 0, qpim_mroute_socket_fd);
+}
+
+static void mroute_read_off()
+{
+  THREAD_OFF(qpim_mroute_socket_reader);
+}
+
+int pim_mroute_socket_enable()
+{
+  int fd;
+
+  if (PIM_MROUTE_IS_ENABLED)
+    return -1;
+
+  fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
+  if (fd < 0) {
+    zlog_warn("Could not create mroute socket: errno=%d: %s",
+	      errno, strerror(errno));
+    return -2;
+  }
+
+  if (pim_mroute_set(fd, 1)) {
+    zlog_warn("Could not enable mroute on socket fd=%d: errno=%d: %s",
+	      fd, errno, strerror(errno));
+    close(fd);
+    return -3;
+  }
+
+  qpim_mroute_socket_fd       = fd;
+  qpim_mroute_socket_creation = pim_time_monotonic_sec();
+  mroute_read_on();
+
+  zassert(PIM_MROUTE_IS_ENABLED);
+
+  return 0;
+}
+
+int pim_mroute_socket_disable()
+{
+  if (PIM_MROUTE_IS_DISABLED)
+    return -1;
+
+  if (pim_mroute_set(qpim_mroute_socket_fd, 0)) {
+    zlog_warn("Could not disable mroute on socket fd=%d: errno=%d: %s",
+	      qpim_mroute_socket_fd, errno, strerror(errno));
+    return -2;
+  }
+
+  if (close(qpim_mroute_socket_fd)) {
+    zlog_warn("Failure closing mroute socket: fd=%d errno=%d: %s",
+	      qpim_mroute_socket_fd, errno, strerror(errno));
+    return -3;
+  }
+
+  mroute_read_off();
+  qpim_mroute_socket_fd = -1;
+
+  zassert(PIM_MROUTE_IS_DISABLED);
+
+  return 0;
+}
+
+/*
+  For each network interface (e.g., physical or a virtual tunnel) that
+  would be used for multicast forwarding, a corresponding multicast
+  interface must be added to the kernel.
+ */
+int pim_mroute_add_vif(int vif_index, struct in_addr ifaddr)
+{
+  struct vifctl vc;
+  int err;
+
+  if (PIM_MROUTE_IS_DISABLED) {
+    zlog_warn("%s: global multicast is disabled",
+	      __PRETTY_FUNCTION__);
+    return -1;
+  }
+
+  memset(&vc, 0, sizeof(vc));
+  vc.vifc_vifi = vif_index;
+  vc.vifc_flags = 0;
+  vc.vifc_threshold = PIM_MROUTE_MIN_TTL;
+  vc.vifc_rate_limit = 0;
+  memcpy(&vc.vifc_lcl_addr, &ifaddr, sizeof(vc.vifc_lcl_addr));
+
+#ifdef PIM_DVMRP_TUNNEL  
+  if (vc.vifc_flags & VIFF_TUNNEL) {
+    memcpy(&vc.vifc_rmt_addr, &vif_remote_addr, sizeof(vc.vifc_rmt_addr));
+  }
+#endif
+
+  err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_ADD_VIF, (void*) &vc, sizeof(vc)); 
+  if (err) {
+    char ifaddr_str[100];
+    int e = errno;
+
+    pim_inet4_dump("<ifaddr?>", ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+    zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_ADD_VIF,vif_index=%d,ifaddr=%s): errno=%d: %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      qpim_mroute_socket_fd, vif_index, ifaddr_str,
+	      e, strerror(e));
+    errno = e;
+    return -2;
+  }
+
+  return 0;
+}
+
+int pim_mroute_del_vif(int vif_index)
+{
+  struct vifctl vc;
+  int err;
+
+  if (PIM_MROUTE_IS_DISABLED) {
+    zlog_warn("%s: global multicast is disabled",
+	      __PRETTY_FUNCTION__);
+    return -1;
+  }
+
+  memset(&vc, 0, sizeof(vc));
+  vc.vifc_vifi = vif_index;
+
+  err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_DEL_VIF, (void*) &vc, sizeof(vc)); 
+  if (err) {
+    int e = errno;
+    zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_DEL_VIF,vif_index=%d): errno=%d: %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      qpim_mroute_socket_fd, vif_index,
+	      e, strerror(e));
+    errno = e;
+    return -2;
+  }
+
+  return 0;
+}
+
+int pim_mroute_add(struct mfcctl *mc)
+{
+  int err;
+
+  if (PIM_MROUTE_IS_DISABLED) {
+    zlog_warn("%s: global multicast is disabled",
+	      __PRETTY_FUNCTION__);
+    return -1;
+  }
+
+  err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_ADD_MFC,
+		   mc, sizeof(*mc));
+  if (err) {
+    int e = errno;
+    zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_ADD_MFC): errno=%d: %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      qpim_mroute_socket_fd,
+	      e, strerror(e));
+    errno = e;
+    return -2;
+  }
+
+  return 0;
+}
+
+int pim_mroute_del(struct mfcctl *mc)
+{
+  int err;
+
+  if (PIM_MROUTE_IS_DISABLED) {
+    zlog_warn("%s: global multicast is disabled",
+	      __PRETTY_FUNCTION__);
+    return -1;
+  }
+
+  err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_DEL_MFC, mc, sizeof(*mc));
+  if (err) {
+    int e = errno;
+    zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_DEL_MFC): errno=%d: %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      qpim_mroute_socket_fd,
+	      e, strerror(e));
+    errno = e;
+    return -2;
+  }
+
+  return 0;
+}
diff --git a/pimd/pim_mroute.h b/pimd/pim_mroute.h
new file mode 100644
index 0000000..bcbbd20
--- /dev/null
+++ b/pimd/pim_mroute.h
@@ -0,0 +1,171 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_MROUTE_H
+#define PIM_MROUTE_H
+
+/*
+  For msghdr.msg_control in Solaris 10
+*/
+#ifndef _XPG4_2
+#define _XPG4_2
+#endif
+#ifndef __EXTENSIONS__
+#define __EXTENSIONS__
+#endif
+
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_IP_MROUTE_H
+#include <netinet/ip_mroute.h>
+#endif
+
+#define PIM_MROUTE_MIN_TTL (1)
+
+/*
+  Below: from <linux/mroute.h>
+*/
+
+#ifndef MAXVIFS
+#define MAXVIFS (32)
+#endif
+
+#ifndef SIOCGETVIFCNT
+#define SIOCGETVIFCNT   SIOCPROTOPRIVATE        /* IP protocol privates */
+#define SIOCGETSGCNT    (SIOCPROTOPRIVATE+1)
+#define SIOCGETRPF      (SIOCPROTOPRIVATE+2)
+#endif
+
+#ifndef MRT_INIT
+#define MRT_BASE     200
+#define MRT_INIT     (MRT_BASE)      /* Activate the kernel mroute code      */
+#define MRT_DONE     (MRT_BASE+1)    /* Shutdown the kernel mroute           */
+#define MRT_ADD_VIF  (MRT_BASE+2)    /* Add a virtual interface              */
+#define MRT_DEL_VIF  (MRT_BASE+3)    /* Delete a virtual interface           */
+#define MRT_ADD_MFC  (MRT_BASE+4)    /* Add a multicast forwarding entry     */
+#define MRT_DEL_MFC  (MRT_BASE+5)    /* Delete a multicast forwarding entry  */
+#define MRT_VERSION  (MRT_BASE+6)    /* Get the kernel multicast version     */
+#define MRT_ASSERT   (MRT_BASE+7)    /* Activate PIM assert mode             */
+#define MRT_PIM      (MRT_BASE+8)    /* enable PIM code      */
+#endif
+
+#ifndef HAVE_VIFI_T
+typedef unsigned short vifi_t;
+#endif
+
+#ifndef HAVE_STRUCT_VIFCTL
+struct vifctl {
+	vifi_t	vifc_vifi;		/* Index of VIF */
+	unsigned char vifc_flags;	/* VIFF_ flags */
+	unsigned char vifc_threshold;	/* ttl limit */
+	unsigned int vifc_rate_limit;	/* Rate limiter values (NI) */
+	struct in_addr vifc_lcl_addr;	/* Our address */
+	struct in_addr vifc_rmt_addr;	/* IPIP tunnel addr */
+};
+#endif
+
+#ifndef HAVE_STRUCT_MFCCTL
+struct mfcctl {
+  struct in_addr mfcc_origin;             /* Origin of mcast      */
+  struct in_addr mfcc_mcastgrp;           /* Group in question    */
+  vifi_t         mfcc_parent;             /* Where it arrived     */
+  unsigned char  mfcc_ttls[MAXVIFS];      /* Where it is going    */
+  unsigned int   mfcc_pkt_cnt;            /* pkt count for src-grp */
+  unsigned int   mfcc_byte_cnt;
+  unsigned int   mfcc_wrong_if;
+  int            mfcc_expire;
+};
+#endif
+
+/*
+ *      Group count retrieval for mrouted
+ */
+/*
+  struct sioc_sg_req sgreq;
+  memset(&sgreq, 0, sizeof(sgreq));
+  memcpy(&sgreq.src, &source_addr, sizeof(sgreq.src));
+  memcpy(&sgreq.grp, &group_addr, sizeof(sgreq.grp));
+  ioctl(mrouter_s4, SIOCGETSGCNT, &sgreq);
+ */
+#ifndef HAVE_STRUCT_SIOC_SG_REQ
+struct sioc_sg_req {
+  struct in_addr src;
+  struct in_addr grp;
+  unsigned long pktcnt;
+  unsigned long bytecnt;
+  unsigned long wrong_if;
+};
+#endif
+
+/*
+ *      To get vif packet counts
+ */
+/*
+  struct sioc_vif_req vreq;
+  memset(&vreq, 0, sizeof(vreq));
+  vreq.vifi = vif_index;
+  ioctl(mrouter_s4, SIOCGETVIFCNT, &vreq);
+ */
+#ifndef HAVE_STRUCT_SIOC_VIF_REQ
+struct sioc_vif_req {
+  vifi_t  vifi;           /* Which iface */
+  unsigned long icount;   /* In packets */
+  unsigned long ocount;   /* Out packets */
+  unsigned long ibytes;   /* In bytes */
+  unsigned long obytes;   /* Out bytes */
+};
+#endif
+
+/*
+ *      Pseudo messages used by mrouted
+ */
+#ifndef IGMPMSG_NOCACHE
+#define IGMPMSG_NOCACHE         1               /* Kern cache fill request to mrouted */
+#define IGMPMSG_WRONGVIF        2               /* For PIM assert processing (unused) */
+#define IGMPMSG_WHOLEPKT        3               /* For PIM Register processing */
+#endif
+
+#ifndef HAVE_STRUCT_IGMPMSG
+struct igmpmsg
+{
+  uint32_t unused1,unused2;
+  unsigned char im_msgtype;               /* What is this */
+  unsigned char im_mbz;                   /* Must be zero */
+  unsigned char im_vif;                   /* Interface (this ought to be a vifi_t!) */
+  unsigned char unused3;
+  struct in_addr im_src,im_dst;
+};
+#endif
+
+/*
+  Above: from <linux/mroute.h>
+*/
+
+int pim_mroute_socket_enable(void);
+int pim_mroute_socket_disable(void);
+
+int pim_mroute_add_vif(int vif_index, struct in_addr ifaddr);
+int pim_mroute_del_vif(int vif_index);
+
+int pim_mroute_add(struct mfcctl *mc);
+int pim_mroute_del(struct mfcctl *mc);
+
+#endif /* PIM_MROUTE_H */
diff --git a/pimd/pim_msg.c b/pimd/pim_msg.c
new file mode 100644
index 0000000..04ac89a
--- /dev/null
+++ b/pimd/pim_msg.c
@@ -0,0 +1,107 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_msg.h"
+#include "pim_util.h"
+
+void pim_msg_build_header(const char *ifname,
+			  char *pim_msg, int pim_msg_size,
+			  uint8_t pim_msg_type)
+{
+  uint16_t checksum;
+
+  zassert(pim_msg_size >= PIM_PIM_MIN_LEN);
+
+  /*
+   * Write header
+   */
+
+  *(uint8_t *) PIM_MSG_HDR_OFFSET_VERSION(pim_msg) = (PIM_PROTO_VERSION << 4) | pim_msg_type;
+  *(uint8_t *) PIM_MSG_HDR_OFFSET_RESERVED(pim_msg) = 0;
+
+  /*
+   * Compute checksum
+   */
+
+  *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) = 0;
+  checksum = pim_inet_checksum(pim_msg, pim_msg_size);
+  *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) = checksum;
+}
+
+char *pim_msg_addr_encode_ipv4_ucast(char *buf,
+				     int buf_size,
+				     struct in_addr addr)
+{
+  const int ENCODED_IPV4_UCAST_SIZE = 6;
+
+  if (buf_size < ENCODED_IPV4_UCAST_SIZE) {
+    return 0;
+  }
+
+  buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */
+  buf[1] = '\0';    /* native encoding */
+  *(struct in_addr *)(buf + 2) = addr;
+
+  return buf + ENCODED_IPV4_UCAST_SIZE;
+}
+
+char *pim_msg_addr_encode_ipv4_group(char *buf,
+				     int buf_size,
+				     struct in_addr addr)
+{
+  const int ENCODED_IPV4_GROUP_SIZE = 8;
+
+  if (buf_size < ENCODED_IPV4_GROUP_SIZE) {
+    return 0;
+  }
+
+  buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */
+  buf[1] = '\0';    /* native encoding */
+  buf[2] = '\0';    /* reserved */
+  buf[3] = 32;      /* mask len */
+  *(struct in_addr *)(buf + 4) = addr;
+
+  return buf + ENCODED_IPV4_GROUP_SIZE;
+}
+
+char *pim_msg_addr_encode_ipv4_source(char *buf,
+				      int buf_size,
+				      struct in_addr addr)
+{
+  const int ENCODED_IPV4_SOURCE_SIZE = 8;
+
+  if (buf_size < ENCODED_IPV4_SOURCE_SIZE) {
+    return 0;
+  }
+
+  buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */
+  buf[1] = '\0';    /* native encoding */
+  buf[2] = '\0';    /* reserved */
+  buf[3] = 32;      /* mask len */
+  *(struct in_addr *)(buf + 4) = addr;
+
+  return buf + ENCODED_IPV4_SOURCE_SIZE;
+}
diff --git a/pimd/pim_msg.h b/pimd/pim_msg.h
new file mode 100644
index 0000000..df2bef8
--- /dev/null
+++ b/pimd/pim_msg.h
@@ -0,0 +1,53 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_MSG_H
+#define PIM_MSG_H
+
+#include <netinet/in.h>
+
+/*
+  Number       Description       
+  ----------   ------------------
+  0            Reserved
+  1            IP (IP version 4)
+  2            IP6 (IP version 6)
+
+  From:
+  http://www.iana.org/assignments/address-family-numbers
+*/
+#define PIM_MSG_ADDRESS_FAMILY_IPV4 (1)
+
+void pim_msg_build_header(const char *ifname,
+			  char *pim_msg, int pim_msg_size,
+			  uint8_t pim_msg_type);
+char *pim_msg_addr_encode_ipv4_ucast(char *buf,
+				     int buf_size,
+				     struct in_addr addr);
+char *pim_msg_addr_encode_ipv4_group(char *buf,
+				     int buf_size,
+				     struct in_addr addr);
+char *pim_msg_addr_encode_ipv4_source(char *buf,
+				      int buf_size,
+				      struct in_addr addr);
+
+#endif /* PIM_MSG_H */
diff --git a/pimd/pim_neighbor.c b/pimd/pim_neighbor.c
new file mode 100644
index 0000000..91b60d1
--- /dev/null
+++ b/pimd/pim_neighbor.c
@@ -0,0 +1,713 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "prefix.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_neighbor.h"
+#include "pim_time.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+#include "pim_pim.h"
+#include "pim_upstream.h"
+#include "pim_ifchannel.h"
+
+static void dr_election_by_addr(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *node;
+  struct pim_neighbor  *neigh;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  pim_ifp->pim_dr_addr = pim_ifp->primary_address;
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
+    if (ntohl(neigh->source_addr.s_addr) > ntohl(pim_ifp->pim_dr_addr.s_addr)) {
+      pim_ifp->pim_dr_addr = neigh->source_addr;
+    }
+  }
+}
+
+static void dr_election_by_pri(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *node;
+  struct pim_neighbor  *neigh;
+  uint32_t              dr_pri;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  pim_ifp->pim_dr_addr = pim_ifp->primary_address;
+  dr_pri = pim_ifp->pim_dr_priority;
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
+    if (
+	(neigh->dr_priority > dr_pri) ||
+	(
+	 (neigh->dr_priority == dr_pri) &&
+	 (ntohl(neigh->source_addr.s_addr) > ntohl(pim_ifp->pim_dr_addr.s_addr))
+	 )
+	) {
+      pim_ifp->pim_dr_addr = neigh->source_addr;
+      dr_pri               = neigh->dr_priority;
+    }
+  }
+}
+
+static void iface_update_join_desired(struct pim_interface *pim_ifp)
+{
+  struct listnode      *ch_node;
+  struct pim_ifchannel *ch;
+
+  /* clear off flag from interface's upstreams */
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+    PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED(ch->upstream->flags);
+  }
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+    struct pim_upstream *up = ch->upstream;
+
+    if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(up->flags))
+      continue;
+
+    pim_upstream_update_join_desired(up);
+    PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(up->flags);
+  }
+}
+
+/*
+  RFC 4601: 4.3.2.  DR Election
+
+  A router's idea of the current DR on an interface can change when a
+  PIM Hello message is received, when a neighbor times out, or when a
+  router's own DR Priority changes.
+ */
+void pim_if_dr_election(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp = ifp->info;
+  struct in_addr old_dr_addr;
+
+  pim_ifp->pim_dr_election_last = pim_time_monotonic_sec(); /* timestamp */
+  ++pim_ifp->pim_dr_election_count;
+
+  old_dr_addr = pim_ifp->pim_dr_addr;
+
+  if (pim_ifp->pim_dr_num_nondrpri_neighbors) {
+    dr_election_by_addr(ifp);
+  }
+  else {
+    dr_election_by_pri(ifp);
+  }
+
+  /* DR changed ? */
+  if (old_dr_addr.s_addr != pim_ifp->pim_dr_addr.s_addr) {
+    char dr_old_str[100];
+    char dr_new_str[100];
+    pim_inet4_dump("<old_dr?>", old_dr_addr, dr_old_str, sizeof(dr_old_str));
+    pim_inet4_dump("<new_dr?>", pim_ifp->pim_dr_addr, dr_new_str, sizeof(dr_new_str));
+    zlog_info("%s: DR was %s now is %s on interface %s",
+	      __PRETTY_FUNCTION__,
+	      dr_old_str, dr_new_str, ifp->name);
+
+    iface_update_join_desired(pim_ifp);
+  }
+}
+
+static void update_dr_priority(struct pim_neighbor *neigh,
+			       pim_hello_options hello_options,
+			       uint32_t dr_priority)
+{
+  pim_hello_options will_set_pri; /* boolean */
+  pim_hello_options bit_flip;     /* boolean */
+  pim_hello_options pri_change;   /* boolean */
+
+  will_set_pri = PIM_OPTION_IS_SET(hello_options,
+				   PIM_OPTION_MASK_DR_PRIORITY);
+
+  bit_flip =
+    (
+     will_set_pri !=
+     PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY)
+     );
+
+  if (bit_flip) {
+    struct pim_interface *pim_ifp = neigh->interface->info;
+
+    /* update num. of neighbors without dr_pri */
+
+    if (will_set_pri) {
+      --pim_ifp->pim_dr_num_nondrpri_neighbors; 
+    }
+    else {
+      ++pim_ifp->pim_dr_num_nondrpri_neighbors; 
+    }
+  }
+
+  pri_change = 
+    (
+     bit_flip
+     ||
+     (neigh->dr_priority != dr_priority)
+     );
+
+  if (will_set_pri) {
+    neigh->dr_priority = dr_priority;
+  }
+  else {
+    neigh->dr_priority = 0; /* cosmetic unset */
+  }
+
+  if (pri_change) {
+    /*
+      RFC 4601: 4.3.2.  DR Election
+      
+      A router's idea of the current DR on an interface can change when a
+      PIM Hello message is received, when a neighbor times out, or when a
+      router's own DR Priority changes.
+    */
+    pim_if_dr_election(neigh->interface);
+  }
+}
+
+static int on_neighbor_timer(struct thread *t)
+{
+  struct pim_neighbor *neigh;
+  struct interface *ifp;
+  char msg[100];
+
+  zassert(t);
+  neigh = THREAD_ARG(t);
+  zassert(neigh);
+
+  ifp = neigh->interface;
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+    zlog_debug("Expired %d sec holdtime for neighbor %s on interface %s",
+	       neigh->holdtime, src_str, ifp->name);
+  }
+
+  neigh->t_expire_timer = 0;
+
+  snprintf(msg, sizeof(msg), "%d-sec holdtime expired", neigh->holdtime);
+  pim_neighbor_delete(ifp, neigh, msg);
+
+  /*
+    RFC 4601: 4.3.2.  DR Election
+    
+    A router's idea of the current DR on an interface can change when a
+    PIM Hello message is received, when a neighbor times out, or when a
+    router's own DR Priority changes.
+  */
+  pim_if_dr_election(ifp);
+
+  return 0;
+}
+
+static void neighbor_timer_off(struct pim_neighbor *neigh)
+{
+  if (PIM_DEBUG_PIM_TRACE) {
+    if (neigh->t_expire_timer) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+      zlog_debug("%s: cancelling timer for neighbor %s on %s",
+		 __PRETTY_FUNCTION__,
+		 src_str, neigh->interface->name);
+    }
+  }
+  THREAD_OFF(neigh->t_expire_timer);
+  zassert(!neigh->t_expire_timer);
+}
+
+void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime)
+{
+  neigh->holdtime = holdtime;
+
+  neighbor_timer_off(neigh);
+
+  /*
+    0xFFFF is request for no holdtime
+   */
+  if (neigh->holdtime == 0xFFFF) {
+    return;
+  }
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+    zlog_debug("%s: starting %u sec timer for neighbor %s on %s",
+	       __PRETTY_FUNCTION__,
+	       neigh->holdtime, src_str, neigh->interface->name);
+  }
+
+  THREAD_TIMER_ON(master, neigh->t_expire_timer,
+		  on_neighbor_timer,
+		  neigh, neigh->holdtime);
+}
+
+static struct pim_neighbor *pim_neighbor_new(struct interface *ifp,
+					     struct in_addr source_addr,
+					     pim_hello_options hello_options,
+					     uint16_t holdtime,
+					     uint16_t propagation_delay,
+					     uint16_t override_interval,
+					     uint32_t dr_priority,
+					     uint32_t generation_id,
+					     struct list *addr_list)
+{
+  struct pim_interface *pim_ifp;
+  struct pim_neighbor *neigh;
+  char src_str[100];
+
+  zassert(ifp);
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  neigh = XMALLOC(MTYPE_PIM_NEIGHBOR, sizeof(*neigh));
+  if (!neigh) {
+    zlog_err("%s: PIM XMALLOC(%d) failure",
+	     __PRETTY_FUNCTION__, sizeof(*neigh));
+    return 0;
+  }
+
+  neigh->creation               = pim_time_monotonic_sec();
+  neigh->source_addr            = source_addr;
+  neigh->hello_options          = hello_options;
+  neigh->propagation_delay_msec = propagation_delay;
+  neigh->override_interval_msec = override_interval;
+  neigh->dr_priority            = dr_priority;
+  neigh->generation_id          = generation_id;
+  neigh->prefix_list            = addr_list;
+  neigh->t_expire_timer         = 0;
+  neigh->interface              = ifp;
+
+  pim_neighbor_timer_reset(neigh, holdtime);
+
+  pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+
+  if (PIM_DEBUG_PIM_EVENTS) {
+    zlog_debug("%s: creating PIM neighbor %s on interface %s",
+	       __PRETTY_FUNCTION__,
+	       src_str, ifp->name);
+  }
+
+  zlog_info("PIM NEIGHBOR UP: neighbor %s on interface %s",
+	    src_str, ifp->name);
+
+  if (neigh->propagation_delay_msec > pim_ifp->pim_neighbors_highest_propagation_delay_msec) {
+    pim_ifp->pim_neighbors_highest_propagation_delay_msec = neigh->propagation_delay_msec;
+  }
+  if (neigh->override_interval_msec > pim_ifp->pim_neighbors_highest_override_interval_msec) {
+    pim_ifp->pim_neighbors_highest_override_interval_msec = neigh->override_interval_msec;
+  }
+
+  if (!PIM_OPTION_IS_SET(neigh->hello_options,
+			 PIM_OPTION_MASK_LAN_PRUNE_DELAY)) {
+    /* update num. of neighbors without hello option lan_delay */
+    ++pim_ifp->pim_number_of_nonlandelay_neighbors; 
+  }
+
+  if (!PIM_OPTION_IS_SET(neigh->hello_options,
+			 PIM_OPTION_MASK_DR_PRIORITY)) {
+    /* update num. of neighbors without hello option dr_pri */
+    ++pim_ifp->pim_dr_num_nondrpri_neighbors; 
+  }
+
+  /*
+    RFC 4601: 4.3.2.  DR Election
+    
+    A router's idea of the current DR on an interface can change when a
+    PIM Hello message is received, when a neighbor times out, or when a
+    router's own DR Priority changes.
+  */
+  pim_if_dr_election(neigh->interface);
+
+  /*
+    RFC 4601: 4.3.1.  Sending Hello Messages
+
+    To allow new or rebooting routers to learn of PIM neighbors quickly,
+    when a Hello message is received from a new neighbor, or a Hello
+    message with a new GenID is received from an existing neighbor, a
+    new Hello message should be sent on this interface after a
+    randomized delay between 0 and Triggered_Hello_Delay.
+  */
+  pim_hello_restart_triggered(neigh->interface);
+  
+  return neigh;
+}
+
+static void delete_prefix_list(struct pim_neighbor *neigh)
+{
+  if (neigh->prefix_list) {
+
+#ifdef DUMP_PREFIX_LIST
+    struct listnode *p_node;
+    struct prefix *p;
+    char addr_str[10];
+    int list_size = neigh->prefix_list ? (int) listcount(neigh->prefix_list) : -1;
+    int i = 0;
+    for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, p_node, p)) {
+      pim_inet4_dump("<addr?>", p->u.prefix4, addr_str, sizeof(addr_str));
+      zlog_debug("%s: DUMP_PREFIX_LIST neigh=%x prefix_list=%x prefix=%x addr=%s [%d/%d]",
+		 __PRETTY_FUNCTION__,
+		 (unsigned) neigh, (unsigned) neigh->prefix_list, (unsigned) p,
+		 addr_str, i, list_size);
+      ++i;
+    }
+#endif
+
+    list_delete(neigh->prefix_list);
+    neigh->prefix_list = 0;
+  }
+}
+
+void pim_neighbor_free(struct pim_neighbor *neigh)
+{
+  zassert(!neigh->t_expire_timer);
+
+  delete_prefix_list(neigh);
+
+  XFREE(MTYPE_PIM_NEIGHBOR, neigh);
+}
+
+struct pim_neighbor *pim_neighbor_find(struct interface *ifp,
+				       struct in_addr source_addr)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode      *node;
+  struct pim_neighbor  *neigh;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
+    if (source_addr.s_addr == neigh->source_addr.s_addr) {
+      return neigh;
+    }
+  }
+
+  return 0;
+}
+
+struct pim_neighbor *pim_neighbor_add(struct interface *ifp,
+				      struct in_addr source_addr,
+				      pim_hello_options hello_options,
+				      uint16_t holdtime,
+				      uint16_t propagation_delay,
+				      uint16_t override_interval,
+				      uint32_t dr_priority,
+				      uint32_t generation_id,
+				      struct list *addr_list)
+{
+  struct pim_interface *pim_ifp;
+  struct pim_neighbor *neigh;
+
+  neigh = pim_neighbor_new(ifp, source_addr,
+			   hello_options,
+			   holdtime,
+			   propagation_delay,
+			   override_interval,
+			   dr_priority,
+			   generation_id,
+			   addr_list);
+  if (!neigh) {
+    return 0;
+  }
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  listnode_add(pim_ifp->pim_neighbor_list, neigh);
+
+  return neigh;
+}
+
+static uint16_t
+find_neighbors_next_highest_propagation_delay_msec(struct interface *ifp,
+						   struct pim_neighbor *highest_neigh)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode *neigh_node;
+  struct pim_neighbor *neigh;
+  uint16_t next_highest_delay_msec;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  next_highest_delay_msec = pim_ifp->pim_propagation_delay_msec;
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, neigh)) {
+    if (neigh == highest_neigh)
+      continue;
+    if (neigh->propagation_delay_msec > next_highest_delay_msec)
+      next_highest_delay_msec = neigh->propagation_delay_msec;
+  }
+
+  return next_highest_delay_msec;
+}
+
+static uint16_t
+find_neighbors_next_highest_override_interval_msec(struct interface *ifp,
+						   struct pim_neighbor *highest_neigh)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode *neigh_node;
+  struct pim_neighbor *neigh;
+  uint16_t next_highest_interval_msec;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  next_highest_interval_msec = pim_ifp->pim_override_interval_msec;
+
+  for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, neigh)) {
+    if (neigh == highest_neigh)
+      continue;
+    if (neigh->override_interval_msec > next_highest_interval_msec)
+      next_highest_interval_msec = neigh->override_interval_msec;
+  }
+
+  return next_highest_interval_msec;
+}
+
+void pim_neighbor_delete(struct interface *ifp,
+			 struct pim_neighbor *neigh,
+			 const char *delete_message)
+{
+  struct pim_interface *pim_ifp;
+  char src_str[100];
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+  zlog_info("PIM NEIGHBOR DOWN: neighbor %s on interface %s: %s",
+	    src_str, ifp->name, delete_message);
+
+  neighbor_timer_off(neigh);
+
+  if (!PIM_OPTION_IS_SET(neigh->hello_options,
+                         PIM_OPTION_MASK_LAN_PRUNE_DELAY)) {
+    /* update num. of neighbors without hello option lan_delay */
+
+    --pim_ifp->pim_number_of_nonlandelay_neighbors;
+  }
+
+  if (!PIM_OPTION_IS_SET(neigh->hello_options,
+			 PIM_OPTION_MASK_DR_PRIORITY)) {
+    /* update num. of neighbors without dr_pri */
+
+    --pim_ifp->pim_dr_num_nondrpri_neighbors; 
+  }
+
+  zassert(neigh->propagation_delay_msec <= pim_ifp->pim_neighbors_highest_propagation_delay_msec);
+  zassert(neigh->override_interval_msec <= pim_ifp->pim_neighbors_highest_override_interval_msec);
+
+  if (pim_if_lan_delay_enabled(ifp)) {
+
+    /* will delete a neighbor with highest propagation delay? */
+    if (neigh->propagation_delay_msec == pim_ifp->pim_neighbors_highest_propagation_delay_msec) {
+      /* then find the next highest propagation delay */
+      pim_ifp->pim_neighbors_highest_propagation_delay_msec =
+	find_neighbors_next_highest_propagation_delay_msec(ifp, neigh);
+    }
+
+    /* will delete a neighbor with highest override interval? */
+    if (neigh->override_interval_msec == pim_ifp->pim_neighbors_highest_override_interval_msec) {
+      /* then find the next highest propagation delay */
+      pim_ifp->pim_neighbors_highest_override_interval_msec =
+	find_neighbors_next_highest_override_interval_msec(ifp, neigh);
+    }
+  }
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    zlog_debug("%s: deleting PIM neighbor %s on interface %s",
+	       __PRETTY_FUNCTION__,
+	       src_str, ifp->name);
+  }
+
+  listnode_delete(pim_ifp->pim_neighbor_list, neigh);
+
+  pim_neighbor_free(neigh);
+}
+
+void pim_neighbor_delete_all(struct interface *ifp,
+			     const char *delete_message)
+{
+  struct pim_interface *pim_ifp;
+  struct listnode *neigh_node;
+  struct listnode *neigh_nextnode;
+  struct pim_neighbor *neigh;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  for (ALL_LIST_ELEMENTS(pim_ifp->pim_neighbor_list, neigh_node,
+			 neigh_nextnode, neigh)) {
+    pim_neighbor_delete(ifp, neigh, delete_message);
+  }
+}
+
+struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh,
+					   struct in_addr addr)
+{
+  struct listnode *node;
+  struct prefix   *p;
+
+  if (!neigh->prefix_list)
+    return 0;
+
+  for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, node, p)) {
+    if (p->family == AF_INET) {
+      if (addr.s_addr == p->u.prefix4.s_addr) {
+	return p;
+      }
+    }
+  }
+
+  return 0;
+}
+
+/*
+  RFC 4601: 4.3.4.  Maintaining Secondary Address Lists
+  
+  All the advertised secondary addresses in received Hello messages
+  must be checked against those previously advertised by all other
+  PIM neighbors on that interface.  If there is a conflict and the
+  same secondary address was previously advertised by another
+  neighbor, then only the most recently received mapping MUST be
+  maintained, and an error message SHOULD be logged to the
+  administrator in a rate-limited manner.
+*/
+static void delete_from_neigh_addr(struct interface *ifp,
+				   struct list *addr_list,
+				   struct in_addr neigh_addr)
+{
+  struct listnode      *addr_node;
+  struct prefix        *addr;
+  struct pim_interface *pim_ifp;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  zassert(addr_list);
+
+  /*
+    Scan secondary address list
+  */
+  for (ALL_LIST_ELEMENTS_RO(addr_list, addr_node,
+			    addr)) {
+    struct listnode      *neigh_node;
+    struct pim_neighbor  *neigh;
+
+    if (addr->family != AF_INET)
+      continue;
+
+    /*
+      Scan neighbors
+    */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node,
+			      neigh)) {
+      {
+	struct prefix *p = pim_neighbor_find_secondary(neigh, addr->u.prefix4);
+	if (p) {
+	  char addr_str[100];
+	  char this_neigh_str[100];
+	  char other_neigh_str[100];
+	  
+	  pim_inet4_dump("<addr?>", addr->u.prefix4, addr_str, sizeof(addr_str));
+	  pim_inet4_dump("<neigh1?>", neigh_addr, this_neigh_str, sizeof(this_neigh_str));
+	  pim_inet4_dump("<neigh2?>", neigh->source_addr, other_neigh_str, sizeof(other_neigh_str));
+	  
+	  zlog_info("secondary addr %s recvd from neigh %s deleted from neigh %s on %s",
+		    addr_str, this_neigh_str, other_neigh_str, ifp->name);
+	  
+	  listnode_delete(neigh->prefix_list, p);
+	  prefix_free(p);
+	}
+      }
+
+    } /* scan neighbors */
+    
+  } /* scan addr list */
+
+}
+
+void pim_neighbor_update(struct pim_neighbor *neigh,
+			 pim_hello_options hello_options,
+			 uint16_t holdtime,
+			 uint32_t dr_priority,
+			 struct list *addr_list)
+{
+  struct pim_interface *pim_ifp = neigh->interface->info;
+
+  /* Received holdtime ? */
+  if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) {
+    pim_neighbor_timer_reset(neigh, holdtime);
+  }
+  else {
+    pim_neighbor_timer_reset(neigh, PIM_IF_DEFAULT_HOLDTIME(pim_ifp));
+  }
+
+#ifdef DUMP_PREFIX_LIST
+  zlog_debug("%s: DUMP_PREFIX_LIST old_prefix_list=%x old_size=%d new_prefix_list=%x new_size=%d",
+	     __PRETTY_FUNCTION__,
+	     (unsigned) neigh->prefix_list,
+	     neigh->prefix_list ? (int) listcount(neigh->prefix_list) : -1,
+	     (unsigned) addr_list,
+	     addr_list ? (int) listcount(addr_list) : -1);
+#endif
+
+  if (neigh->prefix_list == addr_list) {
+    if (addr_list) {
+      zlog_err("%s: internal error: trying to replace same prefix list=%u",
+	       __PRETTY_FUNCTION__, (unsigned) addr_list);
+    }
+  }
+  else {
+    /* Delete existing secondary address list */
+    delete_prefix_list(neigh);
+  }
+
+  if (addr_list) {
+    delete_from_neigh_addr(neigh->interface, addr_list, neigh->source_addr);
+  }
+
+  /* Replace secondary address list */
+  neigh->prefix_list = addr_list;
+
+  update_dr_priority(neigh,
+		     hello_options,
+		     dr_priority);
+  /*
+    Copy flags
+   */
+  neigh->hello_options = hello_options;
+}
diff --git a/pimd/pim_neighbor.h b/pimd/pim_neighbor.h
new file mode 100644
index 0000000..8f19c75
--- /dev/null
+++ b/pimd/pim_neighbor.h
@@ -0,0 +1,74 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_NEIGHBOR_H
+#define PIM_NEIGHBOR_H
+
+#include <zebra.h>
+
+#include "if.h"
+#include "linklist.h"
+
+#include "pim_tlv.h"
+
+struct pim_neighbor {
+  int64_t            creation; /* timestamp of creation */
+  struct in_addr     source_addr;
+  pim_hello_options  hello_options;
+  uint16_t           holdtime;
+  uint16_t           propagation_delay_msec;
+  uint16_t           override_interval_msec;
+  uint32_t           dr_priority;
+  uint32_t           generation_id;
+  struct list       *prefix_list; /* list of struct prefix */
+  struct thread     *t_expire_timer;
+  struct interface  *interface;
+};
+
+void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime);
+void pim_neighbor_free(struct pim_neighbor *neigh);
+struct pim_neighbor *pim_neighbor_find(struct interface *ifp,
+				       struct in_addr source_addr);
+struct pim_neighbor *pim_neighbor_add(struct interface *ifp,
+				      struct in_addr source_addr,
+				      pim_hello_options hello_options,
+				      uint16_t holdtime,
+				      uint16_t propagation_delay,
+				      uint16_t override_interval,
+				      uint32_t dr_priority,
+				      uint32_t generation_id,
+				      struct list *addr_list);
+void pim_neighbor_delete(struct interface *ifp,
+			 struct pim_neighbor *neigh,
+			 const char *delete_message);
+void pim_neighbor_delete_all(struct interface *ifp,
+			     const char *delete_message);
+void pim_neighbor_update(struct pim_neighbor *neigh,
+			 pim_hello_options hello_options,
+			 uint16_t holdtime,
+			 uint32_t dr_priority,
+			 struct list *addr_list);
+struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh,
+					   struct in_addr addr);
+void pim_if_dr_election(struct interface *ifp);
+
+#endif /* PIM_NEIGHBOR_H */
diff --git a/pimd/pim_oil.c b/pimd/pim_oil.c
new file mode 100644
index 0000000..2c8b056
--- /dev/null
+++ b/pimd/pim_oil.c
@@ -0,0 +1,140 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "memory.h"
+#include "linklist.h"
+
+#include "pimd.h"
+#include "pim_oil.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+
+void pim_channel_oil_free(struct channel_oil *c_oil)
+{
+  XFREE(MTYPE_PIM_CHANNEL_OIL, c_oil);
+}
+
+static void pim_channel_oil_delete(struct channel_oil *c_oil)
+{
+  /*
+    notice that listnode_delete() can't be moved
+    into pim_channel_oil_free() because the later is
+    called by list_delete_all_node()
+  */
+  listnode_delete(qpim_channel_oil_list, c_oil);
+
+  pim_channel_oil_free(c_oil);
+}
+
+static struct channel_oil *channel_oil_new(struct in_addr group_addr,
+					   struct in_addr source_addr,
+					   int input_vif_index)
+{
+  struct channel_oil *c_oil;
+  struct interface *ifp_in;
+
+  ifp_in = pim_if_find_by_vif_index(input_vif_index);
+  if (!ifp_in) {
+    /* warning only */
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", source_addr, source_str, sizeof(source_str));
+    zlog_warn("%s: (S,G)=(%s,%s) could not find input interface for input_vif_index=%d",
+	      __PRETTY_FUNCTION__,
+	      source_str, group_str, input_vif_index);
+  }
+
+  c_oil = XCALLOC(MTYPE_PIM_CHANNEL_OIL, sizeof(*c_oil));
+  if (!c_oil) {
+    zlog_err("PIM XCALLOC(%d) failure", sizeof(*c_oil));
+    return 0;
+  }
+
+  c_oil->oil.mfcc_mcastgrp = group_addr;
+  c_oil->oil.mfcc_origin   = source_addr;
+  c_oil->oil.mfcc_parent   = input_vif_index;
+  c_oil->oil_ref_count     = 1;
+
+  zassert(c_oil->oil_size == 0);
+
+  return c_oil;
+}
+
+static struct channel_oil *pim_add_channel_oil(struct in_addr group_addr,
+					       struct in_addr source_addr,
+					       int input_vif_index)
+{
+  struct channel_oil *c_oil;
+
+  c_oil = channel_oil_new(group_addr, source_addr, input_vif_index);
+  if (!c_oil) {
+    zlog_warn("PIM XCALLOC(%d) failure", sizeof(*c_oil));
+    return 0;
+  }
+
+  listnode_add(qpim_channel_oil_list, c_oil);
+
+  return c_oil;
+}
+
+static struct channel_oil *pim_find_channel_oil(struct in_addr group_addr,
+						struct in_addr source_addr)
+{
+  struct listnode    *node;
+  struct channel_oil *c_oil;
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+    if ((group_addr.s_addr == c_oil->oil.mfcc_mcastgrp.s_addr) &&
+	(source_addr.s_addr == c_oil->oil.mfcc_origin.s_addr))
+      return c_oil;
+  }
+  
+  return 0;
+}
+
+struct channel_oil *pim_channel_oil_add(struct in_addr group_addr,
+					struct in_addr source_addr,
+					int input_vif_index)
+{
+  struct channel_oil *c_oil;
+
+  c_oil = pim_find_channel_oil(group_addr, source_addr);
+  if (c_oil) {
+    ++c_oil->oil_ref_count;
+    return c_oil;
+  }
+
+  return pim_add_channel_oil(group_addr, source_addr, input_vif_index);
+}
+
+void pim_channel_oil_del(struct channel_oil *c_oil)
+{
+  --c_oil->oil_ref_count;
+
+  if (c_oil->oil_ref_count < 1) {
+    pim_channel_oil_delete(c_oil);
+  }
+}
diff --git a/pimd/pim_oil.h b/pimd/pim_oil.h
new file mode 100644
index 0000000..1753545
--- /dev/null
+++ b/pimd/pim_oil.h
@@ -0,0 +1,53 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_OIL_H
+#define PIM_OIL_H
+
+#include "pim_mroute.h"
+
+#define PIM_OIF_FLAG_PROTO_IGMP (1 << 0) /* bitmask 1 */
+#define PIM_OIF_FLAG_PROTO_PIM  (1 << 1) /* bitmask 2 */
+#define PIM_OIF_FLAG_PROTO_ANY  (3)      /* bitmask (1 | 2) */
+
+/*
+  qpim_channel_oil_list holds a list of struct channel_oil.
+
+  Each channel_oil.oil is used to control an (S,G) entry in the Kernel
+  Multicast Forwarding Cache.
+*/
+
+struct channel_oil {
+  struct mfcctl oil;
+  int           oil_size;
+  int           oil_ref_count;
+  time_t        oif_creation[MAXVIFS];
+  uint32_t      oif_flags[MAXVIFS];
+};
+
+void pim_channel_oil_free(struct channel_oil *c_oil);
+struct channel_oil *pim_channel_oil_add(struct in_addr group_addr,
+					struct in_addr source_addr,
+					int input_vif_index);
+void pim_channel_oil_del(struct channel_oil *c_oil);
+
+#endif /* PIM_OIL_H */
diff --git a/pimd/pim_pim.c b/pimd/pim_pim.c
new file mode 100644
index 0000000..75f90c7
--- /dev/null
+++ b/pimd/pim_pim.c
@@ -0,0 +1,711 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "thread.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+#include "pim_sock.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#include "pim_tlv.h"
+#include "pim_neighbor.h"
+#include "pim_hello.h"
+#include "pim_join.h"
+#include "pim_assert.h"
+#include "pim_msg.h"
+#include "pim_rand.h"
+
+static int on_pim_hello_send(struct thread *t);
+static int pim_hello_send(struct interface *ifp,
+			  uint16_t holdtime);
+
+static void sock_close(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp = ifp->info;
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    if (pim_ifp->t_pim_sock_read) {
+      zlog_debug("Cancelling READ event for PIM socket fd=%d on interface %s",
+		 pim_ifp->pim_sock_fd,
+		 ifp->name);
+    }
+  }
+  THREAD_OFF(pim_ifp->t_pim_sock_read);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    if (pim_ifp->t_pim_hello_timer) {
+      zlog_debug("Cancelling PIM hello timer for interface %s",
+		 ifp->name);
+    }
+  }
+  THREAD_OFF(pim_ifp->t_pim_hello_timer);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    zlog_debug("Deleting PIM socket fd=%d on interface %s",
+	       pim_ifp->pim_sock_fd, ifp->name);
+  }
+
+  if (close(pim_ifp->pim_sock_fd)) {
+    zlog_warn("Failure closing PIM socket fd=%d on interface %s: errno=%d: %s",
+	      pim_ifp->pim_sock_fd, ifp->name,
+	      errno, strerror(errno));
+  }
+  
+  pim_ifp->pim_sock_fd = -1;
+  pim_ifp->pim_sock_creation = 0;
+
+  zassert(pim_ifp->pim_sock_fd < 0);
+  zassert(!pim_ifp->t_pim_sock_read);
+  zassert(!pim_ifp->t_pim_hello_timer);
+  zassert(!pim_ifp->pim_sock_creation);
+}
+
+void pim_sock_delete(struct interface *ifp, const char *neighbor_delete_message)
+{
+  /*
+    RFC 4601: 4.3.1.  Sending Hello Messages
+    
+    Before an interface goes down or changes primary IP address, a Hello
+    message with a zero HoldTime should be sent immediately (with the
+    old IP address if the IP address changed).
+  */
+  pim_hello_send(ifp, 0 /* zero-sec holdtime */);
+
+  pim_neighbor_delete_all(ifp, neighbor_delete_message);
+
+  sock_close(ifp);
+}
+
+int pim_pim_packet(struct interface *ifp, char *buf, size_t len)
+{
+  struct ip *ip_hdr;
+  size_t ip_hlen; /* ip header length in bytes */
+  char src_str[100];
+  char dst_str[100];
+  char *pim_msg;
+  int pim_msg_len;
+  uint8_t pim_version;
+  uint8_t pim_type;
+  uint16_t pim_checksum; /* received checksum */
+  uint16_t checksum;     /* computed checksum */
+  struct pim_neighbor *neigh;
+
+  if (!ifp->info) {
+    zlog_warn("%s: PIM not enabled on interface %s",
+	      __PRETTY_FUNCTION__, ifp->name);
+    return -1;
+  }
+    
+  if (len < sizeof(*ip_hdr)) {
+    zlog_warn("PIM packet size=%d shorter than minimum=%d",
+	      len, sizeof(*ip_hdr));
+    return -1;
+  }
+
+  ip_hdr = (struct ip *) buf;
+
+  pim_inet4_dump("<src?>", ip_hdr->ip_src, src_str, sizeof(src_str));
+  pim_inet4_dump("<dst?>", ip_hdr->ip_dst, dst_str, sizeof(dst_str));
+
+  ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
+
+  if (PIM_DEBUG_PIM_PACKETS) {
+    zlog_debug("Recv IP packet from %s to %s on %s: size=%d ip_header_size=%d ip_proto=%d",
+	       src_str, dst_str, ifp->name, len, ip_hlen, ip_hdr->ip_p);
+  }
+
+  if (ip_hdr->ip_p != PIM_IP_PROTO_PIM) {
+    zlog_warn("IP packet protocol=%d is not PIM=%d",
+	      ip_hdr->ip_p, PIM_IP_PROTO_PIM);
+    return -1;
+  }
+
+  if (ip_hlen < PIM_IP_HEADER_MIN_LEN) {
+    zlog_warn("IP packet header size=%d shorter than minimum=%d",
+	      ip_hlen, PIM_IP_HEADER_MIN_LEN);
+    return -1;
+  }
+  if (ip_hlen > PIM_IP_HEADER_MAX_LEN) {
+    zlog_warn("IP packet header size=%d greater than maximum=%d",
+	      ip_hlen, PIM_IP_HEADER_MAX_LEN);
+    return -1;
+  }
+
+  pim_msg = buf + ip_hlen;
+  pim_msg_len = len - ip_hlen;
+
+  if (pim_msg_len < PIM_PIM_MIN_LEN) {
+    zlog_warn("PIM message size=%d shorter than minimum=%d",
+	      pim_msg_len, PIM_PIM_MIN_LEN);
+    return -1;
+  }
+
+  pim_version = PIM_MSG_HDR_GET_VERSION(pim_msg);
+  pim_type    = PIM_MSG_HDR_GET_TYPE(pim_msg);
+
+  if (pim_version != PIM_PROTO_VERSION) {
+    zlog_warn("Ignoring PIM pkt from %s with unsupported version: %d",
+	      ifp->name, pim_version);
+    return -1;
+  }
+
+  /* save received checksum */
+  pim_checksum = PIM_MSG_HDR_GET_CHECKSUM(pim_msg);
+
+  /* for computing checksum */
+  *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) = 0;
+
+  checksum = pim_inet_checksum(pim_msg, pim_msg_len);
+  if (checksum != pim_checksum) {
+    zlog_warn("Ignoring PIM pkt from %s with invalid checksum: received=%x calculated=%x",
+	      ifp->name, pim_checksum, checksum);
+    return -1;
+  }
+
+  if (PIM_DEBUG_PIM_PACKETS) {
+    zlog_debug("Recv PIM packet from %s to %s on %s: ttl=%d pim_version=%d pim_type=%d pim_msg_size=%d checksum=%x",
+	       src_str, dst_str, ifp->name, ip_hdr->ip_ttl,
+	       pim_version, pim_type, pim_msg_len, checksum);
+  }
+
+  if (pim_type == PIM_MSG_TYPE_HELLO) {
+    return pim_hello_recv(ifp,
+			  ip_hdr->ip_src,
+			  pim_msg + PIM_MSG_HEADER_LEN,
+			  pim_msg_len - PIM_MSG_HEADER_LEN);
+  }
+
+  neigh = pim_neighbor_find(ifp, ip_hdr->ip_src);
+  if (!neigh) {
+    zlog_warn("%s %s: non-hello PIM message type=%d from non-neighbor %s on %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      pim_type, src_str, ifp->name);
+    return -1;
+  }
+
+  switch (pim_type) {
+  case PIM_MSG_TYPE_JOIN_PRUNE:
+    return pim_joinprune_recv(ifp, neigh,
+			      ip_hdr->ip_src,
+			      pim_msg + PIM_MSG_HEADER_LEN,
+			      pim_msg_len - PIM_MSG_HEADER_LEN);
+  case PIM_MSG_TYPE_ASSERT:
+    return pim_assert_recv(ifp, neigh,
+			   ip_hdr->ip_src,
+			   pim_msg + PIM_MSG_HEADER_LEN,
+			   pim_msg_len - PIM_MSG_HEADER_LEN);
+  default:
+    zlog_warn("%s %s: unsupported PIM message type=%d from %s on %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      pim_type, src_str, ifp->name);
+  }
+
+  return -1;
+}
+
+static void pim_sock_read_on(struct interface *ifp);
+
+static int pim_sock_read(struct thread *t)
+{
+  struct interface *ifp;
+  struct pim_interface *pim_ifp;
+  int fd;
+  struct sockaddr_in from;
+  struct sockaddr_in to;
+  socklen_t fromlen = sizeof(from);
+  socklen_t tolen = sizeof(to);
+  char buf[PIM_PIM_BUFSIZE_READ];
+  int len;
+  int ifindex = -1;
+  int result = -1; /* defaults to bad */
+
+  zassert(t);
+
+  ifp = THREAD_ARG(t);
+  zassert(ifp);
+
+  fd = THREAD_FD(t);
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  zassert(fd == pim_ifp->pim_sock_fd);
+
+  len = pim_socket_recvfromto(fd, buf, sizeof(buf),
+			      &from, &fromlen,
+			      &to, &tolen,
+			      &ifindex);
+  if (len < 0) {
+    zlog_warn("Failure receiving IP PIM packet on fd=%d: errno=%d: %s",
+	      fd, errno, strerror(errno));
+    goto done;
+  }
+
+  if (PIM_DEBUG_PIM_PACKETS) {
+    char from_str[100];
+    char to_str[100];
+    
+    if (!inet_ntop(AF_INET, &from.sin_addr, from_str, sizeof(from_str)))
+      sprintf(from_str, "<from?>");
+    if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str)))
+      sprintf(to_str, "<to?>");
+    
+    zlog_debug("Recv IP PIM pkt size=%d from %s to %s on fd=%d on ifindex=%d (sock_ifindex=%d)",
+	       len, from_str, to_str, fd, ifindex, ifp->ifindex);
+  }
+
+#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
+  /* ifindex sanity check */
+  if (ifindex != (int) ifp->ifindex) {
+    char from_str[100];
+    char to_str[100];
+    struct interface *recv_ifp;
+
+    if (!inet_ntop(AF_INET, &from.sin_addr, from_str , sizeof(from_str)))
+      sprintf(from_str, "<from?>");
+    if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str)))
+      sprintf(to_str, "<to?>");
+
+    recv_ifp = if_lookup_by_index(ifindex);
+    if (recv_ifp) {
+      zassert(ifindex == (int) recv_ifp->ifindex);
+    }
+
+#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
+    zlog_warn("Interface mismatch: recv PIM pkt from %s to %s on fd=%d: recv_ifindex=%d (%s) sock_ifindex=%d (%s)",
+	      from_str, to_str, fd,
+	      ifindex, recv_ifp ? recv_ifp->name : "<if-notfound>",
+	      ifp->ifindex, ifp->name);
+#endif
+    goto done;
+  }
+#endif
+
+  if (pim_pim_packet(ifp, buf, len)) {
+    goto done;
+  }
+
+  result = 0; /* good */
+
+ done:
+  pim_sock_read_on(ifp);
+
+  if (result) {
+    ++pim_ifp->pim_ifstat_hello_recvfail;
+  }
+
+  return result;
+}
+
+static void pim_sock_read_on(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+  zassert(ifp->info);
+
+  pim_ifp = ifp->info;
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    zlog_debug("Scheduling READ event on PIM socket fd=%d",
+	       pim_ifp->pim_sock_fd);
+  }
+  pim_ifp->t_pim_sock_read = 0;
+  zassert(!pim_ifp->t_pim_sock_read);
+  THREAD_READ_ON(master, pim_ifp->t_pim_sock_read, pim_sock_read, ifp,
+		 pim_ifp->pim_sock_fd);
+}
+
+static int pim_sock_open(struct in_addr ifaddr, int ifindex)
+{
+  int fd;
+
+  fd = pim_socket_mcast(IPPROTO_PIM, ifaddr, 0 /* loop=false */);
+  if (fd < 0)
+    return -1;
+
+  if (pim_socket_join(fd, qpim_all_pim_routers_addr, ifaddr, ifindex)) {
+    return -2;
+  }
+
+  return fd;
+}
+
+void pim_ifstat_reset(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+
+  pim_ifp = ifp->info;
+  if (!pim_ifp) {
+    return;
+  }
+
+  pim_ifp->pim_ifstat_start          = pim_time_monotonic_sec();
+  pim_ifp->pim_ifstat_hello_sent     = 0;
+  pim_ifp->pim_ifstat_hello_sendfail = 0;
+  pim_ifp->pim_ifstat_hello_recv     = 0;
+  pim_ifp->pim_ifstat_hello_recvfail = 0;
+}
+
+void pim_sock_reset(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+  zassert(ifp->info);
+
+  pim_ifp = ifp->info;
+
+  pim_ifp->primary_address = pim_find_first_addr(ifp);
+
+  pim_ifp->pim_sock_fd       = -1;
+  pim_ifp->pim_sock_creation = 0;
+  pim_ifp->t_pim_sock_read   = 0;
+
+  pim_ifp->t_pim_hello_timer          = 0;
+  pim_ifp->pim_hello_period           = PIM_DEFAULT_HELLO_PERIOD;
+  pim_ifp->pim_default_holdtime       = -1; /* unset: means 3.5 * pim_hello_period */
+  pim_ifp->pim_triggered_hello_delay  = PIM_DEFAULT_TRIGGERED_HELLO_DELAY;
+  pim_ifp->pim_dr_priority            = PIM_DEFAULT_DR_PRIORITY;
+  pim_ifp->pim_propagation_delay_msec = PIM_DEFAULT_PROPAGATION_DELAY_MSEC;
+  pim_ifp->pim_override_interval_msec = PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC;
+  if (PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION) {
+    PIM_IF_DO_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options);
+  }
+  else {
+    PIM_IF_DONT_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options);
+  }
+
+  /* neighbors without lan_delay */
+  pim_ifp->pim_number_of_nonlandelay_neighbors = 0;
+  pim_ifp->pim_neighbors_highest_propagation_delay_msec = 0;
+  pim_ifp->pim_neighbors_highest_override_interval_msec = 0;
+
+  /* DR Election */
+  pim_ifp->pim_dr_election_last          = 0; /* timestamp */
+  pim_ifp->pim_dr_election_count         = 0;
+  pim_ifp->pim_dr_num_nondrpri_neighbors = 0; /* neighbors without dr_pri */
+  pim_ifp->pim_dr_addr                   = pim_ifp->primary_address;
+
+  pim_ifstat_reset(ifp);
+}
+
+int pim_msg_send(int fd,
+		 struct in_addr dst,
+		 char *pim_msg,
+		 int pim_msg_size,
+		 const char *ifname)
+{
+  ssize_t            sent;
+  struct sockaddr_in to;
+  socklen_t          tolen;
+
+  if (PIM_DEBUG_PIM_PACKETS) {
+    char dst_str[100];
+    pim_inet4_dump("<dst?>", dst, dst_str, sizeof(dst_str));
+    zlog_debug("%s: to %s on %s: msg_size=%d checksum=%x",
+	       __PRETTY_FUNCTION__,
+	       dst_str, ifname, pim_msg_size,
+	       *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg));
+  }
+
+#if 0
+  memset(&to, 0, sizeof(to));
+#endif
+  to.sin_family = AF_INET;
+  to.sin_addr = dst;
+#if 0
+  to.sin_port = htons(0);
+#endif
+  tolen = sizeof(to);
+
+  sent = sendto(fd, pim_msg, pim_msg_size, MSG_DONTWAIT, &to, tolen);
+  if (sent != (ssize_t) pim_msg_size) {
+    int e = errno;
+    char dst_str[100];
+    pim_inet4_dump("<dst?>", dst, dst_str, sizeof(dst_str));
+    if (sent < 0) {
+      zlog_warn("%s: sendto() failure to %s on %s: fd=%d msg_size=%d: errno=%d: %s",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, fd, pim_msg_size,
+		e, strerror(e));
+    }
+    else {
+      zlog_warn("%s: sendto() partial to %s on %s: fd=%d msg_size=%d: sent=%d",
+		__PRETTY_FUNCTION__,
+		dst_str, ifname, fd,
+		pim_msg_size, sent);
+    }
+    return -1;
+  }
+
+  return 0;
+}
+
+static int hello_send(struct interface *ifp,
+		      uint16_t holdtime)
+{
+  char pim_msg[PIM_PIM_BUFSIZE_WRITE];
+  struct pim_interface *pim_ifp;
+  int pim_tlv_size;
+  int pim_msg_size;
+
+  pim_ifp = ifp->info;
+
+  if (PIM_DEBUG_PIM_PACKETS) {
+    char dst_str[100];
+    pim_inet4_dump("<dst?>", qpim_all_pim_routers_addr, dst_str, sizeof(dst_str));
+    zlog_debug("%s: to %s on %s: holdt=%u prop_d=%u overr_i=%u dis_join_supp=%d dr_prio=%u gen_id=%08x addrs=%d",
+	       __PRETTY_FUNCTION__,
+	       dst_str, ifp->name,
+	       holdtime,
+	       pim_ifp->pim_propagation_delay_msec, pim_ifp->pim_override_interval_msec,
+	       PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options),
+	       pim_ifp->pim_dr_priority, pim_ifp->pim_generation_id,
+	       listcount(ifp->connected));
+  }
+
+  pim_tlv_size = pim_hello_build_tlv(ifp->name,
+				     pim_msg + PIM_PIM_MIN_LEN,
+				     sizeof(pim_msg) - PIM_PIM_MIN_LEN,
+				     holdtime,
+				     pim_ifp->pim_dr_priority,
+				     pim_ifp->pim_generation_id,
+				     pim_ifp->pim_propagation_delay_msec,
+				     pim_ifp->pim_override_interval_msec,
+				     PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options),
+				     ifp->connected);
+  if (pim_tlv_size < 0) {
+    return -1;
+  }
+
+  pim_msg_size = pim_tlv_size + PIM_PIM_MIN_LEN;
+
+  zassert(pim_msg_size >= PIM_PIM_MIN_LEN);
+  zassert(pim_msg_size <= PIM_PIM_BUFSIZE_WRITE);
+
+  pim_msg_build_header(ifp->name,
+		       pim_msg, pim_msg_size,
+		       PIM_MSG_TYPE_HELLO);
+
+  if (pim_msg_send(pim_ifp->pim_sock_fd,
+		   qpim_all_pim_routers_addr,
+		   pim_msg,
+		   pim_msg_size,
+		   ifp->name)) {
+    zlog_warn("%s: could not send PIM message on interface %s",
+	      __PRETTY_FUNCTION__, ifp->name);
+    return -2;
+  }
+
+  return 0;
+}
+
+static int pim_hello_send(struct interface *ifp,
+			  uint16_t holdtime)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  if (hello_send(ifp, holdtime)) {
+    ++pim_ifp->pim_ifstat_hello_sendfail;
+
+    zlog_warn("Could not send PIM hello on interface %s",
+	      ifp->name);
+    return -1;
+  }
+
+  ++pim_ifp->pim_ifstat_hello_sent;
+
+  return 0;
+}
+
+static void hello_resched(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    zlog_debug("Rescheduling %d sec hello on interface %s",
+	       pim_ifp->pim_hello_period, ifp->name);
+  }
+  THREAD_OFF(pim_ifp->t_pim_hello_timer);
+  THREAD_TIMER_ON(master, pim_ifp->t_pim_hello_timer,
+		  on_pim_hello_send,
+		  ifp, pim_ifp->pim_hello_period);
+}
+
+/*
+  Periodic hello timer
+ */
+static int on_pim_hello_send(struct thread *t)
+{
+  struct pim_interface *pim_ifp;
+  struct interface *ifp;
+
+  zassert(t);
+  ifp = THREAD_ARG(t);
+  zassert(ifp);
+
+  pim_ifp = ifp->info;
+
+  /*
+   * Schedule next hello
+   */
+  pim_ifp->t_pim_hello_timer = 0;
+  hello_resched(ifp);
+
+  /*
+   * Send hello
+   */
+  return pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp));
+}
+
+/*
+  RFC 4601: 4.3.1.  Sending Hello Messages
+
+  Thus, if a router needs to send a Join/Prune or Assert message on an
+  interface on which it has not yet sent a Hello message with the
+  currently configured IP address, then it MUST immediately send the
+  relevant Hello message without waiting for the Hello Timer to
+  expire, followed by the Join/Prune or Assert message.
+ */
+void pim_hello_restart_now(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+
+  zassert(ifp);
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  /*
+   * Reset next hello timer
+   */
+  hello_resched(ifp);
+
+  /*
+   * Immediately send hello
+   */
+  pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp));
+}
+
+/*
+  RFC 4601: 4.3.1.  Sending Hello Messages
+
+  To allow new or rebooting routers to learn of PIM neighbors quickly,
+  when a Hello message is received from a new neighbor, or a Hello
+  message with a new GenID is received from an existing neighbor, a
+  new Hello message should be sent on this interface after a
+  randomized delay between 0 and Triggered_Hello_Delay.
+ */
+void pim_hello_restart_triggered(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  int triggered_hello_delay_msec;
+  int random_msec;
+
+  zassert(ifp);
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  triggered_hello_delay_msec = 1000 * pim_ifp->pim_triggered_hello_delay;
+
+  if (pim_ifp->t_pim_hello_timer) {
+    long remain_msec = pim_time_timer_remain_msec(pim_ifp->t_pim_hello_timer);
+    if (remain_msec <= triggered_hello_delay_msec) {
+      /* Rescheduling hello would increase the delay, then it's faster
+	 to just wait for the scheduled periodic hello. */
+      return;
+    }
+
+    THREAD_OFF(pim_ifp->t_pim_hello_timer);
+    pim_ifp->t_pim_hello_timer = 0;
+  }
+  zassert(!pim_ifp->t_pim_hello_timer);
+
+  random_msec = pim_rand_next(0, triggered_hello_delay_msec);
+
+  if (PIM_DEBUG_PIM_EVENTS) {
+    zlog_debug("Scheduling %d msec triggered hello on interface %s",
+	       random_msec, ifp->name);
+  }
+
+  THREAD_TIMER_MSEC_ON(master, pim_ifp->t_pim_hello_timer,
+		       on_pim_hello_send,
+		       ifp, triggered_hello_delay_msec);
+}
+
+int pim_sock_add(struct interface *ifp)
+{
+  struct pim_interface *pim_ifp;
+  struct in_addr ifaddr;
+
+  pim_ifp = ifp->info;
+  zassert(pim_ifp);
+
+  if (pim_ifp->pim_sock_fd >= 0) {
+    zlog_warn("Can't recreate existing PIM socket fd=%d for interface %s",
+	      pim_ifp->pim_sock_fd, ifp->name);
+    return -1;
+  }
+
+  ifaddr = pim_ifp->primary_address;
+
+  pim_ifp->pim_sock_fd = pim_sock_open(ifaddr, ifp->ifindex);
+  if (pim_ifp->pim_sock_fd < 0) {
+    zlog_warn("Could not open PIM socket on interface %s",
+	      ifp->name);
+    return -2;
+  }
+
+  pim_ifp->t_pim_sock_read   = 0;
+  pim_ifp->pim_sock_creation = pim_time_monotonic_sec();
+
+  pim_ifp->pim_generation_id = pim_rand() & (int64_t) 0xFFFFFFFF;
+
+  /*
+   * Start receiving PIM messages
+   */
+  pim_sock_read_on(ifp);
+
+  /*
+   * Start sending PIM hello's
+   */
+  pim_hello_restart_triggered(ifp);
+
+  return 0;
+}
diff --git a/pimd/pim_pim.h b/pimd/pim_pim.h
new file mode 100644
index 0000000..365066d
--- /dev/null
+++ b/pimd/pim_pim.h
@@ -0,0 +1,71 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_PIM_H
+#define PIM_PIM_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#define PIM_PIM_BUFSIZE_READ  (20000)
+#define PIM_PIM_BUFSIZE_WRITE (20000)
+
+#define PIM_NEXTHOP_IFINDEX_TAB_SIZE (20)
+
+#define PIM_DEFAULT_HELLO_PERIOD                 (30)   /* seconds, RFC 4601: 4.11 */
+#define PIM_DEFAULT_TRIGGERED_HELLO_DELAY        (5)    /* seconds, RFC 4601: 4.11 */
+#define PIM_DEFAULT_DR_PRIORITY                  (1)    /* RFC 4601: 4.3.1 */
+#define PIM_DEFAULT_PROPAGATION_DELAY_MSEC       (500)  /* RFC 4601: 4.11.  Timer Values */
+#define PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC       (2500) /* RFC 4601: 4.11.  Timer Values */
+#define PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION (0)    /* boolean */
+#define PIM_DEFAULT_T_PERIODIC                   (60)   /* RFC 4601: 4.11.  Timer Values */
+
+#define PIM_MSG_TYPE_HELLO      (0)
+#define PIM_MSG_TYPE_JOIN_PRUNE (3)
+#define PIM_MSG_TYPE_ASSERT     (5)
+
+#define PIM_MSG_HDR_OFFSET_VERSION(pim_msg) (pim_msg)
+#define PIM_MSG_HDR_OFFSET_TYPE(pim_msg) (pim_msg)
+#define PIM_MSG_HDR_OFFSET_RESERVED(pim_msg) (((char *)(pim_msg)) + 1)
+#define PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) (((char *)(pim_msg)) + 2)
+
+#define PIM_MSG_HDR_GET_VERSION(pim_msg) ((*(uint8_t*) PIM_MSG_HDR_OFFSET_VERSION(pim_msg)) >> 4)
+#define PIM_MSG_HDR_GET_TYPE(pim_msg) ((*(uint8_t*) PIM_MSG_HDR_OFFSET_TYPE(pim_msg)) & 0xF)
+#define PIM_MSG_HDR_GET_CHECKSUM(pim_msg) (*(uint16_t*) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg))
+
+void pim_ifstat_reset(struct interface *ifp);
+void pim_sock_reset(struct interface *ifp);
+int pim_sock_add(struct interface *ifp);
+void pim_sock_delete(struct interface *ifp, const char *neighbor_delete_message);
+void pim_hello_restart_now(struct interface *ifp);
+void pim_hello_restart_triggered(struct interface *ifp);
+
+int pim_pim_packet(struct interface *ifp, char *buf, size_t len);
+
+int pim_msg_send(int fd,
+		 struct in_addr dst,
+		 char *pim_msg,
+		 int pim_msg_size,
+		 const char *ifname);
+
+#endif /* PIM_PIM_H */
diff --git a/pimd/pim_rand.c b/pimd/pim_rand.c
new file mode 100644
index 0000000..df2a111
--- /dev/null
+++ b/pimd/pim_rand.c
@@ -0,0 +1,60 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include "pim_rand.h"
+#include "pim_time.h"
+
+/* Quick and dirty random number generator from NUMERICAL RECIPES IN C:
+   THE ART OF SCIENTIFIC COMPUTING (ISBN 0-521-43108-5). */
+/* BEWARE: '_qseed_' is assigned! */
+#define QRANDOM(_qseed_)  ((_qseed_) = (((_qseed_) * 1664525L) + 1013904223L))
+
+static long qpim_rand_seed;
+
+void pim_rand_init()
+{
+  qpim_rand_seed = pim_time_monotonic_sec() ^ getpid();
+}
+
+long pim_rand()
+{
+  return QRANDOM(qpim_rand_seed);
+}
+
+int pim_rand_next(int min, int max)
+{
+  long rand;
+
+  assert(min <= max);
+
+  /* FIXME better random generator ? */
+
+  rand = QRANDOM(qpim_rand_seed); 
+  if (rand < 0)
+    rand = -rand;
+  rand = rand % (1 + max - min) + min;
+
+  assert(rand >= min);
+  assert(rand <= max);
+
+  return rand;
+}
diff --git a/pimd/pim_rand.h b/pimd/pim_rand.h
new file mode 100644
index 0000000..a1df505
--- /dev/null
+++ b/pimd/pim_rand.h
@@ -0,0 +1,30 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_RAND_H
+#define PIM_RAND_H
+
+void pim_rand_init(void);
+long pim_rand(void);
+int pim_rand_next(int min, int max);
+
+#endif /* PIM_RAND_H */
diff --git a/pimd/pim_rpf.c b/pimd/pim_rpf.c
new file mode 100644
index 0000000..94f0e22
--- /dev/null
+++ b/pimd/pim_rpf.c
@@ -0,0 +1,200 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "prefix.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_rpf.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+#include "pim_zlookup.h"
+#include "pim_ifchannel.h"
+
+int pim_nexthop_lookup(struct pim_nexthop *nexthop,
+		       struct in_addr addr)
+{
+  struct pim_zlookup_nexthop nexthop_tab[PIM_NEXTHOP_IFINDEX_TAB_SIZE];
+  int num_ifindex;
+  struct interface *ifp;
+  int first_ifindex;
+
+  num_ifindex = zclient_lookup_nexthop(qpim_zclient_lookup, nexthop_tab,
+				       PIM_NEXTHOP_IFINDEX_TAB_SIZE,
+				       addr, PIM_NEXTHOP_LOOKUP_MAX);
+  if (num_ifindex < 1) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_warn("%s %s: could not find nexthop ifindex for address %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      addr_str);
+    return -1;
+  }
+
+  first_ifindex = nexthop_tab[0].ifindex;
+
+  if (num_ifindex > 1) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_debug("%s %s: FIXME ignoring multiple nexthop ifindex'es num_ifindex=%d for address %s (using only ifindex=%d)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      num_ifindex, addr_str, first_ifindex);
+    /* debug warning only, do not return */
+  }
+
+  ifp = if_lookup_by_index(first_ifindex);
+  if (!ifp) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_warn("%s %s: could not find interface for ifindex %d (address %s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      first_ifindex, addr_str);
+    return -2;
+  }
+
+  if (!ifp->info) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_warn("%s: multicast not enabled on input interface %s (ifindex=%d, RPF for source %s)",
+	      __PRETTY_FUNCTION__,
+	      ifp->name, first_ifindex, addr_str);
+    /* debug warning only, do not return */
+  }
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char nexthop_str[100];
+    char addr_str[100];
+    pim_inet4_dump("<nexthop?>", nexthop_tab[0].nexthop_addr, nexthop_str, sizeof(nexthop_str));
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_debug("%s %s: found nexthop %s for address %s: interface %s ifindex=%d metric=%d pref=%d",
+	       __FILE__, __PRETTY_FUNCTION__,
+	       nexthop_str, addr_str,
+	       ifp->name, first_ifindex,
+	       nexthop_tab[0].route_metric,
+	       nexthop_tab[0].protocol_distance);
+  }
+
+  /* update nextop data */
+  nexthop->interface              = ifp;
+  nexthop->mrib_nexthop_addr      = nexthop_tab[0].nexthop_addr;
+  nexthop->mrib_metric_preference = nexthop_tab[0].protocol_distance;
+  nexthop->mrib_route_metric      = nexthop_tab[0].route_metric;
+
+  return 0;
+}
+
+enum pim_rpf_result pim_rpf_update(struct pim_upstream *up,
+				   struct in_addr *old_rpf_addr)
+{
+  struct in_addr save_rpf_addr;
+  struct pim_rpf *rpf = &up->rpf;
+
+  save_rpf_addr = rpf->rpf_addr;
+
+  if (pim_nexthop_lookup(&rpf->source_nexthop,
+			 up->source_addr)) {
+    return PIM_RPF_FAILURE;
+  }
+
+  rpf->rpf_addr = pim_rpf_find_rpf_addr(up);
+  if (PIM_INADDR_IS_ANY(rpf->rpf_addr)) {
+    /* RPF'(S,G) not found */
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s %s: RPF'(%s,%s) not found: won't send join upstream",
+              __FILE__, __PRETTY_FUNCTION__,
+              src_str, grp_str);
+    /* warning only */
+  }
+
+  /* detect rpf change */
+  if (save_rpf_addr.s_addr != rpf->rpf_addr.s_addr) {
+
+    /* return old rpf to caller ? */
+    if (old_rpf_addr)
+      *old_rpf_addr = save_rpf_addr;
+
+    return PIM_RPF_CHANGED;
+  }
+
+  return PIM_RPF_OK;
+}
+
+/*
+  RFC 4601: 4.1.6.  State Summarization Macros
+
+     neighbor RPF'(S,G) {
+         if ( I_Am_Assert_Loser(S, G, RPF_interface(S) )) {
+              return AssertWinner(S, G, RPF_interface(S) )
+         } else {
+              return NBR( RPF_interface(S), MRIB.next_hop( S ) )
+         }
+     }
+
+  RPF'(*,G) and RPF'(S,G) indicate the neighbor from which data
+  packets should be coming and to which joins should be sent on the RP
+  tree and SPT, respectively.
+*/
+struct in_addr pim_rpf_find_rpf_addr(struct pim_upstream *up)
+{
+  struct pim_ifchannel *rpf_ch;
+  struct pim_neighbor *neigh;
+  struct in_addr rpf_addr;
+
+  if (!up->rpf.source_nexthop.interface) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_warn("%s: missing RPF interface for upstream (S,G)=(%s,%s)",
+	      __PRETTY_FUNCTION__,
+	      src_str, grp_str);
+
+    rpf_addr.s_addr = PIM_NET_INADDR_ANY;
+    return rpf_addr;
+  }
+
+  rpf_ch = pim_ifchannel_find(up->rpf.source_nexthop.interface,
+			      up->source_addr, up->group_addr);
+  if (rpf_ch) {
+    if (rpf_ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) {
+      return rpf_ch->ifassert_winner;
+    }
+  }
+
+  /* return NBR( RPF_interface(S), MRIB.next_hop( S ) ) */
+
+  neigh = pim_if_find_neighbor(up->rpf.source_nexthop.interface,
+			       up->rpf.source_nexthop.mrib_nexthop_addr);
+  if (neigh)
+    rpf_addr = neigh->source_addr;
+  else
+    rpf_addr.s_addr = PIM_NET_INADDR_ANY;
+
+  return rpf_addr;
+}
diff --git a/pimd/pim_rpf.h b/pimd/pim_rpf.h
new file mode 100644
index 0000000..08e92e4
--- /dev/null
+++ b/pimd/pim_rpf.h
@@ -0,0 +1,37 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_RPF_H
+#define PIM_RPF_H
+
+#include <zebra.h>
+
+#include "pim_upstream.h"
+#include "pim_neighbor.h"
+
+int pim_nexthop_lookup(struct pim_nexthop *nexthop,
+		       struct in_addr addr);
+enum pim_rpf_result pim_rpf_update(struct pim_upstream *up,
+				   struct in_addr *old_rpf_addr);
+struct in_addr pim_rpf_find_rpf_addr(struct pim_upstream *up);
+
+#endif /* PIM_RPF_H */
diff --git a/pimd/pim_signals.c b/pimd/pim_signals.c
new file mode 100644
index 0000000..1b146f6
--- /dev/null
+++ b/pimd/pim_signals.c
@@ -0,0 +1,85 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <signal.h>
+
+#include <zebra.h>
+#include "sigevent.h"
+#include "log.h"
+
+#include "pim_signals.h"
+#include "pimd.h"
+
+/*
+ * Signal handlers
+ */
+
+static void pim_sighup()
+{
+  zlog_debug ("SIGHUP received, ignoring");
+}
+
+static void pim_sigint()
+{
+  zlog_notice("Terminating on signal SIGINT");
+  pim_terminate();
+  exit(1);
+}
+
+static void pim_sigterm()
+{
+  zlog_notice("Terminating on signal SIGTERM");
+  pim_terminate();
+  exit(1);
+}
+
+static void pim_sigusr1()
+{
+  zlog_debug ("SIGUSR1 received");
+  zlog_rotate (NULL);
+}
+
+struct quagga_signal_t pimd_signals[] =
+{
+  {
+   .signal = SIGHUP,
+   .handler = &pim_sighup,
+   },
+  {
+   .signal = SIGUSR1,
+   .handler = &pim_sigusr1,
+   },
+  {
+   .signal = SIGINT,
+   .handler = &pim_sigint,
+   },
+  {
+   .signal = SIGTERM,
+   .handler = &pim_sigterm,
+   },
+};
+
+void pim_signals_init()
+{
+  signal_init(master, Q_SIGC(pimd_signals), pimd_signals);
+}
+
diff --git a/pimd/pim_signals.h b/pimd/pim_signals.h
new file mode 100644
index 0000000..62523c0
--- /dev/null
+++ b/pimd/pim_signals.h
@@ -0,0 +1,28 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_SIGNALS_H
+#define PIM_SIGNALS_H
+
+void pim_signals_init(void);
+
+#endif /* PIM_SIGNALS_H */
diff --git a/pimd/pim_sock.c b/pimd/pim_sock.c
new file mode 100644
index 0000000..fb59b8d
--- /dev/null
+++ b/pimd/pim_sock.c
@@ -0,0 +1,281 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include "pim_mroute.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+
+#include <zebra.h>
+#include "log.h"
+
+#include "pimd.h"
+#include "pim_sock.h"
+
+int pim_socket_mcast(int protocol, struct in_addr ifaddr, int loop)
+{
+  int fd;
+
+  fd = socket(AF_INET, SOCK_RAW, protocol);
+  if (fd < 0) {
+    zlog_warn("Could not create multicast socket: errno=%d: %s",
+	      errno, strerror(errno));
+    return PIM_SOCK_ERR_SOCKET;
+  }
+
+  /* Needed to obtain destination address from recvmsg() */
+  {
+#if defined(HAVE_IP_PKTINFO)
+    /* Linux IP_PKTINFO */
+    int opt = 1;
+    if (setsockopt(fd, SOL_IP, IP_PKTINFO, &opt, sizeof(opt))) {
+      zlog_warn("Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s",
+		fd, errno, strerror(errno));
+    }
+#elif defined(HAVE_IP_RECVDSTADDR)
+    /* BSD IP_RECVDSTADDR */
+    int opt = 1;
+    if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt))) {
+      zlog_warn("Could not set IP_RECVDSTADDR on socket fd=%d: errno=%d: %s",
+		fd, errno, strerror(errno));
+    }
+#else
+    zlog_err("%s %s: Missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()",
+	     __FILE__, __PRETTY_FUNCTION__);
+    close(fd);
+    return PIM_SOCK_ERR_DSTADDR;
+#endif
+  }
+
+  
+  /* Set router alert (RFC 2113) */
+  {
+    char ra[4];
+    ra[0] = 148;
+    ra[1] = 4;
+    ra[2] = 0;
+    ra[3] = 0;
+    if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, ra, 4)) {
+      zlog_warn("Could not set Router Alert Option on socket fd=%d: errno=%d: %s",
+		fd, errno, strerror(errno));
+      close(fd);
+      return PIM_SOCK_ERR_RA;
+    }
+  }
+
+  {
+    int reuse = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+		   (void *) &reuse, sizeof(reuse))) {
+      zlog_warn("Could not set Reuse Address Option on socket fd=%d: errno=%d: %s",
+		fd, errno, strerror(errno));
+      close(fd);
+      return PIM_SOCK_ERR_REUSE;
+    }
+  }
+
+  {
+    const int MTTL = 1;
+    int ttl = MTTL;
+    if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
+		   (void *) &ttl, sizeof(ttl))) {
+      zlog_warn("Could not set multicast TTL=%d on socket fd=%d: errno=%d: %s",
+		MTTL, fd, errno, strerror(errno));
+      close(fd);
+      return PIM_SOCK_ERR_TTL;
+    }
+  }
+
+  if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+		 (void *) &loop, sizeof(loop))) {
+    zlog_warn("Could not %s Multicast Loopback Option on socket fd=%d: errno=%d: %s",
+	      loop ? "enable" : "disable",
+	      fd, errno, strerror(errno));
+    close(fd);
+    return PIM_SOCK_ERR_LOOP;
+  }
+
+  if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
+		 (void *) &ifaddr, sizeof(ifaddr))) {
+    zlog_warn("Could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s",
+	      fd, errno, strerror(errno));
+    close(fd);
+    return PIM_SOCK_ERR_IFACE;
+  }
+
+  {
+    long flags;
+
+    flags = fcntl(fd, F_GETFL, 0);
+    if (flags < 0) {
+      zlog_warn("Could not get fcntl(F_GETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
+		fd, errno, strerror(errno));
+      close(fd);
+      return PIM_SOCK_ERR_NONBLOCK_GETFL;
+    }
+
+    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
+      zlog_warn("Could not set fcntl(F_SETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
+		fd, errno, strerror(errno));
+      close(fd);
+      return PIM_SOCK_ERR_NONBLOCK_SETFL;
+    }
+  }
+
+  return fd;
+}
+
+int pim_socket_join(int fd, struct in_addr group,
+		    struct in_addr ifaddr, int ifindex)
+{
+  int ret;
+
+#ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+  struct ip_mreqn opt;
+#else
+  struct ip_mreq opt;
+#endif
+
+  opt.imr_multiaddr = group;
+
+#ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+  opt.imr_address   = ifaddr;
+  opt.imr_ifindex   = ifindex;
+#else
+  opt.imr_interface = ifaddr;
+#endif
+
+  ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));
+  if (ret) {
+    char group_str[100];
+    char ifaddr_str[100];
+    if (!inet_ntop(AF_INET, &group, group_str , sizeof(group_str)))
+      sprintf(group_str, "<group?>");
+    if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str , sizeof(ifaddr_str)))
+      sprintf(ifaddr_str, "<ifaddr?>");
+
+    zlog_err("Failure socket joining fd=%d group %s on interface address %s: errno=%d: %s",
+	     fd, group_str, ifaddr_str, errno, strerror(errno));
+    return ret;
+  }
+
+  if (PIM_DEBUG_TRACE) {
+    char group_str[100];
+    char ifaddr_str[100];
+    if (!inet_ntop(AF_INET, &group, group_str , sizeof(group_str)))
+      sprintf(group_str, "<group?>");
+    if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str , sizeof(ifaddr_str)))
+      sprintf(ifaddr_str, "<ifaddr?>");
+
+    zlog_debug("Socket fd=%d joined group %s on interface address %s",
+	       fd, group_str, ifaddr_str);
+  }
+
+  return ret;
+}
+
+int pim_socket_recvfromto(int fd, char *buf, size_t len,
+			  struct sockaddr_in *from, socklen_t *fromlen,
+			  struct sockaddr_in *to, socklen_t *tolen,
+			  int *ifindex)
+{
+  struct msghdr msgh;
+  struct cmsghdr *cmsg;
+  struct iovec iov;
+  char cbuf[1000];
+  int err;
+
+  memset(&msgh, 0, sizeof(struct msghdr));
+  iov.iov_base = buf;
+  iov.iov_len  = len;
+  msgh.msg_control = cbuf;
+  msgh.msg_controllen = sizeof(cbuf);
+  msgh.msg_name = from;
+  msgh.msg_namelen = fromlen ? *fromlen : 0;
+  msgh.msg_iov  = &iov;
+  msgh.msg_iovlen = 1;
+  msgh.msg_flags = 0;
+
+  err = recvmsg(fd, &msgh, 0);
+  if (err < 0)
+    return err;
+
+  if (fromlen)
+    *fromlen = msgh.msg_namelen;
+
+  for (cmsg = CMSG_FIRSTHDR(&msgh);
+       cmsg != NULL;
+       cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
+
+#ifdef HAVE_IP_PKTINFO
+    if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
+      struct in_pktinfo *i = (struct in_pktinfo *) CMSG_DATA(cmsg);
+      if (to)
+	((struct sockaddr_in *) to)->sin_addr = i->ipi_addr;
+      if (tolen)
+	*tolen = sizeof(struct sockaddr_in);
+      if (ifindex)
+	*ifindex = i->ipi_ifindex;
+      break;
+    }
+#endif
+
+#ifdef HAVE_IP_RECVDSTADDR
+    if ((cmsg->cmsg_level == IPPROTO_IP) && (cmsg->cmsg_type == IP_RECVDSTADDR)) {
+      struct in_addr *i = (struct in_addr *) CMSG_DATA(cmsg);
+      if (to)
+	((struct sockaddr_in *) to)->sin_addr = *i;
+      if (tolen)
+	*tolen = sizeof(struct sockaddr_in);
+      break;
+    }
+#endif
+
+#if defined(HAVE_IP_RECVIF) && defined(CMSG_IFINDEX)
+      if (cmsg->cmsg_type == IP_RECVIF)
+	if (ifindex)
+	  *ifindex = CMSG_IFINDEX(cmsg);
+#endif
+
+  } /* for (cmsg) */
+
+  return err; /* len */
+}
+
+int pim_socket_mcastloop_get(int fd)
+{
+  int loop;
+  socklen_t loop_len = sizeof(loop);
+  
+  if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+		 &loop, &loop_len)) {
+    int e = errno;
+    zlog_warn("Could not get Multicast Loopback Option on socket fd=%d: errno=%d: %s",
+	      fd, errno, strerror(errno));
+    errno = e;
+    return PIM_SOCK_ERR_LOOP;
+  }
+  
+  return loop;
+}
diff --git a/pimd/pim_sock.h b/pimd/pim_sock.h
new file mode 100644
index 0000000..a2fb7fa
--- /dev/null
+++ b/pimd/pim_sock.h
@@ -0,0 +1,47 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_SOCK_H
+#define PIM_SOCK_H
+
+#define PIM_SOCK_ERR_NONE    (0)  /* No error */
+#define PIM_SOCK_ERR_SOCKET  (-1) /* socket() */
+#define PIM_SOCK_ERR_RA      (-2) /* Router Alert option */
+#define PIM_SOCK_ERR_REUSE   (-3) /* Reuse option */
+#define PIM_SOCK_ERR_TTL     (-4) /* TTL option */
+#define PIM_SOCK_ERR_LOOP    (-5) /* Loopback option */
+#define PIM_SOCK_ERR_IFACE   (-6) /* Outgoing interface option */
+#define PIM_SOCK_ERR_DSTADDR (-7) /* Outgoing interface option */
+#define PIM_SOCK_ERR_NONBLOCK_GETFL (-8) /* Get O_NONBLOCK */
+#define PIM_SOCK_ERR_NONBLOCK_SETFL (-9) /* Set O_NONBLOCK */
+
+int pim_socket_mcast(int protocol, struct in_addr ifaddr, int loop);
+int pim_socket_join(int fd, struct in_addr group,
+		    struct in_addr ifaddr, int ifindex);
+int pim_socket_recvfromto(int fd, char *buf, size_t len,
+			  struct sockaddr_in *from, socklen_t *fromlen,
+			  struct sockaddr_in *to, socklen_t *tolen,
+			  int *ifindex);
+
+int pim_socket_mcastloop_get(int fd);
+
+#endif /* PIM_SOCK_H */
diff --git a/pimd/pim_str.c b/pimd/pim_str.c
new file mode 100644
index 0000000..7dce7a8
--- /dev/null
+++ b/pimd/pim_str.c
@@ -0,0 +1,46 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include <zebra.h>
+
+#include "log.h"
+
+#include "pim_str.h"
+
+void pim_inet4_dump(const char *onfail, struct in_addr addr, char *buf, int buf_size)
+{
+  int save_errno = errno;
+
+  if (!inet_ntop(AF_INET, &addr, buf, buf_size)) {
+    int e = errno;
+    zlog_warn("pim_inet4_dump: inet_ntop(AF_INET,buf_size=%d): errno=%d: %s",
+	      buf_size, e, strerror(e));
+    if (onfail)
+      snprintf(buf, buf_size, "%s", onfail);
+  }
+
+  errno = save_errno;
+}
diff --git a/pimd/pim_str.h b/pimd/pim_str.h
new file mode 100644
index 0000000..925f17f
--- /dev/null
+++ b/pimd/pim_str.h
@@ -0,0 +1,32 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_STR_H
+#define PIM_STR_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+void pim_inet4_dump(const char *onfail, struct in_addr addr, char *buf, int buf_size);
+
+#endif
diff --git a/pimd/pim_time.c b/pimd/pim_time.c
new file mode 100644
index 0000000..a837e5b
--- /dev/null
+++ b/pimd/pim_time.c
@@ -0,0 +1,151 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <zebra.h>
+#include "log.h"
+#include "thread.h"
+
+#include "pim_time.h"
+
+/*
+  see man clock_gettime
+ */
+static int pim_gettime(clockid_t clk_id, struct timeval *tv)
+{
+  return quagga_gettime(clk_id, tv);
+}
+
+/*
+  pim_time_monotonic_sec():
+  number of seconds since some unspecified starting point
+*/
+int64_t pim_time_monotonic_sec()
+{
+  struct timeval now_tv;
+
+  if (pim_gettime(CLOCK_MONOTONIC, &now_tv)) {
+    zlog_err("%s: gettime(CLOCK_MONOTONIC) failure: errno=%d: %s",
+	     __PRETTY_FUNCTION__,
+	     errno, strerror(errno));
+    return -1;
+  }
+
+  return now_tv.tv_sec;
+}
+
+/*
+  pim_time_monotonic_dsec():
+  number of deciseconds since some unspecified starting point
+*/
+int64_t pim_time_monotonic_dsec()
+{
+  struct timeval now_tv;
+  int64_t        now_dsec;
+
+  if (pim_gettime(CLOCK_MONOTONIC, &now_tv)) {
+    zlog_err("%s: gettime(CLOCK_MONOTONIC) failure: errno=%d: %s",
+	     __PRETTY_FUNCTION__,
+	     errno, strerror(errno));
+    return -1;
+  }
+
+  now_dsec = ((int64_t) now_tv.tv_sec) * 10 + ((int64_t) now_tv.tv_usec) / 100000;
+
+  return now_dsec;
+}
+
+int pim_time_mmss(char *buf, int buf_size, long sec)
+{
+  long mm;
+  int wr;
+
+  zassert(buf_size >= 5);
+
+  mm = sec / 60;
+  sec %= 60;
+  
+  wr = snprintf(buf, buf_size, "%02ld:%02ld", mm, sec);
+
+  return wr != 8;
+}
+
+static int pim_time_hhmmss(char *buf, int buf_size, long sec)
+{
+  long hh;
+  long mm;
+  int wr;
+
+  zassert(buf_size >= 8);
+
+  hh = sec / 3600;
+  sec %= 3600;
+  mm = sec / 60;
+  sec %= 60;
+  
+  wr = snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hh, mm, sec);
+
+  return wr != 8;
+}
+
+void pim_time_timer_to_mmss(char *buf, int buf_size, struct thread *t_timer)
+{
+  if (t_timer) {
+    pim_time_mmss(buf, buf_size,
+		  thread_timer_remain_second(t_timer));
+  }
+  else {
+    snprintf(buf, buf_size, "--:--");
+  }
+}
+
+void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct thread *t_timer)
+{
+  if (t_timer) {
+    pim_time_hhmmss(buf, buf_size,
+		    thread_timer_remain_second(t_timer));
+  }
+  else {
+    snprintf(buf, buf_size, "--:--:--");
+  }
+}
+
+void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec)
+{
+  zassert(buf_size >= 8);
+
+  pim_time_hhmmss(buf, buf_size, uptime_sec);
+}
+
+long pim_time_timer_remain_msec(struct thread *t_timer)
+{
+  /* FIXME: Actually fetch msec resolution from thread */
+
+  /* no timer thread running means timer has expired: return 0 */
+
+  return t_timer ?
+    1000 * thread_timer_remain_second(t_timer) :
+    0;
+}
diff --git a/pimd/pim_time.h b/pimd/pim_time.h
new file mode 100644
index 0000000..379eb6c
--- /dev/null
+++ b/pimd/pim_time.h
@@ -0,0 +1,39 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_TIME_H
+#define PIM_TIME_H
+
+#include <stdint.h>
+
+#include <zebra.h>
+#include "thread.h"
+
+int64_t pim_time_monotonic_sec(void);
+int64_t pim_time_monotonic_dsec(void);
+int pim_time_mmss(char *buf, int buf_size, long sec);
+void pim_time_timer_to_mmss(char *buf, int buf_size, struct thread *t);
+void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct thread *t);
+void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec);
+long pim_time_timer_remain_msec(struct thread *t_timer);
+
+#endif /* PIM_TIME_H */
diff --git a/pimd/pim_tlv.c b/pimd/pim_tlv.c
new file mode 100644
index 0000000..c578a70
--- /dev/null
+++ b/pimd/pim_tlv.c
@@ -0,0 +1,707 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "prefix.h"
+
+#include "pimd.h"
+#include "pim_tlv.h"
+#include "pim_str.h"
+#include "pim_msg.h"
+
+char *pim_tlv_append_uint16(char *buf,
+			    const char *buf_pastend,
+			    uint16_t option_type,
+			    uint16_t option_value)
+{
+  uint16_t option_len = 2;
+
+  if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) {
+    zlog_warn("%s: buffer overflow: left=%d needed=%d",
+	      __PRETTY_FUNCTION__,
+	      buf_pastend - buf, PIM_TLV_OPTION_SIZE(option_len));
+    return 0;
+  }
+
+  *(uint16_t *) buf = htons(option_type);
+  buf += 2;
+  *(uint16_t *) buf = htons(option_len);
+  buf += 2;
+  *(uint16_t *) buf = htons(option_value);
+  buf += option_len;
+
+  return buf;
+}
+
+char *pim_tlv_append_2uint16(char *buf,
+			     const char *buf_pastend,
+			     uint16_t option_type,
+			     uint16_t option_value1,
+			     uint16_t option_value2)
+{
+  uint16_t option_len = 4;
+
+  if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) {
+    zlog_warn("%s: buffer overflow: left=%d needed=%d",
+	      __PRETTY_FUNCTION__,
+	      buf_pastend - buf, PIM_TLV_OPTION_SIZE(option_len));
+    return 0;
+  }
+
+  *(uint16_t *) buf = htons(option_type);
+  buf += 2;
+  *(uint16_t *) buf = htons(option_len);
+  buf += 2;
+  *(uint16_t *) buf = htons(option_value1);
+  buf += 2;
+  *(uint16_t *) buf = htons(option_value2);
+  buf += 2;
+
+  return buf;
+}
+
+char *pim_tlv_append_uint32(char *buf,
+			    const char *buf_pastend,
+			    uint16_t option_type,
+			    uint32_t option_value)
+{
+  uint16_t option_len = 4;
+
+  if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) {
+    zlog_warn("%s: buffer overflow: left=%d needed=%d",
+	      __PRETTY_FUNCTION__,
+	      buf_pastend - buf, PIM_TLV_OPTION_SIZE(option_len));
+    return 0;
+  }
+
+  *(uint16_t *) buf = htons(option_type);
+  buf += 2;
+  *(uint16_t *) buf = htons(option_len);
+  buf += 2;
+  *(uint32_t *) buf = htonl(option_value);
+  buf += option_len;
+
+  return buf;
+}
+
+#define ucast_ipv4_encoding_len (2 + sizeof(struct in_addr))
+
+char *pim_tlv_append_addrlist_ucast(char *buf,
+				    const char *buf_pastend,
+				    struct list *ifconnected)
+{
+  struct listnode *node;
+  uint16_t option_len = 0;
+
+  char *curr;
+
+  node = listhead(ifconnected);
+
+  /* Empty address list ? */
+  if (!node) {
+    return buf;
+  }
+
+  /* Skip first address (primary) */
+  node = listnextnode(node);
+
+  /* Scan secondary address list */
+  curr = buf + 4; /* skip T and L */
+  for (; node; node = listnextnode(node)) {
+    struct connected *ifc = listgetdata(node);
+    struct prefix *p = ifc->address;
+    
+    if (p->family != AF_INET)
+      continue;
+
+    if ((curr + ucast_ipv4_encoding_len) > buf_pastend) {
+      zlog_warn("%s: buffer overflow: left=%d needed=%d",
+		__PRETTY_FUNCTION__,
+		buf_pastend - curr, ucast_ipv4_encoding_len);
+      return 0;
+    }
+
+    /* Write encoded unicast IPv4 address */
+    *(uint8_t *) curr = PIM_MSG_ADDRESS_FAMILY_IPV4; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+    ++curr;
+    *(uint8_t *) curr = 0; /* ucast IPv4 native encoding type (RFC 4601: 4.9.1) */
+    ++curr;
+    *(struct in_addr *) curr = p->u.prefix4;
+    curr += sizeof(struct in_addr);
+
+    option_len += ucast_ipv4_encoding_len; 
+  }
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    zlog_warn("%s: number of encoded secondary unicast IPv4 addresses: %d",
+	      __PRETTY_FUNCTION__,
+	      option_len / ucast_ipv4_encoding_len);
+  }
+
+  if (option_len < 1) {
+    /* Empty secondary unicast IPv4 address list */
+    return buf;
+  }
+
+  /*
+   * Write T and L
+   */
+  *(uint16_t *) buf       = htons(PIM_MSG_OPTION_TYPE_ADDRESS_LIST);
+  *(uint16_t *) (buf + 2) = htons(option_len);
+
+  return curr;
+}
+
+static int check_tlv_length(const char *label, const char *tlv_name,
+			    const char *ifname, struct in_addr src_addr,
+			    int correct_len, int option_len)
+{
+  if (option_len != correct_len) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: PIM hello %s TLV with incorrect value size=%d correct=%d from %s on interface %s",
+	      label, tlv_name,
+	      option_len, correct_len,
+	      src_str, ifname);
+    return -1;
+  }
+
+  return 0;
+}
+
+static void check_tlv_redefinition_uint16(const char *label, const char *tlv_name,
+					  const char *ifname, struct in_addr src_addr,
+					  pim_hello_options options,
+					  pim_hello_options opt_mask,
+					  uint16_t new, uint16_t old)
+{
+  if (PIM_OPTION_IS_SET(options, opt_mask)) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: PIM hello TLV redefined %s=%u old=%u from %s on interface %s",
+	      label, tlv_name,
+	      new, old,
+	      src_str, ifname);
+  }
+}
+
+static void check_tlv_redefinition_uint32(const char *label, const char *tlv_name,
+					  const char *ifname, struct in_addr src_addr,
+					  pim_hello_options options,
+					  pim_hello_options opt_mask,
+					  uint32_t new, uint32_t old)
+{
+  if (PIM_OPTION_IS_SET(options, opt_mask)) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: PIM hello TLV redefined %s=%u old=%u from %s on interface %s",
+	      label, tlv_name,
+	      new, old,
+	      src_str, ifname);
+  }
+}
+
+static void check_tlv_redefinition_uint32_hex(const char *label, const char *tlv_name,
+					      const char *ifname, struct in_addr src_addr,
+					      pim_hello_options options,
+					      pim_hello_options opt_mask,
+					      uint32_t new, uint32_t old)
+{
+  if (PIM_OPTION_IS_SET(options, opt_mask)) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: PIM hello TLV redefined %s=%08x old=%08x from %s on interface %s",
+	      label, tlv_name,
+	      new, old,
+	      src_str, ifname);
+  }
+}
+
+int pim_tlv_parse_holdtime(const char *ifname, struct in_addr src_addr,
+			   pim_hello_options *hello_options,
+			   uint16_t *hello_option_holdtime,
+			   uint16_t option_len,
+			   const char *tlv_curr) 
+{
+  const char *label = "holdtime";
+
+  if (check_tlv_length(__PRETTY_FUNCTION__, label,
+		       ifname, src_addr,
+		       sizeof(uint16_t), option_len)) {
+    return -1;
+  }
+  
+  check_tlv_redefinition_uint16(__PRETTY_FUNCTION__, label,
+				ifname, src_addr,
+				*hello_options, PIM_OPTION_MASK_HOLDTIME,
+				PIM_TLV_GET_HOLDTIME(tlv_curr),
+				*hello_option_holdtime);
+  
+  PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_HOLDTIME);
+  
+  *hello_option_holdtime = PIM_TLV_GET_HOLDTIME(tlv_curr);
+  
+  return 0;
+}
+
+int pim_tlv_parse_lan_prune_delay(const char *ifname, struct in_addr src_addr,
+				  pim_hello_options *hello_options,
+				  uint16_t *hello_option_propagation_delay,
+				  uint16_t *hello_option_override_interval,
+				  uint16_t option_len,
+				  const char *tlv_curr) 
+{
+  if (check_tlv_length(__PRETTY_FUNCTION__, "lan_prune_delay",
+		       ifname, src_addr,
+		       sizeof(uint32_t), option_len)) {
+    return -1;
+  }
+  
+  check_tlv_redefinition_uint16(__PRETTY_FUNCTION__, "propagation_delay",
+				ifname, src_addr,
+				*hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY,
+				PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr),
+				*hello_option_propagation_delay);
+  
+  PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY);
+  
+  *hello_option_propagation_delay = PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr);
+  if (PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(tlv_curr)) {
+    PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION);
+  }
+  else {
+    PIM_OPTION_UNSET(*hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION);
+  }
+  ++tlv_curr;
+  ++tlv_curr;
+  *hello_option_override_interval = PIM_TLV_GET_OVERRIDE_INTERVAL(tlv_curr);
+  
+  return 0;
+}
+
+int pim_tlv_parse_dr_priority(const char *ifname, struct in_addr src_addr,
+			      pim_hello_options *hello_options,
+			      uint32_t *hello_option_dr_priority,
+			      uint16_t option_len,
+			      const char *tlv_curr) 
+{
+  const char *label = "dr_priority";
+
+  if (check_tlv_length(__PRETTY_FUNCTION__, label,
+		       ifname, src_addr,
+		       sizeof(uint32_t), option_len)) {
+    return -1;
+  }
+  
+  check_tlv_redefinition_uint32(__PRETTY_FUNCTION__, label,
+				ifname, src_addr,
+				*hello_options, PIM_OPTION_MASK_DR_PRIORITY,
+				PIM_TLV_GET_DR_PRIORITY(tlv_curr),
+				*hello_option_dr_priority);
+  
+  PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_DR_PRIORITY);
+  
+  *hello_option_dr_priority = PIM_TLV_GET_DR_PRIORITY(tlv_curr);
+  
+  return 0;
+}
+
+int pim_tlv_parse_generation_id(const char *ifname, struct in_addr src_addr,
+				pim_hello_options *hello_options,
+				uint32_t *hello_option_generation_id,
+				uint16_t option_len,
+				const char *tlv_curr) 
+{
+  const char *label = "generation_id";
+
+  if (check_tlv_length(__PRETTY_FUNCTION__, label,
+		       ifname, src_addr,
+		       sizeof(uint32_t), option_len)) {
+    return -1;
+  }
+  
+  check_tlv_redefinition_uint32_hex(__PRETTY_FUNCTION__, label,
+				    ifname, src_addr,
+				    *hello_options, PIM_OPTION_MASK_GENERATION_ID,
+				    PIM_TLV_GET_GENERATION_ID(tlv_curr),
+				    *hello_option_generation_id);
+  
+  PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_GENERATION_ID);
+  
+  *hello_option_generation_id = PIM_TLV_GET_GENERATION_ID(tlv_curr);
+  
+  return 0;
+}
+
+int pim_parse_addr_ucast(const char *ifname, struct in_addr src_addr,
+			 struct prefix *p,
+			 const char *buf,
+			 int buf_size)
+{
+  const int ucast_encoding_min_len = 3; /* 1 family + 1 type + 1 addr */
+  const char *addr;
+  const char *pastend;
+  int family;
+  int type;
+
+  if (buf_size < ucast_encoding_min_len) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: unicast address encoding overflow: left=%d needed=%d from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      buf_size, ucast_encoding_min_len,
+	      src_str, ifname);
+    return -1;
+  }
+
+  addr = buf;
+  pastend = buf + buf_size;
+
+  family = *(const uint8_t *) addr;
+  ++addr;
+  type = *(const uint8_t *) addr;
+  ++addr;
+
+  switch (family) {
+  case PIM_MSG_ADDRESS_FAMILY_IPV4:
+    if (type) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: unknown unicast address encoding type=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		type, src_str, ifname);
+      return -2;
+    }
+
+    if ((addr + sizeof(struct in_addr)) > pastend) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: IPv4 unicast address overflow: left=%d needed=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		pastend - addr, sizeof(struct in_addr),
+		src_str, ifname);
+      return -3;
+    }
+
+    p->family = AF_INET; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+    p->u.prefix4 = *(const struct in_addr *) addr;
+    addr += sizeof(struct in_addr);
+
+    break;
+  default:
+    {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: unknown unicast address encoding family=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		family, src_str, ifname);
+      return -4;
+    }
+  }
+
+  return addr - buf;
+}
+
+int pim_parse_addr_group(const char *ifname, struct in_addr src_addr,
+			 struct prefix *p,
+			 const char *buf,
+			 int buf_size)
+{
+  const int grp_encoding_min_len = 4; /* 1 family + 1 type + 1 reserved + 1 addr */
+  const char *addr;
+  const char *pastend;
+  int family;
+  int type;
+  int mask_len;
+
+  if (buf_size < grp_encoding_min_len) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: group address encoding overflow: left=%d needed=%d from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      buf_size, grp_encoding_min_len,
+	      src_str, ifname);
+    return -1;
+  }
+
+  addr = buf;
+  pastend = buf + buf_size;
+
+  family = *(const uint8_t *) addr;
+  ++addr;
+  type = *(const uint8_t *) addr;
+  ++addr;
+  ++addr; /* skip b_reserved_z fields */
+  mask_len = *(const uint8_t *) addr;
+  ++addr;
+
+  switch (family) {
+  case PIM_MSG_ADDRESS_FAMILY_IPV4:
+    if (type) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: unknown group address encoding type=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		type, src_str, ifname);
+      return -2;
+    }
+
+    if ((addr + sizeof(struct in_addr)) > pastend) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: IPv4 group address overflow: left=%d needed=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		pastend - addr, sizeof(struct in_addr),
+		src_str, ifname);
+      return -3;
+    }
+
+    p->family = AF_INET; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+    p->u.prefix4 = *(const struct in_addr *) addr;
+    p->prefixlen = mask_len;
+
+    addr += sizeof(struct in_addr);
+
+    break;
+  default:
+    {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: unknown group address encoding family=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		family, src_str, ifname);
+      return -4;
+    }
+  }
+
+  return addr - buf;
+}
+
+int pim_parse_addr_source(const char *ifname,
+			  struct in_addr src_addr,
+			  struct prefix *p,
+			  uint8_t *flags,
+			  const char *buf,
+			  int buf_size)
+{
+  const int src_encoding_min_len = 4; /* 1 family + 1 type + 1 reserved + 1 addr */
+  const char *addr;
+  const char *pastend;
+  int family;
+  int type;
+  int mask_len;
+
+  if (buf_size < src_encoding_min_len) {
+    char src_str[100];
+    pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+    zlog_warn("%s: source address encoding overflow: left=%d needed=%d from %s on %s",
+	      __PRETTY_FUNCTION__,
+	      buf_size, src_encoding_min_len,
+	      src_str, ifname);
+    return -1;
+  }
+
+  addr = buf;
+  pastend = buf + buf_size;
+
+  family = *(const uint8_t *) addr;
+  ++addr;
+  type = *(const uint8_t *) addr;
+  ++addr;
+  *flags = *(const uint8_t *) addr;
+  ++addr;
+  mask_len = *(const uint8_t *) addr;
+  ++addr;
+
+  switch (family) {
+  case PIM_MSG_ADDRESS_FAMILY_IPV4:
+    if (type) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: unknown source address encoding type=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		type, src_str, ifname);
+      return -2;
+    }
+
+    if ((addr + sizeof(struct in_addr)) > pastend) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: IPv4 source address overflow: left=%d needed=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		pastend - addr, sizeof(struct in_addr),
+		src_str, ifname);
+      return -3;
+    }
+
+    p->family = AF_INET; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+    p->u.prefix4 = *(const struct in_addr *) addr;
+    p->prefixlen = mask_len;
+
+    addr += sizeof(struct in_addr);
+
+    break;
+  default:
+    {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: unknown source address encoding family=%d from %s on %s",
+		__PRETTY_FUNCTION__,
+		family, src_str, ifname);
+      return -4;
+    }
+  }
+
+  return addr - buf;
+}
+
+#define FREE_ADDR_LIST(hello_option_addr_list) \
+{ \
+  if (hello_option_addr_list) { \
+    list_delete(hello_option_addr_list); \
+    hello_option_addr_list = 0; \
+  } \
+}
+
+int pim_tlv_parse_addr_list(const char *ifname, struct in_addr src_addr,
+			    pim_hello_options *hello_options,
+			    struct list **hello_option_addr_list,
+			    uint16_t option_len,
+			    const char *tlv_curr) 
+{
+  const char *addr;
+  const char *pastend;
+
+  zassert(hello_option_addr_list);
+
+  /*
+    Scan addr list
+   */
+  addr = tlv_curr;
+  pastend = tlv_curr + option_len;
+  while (addr < pastend) {
+    struct prefix tmp;
+    int addr_offset;
+
+    /*
+      Parse ucast addr
+     */
+    addr_offset = pim_parse_addr_ucast(ifname, src_addr, &tmp,
+				       addr, pastend - addr);
+    if (addr_offset < 1) {
+      char src_str[100];
+      pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+      zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s",
+		__PRETTY_FUNCTION__,
+		src_str, ifname);
+      FREE_ADDR_LIST(*hello_option_addr_list);
+      return -1;
+    }
+    addr += addr_offset;
+
+    /*
+      Debug
+     */
+    if (PIM_DEBUG_PIM_TRACE) {
+      switch (tmp.family) {
+      case AF_INET:
+	{
+	  char addr_str[100];
+	  char src_str[100];
+	  pim_inet4_dump("<addr?>", tmp.u.prefix4, addr_str, sizeof(addr_str));
+	  pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+	  zlog_debug("%s: PIM hello TLV option: list_old_size=%d IPv4 address %s from %s on %s",
+		     __PRETTY_FUNCTION__,
+		     *hello_option_addr_list ?
+		     ((int) listcount(*hello_option_addr_list)) : -1,
+		     addr_str, src_str, ifname);
+	}
+	break;
+      default:
+	{
+	  char src_str[100];
+	  pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+	  zlog_debug("%s: PIM hello TLV option: list_old_size=%d UNKNOWN address family from %s on %s",
+		     __PRETTY_FUNCTION__,
+		     *hello_option_addr_list ?
+		     ((int) listcount(*hello_option_addr_list)) : -1,
+		     src_str, ifname);
+	}
+      }
+    }
+
+    /*
+      Exclude neighbor's primary address if incorrectly included in
+      the secondary address list
+     */
+    if (tmp.family == AF_INET) {
+      if (tmp.u.prefix4.s_addr == src_addr.s_addr) {
+	  char src_str[100];
+	  pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+	  zlog_warn("%s: ignoring primary address in secondary list from %s on %s",
+		    __PRETTY_FUNCTION__,
+		    src_str, ifname);
+	  continue;
+      }
+    }
+
+    /*
+      Allocate list if needed
+     */
+    if (!*hello_option_addr_list) {
+      *hello_option_addr_list = list_new();
+      if (!*hello_option_addr_list) {
+	zlog_err("%s %s: failure: hello_option_addr_list=list_new()",
+		 __FILE__, __PRETTY_FUNCTION__);
+	return -2;
+      }
+      (*hello_option_addr_list)->del = (void (*)(void *)) prefix_free;
+    }
+
+    /*
+      Attach addr to list
+     */
+    {
+      struct prefix *p;
+      p = prefix_new();
+      if (!p) {
+	zlog_err("%s %s: failure: prefix_new()",
+		 __FILE__, __PRETTY_FUNCTION__);
+	FREE_ADDR_LIST(*hello_option_addr_list);
+	return -3;
+      }
+      p->family = tmp.family;
+      p->u.prefix4 = tmp.u.prefix4;
+      listnode_add(*hello_option_addr_list, p);
+    }
+
+  } /* while (addr < pastend) */
+  
+  /*
+    Mark hello option
+   */
+  PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_ADDRESS_LIST);
+  
+  return 0;
+}
diff --git a/pimd/pim_tlv.h b/pimd/pim_tlv.h
new file mode 100644
index 0000000..9602cb1
--- /dev/null
+++ b/pimd/pim_tlv.h
@@ -0,0 +1,133 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_TLV_H
+#define PIM_TLV_H
+
+#include <zebra.h>
+
+#include "config.h"
+#include "if.h"
+#include "linklist.h"
+
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif /* HAVE_INTTYPES_H */
+
+#define PIM_MSG_OPTION_TYPE_HOLDTIME         (1)
+#define PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY  (2)
+#define PIM_MSG_OPTION_TYPE_DR_PRIORITY      (19)
+#define PIM_MSG_OPTION_TYPE_GENERATION_ID    (20)
+#define PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH (21)
+#define PIM_MSG_OPTION_TYPE_ADDRESS_LIST     (24)
+
+typedef uint32_t pim_hello_options;
+#define PIM_OPTION_MASK_HOLDTIME                     (1 << 0) /* recv holdtime */
+#define PIM_OPTION_MASK_LAN_PRUNE_DELAY              (1 << 1) /* recv lan_prune_delay */
+#define PIM_OPTION_MASK_DR_PRIORITY                  (1 << 2) /* recv dr_priority */
+#define PIM_OPTION_MASK_GENERATION_ID                (1 << 3) /* recv generation_id */
+#define PIM_OPTION_MASK_ADDRESS_LIST                 (1 << 4) /* recv secondary address list */
+#define PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION (1 << 5) /* T bit value (valid if recv lan_prune_delay) */
+
+#define PIM_RPT_BIT_MASK      (1 << 0)
+#define PIM_WILDCARD_BIT_MASK (1 << 1)
+
+#define PIM_OPTION_SET(options, option_mask) ((options) |= (option_mask))
+#define PIM_OPTION_UNSET(options, option_mask) ((options) &= ~(option_mask))
+#define PIM_OPTION_IS_SET(options, option_mask) ((options) & (option_mask))
+
+#define PIM_TLV_GET_UINT16(buf) ntohs(*(const uint16_t *)(buf))
+#define PIM_TLV_GET_UINT32(buf) ntohl(*(const uint32_t *)(buf))
+#define PIM_TLV_GET_TYPE(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_LENGTH(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_HOLDTIME(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_PROPAGATION_DELAY(buf) (PIM_TLV_GET_UINT16(buf) & 0x7FFF)
+#define PIM_TLV_GET_OVERRIDE_INTERVAL(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(buf) ((*(const uint8_t *)(buf)) & 0x80)
+#define PIM_TLV_GET_DR_PRIORITY(buf) PIM_TLV_GET_UINT32(buf)
+#define PIM_TLV_GET_GENERATION_ID(buf) PIM_TLV_GET_UINT32(buf)
+
+#define PIM_TLV_TYPE_SIZE               (2)
+#define PIM_TLV_LENGTH_SIZE             (2)
+#define PIM_TLV_MIN_SIZE                (PIM_TLV_TYPE_SIZE + PIM_TLV_LENGTH_SIZE)
+#define PIM_TLV_OPTION_SIZE(option_len) (PIM_TLV_MIN_SIZE + (option_len))
+
+char *pim_tlv_append_uint16(char *buf,
+			    const char *buf_pastend,
+			    uint16_t option_type,
+			    uint16_t option_value);
+char *pim_tlv_append_2uint16(char *buf,
+			     const char *buf_pastend,
+			     uint16_t option_type,
+			     uint16_t option_value1,
+			     uint16_t option_value2);
+char *pim_tlv_append_uint32(char *buf,
+			    const char *buf_pastend,
+			    uint16_t option_type,
+			    uint32_t option_value);
+char *pim_tlv_append_addrlist_ucast(char *buf,
+				    const char *buf_pastend,
+				    struct list *ifconnected);
+
+int pim_tlv_parse_holdtime(const char *ifname, struct in_addr src_addr,
+			   pim_hello_options *hello_options,
+			   uint16_t *hello_option_holdtime,
+			   uint16_t option_len,
+			   const char *tlv_curr);
+int pim_tlv_parse_lan_prune_delay(const char *ifname, struct in_addr src_addr,
+				  pim_hello_options *hello_options,
+				  uint16_t *hello_option_propagation_delay,
+				  uint16_t *hello_option_override_interval,
+				  uint16_t option_len,
+				  const char *tlv_curr);
+int pim_tlv_parse_dr_priority(const char *ifname, struct in_addr src_addr,
+			      pim_hello_options *hello_options,
+			      uint32_t *hello_option_dr_priority,
+			      uint16_t option_len,
+			      const char *tlv_curr);
+int pim_tlv_parse_generation_id(const char *ifname, struct in_addr src_addr,
+				pim_hello_options *hello_options,
+				uint32_t *hello_option_generation_id,
+				uint16_t option_len,
+				const char *tlv_curr);
+int pim_tlv_parse_addr_list(const char *ifname, struct in_addr src_addr,
+			    pim_hello_options *hello_options,
+			    struct list **hello_option_addr_list,
+			    uint16_t option_len,
+			    const char *tlv_curr);
+
+int pim_parse_addr_ucast(const char *ifname, struct in_addr src_addr,
+			 struct prefix *p,
+			 const char *buf,
+			 int buf_size);
+int pim_parse_addr_group(const char *ifname, struct in_addr src_addr,
+			 struct prefix *p,
+			 const char *buf,
+			 int buf_size);
+int pim_parse_addr_source(const char *ifname,
+			  struct in_addr src_addr,
+			  struct prefix *p,
+			  uint8_t *flags,
+			  const char *buf,
+			  int buf_size);
+
+#endif /* PIM_TLV_H */
diff --git a/pimd/pim_upstream.c b/pimd/pim_upstream.c
new file mode 100644
index 0000000..f5c75ca
--- /dev/null
+++ b/pimd/pim_upstream.c
@@ -0,0 +1,555 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "zebra/rib.h"
+
+#include "log.h"
+#include "zclient.h"
+#include "memory.h"
+#include "thread.h"
+#include "linklist.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+#include "pim_join.h"
+#include "pim_zlookup.h"
+#include "pim_upstream.h"
+#include "pim_ifchannel.h"
+#include "pim_neighbor.h"
+#include "pim_rpf.h"
+#include "pim_zebra.h"
+#include "pim_oil.h"
+
+static void join_timer_start(struct pim_upstream *up);
+
+void pim_upstream_free(struct pim_upstream *up)
+{
+  XFREE(MTYPE_PIM_UPSTREAM, up);
+}
+
+static void upstream_channel_oil_detach(struct pim_upstream *up)
+{
+  if (up->channel_oil) {
+    pim_channel_oil_del(up->channel_oil);
+    up->channel_oil = 0;
+  }
+}
+
+void pim_upstream_delete(struct pim_upstream *up)
+{
+  THREAD_OFF(up->t_join_timer);
+
+  upstream_channel_oil_detach(up);
+
+  /*
+    notice that listnode_delete() can't be moved
+    into pim_upstream_free() because the later is
+    called by list_delete_all_node()
+  */
+  listnode_delete(qpim_upstream_list, up);
+
+  pim_upstream_free(up);
+}
+
+static void send_join(struct pim_upstream *up)
+{
+  zassert(up->join_state == PIM_UPSTREAM_JOINED);
+
+  
+  if (PIM_DEBUG_PIM_TRACE) {
+    if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) {
+      char src_str[100];
+      char grp_str[100];
+      char rpf_str[100];
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str));
+      zlog_warn("%s: can't send join upstream: RPF'(%s,%s)=%s",
+		__PRETTY_FUNCTION__,
+		src_str, grp_str, rpf_str);
+      /* warning only */
+    }
+  }
+  
+  /* send Join(S,G) to the current upstream neighbor */
+  pim_joinprune_send(up->rpf.source_nexthop.interface,
+  		     up->rpf.rpf_addr,
+		     up->source_addr,
+		     up->group_addr,
+		     1 /* join */);
+}
+
+static int on_join_timer(struct thread *t)
+{
+  struct pim_upstream *up;
+
+  zassert(t);
+  up = THREAD_ARG(t);
+  zassert(up);
+
+  send_join(up);
+
+  up->t_join_timer = 0;
+  join_timer_start(up);
+
+  return 0;
+}
+
+static void join_timer_start(struct pim_upstream *up)
+{
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_debug("%s: starting %d sec timer for upstream (S,G)=(%s,%s)",
+	       __PRETTY_FUNCTION__,
+	       qpim_t_periodic,
+	       src_str, grp_str);
+  }
+
+  zassert(!up->t_join_timer);
+
+  THREAD_TIMER_ON(master, up->t_join_timer,
+		  on_join_timer,
+		  up, qpim_t_periodic);
+}
+
+void pim_upstream_join_timer_restart(struct pim_upstream *up)
+{
+  THREAD_OFF(up->t_join_timer);
+  join_timer_start(up);
+}
+
+static void pim_upstream_join_timer_restart_msec(struct pim_upstream *up,
+						 int interval_msec)
+{
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_debug("%s: restarting %d msec timer for upstream (S,G)=(%s,%s)",
+	       __PRETTY_FUNCTION__,
+	       interval_msec,
+	       src_str, grp_str);
+  }
+
+  THREAD_OFF(up->t_join_timer);
+  THREAD_TIMER_MSEC_ON(master, up->t_join_timer,
+		       on_join_timer,
+		       up, interval_msec);
+}
+
+void pim_upstream_join_suppress(struct pim_upstream *up,
+				struct in_addr rpf_addr,
+				int holdtime)
+{
+  long t_joinsuppress_msec;
+  long join_timer_remain_msec;
+
+  t_joinsuppress_msec = MIN(pim_if_t_suppressed_msec(up->rpf.source_nexthop.interface),
+			    1000 * holdtime);
+
+  join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    char grp_str[100];
+    char rpf_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
+    zlog_debug("%s %s: detected Join(%s,%s) to RPF'(S,G)=%s: join_timer=%ld msec t_joinsuppress=%ld msec",
+	       __FILE__, __PRETTY_FUNCTION__, 
+	       src_str, grp_str,
+	       rpf_str,
+	       join_timer_remain_msec, t_joinsuppress_msec);
+  }
+
+  if (join_timer_remain_msec < t_joinsuppress_msec) {
+    if (PIM_DEBUG_PIM_TRACE) {
+      char src_str[100];
+      char grp_str[100];
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      zlog_debug("%s %s: suppressing Join(S,G)=(%s,%s) for %ld msec",
+		 __FILE__, __PRETTY_FUNCTION__, 
+		 src_str, grp_str, t_joinsuppress_msec);
+    }
+
+    pim_upstream_join_timer_restart_msec(up, t_joinsuppress_msec);
+  }
+}
+
+void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label,
+						    struct pim_upstream *up,
+						    struct in_addr rpf_addr)
+{
+  long join_timer_remain_msec;
+  int t_override_msec;
+
+  join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
+  t_override_msec = pim_if_t_override_msec(up->rpf.source_nexthop.interface);
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char src_str[100];
+    char grp_str[100];
+    char rpf_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
+    zlog_debug("%s: to RPF'(%s,%s)=%s: join_timer=%ld msec t_override=%d msec",
+	       debug_label,
+	       src_str, grp_str, rpf_str,
+	       join_timer_remain_msec, t_override_msec);
+  }
+    
+  if (join_timer_remain_msec > t_override_msec) {
+    if (PIM_DEBUG_PIM_TRACE) {
+      char src_str[100];
+      char grp_str[100];
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      zlog_debug("%s: decreasing (S,G)=(%s,%s) join timer to t_override=%d msec",
+		 debug_label,
+		 src_str, grp_str,
+		 t_override_msec);
+    }
+
+    pim_upstream_join_timer_restart_msec(up, t_override_msec);
+  }
+}
+
+static void forward_on(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+  struct in_addr        ifaddr;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    ifaddr = pim_ifp->primary_address;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+      if (ch->upstream != up)
+	continue;
+
+      if (pim_ifchannel_isin_oiflist(ch, ifaddr))
+	pim_forward_start(ch);
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+}
+
+static void forward_off(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *ifnextnode;
+  struct listnode      *chnode;
+  struct listnode      *chnextnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+      if (ch->upstream != up)
+	continue;
+
+      pim_forward_stop(ch);
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+}
+
+static void pim_upstream_switch(struct pim_upstream *up,
+				enum pim_upstream_state new_state)
+{
+  enum pim_upstream_state old_state = up->join_state;
+
+  zassert(old_state != new_state);
+  
+  up->join_state       = new_state;
+  up->state_transition = pim_time_monotonic_sec();
+
+  if (PIM_DEBUG_PIM_EVENTS) {
+    char src_str[100];
+    char grp_str[100];
+    pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+    pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+    zlog_debug("%s: PIM_UPSTREAM_%s: (S,G)=(%s,%s)",
+	       __PRETTY_FUNCTION__,
+	       ((new_state == PIM_UPSTREAM_JOINED) ? "JOINED" : "NOTJOINED"),
+	       src_str, grp_str);
+  }
+
+  if (new_state == PIM_UPSTREAM_JOINED) {
+    forward_on(up);
+    send_join(up);
+    join_timer_start(up);
+  }
+  else {
+    forward_off(up);
+    pim_joinprune_send(up->rpf.source_nexthop.interface,
+		       up->rpf.rpf_addr,
+		       up->source_addr,
+		       up->group_addr,
+		       0 /* prune */);
+    zassert(up->t_join_timer);
+    THREAD_OFF(up->t_join_timer);
+  }
+
+}
+
+static struct pim_upstream *pim_upstream_new(struct in_addr source_addr,
+					     struct in_addr group_addr)
+{
+  struct pim_upstream *up;
+
+  up = XMALLOC(MTYPE_PIM_UPSTREAM, sizeof(*up));
+  if (!up) {
+    zlog_err("%s: PIM XMALLOC(%d) failure",
+	     __PRETTY_FUNCTION__, sizeof(*up));
+    return 0;
+  }
+  
+  up->source_addr                = source_addr;
+  up->group_addr                 = group_addr;
+  up->flags                      = 0;
+  up->ref_count                  = 1;
+  up->t_join_timer               = 0;
+  up->join_state                 = 0;
+  up->state_transition           = pim_time_monotonic_sec();
+  up->channel_oil                = 0;
+
+  up->rpf.source_nexthop.interface                = 0;
+  up->rpf.source_nexthop.mrib_nexthop_addr.s_addr = PIM_NET_INADDR_ANY;
+  up->rpf.source_nexthop.mrib_metric_preference   = qpim_infinite_assert_metric.metric_preference;
+  up->rpf.source_nexthop.mrib_route_metric        = qpim_infinite_assert_metric.route_metric;
+  up->rpf.rpf_addr.s_addr                         = PIM_NET_INADDR_ANY;
+
+  pim_rpf_update(up, 0);
+
+  listnode_add(qpim_upstream_list, up);
+
+  return up;
+}
+
+struct pim_upstream *pim_upstream_find(struct in_addr source_addr,
+				       struct in_addr group_addr)
+{
+  struct listnode     *up_node;
+  struct pim_upstream *up;
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, up_node, up)) {
+    if (
+	(source_addr.s_addr == up->source_addr.s_addr) &&
+	(group_addr.s_addr == up->group_addr.s_addr)
+	) {
+      return up;
+    }
+  }
+
+  return 0;
+}
+
+struct pim_upstream *pim_upstream_add(struct in_addr source_addr,
+				      struct in_addr group_addr)
+{
+  struct pim_upstream *up;
+
+  up = pim_upstream_find(source_addr, group_addr);
+  if (up) {
+    ++up->ref_count;
+  }
+  else {
+    up = pim_upstream_new(source_addr, group_addr);
+  }
+
+  return up;
+}
+
+void pim_upstream_del(struct pim_upstream *up)
+{
+  --up->ref_count;
+
+  if (up->ref_count < 1) {
+    pim_upstream_delete(up);
+  }
+}
+
+/*
+  Evaluate JoinDesired(S,G):
+
+  JoinDesired(S,G) is true if there is a downstream (S,G) interface I
+  in the set:
+
+  joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+ */
+int pim_upstream_evaluate_join_desired(struct pim_upstream *up)
+{
+  struct listnode      *ifnode;
+  struct listnode      *chnode;
+  struct interface     *ifp;
+  struct pim_interface *pim_ifp;
+  struct pim_ifchannel *ch;
+  struct in_addr        me_ifaddr;
+
+  /* scan all interfaces */
+  for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+    pim_ifp = ifp->info;
+    if (!pim_ifp)
+      continue;
+
+    me_ifaddr = pim_ifp->primary_address;
+
+    /* scan per-interface (S,G) state */
+    for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, chnode, ch)) {
+      if (ch->upstream != up)
+	continue;
+
+      if (pim_ifchannel_isin_lost_assert(ch, me_ifaddr))
+	continue; /* keep searching */
+
+      if (pim_ifchannel_isin_joins(ch))
+	return 1; /* true */
+
+      if (pim_ifchannel_isin_pim_include(ch, me_ifaddr))
+	return 1; /* true */
+
+    } /* scan iface channel list */
+  } /* scan iflist */
+
+  return 0; /* false */
+}
+
+/*
+  Iface Set   Affected by changes in               See file
+  ---------   ------------------------------       -----------------------------------
+  joins       pim_ifchannel.ifjoin_state           pim_ifchannel.c
+  lost_assert pim_ifchannel.ifassert_winner        pim_ifchannel.c
+  lost_assert pim_ifchannel.ifassert_winner_metric pim_ifchannel.c
+  lost_assert RPF_interface(S)                     pim_rpf.c pim_rpf_update()
+  pim_include same as lost_assert                  same as lost_assert
+  pim_include pim_ifchannel.local_ifmembership     pim_ifchannel.c
+  pim_include pim_interface.pim_dr_addr            pim_neighbor.c pim_if_dr_election()
+*/
+void pim_upstream_update_join_desired(struct pim_upstream *up)
+{
+  int was_join_desired; /* boolean */
+  int is_join_desired; /* boolean */
+
+  was_join_desired = PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags);
+
+  is_join_desired = pim_upstream_evaluate_join_desired(up);
+  if (is_join_desired)
+    PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(up->flags);
+  else
+    PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags);
+
+  /* switched from false to true */
+  if (is_join_desired && !was_join_desired) {
+    zassert(up->join_state == PIM_UPSTREAM_NOTJOINED);
+    pim_upstream_switch(up, PIM_UPSTREAM_JOINED);
+    return;
+  }
+      
+  /* switched from true to false */
+  if (!is_join_desired && was_join_desired) {
+    zassert(up->join_state == PIM_UPSTREAM_JOINED);
+    pim_upstream_switch(up, PIM_UPSTREAM_NOTJOINED);
+    return;
+  }
+}
+
+/*
+  RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages
+  Transitions from Joined State
+  RPF'(S,G) GenID changes
+
+  The upstream (S,G) state machine remains in Joined state.  If the
+  Join Timer is set to expire in more than t_override seconds, reset
+  it so that it expires after t_override seconds.
+*/
+void pim_upstream_rpf_genid_changed(struct in_addr neigh_addr)
+{
+  struct listnode     *up_node;
+  struct listnode     *up_nextnode;
+  struct pim_upstream *up;
+
+  /*
+    Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr
+  */
+  for (ALL_LIST_ELEMENTS(qpim_upstream_list, up_node, up_nextnode, up)) {
+
+    if (PIM_DEBUG_PIM_TRACE) {
+      char neigh_str[100];
+      char src_str[100];
+      char grp_str[100];
+      char rpf_addr_str[100];
+      pim_inet4_dump("<neigh?>", neigh_addr, neigh_str, sizeof(neigh_str));
+      pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+      pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+      pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
+      zlog_debug("%s: matching neigh=%s against upstream (S,G)=(%s,%s) joined=%d rpf_addr=%s",
+		 __PRETTY_FUNCTION__,
+		 neigh_str, src_str, grp_str,
+		 up->join_state == PIM_UPSTREAM_JOINED,
+		 rpf_addr_str);
+    }
+
+    /* consider only (S,G) upstream in Joined state */
+    if (up->join_state != PIM_UPSTREAM_JOINED)
+      continue;
+
+    /* match RPF'(S,G)=neigh_addr */
+    if (up->rpf.rpf_addr.s_addr != neigh_addr.s_addr)
+      continue;
+
+    pim_upstream_join_timer_decrease_to_t_override("RPF'(S,G) GenID change",
+						   up, neigh_addr);
+  }
+}
diff --git a/pimd/pim_upstream.h b/pimd/pim_upstream.h
new file mode 100644
index 0000000..072f70f
--- /dev/null
+++ b/pimd/pim_upstream.h
@@ -0,0 +1,117 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_UPSTREAM_H
+#define PIM_UPSTREAM_H
+
+#include <zebra.h>
+
+#define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED         (1 << 0)
+#define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED (2 << 0)
+
+#define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED)
+#define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED)
+
+#define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED)
+#define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED)
+
+#define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED)
+#define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED)
+
+/*
+  RFC 4601:
+
+  Metric Preference
+    Preference value assigned to the unicast routing protocol that
+    provided the route to the multicast source or Rendezvous-Point.
+
+  Metric
+    The unicast routing table metric associated with the route used to
+    reach the multicast source or Rendezvous-Point.  The metric is in
+    units applicable to the unicast routing protocol used.
+*/
+struct pim_nexthop {
+  struct interface *interface;              /* RPF_interface(S) */
+  struct in_addr    mrib_nexthop_addr;      /* MRIB.next_hop(S) */
+  uint32_t          mrib_metric_preference; /* MRIB.pref(S) */
+  uint32_t          mrib_route_metric;      /* MRIB.metric(S) */
+};
+
+struct pim_rpf {
+  struct pim_nexthop source_nexthop;
+  struct in_addr     rpf_addr;               /* RPF'(S,G) */
+};
+
+enum pim_rpf_result {
+  PIM_RPF_OK = 0,
+  PIM_RPF_CHANGED,
+  PIM_RPF_FAILURE
+};
+
+enum pim_upstream_state {
+  PIM_UPSTREAM_NOTJOINED,
+  PIM_UPSTREAM_JOINED
+};
+
+/*
+  Upstream (S,G) channel in Joined state
+  
+  (S,G) in the "Not Joined" state is not represented
+  
+  See RFC 4601: 4.5.7.  Sending (S,G) Join/Prune Message
+*/
+struct pim_upstream {
+  struct in_addr           source_addr;  /* (S,G) source key */
+  struct in_addr           group_addr;   /* (S,G) group key */
+  uint32_t                 flags;
+  struct channel_oil      *channel_oil;
+
+  enum pim_upstream_state  join_state;
+  int                      ref_count;
+
+  struct pim_rpf           rpf;
+
+  struct thread           *t_join_timer;
+  int64_t                  state_transition; /* Record current state uptime */
+};
+
+void pim_upstream_free(struct pim_upstream *up);
+void pim_upstream_delete(struct pim_upstream *up);
+struct pim_upstream *pim_upstream_find(struct in_addr source_addr,
+				       struct in_addr group_addr);
+struct pim_upstream *pim_upstream_add(struct in_addr source_addr,
+				      struct in_addr group_addr);
+void pim_upstream_del(struct pim_upstream *up);
+
+int pim_upstream_evaluate_join_desired(struct pim_upstream *up);
+void pim_upstream_update_join_desired(struct pim_upstream *up);
+
+void pim_upstream_join_suppress(struct pim_upstream *up,
+				struct in_addr rpf_addr,
+				int holdtime);
+void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label,
+						    struct pim_upstream *up,
+						    struct in_addr rpf_addr);
+void pim_upstream_join_timer_restart(struct pim_upstream *up);
+void pim_upstream_rpf_genid_changed(struct in_addr neigh_addr);
+
+#endif /* PIM_UPSTREAM_H */
diff --git a/pimd/pim_util.c b/pimd/pim_util.c
new file mode 100644
index 0000000..5bc8d07
--- /dev/null
+++ b/pimd/pim_util.c
@@ -0,0 +1,132 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include "pim_util.h"
+
+/*
+  RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
+  
+  If QQIC < 128,  QQI = QQIC
+  If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3)
+  
+  0 1 2 3 4 5 6 7
+  +-+-+-+-+-+-+-+-+
+  |1| exp | mant  |
+  +-+-+-+-+-+-+-+-+
+  
+  Since exp=0..7 then (exp+3)=3..10, then QQI has
+  one of the following bit patterns:
+  
+  exp=0: QQI = 0000.0000.1MMM.M000
+  exp=1: QQI = 0000.0001.MMMM.0000
+  ...
+  exp=6: QQI = 001M.MMM0.0000.0000
+  exp=7: QQI = 01MM.MM00.0000.0000
+  --------- ---------
+  0x4  0x0  0x0  0x0
+*/
+uint8_t igmp_msg_encode16to8(uint16_t value)
+{
+  uint8_t code;
+
+  if (value < 128) {
+    code = value;
+  }
+  else {
+    uint16_t mask = 0x4000;
+    uint8_t  exp;
+    uint16_t mant;
+    for (exp = 7; exp > 0; --exp) {
+      if (mask & value)
+	break;
+      mask >>= 1;
+    }
+    mant = 0x000F & (value >> (exp + 3));
+    code = ((uint8_t) 1 << 7) | ((uint8_t) exp << 4) | (uint8_t) mant;
+  }
+
+  return code;
+}
+
+/*
+  RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
+  
+  If QQIC < 128,  QQI = QQIC
+  If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3)
+  
+  0 1 2 3 4 5 6 7
+  +-+-+-+-+-+-+-+-+
+  |1| exp | mant  |
+  +-+-+-+-+-+-+-+-+
+*/
+uint16_t igmp_msg_decode8to16(uint8_t code)
+{
+  uint16_t value;
+
+  if (code < 128) {
+    value = code;
+  }
+  else {
+    uint16_t mant = (code & 0x0F);
+    uint8_t  exp  = (code & 0x70) >> 4;
+    value = (mant | 0x10) << (exp + 3);
+  }
+
+  return value;
+}
+
+#ifndef PIM_USE_QUAGGA_INET_CHECKSUM
+/*
+  RFC 3376: 4.1.2. Checksum
+
+  The Checksum is the 16-bit one's complement of the one's complement
+  sum of the whole IGMP message (the entire IP payload).  For
+  computing the checksum, the Checksum field is set to zero.  When
+  receiving packets, the checksum MUST be verified before processing a
+  packet.  [RFC-1071]
+*/
+uint16_t pim_inet_checksum(const char *buf, int size)
+{
+  const uint16_t *ptr;
+  uint32_t        sum;
+  uint16_t        checksum;
+
+  ptr = (const uint16_t *) buf;
+  sum = 0;
+  while (size > 1) {
+    sum += *ptr;
+    ++ptr;
+    size -= 2;
+  }
+
+  /* Add left-over byte, if any */
+  if (size > 0)
+    sum += (uint16_t) *(const uint8_t *) ptr;
+
+  /* Fold 32-bit sum to 16 bits */
+  sum = (sum & 0xffff) + (sum >> 16);
+
+  checksum = ~sum;
+
+  return checksum;
+}
+#endif /* PIM_USE_QUAGGA_INET_CHECKSUM */
diff --git a/pimd/pim_util.h b/pimd/pim_util.h
new file mode 100644
index 0000000..0fcdce0
--- /dev/null
+++ b/pimd/pim_util.h
@@ -0,0 +1,41 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_UTIL_H
+#define PIM_UTIL_H
+
+#include <stdint.h>
+
+#include <zebra.h>
+
+#include "checksum.h"
+
+uint8_t igmp_msg_encode16to8(uint16_t value);
+uint16_t igmp_msg_decode8to16(uint8_t code);
+
+#ifdef PIM_USE_QUAGGA_INET_CHECKSUM
+#define pim_inet_checksum(buf,size) in_cksum(buf,size)
+#else
+uint16_t pim_inet_checksum(const char *buf, int size);
+#endif /* PIM_USE_QUAGGA_INET_CHECKSUM */
+
+#endif /* PIM_UTIL_H */
diff --git a/pimd/pim_version.c b/pimd/pim_version.c
new file mode 100644
index 0000000..fe7e563
--- /dev/null
+++ b/pimd/pim_version.c
@@ -0,0 +1,25 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include "pim_version.h"
+
+const char * const PIMD_VERSION = PIMD_VERSION_STR;
diff --git a/pimd/pim_version.h b/pimd/pim_version.h
new file mode 100644
index 0000000..d72da94
--- /dev/null
+++ b/pimd/pim_version.h
@@ -0,0 +1,30 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_VERSION_H
+#define PIM_VERSION_H
+
+#define PIMD_VERSION_STR "0.149"
+
+const char * const PIMD_VERSION;
+
+#endif /* PIM_VERSION_H */
diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c
new file mode 100644
index 0000000..7d1401a
--- /dev/null
+++ b/pimd/pim_vty.c
@@ -0,0 +1,128 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "if.h"
+#include "linklist.h"
+
+#include "pimd.h"
+#include "pim_vty.h"
+#include "pim_iface.h"
+#include "pim_cmd.h"
+
+int pim_debug_config_write(struct vty *vty)
+{
+  int writes = 0;
+
+  if (PIM_DEBUG_IGMP_EVENTS) {
+    vty_out(vty, "debug igmp events%s", VTY_NEWLINE);
+    ++writes;
+  }
+  if (PIM_DEBUG_IGMP_PACKETS) {
+    vty_out(vty, "debug igmp packets%s", VTY_NEWLINE);
+    ++writes;
+  }
+  if (PIM_DEBUG_IGMP_TRACE) {
+    vty_out(vty, "debug igmp trace%s", VTY_NEWLINE);
+    ++writes;
+  }
+
+  if (PIM_DEBUG_PIM_EVENTS) {
+    vty_out(vty, "debug pim events%s", VTY_NEWLINE);
+    ++writes;
+  }
+  if (PIM_DEBUG_PIM_PACKETS) {
+    vty_out(vty, "debug pim packets%s", VTY_NEWLINE);
+    ++writes;
+  }
+  if (PIM_DEBUG_PIM_TRACE) {
+    vty_out(vty, "debug pim trace%s", VTY_NEWLINE);
+    ++writes;
+  }
+
+  if (PIM_DEBUG_ZEBRA) {
+    vty_out(vty, "debug pim zebra%s", VTY_NEWLINE);
+    ++writes;
+  }
+
+  return writes;
+}
+
+int pim_global_config_write(struct vty *vty)
+{
+  int writes = 0;
+
+  if (PIM_MROUTE_IS_ENABLED) {
+    vty_out(vty, "%s%s", PIM_CMD_IP_MULTICAST_ROUTING, VTY_NEWLINE);
+    ++writes;
+  }
+
+  return writes;
+}
+
+int pim_interface_config_write(struct vty *vty)
+{
+  int writes = 0;
+  struct listnode *node;
+  struct interface *ifp;
+
+  for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+
+    /* IF name */
+    vty_out(vty, "interface %s%s", ifp->name, VTY_NEWLINE);
+    writes++;
+
+    if (ifp->info) {
+      struct pim_interface *pim_ifp = ifp->info;
+
+      /* IF ip pim ssm */
+      if (PIM_IF_TEST_PIM(pim_ifp->options)) {
+	vty_out(vty, " ip pim ssm%s", VTY_NEWLINE);
+	writes++;
+      }
+
+      /* IF ip igmp */
+      if (PIM_IF_TEST_IGMP(pim_ifp->options)) {
+	vty_out(vty, " ip igmp%s", VTY_NEWLINE);
+	writes++;
+      }
+
+      /* IF ip igmp query-interval */
+      vty_out(vty, " %s %d%s",
+	       PIM_CMD_IP_IGMP_QUERY_INTERVAL,
+	       pim_ifp->igmp_default_query_interval,
+	       VTY_NEWLINE);
+      writes++;
+
+      /* IF ip igmp query-max-response-time */
+      vty_out(vty, " %s %d%s",
+	       PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC,
+	       pim_ifp->igmp_query_max_response_time_dsec,
+	       VTY_NEWLINE);
+      writes++;
+    }
+    vty_out(vty, "!%s", VTY_NEWLINE);
+  }
+
+  return writes;
+}
diff --git a/pimd/pim_vty.h b/pimd/pim_vty.h
new file mode 100644
index 0000000..904ee55
--- /dev/null
+++ b/pimd/pim_vty.h
@@ -0,0 +1,32 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_VTY_H
+#define PIM_VTY_H
+
+#include "vty.h"
+
+int pim_debug_config_write(struct vty *vty);
+int pim_global_config_write(struct vty *vty);
+int pim_interface_config_write(struct vty *vty);
+
+#endif /* PIM_VTY_H */
diff --git a/pimd/pim_zebra.c b/pimd/pim_zebra.c
new file mode 100644
index 0000000..8324417
--- /dev/null
+++ b/pimd/pim_zebra.c
@@ -0,0 +1,1158 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "zebra/rib.h"
+
+#include "log.h"
+#include "prefix.h"
+#include "zclient.h"
+#include "stream.h"
+#include "network.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_zebra.h"
+#include "pim_iface.h"
+#include "pim_str.h"
+#include "pim_oil.h"
+#include "pim_rpf.h"
+#include "pim_time.h"
+#include "pim_join.h"
+#include "pim_zlookup.h"
+#include "pim_ifchannel.h"
+
+#undef PIM_DEBUG_IFADDR_DUMP
+
+static int fib_lookup_if_vif_index(struct in_addr addr);
+static int del_oif(struct channel_oil *channel_oil,
+		   struct interface *oif,
+		   uint32_t proto_mask);
+
+/* Router-id update message from zebra. */
+static int pim_router_id_update_zebra(int command, struct zclient *zclient,
+				      zebra_size_t length)
+{
+  struct prefix router_id;
+
+  /* FIXME: actually use router_id for anything ? */
+  zebra_router_id_update_read(zclient->ibuf, &router_id);
+
+  return 0;
+}
+
+static int pim_zebra_if_add(int command, struct zclient *zclient,
+			    zebra_size_t length)
+{
+  struct interface *ifp;
+
+  /*
+    zebra api adds/dels interfaces using the same call
+    interface_add_read below, see comments in lib/zclient.c
+  */
+  ifp = zebra_interface_add_read(zclient->ibuf);
+  if (!ifp)
+    return 0;
+
+  if (PIM_DEBUG_ZEBRA) {
+    zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+	       __PRETTY_FUNCTION__,
+	       ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+	       ifp->mtu, if_is_operative(ifp));
+  }
+
+  if (if_is_operative(ifp))
+    pim_if_addr_add_all(ifp);
+
+  return 0;
+}
+
+static int pim_zebra_if_del(int command, struct zclient *zclient,
+			    zebra_size_t length)
+{
+  struct interface *ifp;
+
+  /*
+    zebra api adds/dels interfaces using the same call
+    interface_add_read below, see comments in lib/zclient.c
+  */
+  ifp = zebra_interface_add_read(zclient->ibuf);
+  if (!ifp)
+    return 0;
+
+  if (PIM_DEBUG_ZEBRA) {
+    zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+	       __PRETTY_FUNCTION__,
+	       ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+	       ifp->mtu, if_is_operative(ifp));
+  }
+
+  if (!if_is_operative(ifp))
+    pim_if_addr_del_all(ifp);
+
+  return 0;
+}
+
+static int pim_zebra_if_state_up(int command, struct zclient *zclient,
+				 zebra_size_t length)
+{
+  struct interface *ifp;
+
+  /*
+    zebra api notifies interface up/down events by using the same call
+    interface_add_read below, see comments in lib/zclient.c
+  */
+  ifp = zebra_interface_state_read(zclient->ibuf);
+  if (!ifp)
+    return 0;
+
+  if (PIM_DEBUG_ZEBRA) {
+    zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+	       __PRETTY_FUNCTION__,
+	       ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+	       ifp->mtu, if_is_operative(ifp));
+  }
+
+  if (if_is_operative(ifp))
+    pim_if_addr_add_all(ifp);
+
+  return 0;
+}
+
+static int pim_zebra_if_state_down(int command, struct zclient *zclient,
+				   zebra_size_t length)
+{
+  struct interface *ifp;
+
+  /*
+    zebra api notifies interface up/down events by using the same call
+    interface_add_read below, see comments in lib/zclient.c
+  */
+  ifp = zebra_interface_state_read(zclient->ibuf);
+  if (!ifp)
+    return 0;
+
+  if (PIM_DEBUG_ZEBRA) {
+    zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+	       __PRETTY_FUNCTION__,
+	       ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+	       ifp->mtu, if_is_operative(ifp));
+  }
+
+  if (!if_is_operative(ifp))
+    pim_if_addr_del_all(ifp);
+
+  return 0;
+}
+
+#ifdef PIM_DEBUG_IFADDR_DUMP
+static void dump_if_address(struct interface *ifp)
+{
+  struct connected *ifc;
+  struct listnode *node;
+
+  zlog_debug("%s %s: interface %s addresses:",
+	     __FILE__, __PRETTY_FUNCTION__,
+	     ifp->name);
+  
+  for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) {
+    struct prefix *p = ifc->address;
+    
+    if (p->family != AF_INET)
+      continue;
+    
+    zlog_debug("%s %s: interface %s address %s",
+	       __FILE__, __PRETTY_FUNCTION__,
+	       ifp->name,
+	       inet_ntoa(p->u.prefix4));
+  }
+}
+#endif
+
+static int pim_zebra_if_address_add(int command, struct zclient *zclient,
+				    zebra_size_t length)
+{
+  struct connected *c;
+  struct prefix *p;
+
+  zassert(command == ZEBRA_INTERFACE_ADDRESS_ADD);
+
+  /*
+    zebra api notifies address adds/dels events by using the same call
+    interface_add_read below, see comments in lib/zclient.c
+
+    zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, ...)
+    will add address to interface list by calling
+    connected_add_by_prefix()
+  */
+  c = zebra_interface_address_read(command, zclient->ibuf);
+  if (!c)
+    return 0;
+
+  p = c->address;
+  if (p->family != AF_INET)
+    return 0;
+  
+  if (PIM_DEBUG_ZEBRA) {
+    char buf[BUFSIZ];
+    prefix2str(p, buf, BUFSIZ);
+    zlog_debug("%s: %s connected IP address %s",
+	       __PRETTY_FUNCTION__,
+	       c->ifp->name,
+	       buf);
+    
+#ifdef PIM_DEBUG_IFADDR_DUMP
+    dump_if_address(c->ifp);
+#endif
+  }
+
+  pim_if_addr_add(c);
+
+  return 0;
+}
+
+static int pim_zebra_if_address_del(int command, struct zclient *client,
+				    zebra_size_t length)
+{
+  struct connected *c;
+  struct prefix *p;
+
+  zassert(command == ZEBRA_INTERFACE_ADDRESS_DELETE);
+
+  /*
+    zebra api notifies address adds/dels events by using the same call
+    interface_add_read below, see comments in lib/zclient.c
+
+    zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, ...)
+    will remove address from interface list by calling
+    connected_delete_by_prefix()
+  */
+  c = zebra_interface_address_read(command, client->ibuf);
+  if (!c)
+    return 0;
+  
+  p = c->address;
+  if (p->family != AF_INET)
+    return 0;
+  
+  if (PIM_DEBUG_ZEBRA) {
+    char buf[BUFSIZ];
+    prefix2str(p, buf, BUFSIZ);
+    zlog_debug("%s: %s disconnected IP address %s",
+	       __PRETTY_FUNCTION__,
+	       c->ifp->name,
+	       buf);
+    
+#ifdef PIM_DEBUG_IFADDR_DUMP
+    dump_if_address(c->ifp);
+#endif
+  }
+  
+  pim_if_addr_del(c);
+  
+  return 0;
+}
+
+static void scan_upstream_rpf_cache()
+{
+  struct listnode     *up_node;
+  struct listnode     *up_nextnode;
+  struct pim_upstream *up;
+
+  for (ALL_LIST_ELEMENTS(qpim_upstream_list, up_node, up_nextnode, up)) {
+    struct in_addr      old_rpf_addr;
+    enum pim_rpf_result rpf_result;
+
+    rpf_result = pim_rpf_update(up, &old_rpf_addr);
+    if (rpf_result == PIM_RPF_FAILURE)
+      continue;
+
+    if (rpf_result == PIM_RPF_CHANGED) {
+      
+      if (up->join_state == PIM_UPSTREAM_JOINED) {
+	
+	/*
+	  RFC 4601: 4.5.7.  Sending (S,G) Join/Prune Messages
+	  
+	  Transitions from Joined State
+	  
+	  RPF'(S,G) changes not due to an Assert
+	  
+	  The upstream (S,G) state machine remains in Joined
+	  state. Send Join(S,G) to the new upstream neighbor, which is
+	  the new value of RPF'(S,G).  Send Prune(S,G) to the old
+	  upstream neighbor, which is the old value of RPF'(S,G).  Set
+	  the Join Timer (JT) to expire after t_periodic seconds.
+	*/
+
+    
+	/* send Prune(S,G) to the old upstream neighbor */
+	pim_joinprune_send(up->rpf.source_nexthop.interface,
+			   old_rpf_addr,
+			   up->source_addr,
+			   up->group_addr,
+			   0 /* prune */);
+	
+	/* send Join(S,G) to the current upstream neighbor */
+	pim_joinprune_send(up->rpf.source_nexthop.interface,
+			   up->rpf.rpf_addr,
+			   up->source_addr,
+			   up->group_addr,
+			   1 /* join */);
+
+	pim_upstream_join_timer_restart(up);
+      } /* up->join_state == PIM_UPSTREAM_JOINED */
+
+      /* FIXME can join_desired actually be changed by pim_rpf_update()
+	 returning PIM_RPF_CHANGED ? */
+      pim_upstream_update_join_desired(up);
+
+    } /* PIM_RPF_CHANGED */
+
+  } /* for (qpim_upstream_list) */
+  
+}
+
+static void scan_oil()
+{
+  struct listnode    *node;
+  struct listnode    *nextnode;
+  struct channel_oil *c_oil;
+
+  for (ALL_LIST_ELEMENTS(qpim_channel_oil_list, node, nextnode, c_oil)) {
+    int old_vif_index;
+    int input_iface_vif_index = fib_lookup_if_vif_index(c_oil->oil.mfcc_origin);
+    if (input_iface_vif_index < 1) {
+      char source_str[100];
+      char group_str[100];
+      pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+      pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+      zlog_warn("%s %s: could not find input interface for (S,G)=(%s,%s)",
+		__FILE__, __PRETTY_FUNCTION__,
+		source_str, group_str);
+      continue;
+    }
+
+    if (input_iface_vif_index == c_oil->oil.mfcc_parent) {
+      /* RPF unchanged */
+      continue;
+    }
+
+    if (PIM_DEBUG_ZEBRA) {
+      struct interface *old_iif = pim_if_find_by_vif_index(c_oil->oil.mfcc_parent);
+      struct interface *new_iif = pim_if_find_by_vif_index(input_iface_vif_index);
+      char source_str[100];
+      char group_str[100];
+      pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+      pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+      zlog_debug("%s %s: (S,G)=(%s,%s) input interface changed from %s vif_index=%d to %s vif_index=%d",
+		 __FILE__, __PRETTY_FUNCTION__,
+		 source_str, group_str,
+		 old_iif ? old_iif->name : "<old_iif?>", c_oil->oil.mfcc_parent,
+		 new_iif ? new_iif->name : "<new_iif?>", input_iface_vif_index);
+    }
+
+    /* new iif loops to existing oif ? */
+    if (c_oil->oil.mfcc_ttls[input_iface_vif_index]) {
+      struct interface *new_iif = pim_if_find_by_vif_index(input_iface_vif_index);
+
+      if (PIM_DEBUG_ZEBRA) {
+	char source_str[100];
+	char group_str[100];
+	pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+	pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+	zlog_debug("%s %s: (S,G)=(%s,%s) new iif loops to existing oif: %s vif_index=%d",
+		   __FILE__, __PRETTY_FUNCTION__,
+		   source_str, group_str,
+		   new_iif ? new_iif->name : "<new_iif?>", input_iface_vif_index);
+      }
+
+      del_oif(c_oil, new_iif, PIM_OIF_FLAG_PROTO_ANY);
+    }
+
+    /* update iif vif_index */
+    old_vif_index = c_oil->oil.mfcc_parent;
+    c_oil->oil.mfcc_parent = input_iface_vif_index;
+
+    /* update kernel multicast forwarding cache (MFC) */
+    if (pim_mroute_add(&c_oil->oil)) {
+      /* just log warning */
+      struct interface *old_iif = pim_if_find_by_vif_index(old_vif_index);
+      struct interface *new_iif = pim_if_find_by_vif_index(input_iface_vif_index);
+      char source_str[100];
+      char group_str[100]; 
+      pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+      pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+      zlog_warn("%s %s: (S,G)=(%s,%s) failure updating input interface from %s vif_index=%d to %s vif_index=%d",
+		 __FILE__, __PRETTY_FUNCTION__,
+		 source_str, group_str,
+		 old_iif ? old_iif->name : "<old_iif?>", c_oil->oil.mfcc_parent,
+		 new_iif ? new_iif->name : "<new_iif?>", input_iface_vif_index);
+      continue;
+    }
+
+  } /* for (qpim_channel_oil_list) */
+}
+
+static int on_rpf_cache_refresh(struct thread *t)
+{
+  zassert(t);
+  zassert(qpim_rpf_cache_refresher);
+
+  qpim_rpf_cache_refresher = 0;
+
+  /* update PIM protocol state */
+  scan_upstream_rpf_cache();
+
+  /* update kernel multicast forwarding cache (MFC) */
+  scan_oil();
+
+  return 0;
+}
+
+static void sched_rpf_cache_refresh()
+{
+  if (qpim_rpf_cache_refresher)
+    return;
+
+  if (PIM_DEBUG_ZEBRA) {
+    zlog_debug("%s: triggering %ld msec timer",
+               __PRETTY_FUNCTION__,
+               qpim_rpf_cache_refresh_delay_msec);
+  }
+
+  THREAD_TIMER_MSEC_ON(master, qpim_rpf_cache_refresher,
+                       on_rpf_cache_refresh,
+                       0, qpim_rpf_cache_refresh_delay_msec);
+}
+
+static int redist_read_ipv4_route(int command, struct zclient *zclient,
+				  zebra_size_t length)
+{
+  struct stream *s;
+  struct zapi_ipv4 api;
+  unsigned long ifindex;
+  struct in_addr nexthop;
+  struct prefix_ipv4 p;
+  int min_len = 4;
+
+  if (length < min_len) {
+    zlog_warn("%s %s: short buffer: length=%d min=%d",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      length, min_len);
+    return -1;
+  }
+
+  s = zclient->ibuf;
+  ifindex = 0;
+  nexthop.s_addr = 0;
+
+  /* Type, flags, message. */
+  api.type = stream_getc(s);
+  api.flags = stream_getc(s);
+  api.message = stream_getc(s);
+
+  /* IPv4 prefix length. */
+  memset(&p, 0, sizeof(struct prefix_ipv4));
+  p.family = AF_INET;
+  p.prefixlen = stream_getc(s);
+
+  min_len +=
+    PSIZE(p.prefixlen) +
+    CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP) ? 5 : 0 +
+    CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX) ? 5 : 0 +
+    CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? 1 : 0 +
+    CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? 4 : 0;
+
+  if (PIM_DEBUG_ZEBRA) {
+    zlog_debug("%s %s: length=%d min_len=%d flags=%s%s%s%s",
+	       __FILE__, __PRETTY_FUNCTION__,
+	       length, min_len,
+	       CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP) ? "nh" : "",
+	       CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX) ? " ifi" : "",
+	       CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? " dist" : "",
+	       CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? " metr" : "");
+  }
+
+  if (length < min_len) {
+    zlog_warn("%s %s: short buffer: length=%d min_len=%d flags=%s%s%s%s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      length, min_len,
+	      CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP) ? "nh" : "",
+	      CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX) ? " ifi" : "",
+	      CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? " dist" : "",
+	      CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? " metr" : "");
+    return -1;
+  }
+
+  /* IPv4 prefix. */
+  stream_get(&p.prefix, s, PSIZE(p.prefixlen));
+
+  /* Nexthop, ifindex, distance, metric. */
+  if (CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP)) {
+    api.nexthop_num = stream_getc(s);
+    nexthop.s_addr = stream_get_ipv4(s);
+  }
+  if (CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX)) {
+    api.ifindex_num = stream_getc(s);
+    ifindex = stream_getl(s);
+  }
+
+  api.distance = CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ?
+    api.distance = stream_getc(s) :
+    0;
+
+  api.metric = CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ?
+    stream_getl(s) :
+    0;
+
+  switch (command) {
+  case ZEBRA_IPV4_ROUTE_ADD:
+    if (PIM_DEBUG_ZEBRA) {
+      char buf[2][INET_ADDRSTRLEN];
+      zlog_debug("%s: add %s %s/%d "
+		 "nexthop %s ifindex %ld metric%s %u distance%s %u",
+		 __PRETTY_FUNCTION__,
+		 zebra_route_string(api.type),
+		 inet_ntop(AF_INET, &p.prefix, buf[0], sizeof(buf[0])),
+		 p.prefixlen,
+		 inet_ntop(AF_INET, &nexthop, buf[1], sizeof(buf[1])),
+		 ifindex,
+		 CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? "-recv" : "-miss",
+		 api.metric,
+		 CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? "-recv" : "-miss",
+		 api.distance);
+    }
+    break;
+  case ZEBRA_IPV4_ROUTE_DELETE:
+    if (PIM_DEBUG_ZEBRA) {
+      char buf[2][INET_ADDRSTRLEN];
+      zlog_debug("%s: delete %s %s/%d "
+		 "nexthop %s ifindex %ld metric%s %u distance%s %u",
+		 __PRETTY_FUNCTION__,
+		 zebra_route_string(api.type),
+		 inet_ntop(AF_INET, &p.prefix, buf[0], sizeof(buf[0])),
+		 p.prefixlen,
+		 inet_ntop(AF_INET, &nexthop, buf[1], sizeof(buf[1])),
+		 ifindex,
+		 CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? "-recv" : "-miss",
+		 api.metric,
+		 CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? "-recv" : "-miss",
+		 api.distance);
+    }
+    break;
+  default:
+    zlog_warn("%s: unknown command=%d", __PRETTY_FUNCTION__, command);
+    return -1;
+  }
+
+  sched_rpf_cache_refresh();
+
+  return 0;
+}
+
+void pim_zebra_init()
+{
+  struct zclient *zclient;
+  int i;
+
+#ifdef HAVE_TCP_ZEBRA
+  zlog_notice("zclient update contacting ZEBRA daemon at socket TCP %s,%d", "127.0.0.1", ZEBRA_PORT);
+#else
+  zlog_notice("zclient update contacting ZEBRA daemon at socket UNIX %s", ZEBRA_SERV_PATH);
+#endif
+
+  /* Socket for receiving updates from Zebra daemon */
+  zclient = zclient_new();
+
+  zclient->router_id_update         = pim_router_id_update_zebra;
+  zclient->interface_add            = pim_zebra_if_add;
+  zclient->interface_delete         = pim_zebra_if_del;
+  zclient->interface_up             = pim_zebra_if_state_up;
+  zclient->interface_down           = pim_zebra_if_state_down;
+  zclient->interface_address_add    = pim_zebra_if_address_add;
+  zclient->interface_address_delete = pim_zebra_if_address_del;
+  zclient->ipv4_route_add           = redist_read_ipv4_route;
+  zclient->ipv4_route_delete        = redist_read_ipv4_route;
+
+  zclient_init(zclient, ZEBRA_ROUTE_PIM);
+  zlog_info("zclient_init cleared redistribution request");
+
+  zassert(zclient->redist_default == ZEBRA_ROUTE_PIM);
+
+  /* Request all redistribution */
+  for (i = 0; i < ZEBRA_ROUTE_MAX; i++) {
+    if (i == zclient->redist_default)
+      continue;
+    zclient->redist[i] = 1;
+    zlog_info("%s: requesting redistribution for %s (%i)", 
+	      __PRETTY_FUNCTION__, zebra_route_string(i), i);
+  }
+
+  /* Request default information */
+  zclient->default_information = 1;
+  zlog_info("%s: requesting default information redistribution",
+	    __PRETTY_FUNCTION__);
+
+  zlog_notice("%s: zclient update socket initialized",
+	      __PRETTY_FUNCTION__);
+
+  zassert(!qpim_zclient_lookup);
+  qpim_zclient_lookup = zclient_lookup_new();
+  zassert(qpim_zclient_lookup);
+}
+
+void igmp_anysource_forward_start(struct igmp_group *group)
+{
+  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+  zassert(group->group_filtermode_isexcl);
+  zassert(listcount(group->group_source_list) < 1);
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    zlog_debug("%s %s: UNIMPLEMENTED",
+	       __FILE__, __PRETTY_FUNCTION__);
+  }
+}
+
+void igmp_anysource_forward_stop(struct igmp_group *group)
+{
+  /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+  zassert((!group->group_filtermode_isexcl) || (listcount(group->group_source_list) > 0));
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    zlog_debug("%s %s: UNIMPLEMENTED",
+	       __FILE__, __PRETTY_FUNCTION__);
+  }
+}
+
+static int fib_lookup_if_vif_index(struct in_addr addr)
+{
+  struct pim_zlookup_nexthop nexthop_tab[PIM_NEXTHOP_IFINDEX_TAB_SIZE];
+  int num_ifindex;
+  int vif_index;
+  int first_ifindex;
+
+  num_ifindex = zclient_lookup_nexthop(qpim_zclient_lookup, nexthop_tab,
+				       PIM_NEXTHOP_IFINDEX_TAB_SIZE, addr,
+				       PIM_NEXTHOP_LOOKUP_MAX);
+  if (num_ifindex < 1) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_warn("%s %s: could not find nexthop ifindex for address %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      addr_str);
+    return -1;
+  }
+  
+  first_ifindex = nexthop_tab[0].ifindex;
+  
+  if (num_ifindex > 1) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_debug("%s %s: FIXME ignoring multiple nexthop ifindex'es num_ifindex=%d for address %s (using only ifindex=%d)",
+	       __FILE__, __PRETTY_FUNCTION__,
+	       num_ifindex, addr_str, first_ifindex);
+    /* debug warning only, do not return */
+  }
+  
+  if (PIM_DEBUG_ZEBRA) {
+    char addr_str[100];
+    pim_inet4_dump("<ifaddr?>", addr, addr_str, sizeof(addr_str));
+    zlog_debug("%s %s: found nexthop ifindex=%d (interface %s) for address %s",
+	       __FILE__, __PRETTY_FUNCTION__,
+	       first_ifindex, ifindex2ifname(first_ifindex), addr_str);
+  }
+
+  vif_index = pim_if_find_vifindex_by_ifindex(first_ifindex);
+
+  if (vif_index < 1) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_warn("%s %s: low vif_index=%d < 1 nexthop for address %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      vif_index, addr_str);
+    return -2;
+  }
+
+  zassert(qpim_mroute_oif_highest_vif_index < MAXVIFS);
+
+  if (vif_index > qpim_mroute_oif_highest_vif_index) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_warn("%s %s: high vif_index=%d > highest_vif_index=%d nexthop for address %s",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      vif_index, qpim_mroute_oif_highest_vif_index, addr_str);
+
+    zlog_warn("%s %s: pim disabled on interface %s vif_index=%d ?",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      ifindex2ifname(vif_index),
+	      vif_index);
+
+    return -3;
+  }
+
+  return vif_index;
+}
+
+static int add_oif(struct channel_oil *channel_oil,
+		   struct interface *oif,
+		   uint32_t proto_mask)
+{
+  struct pim_interface *pim_ifp;
+  int old_ttl;
+
+  zassert(channel_oil);
+
+  pim_ifp = oif->info;
+
+  if (pim_ifp->mroute_vif_index < 1) {
+    zlog_warn("%s %s: interface %s vif_index=%d < 1",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      oif->name, pim_ifp->mroute_vif_index);
+    return -1;
+  }
+
+#ifdef PIM_ENFORCE_LOOPFREE_MFC
+  /*
+    Prevent creating MFC entry with OIF=IIF.
+
+    This is a protection against implementation mistakes.
+
+    PIM protocol implicitely ensures loopfree multicast topology.
+
+    IGMP must be protected against adding looped MFC entries created
+    by both source and receiver attached to the same interface. See
+    TODO T22.
+  */
+  if (pim_ifp->mroute_vif_index == channel_oil->oil.mfcc_parent) {
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    zlog_warn("%s %s: refusing protocol mask %u request for IIF=OIF=%s (vif_index=%d) for channel (S,G)=(%s,%s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      proto_mask, oif->name, pim_ifp->mroute_vif_index,
+	      source_str, group_str);
+    return -2;
+  }
+#endif
+
+  zassert(qpim_mroute_oif_highest_vif_index < MAXVIFS);
+  zassert(pim_ifp->mroute_vif_index <= qpim_mroute_oif_highest_vif_index);
+
+  /* Prevent single protocol from subscribing same interface to
+     channel (S,G) multiple times */
+  if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask) {
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    zlog_warn("%s %s: existing protocol mask %u requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      proto_mask, oif->name, pim_ifp->mroute_vif_index,
+	      channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+	      source_str, group_str);
+    return -3;
+  }
+
+  /* Allow other protocol to request subscription of same interface to
+     channel (S,G) multiple times, by silently ignoring further
+     requests */
+  if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & PIM_OIF_FLAG_PROTO_ANY) {
+
+    /* Check the OIF really exists before returning, and only log
+       warning otherwise */
+    if (channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] < 1) {
+      char group_str[100]; 
+      char source_str[100];
+      pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+      pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+      zlog_warn("%s %s: new protocol mask %u requested nonexistent OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)",
+		__FILE__, __PRETTY_FUNCTION__,
+		proto_mask, oif->name, pim_ifp->mroute_vif_index,
+		channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+		source_str, group_str);
+    }
+
+    return 0;
+  }
+
+  old_ttl = channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index];
+
+  if (old_ttl > 0) {
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    zlog_warn("%s %s: interface %s (vif_index=%d) is existing output for channel (S,G)=(%s,%s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      oif->name, pim_ifp->mroute_vif_index,
+	      source_str, group_str);
+    return -4;
+  }
+
+  channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = PIM_MROUTE_MIN_TTL;
+
+  if (pim_mroute_add(&channel_oil->oil)) {
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    zlog_warn("%s %s: could not add output interface %s (vif_index=%d) for channel (S,G)=(%s,%s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      oif->name, pim_ifp->mroute_vif_index,
+	      source_str, group_str);
+
+    channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = old_ttl;
+    return -5;
+  }
+
+  channel_oil->oif_creation[pim_ifp->mroute_vif_index] = pim_time_monotonic_sec();
+  ++channel_oil->oil_size;
+  channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask;
+
+  return 0;
+}
+
+static int del_oif(struct channel_oil *channel_oil,
+		   struct interface *oif,
+		   uint32_t proto_mask)
+{
+  struct pim_interface *pim_ifp;
+  int old_ttl;
+
+  zassert(channel_oil);
+
+  pim_ifp = oif->info;
+
+  zassert(pim_ifp->mroute_vif_index >= 1);
+  zassert(qpim_mroute_oif_highest_vif_index < MAXVIFS);
+  zassert(pim_ifp->mroute_vif_index <= qpim_mroute_oif_highest_vif_index);
+
+  /* Prevent single protocol from unsubscribing same interface from
+     channel (S,G) multiple times */
+  if (!(channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask)) {
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    zlog_warn("%s %s: nonexistent protocol mask %u removed OIF %s (vif_index=%d, min_ttl=%d) from channel (S,G)=(%s,%s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      proto_mask, oif->name, pim_ifp->mroute_vif_index,
+	      channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+	      source_str, group_str);
+    return -2;
+  }
+
+  /* Mark that protocol is no longer interested in this OIF */
+  channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= ~proto_mask;
+
+  /* Allow multiple protocols to unsubscribe same interface from
+     channel (S,G) multiple times, by silently ignoring requests while
+     there is at least one protocol interested in the channel */
+  if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & PIM_OIF_FLAG_PROTO_ANY) {
+
+    /* Check the OIF keeps existing before returning, and only log
+       warning otherwise */
+    if (channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] < 1) {
+      char group_str[100]; 
+      char source_str[100];
+      pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+      pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+      zlog_warn("%s %s: protocol mask %u removing nonexistent OIF %s (vif_index=%d, min_ttl=%d) from channel (S,G)=(%s,%s)",
+		__FILE__, __PRETTY_FUNCTION__,
+		proto_mask, oif->name, pim_ifp->mroute_vif_index,
+		channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+		source_str, group_str);
+    }
+
+    return 0;
+  }
+
+  old_ttl = channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index];
+
+  if (old_ttl < 1) {
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    zlog_warn("%s %s: interface %s (vif_index=%d) is not output for channel (S,G)=(%s,%s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      oif->name, pim_ifp->mroute_vif_index,
+	      source_str, group_str);
+    return -3;
+  }
+
+  channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = 0;
+
+  if (pim_mroute_add(&channel_oil->oil)) {
+    char group_str[100]; 
+    char source_str[100];
+    pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+    zlog_warn("%s %s: could not remove output interface %s (vif_index=%d) from channel (S,G)=(%s,%s)",
+	      __FILE__, __PRETTY_FUNCTION__,
+	      oif->name, pim_ifp->mroute_vif_index,
+	      source_str, group_str);
+    
+    channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = old_ttl;
+    return -4;
+  }
+
+  --channel_oil->oil_size;
+
+  if (channel_oil->oil_size < 1) {
+    if (pim_mroute_del(&channel_oil->oil)) {
+      /* just log a warning in case of failure */
+      char group_str[100]; 
+      char source_str[100];
+      pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+      pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+      zlog_warn("%s %s: failure removing OIL for channel (S,G)=(%s,%s)",
+		__FILE__, __PRETTY_FUNCTION__,
+		source_str, group_str);
+    }
+  }
+
+  return 0;
+}
+
+void igmp_source_forward_start(struct igmp_source *source)
+{
+  struct igmp_group *group;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char source_str[100];
+    char group_str[100]; 
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<group?>", source->source_group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: (S,G)=(%s,%s) igmp_sock=%d oif=%s fwd=%d",
+	       __PRETTY_FUNCTION__,
+	       source_str, group_str,
+	       source->source_group->group_igmp_sock->fd,
+	       source->source_group->group_igmp_sock->interface->name,
+	       IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+  }
+
+  /* Prevent IGMP interface from installing multicast route multiple
+     times */
+  if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+    return;
+  }
+
+  group = source->source_group;
+
+  if (!source->source_channel_oil) {
+    struct pim_interface *pim_oif;
+    int input_iface_vif_index = fib_lookup_if_vif_index(source->source_addr);
+    if (input_iface_vif_index < 1) {
+      char source_str[100];
+      pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+      zlog_warn("%s %s: could not find input interface for source %s",
+		__FILE__, __PRETTY_FUNCTION__,
+		source_str);
+      return;
+    }
+
+    /*
+      Protect IGMP against adding looped MFC entries created by both
+      source and receiver attached to the same interface. See TODO
+      T22.
+    */
+    pim_oif = source->source_group->group_igmp_sock->interface->info;
+    if (!pim_oif) {
+      zlog_warn("%s: multicast not enabled on oif=%s ?",
+		__PRETTY_FUNCTION__,
+		source->source_group->group_igmp_sock->interface->name);
+      return;
+    }
+    if (pim_oif->mroute_vif_index < 1) {
+      zlog_warn("%s %s: oif=%s vif_index=%d < 1",
+		__FILE__, __PRETTY_FUNCTION__,
+		source->source_group->group_igmp_sock->interface->name,
+		pim_oif->mroute_vif_index);
+      return;
+    }
+    if (input_iface_vif_index == pim_oif->mroute_vif_index) {
+      /* ignore request for looped MFC entry */
+      if (PIM_DEBUG_IGMP_TRACE) {
+	char source_str[100];
+	char group_str[100]; 
+	pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+	pim_inet4_dump("<group?>", source->source_group->group_addr, group_str, sizeof(group_str));
+	zlog_debug("%s: ignoring request for looped MFC entry (S,G)=(%s,%s): igmp_sock=%d oif=%s vif_index=%d",
+		   __PRETTY_FUNCTION__,
+		   source_str, group_str,
+		   source->source_group->group_igmp_sock->fd,
+		   source->source_group->group_igmp_sock->interface->name,
+		   input_iface_vif_index);
+      }
+      return;
+    }
+
+    source->source_channel_oil = pim_channel_oil_add(group->group_addr,
+						     source->source_addr,
+						     input_iface_vif_index);
+    if (!source->source_channel_oil) {
+      char group_str[100]; 
+      char source_str[100];
+      pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+      pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+      zlog_warn("%s %s: could not create OIL for channel (S,G)=(%s,%s)",
+		__FILE__, __PRETTY_FUNCTION__,
+		source_str, group_str);
+      return;
+    }
+  }
+
+  if (add_oif(source->source_channel_oil,
+	      group->group_igmp_sock->interface,
+	      PIM_OIF_FLAG_PROTO_IGMP)) {
+    return;
+  }
+
+  /*
+    Feed IGMPv3-gathered local membership information into PIM
+    per-interface (S,G) state.
+   */
+  pim_ifchannel_local_membership_add(group->group_igmp_sock->interface,
+				     source->source_addr, group->group_addr);
+
+  IGMP_SOURCE_DO_FORWARDING(source->source_flags);
+}
+
+void igmp_source_forward_stop(struct igmp_source *source)
+{
+  struct igmp_group *group;
+
+  if (PIM_DEBUG_IGMP_TRACE) {
+    char source_str[100];
+    char group_str[100]; 
+    pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<group?>", source->source_group->group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: (S,G)=(%s,%s) igmp_sock=%d oif=%s fwd=%d",
+	       __PRETTY_FUNCTION__,
+	       source_str, group_str,
+	       source->source_group->group_igmp_sock->fd,
+	       source->source_group->group_igmp_sock->interface->name,
+	       IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+  }
+
+  /* Prevent IGMP interface from removing multicast route multiple
+     times */
+  if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+    return;
+  }
+
+  group = source->source_group;
+
+  if (del_oif(source->source_channel_oil,
+	      group->group_igmp_sock->interface,
+	      PIM_OIF_FLAG_PROTO_IGMP)) {
+    return;
+  }
+
+  /*
+    Feed IGMPv3-gathered local membership information into PIM
+    per-interface (S,G) state.
+   */
+  pim_ifchannel_local_membership_del(group->group_igmp_sock->interface,
+				     source->source_addr, group->group_addr);
+
+  IGMP_SOURCE_DONT_FORWARDING(source->source_flags);
+}
+
+void pim_forward_start(struct pim_ifchannel *ch)
+{
+  struct pim_upstream *up = ch->upstream;
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char source_str[100];
+    char group_str[100]; 
+    pim_inet4_dump("<source?>", ch->source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<group?>", ch->group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: (S,G)=(%s,%s) oif=%s",
+	       __PRETTY_FUNCTION__,
+	       source_str, group_str, ch->interface->name);
+  }
+
+  if (!up->channel_oil) {
+    int input_iface_vif_index = fib_lookup_if_vif_index(up->source_addr);
+    if (input_iface_vif_index < 1) {
+      char source_str[100];
+      pim_inet4_dump("<source?>", up->source_addr, source_str, sizeof(source_str));
+      zlog_warn("%s %s: could not find input interface for source %s",
+		__FILE__, __PRETTY_FUNCTION__,
+		source_str);
+      return;
+    }
+
+    up->channel_oil = pim_channel_oil_add(up->group_addr, up->source_addr,
+					  input_iface_vif_index);
+    if (!up->channel_oil) {
+      char group_str[100]; 
+      char source_str[100];
+      pim_inet4_dump("<group?>", up->group_addr, group_str, sizeof(group_str));
+      pim_inet4_dump("<source?>", up->source_addr, source_str, sizeof(source_str));
+      zlog_warn("%s %s: could not create OIL for channel (S,G)=(%s,%s)",
+		__FILE__, __PRETTY_FUNCTION__,
+		source_str, group_str);
+      return;
+    }
+  }
+
+  add_oif(up->channel_oil,
+	  ch->interface,
+	  PIM_OIF_FLAG_PROTO_PIM);
+}
+
+void pim_forward_stop(struct pim_ifchannel *ch)
+{
+  struct pim_upstream *up = ch->upstream;
+
+  if (PIM_DEBUG_PIM_TRACE) {
+    char source_str[100];
+    char group_str[100]; 
+    pim_inet4_dump("<source?>", ch->source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<group?>", ch->group_addr, group_str, sizeof(group_str));
+    zlog_debug("%s: (S,G)=(%s,%s) oif=%s",
+	       __PRETTY_FUNCTION__,
+	       source_str, group_str, ch->interface->name);
+  }
+
+  if (!up->channel_oil) {
+    char source_str[100];
+    char group_str[100]; 
+    pim_inet4_dump("<source?>", ch->source_addr, source_str, sizeof(source_str));
+    pim_inet4_dump("<group?>", ch->group_addr, group_str, sizeof(group_str));
+    zlog_warn("%s: (S,G)=(%s,%s) oif=%s missing channel OIL",
+	       __PRETTY_FUNCTION__,
+	       source_str, group_str, ch->interface->name);
+
+    return;
+  }
+
+  del_oif(up->channel_oil,
+	  ch->interface,
+	  PIM_OIF_FLAG_PROTO_PIM);
+}
diff --git a/pimd/pim_zebra.h b/pimd/pim_zebra.h
new file mode 100644
index 0000000..e0c9bdc
--- /dev/null
+++ b/pimd/pim_zebra.h
@@ -0,0 +1,40 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_ZEBRA_H
+#define PIM_ZEBRA_H
+
+#include "pim_igmp.h"
+#include "pim_ifchannel.h"
+
+void pim_zebra_init(void);
+
+void igmp_anysource_forward_start(struct igmp_group *group);
+void igmp_anysource_forward_stop(struct igmp_group *group);
+
+void igmp_source_forward_start(struct igmp_source *source);
+void igmp_source_forward_stop(struct igmp_source *source);
+
+void pim_forward_start(struct pim_ifchannel *ch);
+void pim_forward_stop(struct pim_ifchannel *ch);
+
+#endif /* PIM_ZEBRA_H */
diff --git a/pimd/pim_zlookup.c b/pimd/pim_zlookup.c
new file mode 100644
index 0000000..98548e7
--- /dev/null
+++ b/pimd/pim_zlookup.c
@@ -0,0 +1,455 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+#include "zebra/rib.h"
+
+#include "log.h"
+#include "prefix.h"
+#include "zclient.h"
+#include "stream.h"
+#include "network.h"
+#include "thread.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_zlookup.h"
+
+extern int zclient_debug;
+
+static void zclient_lookup_sched(struct zclient *zlookup, int delay);
+
+/* Connect to zebra for nexthop lookup. */
+static int zclient_lookup_connect(struct thread *t)
+{
+  struct zclient *zlookup;
+
+  zlookup = THREAD_ARG(t);
+  zlookup->t_connect = NULL;
+
+  if (zlookup->sock >= 0) {
+    return 0;
+  }
+
+#ifdef HAVE_TCP_ZEBRA
+  zlog_debug("%s: FIXME blocking connect: zclient_socket()",
+	     __PRETTY_FUNCTION__);
+  zlookup->sock = zclient_socket();
+  if (zlookup->sock < 0) {
+    zlog_warn("%s: failure connecting TCP socket %s,%d",
+	      __PRETTY_FUNCTION__, "127.0.0.1", ZEBRA_PORT);
+  }
+  else if (zclient_debug) {
+    zlog_notice("%s: connected TCP socket %s,%d",
+		__PRETTY_FUNCTION__, "127.0.0.1", ZEBRA_PORT);
+  }
+#else
+  zlog_debug("%s: FIXME blocking connect: zclient_socket_un()",
+	     __PRETTY_FUNCTION__);
+  zlookup->sock = zclient_socket_un(ZEBRA_SERV_PATH);
+  if (zlookup->sock < 0) {
+    zlog_warn("%s: failure connecting UNIX socket %s",
+	      __PRETTY_FUNCTION__, ZEBRA_SERV_PATH);
+  }
+  else if (zclient_debug) { 
+    zlog_notice("%s: connected UNIX socket %s",
+		__PRETTY_FUNCTION__, ZEBRA_SERV_PATH);
+  }
+#endif /* HAVE_TCP_ZEBRA */
+
+  zassert(!zlookup->t_connect);
+  if (zlookup->sock < 0) {
+    /* Since last connect failed, retry within 10 secs */
+    zclient_lookup_sched(zlookup, 10);
+    return -1;
+  }
+
+  return 0;
+}
+
+/* Schedule connection with delay. */
+static void zclient_lookup_sched(struct zclient *zlookup, int delay)
+{
+  zassert(!zlookup->t_connect);
+
+  THREAD_TIMER_ON(master, zlookup->t_connect,
+		  zclient_lookup_connect,
+		  zlookup, delay);
+
+  zlog_notice("%s: zclient lookup connection scheduled for %d seconds",
+	      __PRETTY_FUNCTION__, delay);
+}
+
+/* Schedule connection for now. */
+static void zclient_lookup_sched_now(struct zclient *zlookup)
+{
+  zassert(!zlookup->t_connect);
+
+  zlookup->t_connect = thread_add_event(master, zclient_lookup_connect,
+					zlookup, 0);
+
+  zlog_notice("%s: zclient lookup immediate connection scheduled",
+	      __PRETTY_FUNCTION__);
+}
+
+/* Schedule reconnection, if needed. */
+static void zclient_lookup_reconnect(struct zclient *zlookup)
+{
+  if (zlookup->t_connect) {
+    return;
+  }
+
+  zclient_lookup_sched_now(zlookup);
+}
+
+struct zclient *zclient_lookup_new()
+{
+  struct zclient *zlookup;
+
+  zlookup = zclient_new();
+  if (!zlookup) {
+    zlog_err("%s: zclient_new() failure",
+	     __PRETTY_FUNCTION__);
+    return 0;
+  }
+
+  zlookup->sock = -1;
+  zlookup->ibuf = stream_new(ZEBRA_MAX_PACKET_SIZ);
+  zlookup->obuf = stream_new(ZEBRA_MAX_PACKET_SIZ);
+  zlookup->t_connect = 0;
+
+  zclient_lookup_sched_now(zlookup);
+
+  zlog_notice("%s: zclient lookup socket initialized",
+	      __PRETTY_FUNCTION__);
+
+  return zlookup;
+}
+
+static int zclient_read_nexthop(struct zclient *zlookup,
+				struct pim_zlookup_nexthop nexthop_tab[],
+				const int tab_size,
+				struct in_addr addr)
+{
+  int num_ifindex = 0;
+  struct stream *s;
+  const uint16_t MIN_LEN = 14; /* getc=1 getc=1 getw=2 getipv4=4 getc=1 getl=4 getc=1 */
+  uint16_t length, len;
+  u_char marker;
+  u_char version;
+  uint16_t command;
+  int nbytes;
+  struct in_addr raddr;
+  uint8_t distance;
+  uint32_t metric;
+  int nexthop_num;
+  int i;
+
+  if (PIM_DEBUG_ZEBRA) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_debug("%s: addr=%s", 
+	       __PRETTY_FUNCTION__,
+	       addr_str);
+  }
+
+  s = zlookup->ibuf;
+  stream_reset(s);
+
+  nbytes = stream_read(s, zlookup->sock, 2);
+  if (nbytes < 2) {
+    zlog_err("%s %s: failure reading zclient lookup socket: nbytes=%d",
+	     __FILE__, __PRETTY_FUNCTION__, nbytes);
+    close(zlookup->sock);
+    zlookup->sock = -1;
+    zclient_lookup_reconnect(zlookup);
+    return -1;
+  }
+  length = stream_getw(s);
+
+  len = length - 2;
+
+  if (len < MIN_LEN) {
+    zlog_err("%s %s: failure reading zclient lookup socket: len=%d < MIN_LEN=%d",
+	     __FILE__, __PRETTY_FUNCTION__, len, MIN_LEN);
+    close(zlookup->sock);
+    zlookup->sock = -1;
+    zclient_lookup_reconnect(zlookup);
+    return -2;
+  }
+
+  nbytes = stream_read(s, zlookup->sock, len);
+  if (nbytes < (length - 2)) {
+    zlog_err("%s %s: failure reading zclient lookup socket: nbytes=%d < len=%d",
+	     __FILE__, __PRETTY_FUNCTION__, nbytes, len);
+    close(zlookup->sock);
+    zlookup->sock = -1;
+    zclient_lookup_reconnect(zlookup);
+    return -3;
+  }
+  marker = stream_getc(s);
+  version = stream_getc(s);
+  
+  if (version != ZSERV_VERSION || marker != ZEBRA_HEADER_MARKER) {
+    zlog_err("%s: socket %d version mismatch, marker %d, version %d",
+	     __func__, zlookup->sock, marker, version);
+    return -4;
+  }
+    
+  command = stream_getw(s);
+  if (command != ZEBRA_IPV4_NEXTHOP_LOOKUP_V2) {
+    zlog_err("%s: socket %d command mismatch: %d",
+            __func__, zlookup->sock, command);
+    return -5;
+  }
+
+  raddr.s_addr = stream_get_ipv4(s);
+
+  if (raddr.s_addr != addr.s_addr) {
+    char addr_str[100];
+    char raddr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    pim_inet4_dump("<raddr?>", raddr, raddr_str, sizeof(raddr_str));
+    zlog_warn("%s: address mismatch: addr=%s raddr=%s", 
+	       __PRETTY_FUNCTION__,
+	       addr_str, raddr_str);
+    /* warning only */
+  }
+
+  distance = stream_getc(s);
+  metric = stream_getl(s);
+  nexthop_num = stream_getc(s);
+
+  if (nexthop_num < 1) {
+    zlog_err("%s: socket %d bad nexthop_num=%d",
+            __func__, zlookup->sock, nexthop_num);
+    return -6;
+  }
+
+  len -= MIN_LEN;
+
+  for (i = 0; i < nexthop_num; ++i) {
+    enum nexthop_types_t nexthop_type;
+
+    if (len < 1) {
+      zlog_err("%s: socket %d empty input expecting nexthop_type: len=%d",
+	       __func__, zlookup->sock, len);
+      return -7;
+    }
+    
+    nexthop_type = stream_getc(s);
+    --len;
+
+    switch (nexthop_type) {
+    case ZEBRA_NEXTHOP_IFINDEX:
+    case ZEBRA_NEXTHOP_IFNAME:
+    case ZEBRA_NEXTHOP_IPV4_IFINDEX:
+      if (num_ifindex >= tab_size) {
+	char addr_str[100];
+	pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+	zlog_warn("%s %s: found too many nexthop ifindexes (%d > %d) for address %s",
+		 __FILE__, __PRETTY_FUNCTION__,
+		 (num_ifindex + 1), tab_size, addr_str);
+	return num_ifindex;
+      }
+      if (nexthop_type == ZEBRA_NEXTHOP_IPV4_IFINDEX) {
+	if (len < 4) {
+	  zlog_err("%s: socket %d short input expecting nexthop IPv4-addr: len=%d",
+		   __func__, zlookup->sock, len);
+	  return -8;
+	}
+	nexthop_tab[num_ifindex].nexthop_addr.s_addr = stream_get_ipv4(s);
+	len -= 4;
+      }
+      else {
+	nexthop_tab[num_ifindex].nexthop_addr.s_addr = PIM_NET_INADDR_ANY;
+      }
+      nexthop_tab[num_ifindex].ifindex           = stream_getl(s);
+      nexthop_tab[num_ifindex].protocol_distance = distance;
+      nexthop_tab[num_ifindex].route_metric      = metric;
+      ++num_ifindex;
+      break;
+    case ZEBRA_NEXTHOP_IPV4:
+      if (num_ifindex >= tab_size) {
+	char addr_str[100];
+	pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+	zlog_warn("%s %s: found too many nexthop ifindexes (%d > %d) for address %s",
+		 __FILE__, __PRETTY_FUNCTION__,
+		 (num_ifindex + 1), tab_size, addr_str);
+	return num_ifindex;
+      }
+      nexthop_tab[num_ifindex].nexthop_addr.s_addr = stream_get_ipv4(s);
+      len -= 4;
+      nexthop_tab[num_ifindex].ifindex             = 0;
+      nexthop_tab[num_ifindex].protocol_distance   = distance;
+      nexthop_tab[num_ifindex].route_metric        = metric;
+      {
+	char addr_str[100];
+	char nexthop_str[100];
+	pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+	pim_inet4_dump("<nexthop?>", nexthop_tab[num_ifindex].nexthop_addr, nexthop_str, sizeof(nexthop_str));
+	zlog_warn("%s %s: zebra returned recursive nexthop %s for address %s",
+		  __FILE__, __PRETTY_FUNCTION__,
+		  nexthop_str, addr_str);
+      }
+      ++num_ifindex;
+      break;
+    default:
+      /* do nothing */
+      {
+	char addr_str[100];
+	pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+	zlog_warn("%s %s: found non-ifindex nexthop type=%d for address %s",
+		 __FILE__, __PRETTY_FUNCTION__,
+		  nexthop_type, addr_str);
+      }
+      break;
+    }
+  }
+
+  return num_ifindex;
+}
+
+static int zclient_lookup_nexthop_once(struct zclient *zlookup,
+				       struct pim_zlookup_nexthop nexthop_tab[],
+				       const int tab_size,
+				       struct in_addr addr)
+{
+  struct stream *s;
+  int ret;
+
+  if (PIM_DEBUG_ZEBRA) {
+    char addr_str[100];
+    pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+    zlog_debug("%s: addr=%s", 
+	       __PRETTY_FUNCTION__,
+	       addr_str);
+  }
+
+  /* Check socket. */
+  if (zlookup->sock < 0) {
+    zlog_err("%s %s: zclient lookup socket is not connected",
+	     __FILE__, __PRETTY_FUNCTION__);
+    zclient_lookup_reconnect(zlookup);
+    return -1;
+  }
+  
+  s = zlookup->obuf;
+  stream_reset(s);
+  zclient_create_header(s, ZEBRA_IPV4_NEXTHOP_LOOKUP_V2);
+  stream_put_in_addr(s, &addr);
+  stream_putw_at(s, 0, stream_get_endp(s));
+  
+  ret = writen(zlookup->sock, s->data, stream_get_endp(s));
+  if (ret < 0) {
+    zlog_err("%s %s: writen() failure writing to zclient lookup socket",
+	     __FILE__, __PRETTY_FUNCTION__);
+    close(zlookup->sock);
+    zlookup->sock = -1;
+    zclient_lookup_reconnect(zlookup);
+    return -2;
+  }
+  if (ret == 0) {
+    zlog_err("%s %s: connection closed on zclient lookup socket",
+	     __FILE__, __PRETTY_FUNCTION__);
+    close(zlookup->sock);
+    zlookup->sock = -1;
+    zclient_lookup_reconnect(zlookup);
+    return -3;
+  }
+  
+  return zclient_read_nexthop(zlookup, nexthop_tab,
+			      tab_size, addr);
+}
+
+int zclient_lookup_nexthop(struct zclient *zlookup,
+			   struct pim_zlookup_nexthop nexthop_tab[],
+			   const int tab_size,
+			   struct in_addr addr,
+			   int max_lookup)
+{
+  int lookup;
+
+  for (lookup = 0; lookup < max_lookup; ++lookup) {
+    int num_ifindex;
+    int first_ifindex;
+    struct in_addr nexthop_addr;
+
+    num_ifindex = zclient_lookup_nexthop_once(qpim_zclient_lookup, nexthop_tab,
+					      PIM_NEXTHOP_IFINDEX_TAB_SIZE, addr);
+    if (num_ifindex < 1) {
+      char addr_str[100];
+      pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+      zlog_warn("%s %s: lookup=%d/%d: could not find nexthop ifindex for address %s",
+		__FILE__, __PRETTY_FUNCTION__,
+		lookup, max_lookup, addr_str);
+      return -1;
+    }
+    
+    /*
+      FIXME: Non-recursive nexthop ensured only for first ifindex.
+      However, recursive route lookup should really be fixed in zebra daemon.
+      See also TODO T24.
+     */
+    first_ifindex = nexthop_tab[0].ifindex;
+    nexthop_addr = nexthop_tab[0].nexthop_addr;
+    if (first_ifindex > 0) {
+      /* found: first ifindex is non-recursive nexthop */
+
+      if (lookup > 0) {
+	/* Report non-recursive success after first lookup */
+	char addr_str[100];
+	pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+	zlog_info("%s %s: lookup=%d/%d: found non-recursive ifindex=%d for address %s",
+		  __FILE__, __PRETTY_FUNCTION__,
+		  lookup, max_lookup, first_ifindex, addr_str);
+
+	/* use last address as nexthop address */
+	nexthop_tab[0].nexthop_addr = addr;
+      }
+
+      return num_ifindex;
+    }
+
+    {
+      char addr_str[100];
+      char nexthop_str[100];
+      pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+      pim_inet4_dump("<nexthop?>", nexthop_addr, nexthop_str, sizeof(nexthop_str));
+      zlog_warn("%s %s: lookup=%d/%d: zebra returned recursive nexthop %s for address %s",
+		__FILE__, __PRETTY_FUNCTION__,
+		lookup, max_lookup, nexthop_str, addr_str);
+    }
+
+    addr = nexthop_addr; /* use nexthop addr for recursive lookup */
+
+  } /* for (max_lookup) */
+
+  char addr_str[100];
+  pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+  zlog_warn("%s %s: lookup=%d/%d: failure searching recursive nexthop ifindex for address %s",
+	    __FILE__, __PRETTY_FUNCTION__,
+	    lookup, max_lookup, addr_str);
+
+  return -2;
+}
diff --git a/pimd/pim_zlookup.h b/pimd/pim_zlookup.h
new file mode 100644
index 0000000..1f18494
--- /dev/null
+++ b/pimd/pim_zlookup.h
@@ -0,0 +1,47 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIM_ZLOOKUP_H
+#define PIM_ZLOOKUP_H
+
+#include <zebra.h>
+
+#include "zclient.h"
+
+#define PIM_NEXTHOP_LOOKUP_MAX (3) /* max. recursive route lookup */
+
+struct pim_zlookup_nexthop {
+  struct in_addr nexthop_addr;
+  int            ifindex;
+  uint32_t       route_metric;
+  uint8_t        protocol_distance;
+};
+
+struct zclient *zclient_lookup_new(void);
+
+int zclient_lookup_nexthop(struct zclient *zlookup,
+			   struct pim_zlookup_nexthop nexthop_tab[],
+			   const int tab_size,
+			   struct in_addr addr,
+			   int max_lookup);
+
+#endif /* PIM_ZLOOKUP_H */
diff --git a/pimd/pimd.c b/pimd/pimd.c
new file mode 100644
index 0000000..6c3d616
--- /dev/null
+++ b/pimd/pimd.c
@@ -0,0 +1,122 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#include <zebra.h>
+
+#include "log.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_cmd.h"
+#include "pim_iface.h"
+#include "pim_zebra.h"
+#include "pim_str.h"
+#include "pim_oil.h"
+#include "pim_pim.h"
+#include "pim_upstream.h"
+#include "pim_rand.h"
+#include "pim_rpf.h"
+
+const char *const PIM_ALL_SYSTEMS      = MCAST_ALL_SYSTEMS;
+const char *const PIM_ALL_ROUTERS      = MCAST_ALL_ROUTERS;
+const char *const PIM_ALL_PIM_ROUTERS  = MCAST_ALL_PIM_ROUTERS;
+const char *const PIM_ALL_IGMP_ROUTERS = MCAST_ALL_IGMP_ROUTERS;
+
+struct thread_master     *master = 0;
+uint32_t                  qpim_debugs = 0;
+int                       qpim_mroute_socket_fd = -1;
+int64_t                   qpim_mroute_socket_creation = 0; /* timestamp of creation */
+struct thread            *qpim_mroute_socket_reader = 0;
+int                       qpim_mroute_oif_highest_vif_index = -1;
+struct list              *qpim_channel_oil_list = 0;
+int                       qpim_t_periodic = PIM_DEFAULT_T_PERIODIC; /* Period between Join/Prune Messages */
+struct list              *qpim_upstream_list = 0;
+struct zclient           *qpim_zclient_lookup = 0;
+struct pim_assert_metric  qpim_infinite_assert_metric;
+long                      qpim_rpf_cache_refresh_delay_msec = 10000;
+struct thread            *qpim_rpf_cache_refresher = 0;
+
+static void pim_free()
+{
+  if (qpim_channel_oil_list)
+    list_free(qpim_channel_oil_list);
+
+  if (qpim_upstream_list)
+    list_free(qpim_upstream_list);
+}
+
+void pim_init()
+{
+  pim_rand_init();
+
+  if (!inet_aton(PIM_ALL_PIM_ROUTERS, &qpim_all_pim_routers_addr)) {
+    zlog_err("%s %s: could not solve %s to group address: errno=%d: %s",
+	     __FILE__, __PRETTY_FUNCTION__,
+	     PIM_ALL_PIM_ROUTERS, errno, strerror(errno));
+    zassert(0);
+    return;
+  }
+
+  qpim_channel_oil_list = list_new();
+  if (!qpim_channel_oil_list) {
+    zlog_err("%s %s: failure: channel_oil_list=list_new()",
+	     __FILE__, __PRETTY_FUNCTION__);
+    return;
+  }
+  qpim_channel_oil_list->del = (void (*)(void *)) pim_channel_oil_free;
+
+  qpim_upstream_list = list_new();
+  if (!qpim_upstream_list) {
+    zlog_err("%s %s: failure: upstream_list=list_new()",
+	     __FILE__, __PRETTY_FUNCTION__);
+    pim_free();
+    return;
+  }
+  qpim_upstream_list->del = (void (*)(void *)) pim_upstream_free;
+
+  qpim_mroute_socket_fd = -1; /* mark mroute as disabled */
+  qpim_mroute_oif_highest_vif_index = -1;
+
+  zassert(!qpim_debugs);
+  zassert(!PIM_MROUTE_IS_ENABLED);
+
+  /*
+    RFC 4601: 4.6.3.  Assert Metrics
+
+    assert_metric
+    infinite_assert_metric() {
+    return {1,infinity,infinity,0}
+    }
+  */
+  qpim_infinite_assert_metric.rpt_bit_flag      = 1;
+  qpim_infinite_assert_metric.metric_preference = 0xFFFFFFFF;
+  qpim_infinite_assert_metric.route_metric      = 0xFFFFFFFF;
+  qpim_infinite_assert_metric.ip_address.s_addr = PIM_NET_INADDR_ANY;
+
+  pim_if_init();
+  pim_cmd_init();
+}
+
+void pim_terminate()
+{
+  pim_free();
+}
diff --git a/pimd/pimd.conf.sample b/pimd/pimd.conf.sample
new file mode 100644
index 0000000..2f4543e
--- /dev/null
+++ b/pimd/pimd.conf.sample
@@ -0,0 +1,35 @@
+!
+! pimd sample configuration file
+! $QuaggaId: $Format:%an, %ai, %h$ $
+!
+hostname quagga-pimd-router
+password zebra
+!enable password zebra
+!
+!log file pimd.log
+log stdout
+!
+line vty
+ exec-timeout 60
+!
+!debug igmp
+!debug pim
+!debug pim zebra
+!
+ip multicast-routing
+!
+! ! HINTS:
+! !  - Enable "ip pim ssm" on the interface directly attached to the
+! !    multicast source host (if this is the first-hop router)
+! !  - Enable "ip pim ssm" on pim-routers-facing interfaces
+! !  - Enable "ip igmp" on IGMPv3-hosts-facing interfaces
+! !  - In order to inject IGMPv3 local membership information in the
+! !    PIM protocol state, enable both "ip pim ssm" and "ip igmp" on
+! !    the same interface; otherwise PIM won't advertise
+! !    IGMPv3-learned membership to other PIM routers
+!
+interface eth0
+ ip pim ssm
+ ip igmp
+
+! -x-
diff --git a/pimd/pimd.h b/pimd/pimd.h
new file mode 100644
index 0000000..06482dd
--- /dev/null
+++ b/pimd/pimd.h
@@ -0,0 +1,118 @@
+/*
+  PIM for Quagga
+  Copyright (C) 2008  Everton da Silva Marques
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  General Public License for more details.
+  
+  You should have received a copy of the GNU General Public License
+  along with this program; see the file COPYING; if not, write to the
+  Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+  MA 02110-1301 USA
+  
+  $QuaggaId: $Format:%an, %ai, %h$ $
+*/
+
+#ifndef PIMD_H
+#define PIMD_H
+
+#include <stdint.h>
+
+#include "pim_mroute.h"
+#include "pim_assert.h"
+
+#define PIMD_PROGNAME       "pimd"
+#define PIMD_DEFAULT_CONFIG "pimd.conf"
+#define PIMD_VTY_PORT       2611
+#define PIMD_BUG_ADDRESS    "http://savannah.nongnu.org/projects/qpimd"
+
+#define PIM_IP_HEADER_MIN_LEN         (20)
+#define PIM_IP_HEADER_MAX_LEN         (60)
+#define PIM_IP_PROTO_IGMP             (2)
+#define PIM_IP_PROTO_PIM              (103)
+#define PIM_IGMP_MIN_LEN              (8)
+#define PIM_MSG_HEADER_LEN            (4)
+#define PIM_PIM_MIN_LEN               PIM_MSG_HEADER_LEN
+#define PIM_PROTO_VERSION             (2)
+
+#define MCAST_ALL_SYSTEMS      "224.0.0.1"
+#define MCAST_ALL_ROUTERS      "224.0.0.2"
+#define MCAST_ALL_PIM_ROUTERS  "224.0.0.13"
+#define MCAST_ALL_IGMP_ROUTERS "224.0.0.22"
+
+#define PIM_NET_INADDR_ANY (htonl(INADDR_ANY))
+#define PIM_INADDR_IS_ANY(addr) ((addr).s_addr == PIM_NET_INADDR_ANY) /* struct in_addr addr */
+#define PIM_INADDR_ISNOT_ANY(addr) ((addr).s_addr != PIM_NET_INADDR_ANY) /* struct in_addr addr */
+
+#define PIM_MASK_PIM_EVENTS   (1 << 0)
+#define PIM_MASK_PIM_PACKETS  (1 << 1)
+#define PIM_MASK_PIM_TRACE    (1 << 2)
+#define PIM_MASK_IGMP_EVENTS  (1 << 3)
+#define PIM_MASK_IGMP_PACKETS (1 << 4)
+#define PIM_MASK_IGMP_TRACE   (1 << 5)
+#define PIM_MASK_ZEBRA        (1 << 6)
+
+const char *const PIM_ALL_SYSTEMS;
+const char *const PIM_ALL_ROUTERS;
+const char *const PIM_ALL_PIM_ROUTERS;
+const char *const PIM_ALL_IGMP_ROUTERS;
+
+struct thread_master     *master;
+uint32_t                  qpim_debugs;
+int                       qpim_mroute_socket_fd;
+int64_t                   qpim_mroute_socket_creation; /* timestamp of creation */
+struct thread            *qpim_mroute_socket_reader;
+int                       qpim_mroute_oif_highest_vif_index;
+struct list              *qpim_channel_oil_list; /* list of struct channel_oil */
+struct in_addr            qpim_all_pim_routers_addr;
+int                       qpim_t_periodic; /* Period between Join/Prune Messages */
+struct list              *qpim_upstream_list; /* list of struct pim_upstream */
+struct zclient           *qpim_zclient_lookup;
+struct pim_assert_metric  qpim_infinite_assert_metric;
+long                      qpim_rpf_cache_refresh_delay_msec;
+struct thread            *qpim_rpf_cache_refresher;
+
+#define PIM_JP_HOLDTIME (qpim_t_periodic * 7 / 2)
+
+#define PIM_MROUTE_IS_ENABLED  (qpim_mroute_socket_fd >= 0)
+#define PIM_MROUTE_IS_DISABLED (qpim_mroute_socket_fd < 0)
+
+#define PIM_DEBUG_PIM_EVENTS   (qpim_debugs & PIM_MASK_PIM_EVENTS)
+#define PIM_DEBUG_PIM_PACKETS  (qpim_debugs & PIM_MASK_PIM_PACKETS)
+#define PIM_DEBUG_PIM_TRACE    (qpim_debugs & PIM_MASK_PIM_TRACE)
+#define PIM_DEBUG_IGMP_EVENTS  (qpim_debugs & PIM_MASK_IGMP_EVENTS)
+#define PIM_DEBUG_IGMP_PACKETS (qpim_debugs & PIM_MASK_IGMP_PACKETS)
+#define PIM_DEBUG_IGMP_TRACE   (qpim_debugs & PIM_MASK_IGMP_TRACE)
+#define PIM_DEBUG_ZEBRA        (qpim_debugs & PIM_MASK_ZEBRA)
+
+#define PIM_DEBUG_EVENTS       (qpim_debugs & (PIM_MASK_PIM_EVENTS | PIM_MASK_IGMP_EVENTS))
+#define PIM_DEBUG_PACKETS      (qpim_debugs & (PIM_MASK_PIM_PACKETS | PIM_MASK_IGMP_PACKETS))
+#define PIM_DEBUG_TRACE        (qpim_debugs & (PIM_MASK_PIM_TRACE | PIM_MASK_IGMP_TRACE))
+
+#define PIM_DO_DEBUG_PIM_EVENTS   (qpim_debugs |= PIM_MASK_PIM_EVENTS)
+#define PIM_DO_DEBUG_PIM_PACKETS  (qpim_debugs |= PIM_MASK_PIM_PACKETS)
+#define PIM_DO_DEBUG_PIM_TRACE    (qpim_debugs |= PIM_MASK_PIM_TRACE)
+#define PIM_DO_DEBUG_IGMP_EVENTS  (qpim_debugs |= PIM_MASK_IGMP_EVENTS)
+#define PIM_DO_DEBUG_IGMP_PACKETS (qpim_debugs |= PIM_MASK_IGMP_PACKETS)
+#define PIM_DO_DEBUG_IGMP_TRACE   (qpim_debugs |= PIM_MASK_IGMP_TRACE)
+#define PIM_DO_DEBUG_ZEBRA        (qpim_debugs |= PIM_MASK_ZEBRA)
+
+#define PIM_DONT_DEBUG_PIM_EVENTS   (qpim_debugs &= ~PIM_MASK_PIM_EVENTS)
+#define PIM_DONT_DEBUG_PIM_PACKETS  (qpim_debugs &= ~PIM_MASK_PIM_PACKETS)
+#define PIM_DONT_DEBUG_PIM_TRACE    (qpim_debugs &= ~PIM_MASK_PIM_TRACE)
+#define PIM_DONT_DEBUG_IGMP_EVENTS  (qpim_debugs &= ~PIM_MASK_IGMP_EVENTS)
+#define PIM_DONT_DEBUG_IGMP_PACKETS (qpim_debugs &= ~PIM_MASK_IGMP_PACKETS)
+#define PIM_DONT_DEBUG_IGMP_TRACE   (qpim_debugs &= ~PIM_MASK_IGMP_TRACE)
+#define PIM_DONT_DEBUG_ZEBRA        (qpim_debugs &= ~PIM_MASK_ZEBRA)
+
+void pim_init(void);
+void pim_terminate(void);
+
+#endif /* PIMD_H */
diff --git a/pimd/quagga-bootstrap.sh b/pimd/quagga-bootstrap.sh
new file mode 100755
index 0000000..f478e2a
--- /dev/null
+++ b/pimd/quagga-bootstrap.sh
@@ -0,0 +1,21 @@
+#! /bin/bash
+#
+# Bootstrap Quagga autotools for pimd.
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-bootstrap.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+me=`basename $0`
+msg () {
+	echo >&2 $me: $*
+}
+
+if [ -f ./bootstrap.sh ]; then
+	msg found ./bootstrap.sh from quagga
+	./bootstrap.sh
+else
+	msg missing ./bootstrap.sh from quagga
+	autoreconf -i
+fi
diff --git a/pimd/quagga-build.sh b/pimd/quagga-build.sh
new file mode 100755
index 0000000..2ad476d
--- /dev/null
+++ b/pimd/quagga-build.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+#
+# Build minimum Quagga needed for pimd.
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-build.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+./pimd/quagga-memtypes.sh && ./pimd/quagga-bootstrap.sh && ./pimd/quagga-configure.sh && make
diff --git a/pimd/quagga-configure.sh b/pimd/quagga-configure.sh
new file mode 100755
index 0000000..a1cdd58
--- /dev/null
+++ b/pimd/quagga-configure.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+#
+# Configure for minimum Quagga build needed for pimd.
+#
+# Run from quagga's top dir as:
+# . pimd/quagga-configure.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+./configure --disable-bgpd --disable-ripd --disable-ripngd --disable-ospfd --disable-ospf6d --disable-watchquagga --disable-bgp-announce --disable-ospfapi --disable-ospfclient --disable-rtadv --disable-irdp --enable-pimd --enable-tcp-zebra --enable-ipv6
diff --git a/pimd/quagga-git-add.sh b/pimd/quagga-git-add.sh
new file mode 100755
index 0000000..3824e98
--- /dev/null
+++ b/pimd/quagga-git-add.sh
@@ -0,0 +1,12 @@
+#! /bin/bash
+#
+# Add to git new files created by qpimd patch
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-git-add.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+chmod a+rx pimd/*.sh
+git add doc/pimd.8
+git add pimd
diff --git a/pimd/quagga-memtypes.sh b/pimd/quagga-memtypes.sh
new file mode 100755
index 0000000..e86f414
--- /dev/null
+++ b/pimd/quagga-memtypes.sh
@@ -0,0 +1,22 @@
+#! /bin/bash
+#
+# Check lib/memtypes.h from Quagga
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-memtypes.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+me=`basename $0`
+msg () {
+	echo >&2 $me: $*
+}
+
+memtypes_h=lib/memtypes.h
+if [ -e $memtypes_h ]; then
+	memtypes_h_size=`ls -s $memtypes_h | cut -d' ' -f1`
+	if [ "$memtypes_h_size" -lt 1 ]; then
+		msg WARNING: removing empty file: $memtypes_h -- awk failed?
+		rm $memtypes_h	
+	fi
+fi
diff --git a/pimd/savannah-git-clone.sh b/pimd/savannah-git-clone.sh
new file mode 100755
index 0000000..68fd608
--- /dev/null
+++ b/pimd/savannah-git-clone.sh
@@ -0,0 +1,16 @@
+#! /bin/bash
+#
+# Savannah Developer Git Checkout
+#
+# Delete remote branch qpimd:    git push origin :qpimd
+#                                (git push origin :refs/heads/branch_to_delete)
+# Delete remote tag v0.139:      git push origin :v0.139
+#                                (git push origin :refs/tags/tag_to_delete)
+# Create remote-tracking branch: git checkout -b pim0.142 origin/pim0.142
+# Rename branch qpimd to pim:    git branch -m qpimd pim
+# Commit changes:                git commit -a
+# Send changes:                  git push --all
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+git clone ssh://evertonm@git.sv.gnu.org/srv/git/qpimd.git quagga
diff --git a/redhat/quagga.spec.in b/redhat/quagga.spec.in
index a8c7fbf..2d1f2c0 100644
--- a/redhat/quagga.spec.in
+++ b/redhat/quagga.spec.in
@@ -48,16 +48,16 @@
 %define quagga_buildreqs %{quagga_buildreqs} patch libcap-devel
 
 # FC4 and 5 split texi2html out of tetex package.
-%if "%dist" != "fc2" || "%dist" != "fc3"
+%if "%dist" == "fc4" || "%dist" == "fc5"
 %define  quagga_buildreqs %{quagga_buildreqs} texi2html
 %endif
 
 # pam_stack is deprecated in FC5
 # default to pam_stack, default should be changed later.
-%if "%dist" == "fc4" || "%dist" == "fc3"
-%define	quagga_pam_source quagga.pam.stack
-%else
+%if "%dist" == "fc5"
 %define	quagga_pam_source quagga.pam
+%else
+%define	quagga_pam_source quagga.pam.stack
 %endif
 ############################################################################
 
@@ -303,6 +303,9 @@ zebra_spec_add_service ospfapi  2607/tcp "OSPF-API"
 %if %{with_isisd}
 zebra_spec_add_service isisd    2608/tcp "ISISd vty"
 %endif
+%if %{with_pimd}
+zebra_spec_add_service pimd     2611/tcp "PIMd vty"
+%endif
 
 for daemon in %daemon_list ; do
 	/sbin/chkconfig --add ${daemon}
diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index 90db932..c557831 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -383,6 +383,17 @@ nexthop_active_ipv4 (struct rib *rib, struct nexthop *nexthop, int set,
 	    break;
 	}
 
+      if (IS_ZEBRA_DEBUG_RIB) {
+	char buf[INET6_ADDRSTRLEN];
+	inet_ntop(rn->p.family, &rn->p.u.prefix, buf, INET6_ADDRSTRLEN);
+        zlog_debug("%s: %s/%d: nexthop match %p: %s type=%d sel=%d int=%d",
+		   __func__, buf, rn->p.prefixlen, match,
+		   match ? zebra_route_string(match->type) : "<?>",
+		   match ? match->type : -1,
+		   match ? CHECK_FLAG(match->flags, ZEBRA_FLAG_SELECTED) : -1,
+		   match ? CHECK_FLAG(match->flags, ZEBRA_FLAG_INTERNAL) : -1);
+      }
+
       /* If there is no selected route or matched route is EGP, go up
          tree. */
       if (! match 
@@ -405,7 +416,8 @@ nexthop_active_ipv4 (struct rib *rib, struct nexthop *nexthop, int set,
 	      
 	      return 1;
 	    }
-	  else if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_INTERNAL))
+	  else if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_INTERNAL) ||
+		   match->type == ZEBRA_ROUTE_KERNEL)
 	    {
 	      for (newhop = match->nexthop; newhop; newhop = newhop->next)
 		if (CHECK_FLAG (newhop->flags, NEXTHOP_FLAG_FIB)
@@ -786,6 +798,43 @@ rib_match_ipv6 (struct in6_addr *addr)
 }
 #endif /* HAVE_IPV6 */
 
+static void nexthop_dump(struct nexthop *nexthop,
+			 char *type_str_buf,
+			 int type_str_buf_size,
+			 char *addr_str_buf,
+			 int addr_str_buf_size,
+			 char *via_str_buf,
+			 int via_str_buf_size)
+{
+  switch (nexthop->type) {
+  case NEXTHOP_TYPE_IPV4:
+  case NEXTHOP_TYPE_IPV4_IFINDEX:
+    snprintf(type_str_buf, type_str_buf_size, "ipv4");
+    snprintf(addr_str_buf, addr_str_buf_size, "%s", inet_ntoa(nexthop->gate.ipv4));
+    snprintf(via_str_buf, via_str_buf_size, "%s", nexthop->ifindex ? ifindex2ifname(nexthop->ifindex) : "<?>");
+    break;
+  case NEXTHOP_TYPE_IFINDEX:
+    snprintf(type_str_buf, type_str_buf_size, "connected");
+    snprintf(addr_str_buf, addr_str_buf_size, "<connected>");
+    snprintf(via_str_buf, via_str_buf_size, "%s", ifindex2ifname(nexthop->ifindex));
+    break;
+  case NEXTHOP_TYPE_IFNAME:
+    snprintf(type_str_buf, type_str_buf_size, "connected");
+    snprintf(addr_str_buf, addr_str_buf_size, "<connected>");
+    snprintf(via_str_buf, via_str_buf_size, "%s", nexthop->ifname);
+    break;
+  case NEXTHOP_TYPE_BLACKHOLE:
+    snprintf(type_str_buf, type_str_buf_size, "blackhole");
+    snprintf(addr_str_buf, addr_str_buf_size, "<blackhole>");
+    snprintf(via_str_buf, via_str_buf_size, "Null0");
+    break;
+  default:
+    snprintf(type_str_buf, type_str_buf_size, "unknown");
+    snprintf(addr_str_buf, addr_str_buf_size, "<unknown>");
+    snprintf(via_str_buf, via_str_buf_size, "<?>");
+  }
+}
+
 #define RIB_SYSTEM_ROUTE(R) \
         ((R)->type == ZEBRA_ROUTE_KERNEL || (R)->type == ZEBRA_ROUTE_CONNECT)
 
@@ -839,10 +888,27 @@ nexthop_active_check (struct route_node *rn, struct rib *rib,
     case NEXTHOP_TYPE_IPV4:
     case NEXTHOP_TYPE_IPV4_IFINDEX:
       family = AFI_IP;
-      if (nexthop_active_ipv4 (rib, nexthop, set, rn))
-	SET_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE);
-      else
-	UNSET_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE);
+      {
+	int nh_active = nexthop_active_ipv4 (rib, nexthop, set, rn);
+	if (IS_ZEBRA_DEBUG_RIB) {
+	  char type_str_buf[100];
+	  char addr_str_buf[100];
+	  char via_str_buf[100];
+	  nexthop_dump(nexthop,
+		       type_str_buf, sizeof(type_str_buf),
+		       addr_str_buf, sizeof(addr_str_buf),
+		       via_str_buf, sizeof(via_str_buf));
+	  zlog_debug("%s: rib %p nexthop %p type=%d %s %s via %s ifindex=%d nexthop_active_ipv4=%d",
+		     __func__, rib, nexthop,
+		     nexthop->type, type_str_buf,
+		     addr_str_buf, via_str_buf, nexthop->ifindex,
+		     nh_active);
+	}
+	if (nh_active)
+	  SET_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE);
+	else
+	  UNSET_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE);
+      }
       break;
 #ifdef HAVE_IPV6
     case NEXTHOP_TYPE_IPV6:
@@ -921,8 +987,23 @@ nexthop_active_update (struct route_node *rn, struct rib *rib, int set)
   for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
   {
     prev_active = CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_ACTIVE);
-    if ((new_active = nexthop_active_check (rn, rib, nexthop, set)))
+    new_active = nexthop_active_check (rn, rib, nexthop, set);
+    if (new_active)
       rib->nexthop_active_num++;
+    if (IS_ZEBRA_DEBUG_RIB) {
+      char type_str_buf[100];
+      char addr_str_buf[100];
+      char via_str_buf[100];
+      nexthop_dump(nexthop,
+		   type_str_buf, sizeof(type_str_buf),
+		   addr_str_buf, sizeof(addr_str_buf),
+		   via_str_buf, sizeof(via_str_buf));
+      zlog_debug("%s: rib %p nexthop %p type=%d %s %s via %s ifindex=%d act=%d total_act=%d",
+		 __func__, rib, nexthop,
+		 nexthop->type, type_str_buf,
+		 addr_str_buf, via_str_buf, nexthop->ifindex,
+		 new_active, rib->nexthop_active_num);
+    }
     if (prev_active != new_active)
       SET_FLAG (rib->flags, ZEBRA_FLAG_CHANGED);
   }
@@ -1023,6 +1104,15 @@ rib_process (struct route_node *rn)
        * may be passed to rib_unlink() in the middle of iteration.
        */
       next = rib->next;
+
+      if (IS_ZEBRA_DEBUG_RIB) {
+        zlog_debug("%s: %s/%d: scan rib %p: type=%d sel=%d rem=%d nh_act=%d dist=%d",
+		   __func__, buf, rn->p.prefixlen, rib, rib->type,
+		   CHECK_FLAG(rib->flags, ZEBRA_FLAG_SELECTED),
+		   CHECK_FLAG(rib->status, RIB_ENTRY_REMOVED),
+		   nexthop_active_update(rn, rib, 0),
+		   rib->distance);
+      }
       
       /* Currently installed rib. */
       if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_SELECTED))
diff --git a/zebra/zserv.c b/zebra/zserv.c
index ef79eaa..9b7f2f7 100644
--- a/zebra/zserv.c
+++ b/zebra/zserv.c
@@ -1,5 +1,6 @@
 /* Zebra daemon server routine.
  * Copyright (C) 1997, 98, 99 Kunihiro Ishiguro
+ * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
  *
  * This file is part of GNU Zebra.
  *
@@ -595,6 +596,77 @@ zsend_ipv4_nexthop_lookup (struct zserv *client, struct in_addr addr)
   return zebra_server_send_message(client);
 }
 
+/*
+  Modified version of zsend_ipv4_nexthop_lookup():
+  1) Returns both nexthop address and nexthop ifindex
+     (with ZEBRA_NEXTHOP_IPV4_IFINDEX).
+  2) Returns both route metric and protocol distance.
+*/
+static int
+zsend_ipv4_nexthop_lookup_v2 (struct zserv *client, struct in_addr addr)
+{
+  struct stream *s;
+  struct rib *rib;
+  unsigned long nump;
+  u_char num;
+  struct nexthop *nexthop;
+
+  /* Lookup nexthop. */
+  rib = rib_match_ipv4 (addr);
+
+  /* Get output stream. */
+  s = client->obuf;
+  stream_reset (s);
+
+  /* Fill in result. */
+  zserv_create_header (s, ZEBRA_IPV4_NEXTHOP_LOOKUP_V2);
+  stream_put_in_addr (s, &addr);
+
+  if (rib)
+    {
+      stream_putc (s, rib->distance);
+      stream_putl (s, rib->metric);
+      num = 0;
+      nump = stream_get_endp(s); /* remember position for nexthop_num */
+      stream_putc (s, 0);        /* reserve room for nexthop_num */
+      for (nexthop = rib->nexthop; nexthop; nexthop = nexthop->next)
+	if (CHECK_FLAG (nexthop->flags, NEXTHOP_FLAG_FIB))
+	  {
+	    stream_putc (s, nexthop->type);
+	    switch (nexthop->type)
+	      {
+	      case ZEBRA_NEXTHOP_IPV4:
+		stream_put_in_addr (s, &nexthop->gate.ipv4);
+		break;
+	      case ZEBRA_NEXTHOP_IFINDEX:
+	      case ZEBRA_NEXTHOP_IFNAME:
+		stream_putl (s, nexthop->ifindex);
+		break;
+	      case ZEBRA_NEXTHOP_IPV4_IFINDEX:
+		stream_put_in_addr (s, &nexthop->gate.ipv4);
+		stream_putl (s, nexthop->ifindex);
+		break;
+	      default:
+		/* do nothing */
+		break;
+	      }
+	    num++;
+	  }
+    
+      stream_putc_at (s, nump, num); /* store nexthop_num */
+    }
+  else
+    {
+      stream_putc (s, 0); /* distance */
+      stream_putl (s, 0); /* metric */
+      stream_putc (s, 0); /* nexthop_num */
+    }
+
+  stream_putw_at (s, 0, stream_get_endp (s));
+  
+  return zebra_server_send_message(client);
+}
+
 static int
 zsend_ipv4_import_lookup (struct zserv *client, struct prefix_ipv4 *p)
 {
@@ -891,6 +963,16 @@ zread_ipv4_nexthop_lookup (struct zserv *client, u_short length)
   return zsend_ipv4_nexthop_lookup (client, addr);
 }
 
+/* Nexthop lookup v2 for IPv4. */
+static int
+zread_ipv4_nexthop_lookup_v2 (struct zserv *client, u_short length)
+{
+  struct in_addr addr;
+
+  addr.s_addr = stream_get_ipv4 (client->ibuf);
+  return zsend_ipv4_nexthop_lookup_v2 (client, addr);
+}
+
 /* Nexthop lookup for IPv4. */
 static int
 zread_ipv4_import_lookup (struct zserv *client, u_short length)
@@ -1275,6 +1357,9 @@ zebra_client_read (struct thread *thread)
     case ZEBRA_IPV4_NEXTHOP_LOOKUP:
       zread_ipv4_nexthop_lookup (client, length);
       break;
+    case ZEBRA_IPV4_NEXTHOP_LOOKUP_V2:
+      zread_ipv4_nexthop_lookup_v2 (client, length);
+      break;
 #ifdef HAVE_IPV6
     case ZEBRA_IPV6_NEXTHOP_LOOKUP:
       zread_ipv6_nexthop_lookup (client, length);
