[pim] Initial pim 0.155
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 */
+}