isisd: allow to adjust lsp-mtu

Signed-off-by: Christian Franke <chris@opensourcerouting.org>
Acked-by: Donald Sharp <sharpd@cumulusnetworks.com>
diff --git a/isisd/isis_circuit.c b/isisd/isis_circuit.c
index 6ecaca6..9fe11c2 100644
--- a/isisd/isis_circuit.c
+++ b/isisd/isis_circuit.c
@@ -578,6 +578,29 @@
     }
 }
 
+size_t
+isis_circuit_pdu_size(struct isis_circuit *circuit)
+{
+  return ISO_MTU(circuit);
+}
+
+void
+isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream)
+{
+  size_t stream_size = isis_circuit_pdu_size(circuit);
+
+  if (!*stream)
+    {
+      *stream = stream_new(stream_size);
+    }
+  else
+    {
+      if (STREAM_SIZE(*stream) != stream_size)
+        stream_resize(*stream, stream_size);
+      stream_reset(*stream);
+    }
+}
+
 int
 isis_circuit_up (struct isis_circuit *circuit)
 {
@@ -592,6 +615,15 @@
   if (circuit->is_passive)
     return ISIS_OK;
 
+  if (circuit->area->lsp_mtu > isis_circuit_pdu_size(circuit))
+    {
+      zlog_err("Interface MTU %zu on %s is too low to support area lsp mtu %u!",
+               isis_circuit_pdu_size(circuit), circuit->interface->name,
+               circuit->area->lsp_mtu);
+      isis_circuit_down(circuit);
+      return ISIS_ERROR;
+    }
+
   if (circuit->circ_type == CIRCUIT_T_BROADCAST)
     {
       /*
@@ -624,9 +656,6 @@
       circuit->u.bc.adjdb[0] = list_new ();
       circuit->u.bc.adjdb[1] = list_new ();
 
-      if (circuit->area->min_bcast_mtu == 0 ||
-          ISO_MTU (circuit) < circuit->area->min_bcast_mtu)
-        circuit->area->min_bcast_mtu = ISO_MTU (circuit);
       /*
        * ISO 10589 - 8.4.1 Enabling of broadcast circuits
        */
@@ -688,11 +717,8 @@
     }
 
   /* initialize the circuit streams after opening connection */
-  if (circuit->rcv_stream == NULL)
-    circuit->rcv_stream = stream_new (ISO_MTU (circuit));
-
-  if (circuit->snd_stream == NULL)
-    circuit->snd_stream = stream_new (ISO_MTU (circuit));
+  isis_circuit_stream(circuit, &circuit->rcv_stream);
+  isis_circuit_stream(circuit, &circuit->snd_stream);
 
 #ifdef GNU_LINUX
   THREAD_READ_ON (master, circuit->t_read, isis_receive, circuit,
@@ -1193,6 +1219,7 @@
   struct isis_circuit *circuit;
   struct interface *ifp;
   struct isis_area *area;
+  int rv;
 
   ifp = (struct interface *) vty->index;
   assert (ifp);
@@ -1221,16 +1248,25 @@
   area = vty->index;
 
   circuit = isis_csm_state_change (ISIS_ENABLE, circuit, area);
-  isis_circuit_if_bind (circuit, ifp);
+  if (circuit->state != C_STATE_CONF && circuit->state != C_STATE_UP)
+    {
+      vty_out(vty, "Couldn't bring up interface, please check log.%s", VTY_NEWLINE);
+      rv = CMD_WARNING;
+    }
+  else
+    {
+      isis_circuit_if_bind (circuit, ifp);
 
-  circuit->ip_router = 1;
-  area->ip_circuits++;
-  circuit_update_nlpids (circuit);
+      circuit->ip_router = 1;
+      area->ip_circuits++;
+      circuit_update_nlpids (circuit);
+      rv = CMD_SUCCESS;
+    }
 
   vty->node = INTERFACE_NODE;
   vty->index = ifp;
 
-  return CMD_SUCCESS;
+  return rv;
 }
 
 DEFUN (no_ip_router_isis,
@@ -1291,6 +1327,7 @@
   struct isis_circuit *circuit;
   struct interface *ifp;
   struct isis_area *area;
+  int rv;
 
   ifp = (struct interface *) vty->index;
   assert (ifp);
@@ -1319,16 +1356,25 @@
   area = vty->index;
 
   circuit = isis_csm_state_change (ISIS_ENABLE, circuit, area);
-  isis_circuit_if_bind (circuit, ifp);
+  if (circuit->state != C_STATE_CONF && circuit->state != C_STATE_UP)
+    {
+      vty_out(vty, "Couldn't bring up interface, please check log.%s", VTY_NEWLINE);
+      rv = CMD_WARNING;
+    }
+  else
+    {
+      isis_circuit_if_bind (circuit, ifp);
 
-  circuit->ipv6_router = 1;
-  area->ipv6_circuits++;
-  circuit_update_nlpids (circuit);
+      circuit->ipv6_router = 1;
+      area->ipv6_circuits++;
+      circuit_update_nlpids (circuit);
+      rv = CMD_SUCCESS;
+    }
 
   vty->node = INTERFACE_NODE;
   vty->index = ifp;
 
-  return CMD_SUCCESS;
+  return rv;
 }
 
 DEFUN (no_ipv6_router_isis,
diff --git a/isisd/isis_circuit.h b/isisd/isis_circuit.h
index d86fee0..d883879 100644
--- a/isisd/isis_circuit.h
+++ b/isisd/isis_circuit.h
@@ -164,5 +164,7 @@
 void circuit_update_nlpids (struct isis_circuit *circuit);
 void isis_circuit_print_vty (struct isis_circuit *circuit, struct vty *vty,
                              char detail);
+size_t isis_circuit_pdu_size(struct isis_circuit *circuit);
+void isis_circuit_stream(struct isis_circuit *circuit, struct stream **stream);
 
 #endif /* _ZEBRA_ISIS_CIRCUIT_H */
diff --git a/isisd/isis_constants.h b/isisd/isis_constants.h
index bb2c4b4..8b21894 100644
--- a/isisd/isis_constants.h
+++ b/isisd/isis_constants.h
@@ -34,7 +34,6 @@
 #define ISO_SAP                       0xFE
 #define INTRADOMAIN_ROUTEING_SELECTOR 0
 #define SEQUENCE_MODULUS              4294967296
-#define RECEIVE_LSP_BUFFER_SIZE       1492
 
 /*
  * implementation specific jitter values
diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c
index 050a9f9..dc18aa0 100644
--- a/isisd/isis_lsp.c
+++ b/isisd/isis_lsp.c
@@ -575,15 +575,16 @@
 }
 
 struct isis_lsp *
-lsp_new (u_char * lsp_id, u_int16_t rem_lifetime, u_int32_t seq_num,
-	 u_int8_t lsp_bits, u_int16_t checksum, int level)
+lsp_new(struct isis_area *area, u_char * lsp_id,
+	u_int16_t rem_lifetime, u_int32_t seq_num,
+	u_int8_t lsp_bits, u_int16_t checksum, int level)
 {
   struct isis_lsp *lsp;
 
   lsp = XCALLOC (MTYPE_ISIS_LSP, sizeof (struct isis_lsp));
+  lsp->area = area;
 
-  /* FIXME: Should be minimal mtu? */
-  lsp->pdu = stream_new (1500);
+  lsp->pdu = stream_new(LLC_LEN + area->lsp_mtu);
   if (LSP_FRAGMENT (lsp_id) == 0)
     lsp->lspu.frags = list_new ();
   lsp->isis_header = (struct isis_fixed_hdr *) (STREAM_DATA (lsp->pdu));
@@ -1131,7 +1132,7 @@
       lsp_clear_data (lsp);
       return lsp;
     }
-  lsp = lsp_new (frag_id, ntohs(lsp0->lsp_header->rem_lifetime), 0,
+  lsp = lsp_new (area, frag_id, ntohs(lsp0->lsp_header->rem_lifetime), 0,
                  lsp_bits_generate (level, area->overload_bit,
                  area->attached_bit), 0, level);
   lsp->area = area;
@@ -1593,7 +1594,7 @@
                               area->lspdb[level - 1]);
     }
   rem_lifetime = lsp_rem_lifetime (area, level);
