pimd: add support for configuring multicast static routes

Hi,

This patch adds the ability to configure multicast static routes
directly into pimd. Two source files are introduced to implement the new
feature in addition to changes to existing files.

Here is how it can be used the CLI:

interface <incoming interface>
ip mroute <outgoing interface> <group addr>                          #
for asm
or ip mroute <outgoing interface> <group addr> <source>    # for ssm

Please let me know if you have any questions or concerns,

Regards,
Jafar

Acked-by: Donald Sharp <sharpd@cumulusnetworks.com>
diff --git a/pimd/Makefile.am b/pimd/Makefile.am
index cb525f7..bf8a158 100644
--- a/pimd/Makefile.am
+++ b/pimd/Makefile.am
@@ -53,7 +53,7 @@
 	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_macro.c \
-	pim_igmp_join.c pim_ssmpingd.c pim_int.c
+	pim_igmp_join.c pim_ssmpingd.c pim_int.c pim_static.c
 
 noinst_HEADERS = \
 	pimd.h pim_version.h pim_cmd.h pim_signals.h pim_iface.h \
@@ -62,7 +62,7 @@
 	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_macro.h \
-	pim_igmp_join.h pim_ssmpingd.h pim_int.h
+	pim_igmp_join.h pim_ssmpingd.h pim_int.h pim_static.h
 
 pimd_SOURCES = \
 	pim_main.c $(libpim_a_SOURCES)
diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c
index a5d11b9..10d0c86 100644
--- a/pimd/pim_cmd.c
+++ b/pimd/pim_cmd.c
@@ -51,6 +51,7 @@
 #include "pim_macro.h"
 #include "pim_ssmpingd.h"
 #include "pim_zebra.h"
+#include "pim_static.h"
 
 static struct cmd_node pim_global_node = {
   PIM_NODE,
@@ -1626,6 +1627,44 @@
   }
 }
 
+static void static_mroute_add_all()
+{
+  struct listnode     *node;
+  struct static_route *s_route;
+
+  for (ALL_LIST_ELEMENTS_RO(qpim_static_route_list, node, s_route)) {
+    if (pim_mroute_add(&s_route->mc)) {
+      /* just log warning */
+      char source_str[100];
+      char group_str[100];
+      pim_inet4_dump("<source?>", s_route->mc.mfcc_origin, source_str, sizeof(source_str));
+      pim_inet4_dump("<group?>", s_route->mc.mfcc_mcastgrp, group_str, sizeof(group_str));
+      zlog_warn("%s %s: (S,G)=(%s,%s) failure writing MFC",
+      __FILE__, __PRETTY_FUNCTION__,
+      source_str, group_str);
+    }
+  }
+}
+
+static void static_mroute_del_all()
+{
+   struct listnode     *node;
+   struct static_route *s_route;
+
+   for (ALL_LIST_ELEMENTS_RO(qpim_static_route_list, node, s_route)) {
+     if (pim_mroute_del(&s_route->mc)) {
+       /* just log warning */
+       char source_str[100];
+       char group_str[100];
+       pim_inet4_dump("<source?>", s_route->mc.mfcc_origin, source_str, sizeof(source_str));
+       pim_inet4_dump("<group?>", s_route->mc.mfcc_mcastgrp, group_str, sizeof(group_str));
+       zlog_warn("%s %s: (S,G)=(%s,%s) failure clearing MFC",
+       __FILE__, __PRETTY_FUNCTION__,
+       source_str, group_str);
+     }
+   }
+}
+
 DEFUN (clear_ip_mroute,
        clear_ip_mroute_cmd,
        "clear ip mroute",
@@ -2133,15 +2172,17 @@
 {
   struct listnode    *node;
   struct channel_oil *c_oil;
+  struct static_route *s_route;
   time_t              now;
 
-  vty_out(vty, "Proto: I=IGMP P=PIM%s%s", VTY_NEWLINE, VTY_NEWLINE);
+  vty_out(vty, "Proto: I=IGMP P=PIM S=STATIC%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();
 
+  /* print list of PIM and IGMP routes */
   for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
     char group_str[100]; 
     char source_str[100];
@@ -2187,6 +2228,48 @@
 	      VTY_NEWLINE);
     }
   }
