ospf6d: fix neighbor state machine (faster lsdb sync, RFC compliance)

The OSPFv3 code doesn't do the following things right as part of an adjacency
bringup:
 - Transmit DbDesc frames appropriately to ensure faster state transition to
   Loading state
 - Transmit LsReq frames when switching to exchange state and on receipt of
   an LS update in Loading state
 - Requesting LSAs multiple times in LsReq.

It currently uses retransmit timer expiry to send the LsReq and DbDesc frames
which significantly slows down large lsdb syncs.

Signed-off-by: Dinesh G Dutt <ddutt at cumulusnetworks.com>
Reviewed-by: Scott Feldman <sfeldma at cumulusnetworks.com>
Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
diff --git a/ospf6d/ospf6_flood.c b/ospf6d/ospf6_flood.c
index beae699..e02a432 100644
--- a/ospf6d/ospf6_flood.c
+++ b/ospf6d/ospf6_flood.c
@@ -206,8 +206,8 @@
 void
 ospf6_install_lsa (struct ospf6_lsa *lsa)
 {
-  struct ospf6_lsa *old;
   struct timeval now;
+  struct ospf6_lsa *old;
 
   if (IS_OSPF6_DEBUG_LSA_TYPE (lsa->header->type) ||
       IS_OSPF6_DEBUG_EXAMIN_TYPE (lsa->header->type))
@@ -290,7 +290,7 @@
               if (ospf6_lsa_compare (lsa, req) > 0)
                 {
                   if (is_debug)
-                    zlog_debug ("Requesting is newer, next neighbor");
+                    zlog_debug ("Requesting is older, next neighbor");
                   continue;
                 }
 
@@ -298,18 +298,30 @@
                  examin next neighbor */
               if (ospf6_lsa_compare (lsa, req) == 0)
                 {
-                  if (is_debug)
-                    zlog_debug ("Requesting the same, remove it, next neighbor");
+		  if (is_debug)
+		    zlog_debug ("Requesting the same, remove it, next neighbor");
+		  if (req == on->last_ls_req)
+		    {
+		      ospf6_lsa_unlock (req);
+		      on->last_ls_req = NULL;
+		    }
                   ospf6_lsdb_remove (req, on->request_list);
+		  ospf6_check_nbr_loading (on);
                   continue;
                 }
 
               /* If the new LSA is more recent, delete from request-list */
               if (ospf6_lsa_compare (lsa, req) < 0)
                 {
-                  if (is_debug)
-                    zlog_debug ("Received is newer, remove requesting");
+		  if (is_debug)
+		    zlog_debug ("Received is newer, remove requesting");
+		  if (req == on->last_ls_req)
+		    {
+		      ospf6_lsa_unlock (req);
+		      on->last_ls_req = NULL;
+		    }
                   ospf6_lsdb_remove (req, on->request_list);
+		  ospf6_check_nbr_loading (on);
                   /* fall through */
                 }
             }
@@ -796,7 +808,7 @@
     {
       /* log */
       if (is_debug)
-        zlog_debug ("Drop MaxAge LSA with direct acknowledgement.");
+	zlog_debug ("Drop MaxAge LSA with direct acknowledgement.");
 
       /* a) Acknowledge back to neighbor (Direct acknowledgement, 13.5) */
       ospf6_lsdb_add (ospf6_lsa_copy (new), from->lsack_list);
@@ -950,8 +962,8 @@
               zlog_debug ("The LSA is in Seqnumber Wrapping");
               zlog_debug ("MaxAge & MaxSeqNum, discard");
             }
-          ospf6_lsa_delete (new);
-          return;
+	  ospf6_lsa_delete (new);
+	  return;
         }
 
       /* Otherwise, Send database copy of this LSA to this neighbor */
@@ -968,8 +980,8 @@
           if (from->thread_send_lsupdate == NULL)
             from->thread_send_lsupdate =
               thread_add_event (master, ospf6_lsupdate_send_neighbor, from, 0);
-          ospf6_lsa_delete (new);
-          return;
+	  ospf6_lsa_delete (new);
+	  return;
         }
       return;
     }
diff --git a/ospf6d/ospf6_message.c b/ospf6d/ospf6_message.c
index 31db9a4..dcbb36b 100644
--- a/ospf6d/ospf6_message.c
+++ b/ospf6d/ospf6_message.c
@@ -517,20 +517,20 @@
         {
           if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
             zlog_debug ("Add request (No database copy)");
-          ospf6_lsdb_add (his, on->request_list);
+          ospf6_lsdb_add (ospf6_lsa_copy(his), on->request_list);
         }
       else if (ospf6_lsa_compare (his, mine) < 0)
         {
           if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
             zlog_debug ("Add request (Received MoreRecent)");
-          ospf6_lsdb_add (his, on->request_list);
+          ospf6_lsdb_add (ospf6_lsa_copy(his), on->request_list);
         }
       else
         {
           if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
             zlog_debug ("Discard (Existing MoreRecent)");
-          ospf6_lsa_delete (his);
         }