-  newlsp = lsp_new (lspid, rem_lifetime, seq_num,
+  newlsp = lsp_new (area, lspid, rem_lifetime, seq_num,
                     area->is_type | area->overload_bit | area->attached_bit,
                     0, level);
   newlsp->area = area;
@@ -1966,7 +1967,7 @@
 
   rem_lifetime = lsp_rem_lifetime (circuit->area, level);
   /* RFC3787  section 4 SHOULD not set overload bit in pseudo LSPs */
-  lsp = lsp_new (lsp_id, rem_lifetime, 1,
+  lsp = lsp_new (circuit->area, lsp_id, rem_lifetime, 1,
                  circuit->area->is_type | circuit->area->attached_bit,
                  0, level);
   lsp->area = circuit->area;
@@ -2356,8 +2357,7 @@
   lsp->area = area;
   lsp->level = ((lsp_hdr->lsp_bits & LSPBIT_IST) == IS_LEVEL_1) ?
     IS_LEVEL_1 : IS_LEVEL_2;
-  /* FIXME: Should be minimal mtu? */
-  lsp->pdu = stream_new (1500);
+  lsp->pdu = stream_new(LLC_LEN + area->lsp_mtu);
   lsp->isis_header = (struct isis_fixed_hdr *) STREAM_DATA (lsp->pdu);
   fill_fixed_hdr (lsp->isis_header, (lsp->level == IS_LEVEL_1) ? L1_LINK_STATE
 		  : L2_LINK_STATE);
@@ -2479,11 +2479,11 @@
       lspid[ISIS_SYS_ID_LEN - 2] = ((i >> 8) & 0xFF);
 
       rem_lifetime = lsp_rem_lifetime (area, IS_LEVEL_1);
-      lsp = lsp_new (lspid, rem_lifetime, 1, IS_LEVEL_1 | area->overload_bit
-                     | area->attached_bit, 0, 1);
+      lsp = lsp_new (area, lspid, rem_lifetime, 1,
+                     IS_LEVEL_1 | area->overload_bit | area->attached_bit,
+                     0, 1);
       if (!lsp)
 	return;
-      lsp->area = area;
       lsp->from_topology = 1;
 
       /* Creating LSP data based on topology info. */
diff --git a/isisd/isis_lsp.h b/isisd/isis_lsp.h
index 6e7f745..92a5dfe 100644
--- a/isisd/isis_lsp.h
+++ b/isisd/isis_lsp.h
@@ -66,7 +66,8 @@
 int lsp_generate_pseudo (struct isis_circuit *circuit, int level);
 int lsp_regenerate_schedule_pseudo (struct isis_circuit *circuit, int level);
 
-struct isis_lsp *lsp_new (u_char * lsp_id, u_int16_t rem_lifetime,
+struct isis_lsp *lsp_new (struct isis_area *area, u_char * lsp_id,
+			  u_int16_t rem_lifetime,
 			  u_int32_t seq_num, u_int8_t lsp_bits,
 			  u_int16_t checksum, int level);
 struct isis_lsp *lsp_new_from_stream_ptr (struct stream *stream,
diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c
index 26efe4d..0c3f57f 100644
--- a/isisd/isis_pdu.c
+++ b/isisd/isis_pdu.c
@@ -1895,9 +1895,9 @@
 	    if (entry->rem_lifetime && entry->checksum && entry->seq_num &&
 		memcmp (entry->lsp_id, isis->sysid, ISIS_SYS_ID_LEN))
 	      {
-		lsp = lsp_new (entry->lsp_id, ntohs (entry->rem_lifetime),
-			       0, 0, entry->checksum, level);
-		lsp->area = circuit->area;
+		lsp = lsp_new(circuit->area, entry->lsp_id,
+			      ntohs(entry->rem_lifetime),
+			      0, 0, entry->checksum, level);
 		lsp_insert (lsp, circuit->area->lspdb[level - 1]);
 		ISIS_FLAGS_CLEAR_ALL (lsp->SRMflags);
 		ISIS_SET_FLAG (lsp->SSNflags, circuit);
@@ -2121,10 +2121,7 @@
   circuit = THREAD_ARG (thread);
   assert (circuit);
 
-  if (circuit->rcv_stream == NULL)
-    circuit->rcv_stream = stream_new (ISO_MTU (circuit));
-  else
-    stream_reset (circuit->rcv_stream);
+  isis_circuit_stream(circuit, &circuit->rcv_stream);
 
   retval = circuit->rx (circuit, ssnpa);
   circuit->t_read = NULL;
@@ -2160,10 +2157,7 @@
 
   circuit->t_read = NULL;
 
-  if (circuit->rcv_stream == NULL)
-    circuit->rcv_stream = stream_new (ISO_MTU (circuit));
-  else
-    stream_reset (circuit->rcv_stream);
+  isis_circuit_stream(circuit, &circuit->rcv_stream);
 
   retval = circuit->rx (circuit, ssnpa);
 
@@ -2268,10 +2262,7 @@
       return ISIS_WARNING;
     }
 
-  if (!circuit->snd_stream)
-    circuit->snd_stream = stream_new (ISO_MTU (circuit));
-  else
-    stream_reset (circuit->snd_stream);
+  isis_circuit_stream(circuit, &circuit->snd_stream);
 
   if (circuit->circ_type == CIRCUIT_T_BROADCAST)
     if (level == IS_LEVEL_1)
@@ -2527,10 +2518,7 @@
   unsigned long auth_tlv_offset = 0;
   int retval = ISIS_OK;
 
-  if (circuit->snd_stream == NULL)
-    circuit->snd_stream = stream_new (ISO_MTU (circuit));
-  else
-    stream_reset (circuit->snd_stream);
+  isis_circuit_stream(circuit, &circuit->snd_stream);
 
   if (level == IS_LEVEL_1)
     fill_fixed_hdr_andstream (&fixed_hdr, L1_COMPLETE_SEQ_NUM,
@@ -2854,10 +2842,7 @@
   unsigned long auth_tlv_offset = 0;
   int retval = ISIS_OK;
 
-  if (circuit->snd_stream == NULL)
-    circuit->snd_stream = stream_new (ISO_MTU (circuit));
-  else
-    stream_reset (circuit->snd_stream);
+  isis_circuit_stream(circuit, &circuit->snd_stream);
 
   if (level == IS_LEVEL_1)
     fill_fixed_hdr_andstream (&fixed_hdr, L1_PARTIAL_SEQ_NUM,
@@ -3179,10 +3164,7 @@
   u_int16_t length;
   struct isis_fixed_hdr fixed_hdr;
 
-  if (!circuit->snd_stream)
-    circuit->snd_stream = stream_new (ISO_MTU (circuit));
-  else
-    stream_reset (circuit->snd_stream);
+  isis_circuit_stream(circuit, &circuit->snd_stream);
 
   //  fill_llc_hdr (stream);
   if (level == IS_LEVEL_1)
diff --git a/isisd/isisd.c b/isisd/isisd.c
index 20b8e50..7146689 100644
--- a/isisd/isisd.c
+++ b/isisd/isisd.c
@@ -158,13 +158,11 @@
   area->oldmetric = 0;
   area->newmetric = 1;
   area->lsp_frag_threshold = 90;
+  area->lsp_mtu = DEFAULT_LSP_MTU;
 #ifdef TOPOLOGY_GENERATE
   memcpy (area->topology_baseis, DEFAULT_TOPOLOGY_BASEIS, ISIS_SYS_ID_LEN);
 #endif /* TOPOLOGY_GENERATE */
 
-  /* FIXME: Think of a better way... */
-  area->min_bcast_mtu = 1497;
-
   area->area_tag = strdup (area_tag);
   listnode_add (isis->area_list, area);
   area->isis = isis;
@@ -1545,6 +1543,76 @@
   return area_clear_net_title (vty, argv[0]);
 }
 
+static
+int area_set_lsp_mtu(struct vty *vty, struct isis_area *area, unsigned int lsp_mtu)
+{
+  struct isis_circuit *circuit;
+  struct listnode *node;
+
+  for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
+    {
+      if(lsp_mtu > isis_circuit_pdu_size(circuit))
+        {
+          vty_out(vty, "ISIS area contains circuit %s, which has a maximum PDU size of %zu.%s",
+                  circuit->interface->name, isis_circuit_pdu_size(circuit),
+                  VTY_NEWLINE);
+          return CMD_ERR_AMBIGUOUS;
+        }
+    }
+
+  area->lsp_mtu = lsp_mtu;
+  lsp_regenerate_schedule(area, IS_LEVEL_1_AND_2, 1);
+
+  return CMD_SUCCESS;
+}
+
+DEFUN (area_lsp_mtu,
+       area_lsp_mtu_cmd,
+       "lsp-mtu <128-4352>",
+       "Configure the maximum size of generated LSPs\n"
+       "Maximum size of generated LSPs\n")
+{
+  struct isis_area *area;
+
+  area = vty->index;
+  if (!area)
+    {
+      vty_out (vty, "Can't find ISIS instance %s", VTY_NEWLINE);
+      return CMD_ERR_NO_MATCH;
+    }
+
+  unsigned int lsp_mtu;
+
+  VTY_GET_INTEGER_RANGE("lsp-mtu", lsp_mtu, argv[0], 128, 4352);
+
+  return area_set_lsp_mtu(vty, area, lsp_mtu);
+}
+
+DEFUN(no_area_lsp_mtu,
+      no_area_lsp_mtu_cmd,
+      "no lsp-mtu",
+      NO_STR
+      "Configure the maximum size of generated LSPs\n")
+{
+  struct isis_area *area;
+
+  area = vty->index;
+  if (!area)
+    {
+      vty_out (vty, "Can't find ISIS instance %s", VTY_NEWLINE);
+      return CMD_ERR_NO_MATCH;
+    }
+
+  return area_set_lsp_mtu(vty, area, DEFAULT_LSP_MTU);
+}
+
+ALIAS(no_area_lsp_mtu,
+      no_area_lsp_mtu_arg_cmd,
+      "no lsp-mtu <128-4352>",
+      NO_STR
+      "Configure the maximum size of generated LSPs\n"
+      "Maximum size of generated LSPs\n");
+
 DEFUN (area_passwd_md5,
        area_passwd_md5_cmd,
        "area-password md5 WORD",
@@ -2990,6 +3058,12 @@
 		write++;
 	      }
 	  }
+	if (area->lsp_mtu != DEFAULT_LSP_MTU)
+	  {
+	    vty_out(vty, " lsp-mtu %u%s", area->lsp_mtu, VTY_NEWLINE);
+	    write++;
+	  }
+
 	/* Minimum SPF interval. */
 	if (area->min_spf_interval[0] == area->min_spf_interval[1])
 	  {
@@ -3223,6 +3297,10 @@
   install_element (ISIS_NODE, &is_type_cmd);
   install_element (ISIS_NODE, &no_is_type_cmd);
 
+  install_element (ISIS_NODE, &area_lsp_mtu_cmd);
+  install_element (ISIS_NODE, &no_area_lsp_mtu_cmd);
+  install_element (ISIS_NODE, &no_area_lsp_mtu_arg_cmd);
+
   install_element (ISIS_NODE, &area_passwd_md5_cmd);
   install_element (ISIS_NODE, &area_passwd_md5_snpauth_cmd);
   install_element (ISIS_NODE, &area_passwd_clear_cmd);
diff --git a/isisd/isisd.h b/isisd/isisd.h
index 838a08b..6e95e8e 100644
--- a/isisd/isisd.h
+++ b/isisd/isisd.h
@@ -91,7 +91,8 @@
   struct isis_spftree *spftree6[ISIS_LEVELS];	  /* The v6 SPTs */
   struct route_table *route_table6[ISIS_LEVELS];  /* IPv6 routes */
 #endif
-  unsigned int min_bcast_mtu;
+#define DEFAULT_LSP_MTU 1497
+  unsigned int lsp_mtu;				  /* Size of LSPs to generate */
   struct list *circuit_list;	/* IS-IS circuits */
   struct flags flags;
   struct thread *t_tick;	/* LSP walker */