+
+  /* Print list of static routes */
+  for (ALL_LIST_ELEMENTS_RO(qpim_static_route_list, node, s_route)) {
+    char group_str[100];
+    char source_str[100];
+    int oif_vif_index;
+
+    pim_inet4_dump("<group?>", s_route->group, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", s_route->source, 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 = s_route->oif_ttls[oif_vif_index];
+      if (ttl < 1)
+         continue;
+
+      ifp_in  = pim_if_find_by_vif_index(s_route->iif);
+      ifp_out = pim_if_find_by_vif_index(oif_vif_index);
+
+      pim_time_uptime(oif_uptime, sizeof(oif_uptime), now - s_route->creation[oif_vif_index]);
+
+      proto[0] = '\0';
+      strcat(proto, "S");
+
+      vty_out(vty, "%-15s %-15s %-5s %-5s %5d %-6s %5d %3d %8s %s",
+         source_str,
+         group_str,
+         proto,
+         ifp_in ? ifp_in->name : "<iif?>",
+         s_route->iif,
+         ifp_out ? ifp_out->name : "<oif?>",
+         oif_vif_index,
+         ttl,
+         oif_uptime,
+         VTY_NEWLINE);
+    }
+  }
 }
 
 DEFUN (show_ip_mroute,
@@ -2204,12 +2287,14 @@
 {
   struct listnode    *node;
   struct channel_oil *c_oil;
+  struct static_route *s_route;
 
   vty_out(vty, "%s", VTY_NEWLINE);
   
   vty_out(vty, "Source          Group           Packets      Bytes WrongIf  %s",
 	  VTY_NEWLINE);
 
+  /* Print PIM and IGMP route counts */
   for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
     char group_str[100]; 
     char source_str[100];
@@ -2242,7 +2327,41 @@
 	    sgreq.bytecnt,
 	    sgreq.wrong_if,
 	    VTY_NEWLINE);
+  }
 
+   /* Print static route counts */
+  for (ALL_LIST_ELEMENTS_RO(qpim_static_route_list, node, s_route)) {
+    char group_str[100];
+    char source_str[100];
+    struct sioc_sg_req sgreq;
+
+    memset(&sgreq, 0, sizeof(sgreq));
+    sgreq.src = s_route->mc.mfcc_origin;
+    sgreq.grp = s_route->mc.mfcc_mcastgrp;
+
+    pim_inet4_dump("<group?>", s_route->mc.mfcc_mcastgrp, group_str, sizeof(group_str));
+    pim_inet4_dump("<source?>", s_route->mc.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,
+         safe_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);
   }
 }
 
@@ -2365,6 +2484,7 @@
   pim_mroute_socket_enable();
   pim_if_add_vif_all();
   mroute_add_all();
+  static_mroute_add_all();
   return CMD_SUCCESS;
 }
 
@@ -2377,6 +2497,7 @@
        "Enable IP multicast forwarding\n")
 {
   mroute_del_all();
+  static_mroute_del_all();
   pim_if_del_vif_all();
   pim_mroute_socket_disable();
   return CMD_SUCCESS;
@@ -3071,6 +3192,200 @@
   return CMD_SUCCESS;
 }
 