+      ospf6_lsa_delete (his);
     }
 
   assert (p == OSPF6_MESSAGE_END (oh));
@@ -539,7 +539,7 @@
   on->dbdesc_seqnum ++;
 
   /* schedule send lsreq */
-  if (on->thread_send_lsreq == NULL)
+  if (on->request_list->count && (on->thread_send_lsreq == NULL))
     on->thread_send_lsreq =
       thread_add_event (master, ospf6_lsreq_send, on, 0);
 
@@ -735,10 +735,9 @@
         {
           if (IS_OSPF6_DEBUG_MESSAGE (oh->type, RECV))
             zlog_debug ("Add request-list: %s", his->name);
-          ospf6_lsdb_add (his, on->request_list);
+          ospf6_lsdb_add (ospf6_lsa_copy(his), on->request_list);
         }
-      else
-        ospf6_lsa_delete (his);
+      ospf6_lsa_delete (his);
     }
 
   assert (p == OSPF6_MESSAGE_END (oh));
@@ -747,7 +746,8 @@
   on->dbdesc_seqnum = ntohl (dbdesc->seqnum);
 
   /* schedule send lsreq */
-  if (on->thread_send_lsreq == NULL)
+  if ((on->thread_send_lsreq == NULL) &&
+      (on->request_list->count))
     on->thread_send_lsreq =
       thread_add_event (master, ospf6_lsreq_send, on, 0);
 
@@ -1351,19 +1351,6 @@
 
   assert (p == OSPF6_MESSAGE_END (oh));
 
-  /* RFC2328 Section 10.9: When the neighbor responds to these requests
-     with the proper Link State Update packet(s), the Link state request
-     list is truncated and a new Link State Request packet is sent. */
-  /* send new Link State Request packet if this LS Update packet
-     can be recognized as a response to our previous LS Request */
-  if (! IN6_IS_ADDR_MULTICAST (dst) &&
-      (on->state == OSPF6_NEIGHBOR_EXCHANGE ||
-       on->state == OSPF6_NEIGHBOR_LOADING))
-    {
-      THREAD_OFF (on->thread_send_lsreq);
-      on->thread_send_lsreq =
-        thread_add_event (master, ospf6_lsreq_send, on, 0);
-    }
 }
 
 static void
@@ -1907,7 +1894,7 @@
   struct ospf6_header *oh;
   struct ospf6_lsreq_entry *e;
   u_char *p;
-  struct ospf6_lsa *lsa;
+  struct ospf6_lsa *lsa, *last_req;
 
   on = (struct ospf6_neighbor *) THREAD_ARG (thread);
   on->thread_send_lsreq = (struct thread *) NULL;
@@ -1929,13 +1916,9 @@
       return 0;
     }
 
-  /* set next thread */
-  on->thread_send_lsreq =
-    thread_add_timer (master, ospf6_lsreq_send, on,
-                      on->ospf6_if->rxmt_interval);
-
   memset (sendbuf, 0, iobuflen);
   oh = (struct ospf6_header *) sendbuf;
+  last_req = NULL;
 
   /* set Request entries in lsreq */
   p = (u_char *)((caddr_t) oh + sizeof (struct ospf6_header));
@@ -1954,6 +1937,17 @@
       e->id = lsa->header->id;
       e->adv_router = lsa->header->adv_router;
       p += sizeof (struct ospf6_lsreq_entry);
+      last_req = lsa;
+    }
+
+  if (last_req != NULL)
+    {
+      if (on->last_ls_req != NULL)
+	{
+	  ospf6_lsa_unlock (on->last_ls_req);
+	}
+      ospf6_lsa_lock (last_req);
+      on->last_ls_req = last_req;
     }
 
   oh->type = OSPF6_MESSAGE_TYPE_LSREQ;
@@ -1966,6 +1960,14 @@
     ospf6_send (on->ospf6_if->linklocal_addr, &on->linklocal_addr,
 		on->ospf6_if, oh);
 
+  /* set next thread */
+  if (on->request_list->count != 0)
+    {
+      on->thread_send_lsreq =
+	thread_add_timer (master, ospf6_lsreq_send, on,
+			  on->ospf6_if->rxmt_interval);
+    }
+
   return 0;
 }
 