+DEFUN (interface_ip_mroute,
+       interface_ip_mroute_cmd,
+       "ip mroute INTERFACE A.B.C.D",
+       IP_STR
+       "Add multicast route\n"
+       "Outgoing interface name\n"
+       "Group address\n")
+{
+   struct interface *iif;
+   struct interface *oif;
+   const char       *oifname;
+   const char       *grp_str;
+   struct in_addr    grp_addr;
+   struct in_addr    src_addr;
+   int               result;
+
+   iif = vty->index;
+
+   oifname = argv[0];
+   oif = if_lookup_by_name(oifname);
+   if (!oif) {
+     vty_out(vty, "No such interface name %s%s",
+        oifname, 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, safe_strerror(errno), VTY_NEWLINE);
+     return CMD_WARNING;
+   }
+
+   src_addr.s_addr = INADDR_ANY;
+
+   if (pim_static_add(iif, oif, grp_addr, src_addr)) {
+      vty_out(vty, "Failed to add route%s", VTY_NEWLINE);
+      return CMD_WARNING;
+   }
+
+   return CMD_SUCCESS;
+}
+
+DEFUN (interface_ip_mroute_source,
+       interface_ip_mroute_source_cmd,
+       "ip mroute INTERFACE A.B.C.D A.B.C.D",
+       IP_STR
+       "Add multicast route\n"
+       "Outgoing interface name\n"
+       "Group address\n"
+       "Source address\n")
+{
+   struct interface *iif;
+   struct interface *oif;
+   const char       *oifname;
+   const char       *grp_str;
+   struct in_addr    grp_addr;
+   const char       *src_str;
+   struct in_addr    src_addr;
+   int               result;
+
+   iif = vty->index;
+
+   oifname = argv[0];
+   oif = if_lookup_by_name(oifname);
+   if (!oif) {
+     vty_out(vty, "No such interface name %s%s",
+        oifname, 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, safe_strerror(errno), VTY_NEWLINE);
+     return CMD_WARNING;
+   }
+
+   src_str = argv[2];
+   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, safe_strerror(errno), VTY_NEWLINE);
+     return CMD_WARNING;
+   }
+
+   if (pim_static_add(iif, oif, grp_addr, src_addr)) {
+      vty_out(vty, "Failed to add route%s", VTY_NEWLINE);
+      return CMD_WARNING;
+   }
+
+   return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_mroute,
+       interface_no_ip_mroute_cmd,
+       "no ip mroute INTERFACE A.B.C.D",
+       NO_STR
+       IP_STR
+       "Add multicast route\n"
+       "Outgoing interface name\n"
+       "Group Address\n")
+{
+   struct interface *iif;
+   struct interface *oif;
+   const char       *oifname;
+   const char       *grp_str;
+   struct in_addr    grp_addr;
+   struct in_addr    src_addr;
+   int               result;
+
+   iif = vty->index;
+
+   oifname = argv[0];
+   oif = if_lookup_by_name(oifname);
+   if (!oif) {
+     vty_out(vty, "No such interface name %s%s",
+        oifname, 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, safe_strerror(errno), VTY_NEWLINE);
+     return CMD_WARNING;
+   }
+
+   src_addr.s_addr = INADDR_ANY;
+
+   if (pim_static_del(iif, oif, grp_addr, src_addr)) {
+      vty_out(vty, "Failed to remove route%s", VTY_NEWLINE);
+      return CMD_WARNING;
+   }
+
+   return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_mroute_source,
+       interface_no_ip_mroute_source_cmd,
+       "no ip mroute INTERFACE A.B.C.D A.B.C.D",
+       NO_STR
+       IP_STR
+       "Add multicast route\n"
+       "Outgoing interface name\n"
+       "Group Address\n"
+       "Source Address\n")
+{
+   struct interface *iif;
+   struct interface *oif;
+   const char       *oifname;
+   const char       *grp_str;
+   struct in_addr    grp_addr;
+   const char       *src_str;
+   struct in_addr    src_addr;
+   int               result;
+
+   iif = vty->index;
+
+   oifname = argv[0];
+   oif = if_lookup_by_name(oifname);
+   if (!oif) {
+     vty_out(vty, "No such interface name %s%s",
+        oifname, 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, safe_strerror(errno), VTY_NEWLINE);
+     return CMD_WARNING;
+   }
+
+   src_str = argv[2];
+   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, safe_strerror(errno), VTY_NEWLINE);
+     return CMD_WARNING;
+   }
+
+   if (pim_static_del(iif, oif, grp_addr, src_addr)) {
+      vty_out(vty, "Failed to remove route%s", VTY_NEWLINE);
+      return CMD_WARNING;
+   }
+
+   return CMD_SUCCESS;
+}
+
 DEFUN (debug_igmp,
        debug_igmp_cmd,
        "debug igmp",
@@ -3219,6 +3534,33 @@
        UNDEBUG_STR
        DEBUG_MROUTE_STR)
 
+DEFUN (debug_static,
+       debug_static_cmd,
+       "debug static",
+       DEBUG_STR
+       DEBUG_STATIC_STR)
+{
+  PIM_DO_DEBUG_STATIC;
+  return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_static,
+       no_debug_static_cmd,
+       "no debug static",
+       NO_STR
+       DEBUG_STR
+       DEBUG_STATIC_STR)
+{
+  PIM_DONT_DEBUG_STATIC;
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_static,
+       undebug_static_cmd,
+       "undebug static",
+       UNDEBUG_STR
+       DEBUG_STATIC_STR)
+
 DEFUN (debug_pim,
        debug_pim_cmd,
        "debug pim",
@@ -4344,6 +4686,12 @@
   install_element (INTERFACE_NODE, &interface_ip_pim_ssm_cmd);
   install_element (INTERFACE_NODE, &interface_no_ip_pim_ssm_cmd); 
 
+  // Static mroutes NEB
+  install_element (INTERFACE_NODE, &interface_ip_mroute_cmd);
+  install_element (INTERFACE_NODE, &interface_ip_mroute_source_cmd);
+  install_element (INTERFACE_NODE, &interface_no_ip_mroute_cmd);
+  install_element (INTERFACE_NODE, &interface_no_ip_mroute_source_cmd);
+
   install_element (VIEW_NODE, &show_ip_igmp_interface_cmd);
   install_element (VIEW_NODE, &show_ip_igmp_join_cmd);
   install_element (VIEW_NODE, &show_ip_igmp_parameters_cmd);
@@ -4437,6 +4785,8 @@
   install_element (ENABLE_NODE, &undebug_igmp_trace_cmd);
   install_element (ENABLE_NODE, &debug_mroute_cmd);
   install_element (ENABLE_NODE, &no_debug_mroute_cmd);
+  install_element (ENABLE_NODE, &debug_static_cmd);
+  install_element (ENABLE_NODE, &no_debug_static_cmd);
   install_element (ENABLE_NODE, &debug_pim_cmd);
   install_element (ENABLE_NODE, &no_debug_pim_cmd);
   install_element (ENABLE_NODE, &undebug_pim_cmd);
@@ -4478,6 +4828,8 @@
   install_element (CONFIG_NODE, &undebug_igmp_trace_cmd);
   install_element (CONFIG_NODE, &debug_mroute_cmd);
   install_element (CONFIG_NODE, &no_debug_mroute_cmd);
+  install_element (CONFIG_NODE, &debug_static_cmd);
+  install_element (CONFIG_NODE, &no_debug_static_cmd);
   install_element (CONFIG_NODE, &debug_pim_cmd);
   install_element (CONFIG_NODE, &no_debug_pim_cmd);
   install_element (CONFIG_NODE, &undebug_pim_cmd);
diff --git a/pimd/pim_cmd.h b/pimd/pim_cmd.h
index c503740..25e2444 100644
--- a/pimd/pim_cmd.h
+++ b/pimd/pim_cmd.h
@@ -39,6 +39,7 @@
 #define DEBUG_IGMP_PACKETS_STR                      "IGMP protocol packets\n"
 #define DEBUG_IGMP_TRACE_STR                        "IGMP internal daemon activity\n"
 #define DEBUG_MROUTE_STR                            "PIM interaction with kernel MFC cache\n"
+#define DEBUG_STATIC_STR                            "PIM Static Multicast Route 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"
diff --git a/pimd/pim_static.c b/pimd/pim_static.c
new file mode 100644
index 0000000..f2b8e85
--- /dev/null
+++ b/pimd/pim_static.c
@@ -0,0 +1,305 @@
+/*
+  PIM for Quagga: add the ability to configure multicast static routes
+  Copyright (C) 2014  Nathan Bahr, ATCorp
+
+  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_static.h"
+#include "pim_time.h"
+#include "pim_str.h"
+#include "pimd.h"
+#include "pim_iface.h"
+#include "log.h"
+#include "memory.h"
+#include "linklist.h"
+
+void pim_static_route_free(struct static_route *s_route)
+{
+  XFREE(MTYPE_PIM_STATIC_ROUTE, s_route);
+}
+
+static struct static_route * static_route_alloc()
+{
+   struct static_route *s_route;
+
+   s_route = XCALLOC(MTYPE_PIM_STATIC_ROUTE, sizeof(*s_route));
+   if (!s_route) {
+     zlog_err("PIM XCALLOC(%lu) failure", sizeof(*s_route));
+     return 0;
+   }
+   return s_route;
+}
+
+static struct static_route *static_route_new(unsigned int   iif,
+                                             unsigned int   oif,
+                                             struct in_addr group,
+                                             struct in_addr source)
+{
+  struct static_route * s_route;
+  s_route = static_route_alloc();
+  if (!s_route) {
+     return 0;
+  }
+
+  s_route->group             = group;
+  s_route->source            = source;
+  s_route->iif               = iif;
+  s_route->oif_ttls[oif]     = 1;
+  s_route->oif_count         = 1;
+  s_route->mc.mfcc_origin    = source;
+  s_route->mc.mfcc_mcastgrp  = group;
+  s_route->mc.mfcc_parent    = iif;
+  s_route->mc.mfcc_ttls[oif] = 1;
+  s_route->creation[oif] = pim_time_monotonic_sec();
+
+  return s_route;
+}
+
+
+int pim_static_add(struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source)
+{
+   struct listnode *node = 0;
+   struct static_route *s_route = 0;
+   struct static_route *original_s_route = 0;
+   struct pim_interface *pim_iif = iif ? iif->info : 0;
+   struct pim_interface *pim_oif = oif ? oif->info : 0;
+   unsigned int iif_index = pim_iif ? pim_iif->mroute_vif_index : 0;
+   unsigned int oif_index = pim_oif ? pim_oif->mroute_vif_index : 0;
+
+   if (!iif_index || !oif_index) {
+      zlog_warn("%s %s: Unable to add static route: Invalid interface index(iif=%d,oif=%d)",
+               __FILE__, __PRETTY_FUNCTION__,
+               iif_index,
+               oif_index);
+      return -2;
+   }
+
+#ifdef PIM_ENFORCE_LOOPFREE_MFC
+   if (iif_index == oif_index) {
+      /* looped MFC entry */
+      zlog_warn("%s %s: Unable to add static route: Looped MFC entry(iif=%d,oif=%d)",
+               __FILE__, __PRETTY_FUNCTION__,
+               iif_index,
+               oif_index);
+      return -4;
+   }
+#endif
+
+   for (ALL_LIST_ELEMENTS_RO(qpim_static_route_list, node, s_route)) {
+      if (s_route->group.s_addr == group.s_addr &&
+          s_route->source.s_addr == source.s_addr) {
+         if (s_route->iif == iif_index &&
+             s_route->oif_ttls[oif_index]) {
+            char gifaddr_str[100];
+            char sifaddr_str[100];
+            pim_inet4_dump("<ifaddr?>", group, gifaddr_str, sizeof(gifaddr_str));
+            pim_inet4_dump("<ifaddr?>", source, sifaddr_str, sizeof(sifaddr_str));
+            zlog_warn("%s %s: Unable to add static route: Route already exists (iif=%d,oif=%d,group=%s,source=%s)",
+                     __FILE__, __PRETTY_FUNCTION__,
+                     iif_index,
+                     oif_index,
+                     gifaddr_str,
+                     sifaddr_str);
+            return -3;
+         }
+
+         /* Ok, from here on out we will be making changes to the s_route structure, but if
+          * for some reason we fail to commit these changes to the kernel, we want to be able
+          * restore the state of the list. So copy the node data and if need be, we can copy
+          * back if it fails.
+          */
+         original_s_route = static_route_alloc();
+         if (!original_s_route) {
+            return -5;
+         }
+         memcpy(original_s_route, s_route, sizeof(struct static_route));
+
+         /* Route exists and has the same input interface, but adding a new output interface */
+         if (s_route->iif == iif_index) {
+            s_route->oif_ttls[oif_index] = 1;
+            s_route->mc.mfcc_ttls[oif_index] = 1;
+            s_route->creation[oif_index] = pim_time_monotonic_sec();
+            ++s_route->oif_count;
+         } else {
+            /* input interface changed */
+            s_route->iif = iif_index;
+            s_route->mc.mfcc_parent = iif_index;
+
+#ifdef PIM_ENFORCE_LOOPFREE_MFC
+            /* check to make sure the new input was not an old output */
+            if (s_route->oif_ttls[iif_index]) {
+               s_route->oif_ttls[iif_index] = 0;
+               s_route->creation[iif_index] = 0;
+               s_route->mc.mfcc_ttls[iif_index] = 0;
+               --s_route->oif_count;
+            }
+#endif
+
+            /* now add the new output, if it is new */
+            if (!s_route->oif_ttls[oif_index]) {
+               s_route->oif_ttls[oif_index] = 1;
+               s_route->creation[oif_index] = pim_time_monotonic_sec();
+               s_route->mc.mfcc_ttls[oif_index] = 1;
+               ++s_route->oif_count;
+            }
+         }
+
+         break;
+      }
+   }
+
+   /* If node is null then we reached the end of the list without finding a match */
+   if (!node) {
+      s_route = static_route_new(iif_index, oif_index, group, source);
+      listnode_add(qpim_static_route_list, s_route);
+   }
+
+   if (pim_mroute_add(&(s_route->mc)))
+   {
+      char gifaddr_str[100];
+      char sifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", group, gifaddr_str, sizeof(gifaddr_str));
+      pim_inet4_dump("<ifaddr?>", source, sifaddr_str, sizeof(sifaddr_str));
+      zlog_warn("%s %s: Unable to add static route(iif=%d,oif=%d,group=%s,source=%s)",
+               __FILE__, __PRETTY_FUNCTION__,
+               iif_index,
+               oif_index,
+               gifaddr_str,
+               sifaddr_str);
+
+      /* Need to put s_route back to the way it was */
+      if (original_s_route) {
+         memcpy(s_route, original_s_route, sizeof(struct static_route));
+      } else {
+         /* we never stored off a copy, so it must have been a fresh new route */
+         listnode_delete(qpim_static_route_list, s_route);
+         pim_static_route_free(s_route);
+      }
+
+      return -1;
+   }
+
+   /* Make sure we free the memory for the route copy if used */
+   if (original_s_route) {
+      pim_static_route_free(original_s_route);
+   }
+
+   if (PIM_DEBUG_STATIC) {
+     char gifaddr_str[100];
+     char sifaddr_str[100];
+     pim_inet4_dump("<ifaddr?>", group, gifaddr_str, sizeof(gifaddr_str));
+     pim_inet4_dump("<ifaddr?>", source, sifaddr_str, sizeof(sifaddr_str));
+     zlog_debug("%s: Static route added(iif=%d,oif=%d,group=%s,source=%s)",
+           __PRETTY_FUNCTION__,
+           iif_index,
+           oif_index,
+           gifaddr_str,
+           sifaddr_str);
+   }
+
+   return 0;
+}
+
+int pim_static_del(struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source)
+{
+   struct listnode *node = 0;
+   struct listnode *nextnode = 0;
+   struct static_route *s_route = 0;
+   struct pim_interface *pim_iif = iif ? iif->info : 0;
+   struct pim_interface *pim_oif = oif ? oif->info : 0;
+   unsigned int iif_index = pim_iif ? pim_iif->mroute_vif_index : 0;
+   unsigned int oif_index = pim_oif ? pim_oif->mroute_vif_index : 0;
+
+   if (!iif_index || !oif_index) {
+      zlog_warn("%s %s: Unable to remove static route: Invalid interface index(iif=%d,oif=%d)",
+               __FILE__, __PRETTY_FUNCTION__,
+               iif_index,
+               oif_index);
+      return -2;
+   }
+
+   for (ALL_LIST_ELEMENTS(qpim_static_route_list, node, nextnode, s_route)) {
+      if (s_route->iif == iif_index &&
+          s_route->group.s_addr == group.s_addr &&
+          s_route->source.s_addr == source.s_addr &&
+          s_route->oif_ttls[oif_index]) {
+         s_route->oif_ttls[oif_index] = 0;
+         s_route->mc.mfcc_ttls[oif_index] = 0;
+         --s_route->oif_count;
+
+         /* If there are no more outputs then delete the whole route, otherwise set the route with the new outputs */
+         if (s_route->oif_count <= 0 ? pim_mroute_del(&s_route->mc) : pim_mroute_add(&s_route->mc)) {
+            char gifaddr_str[100];
+            char sifaddr_str[100];
+            pim_inet4_dump("<ifaddr?>", group, gifaddr_str, sizeof(gifaddr_str));
+            pim_inet4_dump("<ifaddr?>", source, sifaddr_str, sizeof(sifaddr_str));
+            zlog_warn("%s %s: Unable to remove static route(iif=%d,oif=%d,group=%s,source=%s)",
+                     __FILE__, __PRETTY_FUNCTION__,
+                     iif_index,
+                     oif_index,
+                     gifaddr_str,
+                     sifaddr_str);
+
+            s_route->oif_ttls[oif_index] = 1;
+            s_route->mc.mfcc_ttls[oif_index] = 1;
+            ++s_route->oif_count;
+
+            return -1;
+         }
+
+         s_route->creation[oif_index] = 0;
+
+         if (s_route->oif_count <= 0) {
+            listnode_delete(qpim_static_route_list, s_route);
+            pim_static_route_free(s_route);
+         }
+
+         if (PIM_DEBUG_STATIC) {
+           char gifaddr_str[100];
+           char sifaddr_str[100];
+           pim_inet4_dump("<ifaddr?>", group, gifaddr_str, sizeof(gifaddr_str));
+           pim_inet4_dump("<ifaddr?>", source, sifaddr_str, sizeof(sifaddr_str));
+           zlog_debug("%s: Static route removed(iif=%d,oif=%d,group=%s,source=%s)",
+                 __PRETTY_FUNCTION__,
+                 iif_index,
+                 oif_index,
+                 gifaddr_str,
+                 sifaddr_str);
+         }
+
+         break;
+      }
+   }
+
+   if (!node) {
+      char gifaddr_str[100];
+      char sifaddr_str[100];
+      pim_inet4_dump("<ifaddr?>", group, gifaddr_str, sizeof(gifaddr_str));
+      pim_inet4_dump("<ifaddr?>", source, sifaddr_str, sizeof(sifaddr_str));
+      zlog_warn("%s %s: Unable to remove static route: Route does not exist(iif=%d,oif=%d,group=%s,source=%s)",
+               __FILE__, __PRETTY_FUNCTION__,
+               iif_index,
+               oif_index,
+               gifaddr_str,
+               sifaddr_str);
+      return -3;
+   }
+
+   return 0;
+}
diff --git a/pimd/pim_static.h b/pimd/pim_static.h
new file mode 100644
index 0000000..3a09693
--- /dev/null
+++ b/pimd/pim_static.h
@@ -0,0 +1,47 @@
+/*
+  PIM for Quagga: add the ability to configure multicast static routes
+  Copyright (C) 2014  Nathan Bahr, ATCorp
+
+  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_STATIC_H_
+#define PIM_STATIC_H_
+
+#include <zebra.h>
+#include "pim_mroute.h"
+#include "if.h"
+
+struct static_route {
+   /* Each static route is unique by these pair of addresses */
+   struct in_addr group;
+   struct in_addr source;
+
+   unsigned int   iif;
+   unsigned char  oif_ttls[MAXVIFS];
+   int            oif_count;
+   struct mfcctl  mc;
+   time_t         creation[MAXVIFS];
+};
+
+void pim_static_route_free(struct static_route *s_route);
+
+int pim_static_add(struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source);
+int pim_static_del(struct interface *iif, struct interface *oif, struct in_addr group, struct in_addr source);
+
+#endif /* PIM_STATIC_H_ */
diff --git a/pimd/pimd.c b/pimd/pimd.c
index 3797d4f..97fb223 100644
--- a/pimd/pimd.c
+++ b/pimd/pimd.c
@@ -36,6 +36,7 @@
 #include "pim_upstream.h"
 #include "pim_rpf.h"
 #include "pim_ssmpingd.h"