diff --git a/ospf6d/ospf6_neighbor.c b/ospf6d/ospf6_neighbor.c
index 806767d..84f0b00 100644
--- a/ospf6d/ospf6_neighbor.c
+++ b/ospf6d/ospf6_neighbor.c
@@ -98,7 +98,6 @@
   on->retrans_list = ospf6_lsdb_create (on);
 
   on->dbdesc_list = ospf6_lsdb_create (on);
-  on->lsreq_list = ospf6_lsdb_create (on);
   on->lsupdate_list = ospf6_lsdb_create (on);
   on->lsack_list = ospf6_lsdb_create (on);
 
@@ -121,7 +120,6 @@
     }
 
   ospf6_lsdb_remove_all (on->dbdesc_list);
-  ospf6_lsdb_remove_all (on->lsreq_list);
   ospf6_lsdb_remove_all (on->lsupdate_list);
   ospf6_lsdb_remove_all (on->lsack_list);
 
@@ -130,7 +128,6 @@
   ospf6_lsdb_delete (on->retrans_list);
 
   ospf6_lsdb_delete (on->dbdesc_list);
-  ospf6_lsdb_delete (on->lsreq_list);
   ospf6_lsdb_delete (on->lsupdate_list);
   ospf6_lsdb_delete (on->lsack_list);
 
@@ -360,11 +357,41 @@
   if (on->request_list->count == 0)
     ospf6_neighbor_state_change (OSPF6_NEIGHBOR_FULL, on);
   else
-    ospf6_neighbor_state_change (OSPF6_NEIGHBOR_LOADING, on);
+    {
+      ospf6_neighbor_state_change (OSPF6_NEIGHBOR_LOADING, on);
+
+      if (on->thread_send_lsreq == NULL)
+	on->thread_send_lsreq =
+	  thread_add_event (master, ospf6_lsreq_send, on, 0);
+    }
 
   return 0;
 }
 
+/* Check loading state. */
+void
+ospf6_check_nbr_loading (struct ospf6_neighbor *on)
+{
+
+  /* RFC2328 Section 10.9: When the neighbor responds to these requests
+     with the proper Link State Update packet(s), the Link state request
+     list is truncated and a new Link State Request packet is sent.
+  */
+  if ((on->state == OSPF6_NEIGHBOR_LOADING) ||
+      (on->state == OSPF6_NEIGHBOR_EXCHANGE))
+    {
+      if (on->request_list->count == 0)
+	thread_add_event (master, loading_done, on, 0);
+      else if (on->last_ls_req == NULL)
+	{
+	  if (on->thread_send_lsreq != NULL)
+	    THREAD_OFF (on->thread_send_lsreq);
+	  on->thread_send_lsreq =
+	    thread_add_event (master, ospf6_lsreq_send, on, 0);
+	}
+    }
+}
+
 int
 loading_done (struct thread *thread)
 {
@@ -727,10 +754,10 @@
     timersub (&on->thread_send_lsreq->u.sands, &now, &res);
   timerstring (&res, duration, sizeof (duration));
   vty_out (vty, "    %d Pending LSAs for LSReq in Time %s [thread %s]%s",
-           on->lsreq_list->count, duration,
+           on->request_list->count, duration,
            (on->thread_send_lsreq ? "on" : "off"),
            VNL);
-  for (lsa = ospf6_lsdb_head (on->lsreq_list); lsa;
+  for (lsa = ospf6_lsdb_head (on->request_list); lsa;
        lsa = ospf6_lsdb_next (lsa))
     vty_out (vty, "      %s%s", lsa->name, VNL);
 
diff --git a/ospf6d/ospf6_neighbor.h b/ospf6d/ospf6_neighbor.h
index 5f46c6f..750e1b2 100644
--- a/ospf6d/ospf6_neighbor.h
+++ b/ospf6d/ospf6_neighbor.h
@@ -86,6 +86,8 @@
   struct ospf6_lsdb *lsupdate_list;
   struct ospf6_lsdb *lsack_list;
 
+  struct ospf6_lsa *last_ls_req;
+
   /* Inactivity timer */
   struct thread *inactivity_timer;
 
@@ -130,6 +132,7 @@
 extern int bad_lsreq (struct thread *);
 extern int oneway_received (struct thread *);
 extern int inactivity_timer (struct thread *);
+extern void ospf6_check_nbr_loading (struct ospf6_neighbor *);
 
 extern void ospf6_neighbor_init (void);
 extern int config_write_ospf6_debug_neighbor (struct vty *vty);