+#include "pim_static.h"
 
 const char *const PIM_ALL_SYSTEMS      = MCAST_ALL_SYSTEMS;
 const char *const PIM_ALL_ROUTERS      = MCAST_ALL_ROUTERS;
@@ -68,6 +69,7 @@
 int64_t                   qpim_mroute_add_last = 0;
 int64_t                   qpim_mroute_del_events = 0;
 int64_t                   qpim_mroute_del_last = 0;
+struct list              *qpim_static_route_list = 0;
 
 static void pim_free()
 {
@@ -78,6 +80,9 @@
 
   if (qpim_upstream_list)
     list_free(qpim_upstream_list);
+
+  if (qpim_static_route_list)
+     list_free(qpim_static_route_list);
 }
 
 void pim_init()
@@ -109,6 +114,14 @@
   }
   qpim_upstream_list->del = (void (*)(void *)) pim_upstream_free;
 
+  qpim_static_route_list = list_new();
+  if (!qpim_static_route_list) {
+    zlog_err("%s %s: failure: static_route_list=list_new()",
+        __FILE__, __PRETTY_FUNCTION__);
+    return;
+  }
+  qpim_static_route_list->del = (void (*)(void *)) pim_static_route_free;
+
   qpim_mroute_socket_fd = -1; /* mark mroute as disabled */
   qpim_mroute_oif_highest_vif_index = -1;
 
diff --git a/pimd/pimd.h b/pimd/pimd.h
index 22a2922..aed26be 100644
--- a/pimd/pimd.h
+++ b/pimd/pimd.h
@@ -64,8 +64,9 @@
 #define PIM_MASK_ZEBRA               (1 << 8)
 #define PIM_MASK_SSMPINGD            (1 << 9)
 #define PIM_MASK_MROUTE              (1 << 10)
-#define PIM_MASK_PIM_HELLO	     (1 << 11)
-#define PIM_MASK_PIM_J_P	     (1 << 12)
+#define PIM_MASK_PIM_HELLO           (1 << 11)
+#define PIM_MASK_PIM_J_P             (1 << 12)
+#define PIM_MASK_STATIC              (1 << 13)
 
 const char *const PIM_ALL_SYSTEMS;
 const char *const PIM_ALL_ROUTERS;
@@ -99,6 +100,7 @@
 int64_t                   qpim_mroute_add_last;
 int64_t                   qpim_mroute_del_events;
 int64_t                   qpim_mroute_del_last;
+struct list              *qpim_static_route_list; /* list of routes added statically */
 
 #define PIM_JP_HOLDTIME (qpim_t_periodic * 7 / 2)
 
@@ -116,8 +118,9 @@
 #define PIM_DEBUG_ZEBRA               (qpim_debugs & PIM_MASK_ZEBRA)
 #define PIM_DEBUG_SSMPINGD            (qpim_debugs & PIM_MASK_SSMPINGD)
 #define PIM_DEBUG_MROUTE              (qpim_debugs & PIM_MASK_MROUTE)
-#define PIM_DEBUG_PIM_HELLO	      (qpim_debugs & PIM_MASK_PIM_HELLO)
-#define PIM_DEBUG_PIM_J_P	      (qpim_debugs & PIM_MASK_PIM_J_P)
+#define PIM_DEBUG_PIM_HELLO           (qpim_debugs & PIM_MASK_PIM_HELLO)
+#define PIM_DEBUG_PIM_J_P             (qpim_debugs & PIM_MASK_PIM_J_P)
+#define PIM_DEBUG_STATIC              (qpim_debugs & PIM_MASK_STATIC)
 
 #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))
@@ -136,6 +139,7 @@
 #define PIM_DO_DEBUG_MROUTE              (qpim_debugs |= PIM_MASK_MROUTE)
 #define PIM_DO_DEBUG_PIM_HELLO           (qpim_debugs |= PIM_MASK_PIM_HELLO)
 #define PIM_DO_DEBUG_PIM_J_P             (qpim_debugs |= PIM_MASK_PIM_J_P)
+#define PIM_DO_DEBUG_STATIC              (qpim_debugs |= PIM_MASK_STATIC)
 
 #define PIM_DONT_DEBUG_PIM_EVENTS          (qpim_debugs &= ~PIM_MASK_PIM_EVENTS)
 #define PIM_DONT_DEBUG_PIM_PACKETS         (qpim_debugs &= ~PIM_MASK_PIM_PACKETS)
@@ -150,6 +154,7 @@
 #define PIM_DONT_DEBUG_MROUTE              (qpim_debugs &= ~PIM_MASK_MROUTE)
 #define PIM_DONT_DEBUG_PIM_HELLO           (qpim_debugs &= ~PIM_MASK_PIM_HELLO)
 #define PIM_DONT_DEBUG_PIM_J_P             (qpim_debugs &= ~PIM_MASK_PIM_J_P)
+#define PIM_DONT_DEBUG_STATIC              (qpim_debugs &= ~PIM_MASK_STATIC)
 
 void pim_init(void);
 void pim_terminate(void);