ipsec: Record the number of packets lost from an SA

Type: feature

Gaps in the sequence numbers received on an SA indicate packets that were lost.
Gaps are identified using the anti-replay window that records the sequences seen.

Publish the number of lost packets in the stats segment at /net/ipsec/sa/lost

Signed-off-by: Neale Ranns <neale@graphiant.com>
Change-Id: I8af1c09b7b25a705e18bf82e1623b3ce19e5a74d
diff --git a/src/vnet/ipsec/ah_decrypt.c b/src/vnet/ipsec/ah_decrypt.c
index 182ed3d..1ad372a 100644
--- a/src/vnet/ipsec/ah_decrypt.c
+++ b/src/vnet/ipsec/ah_decrypt.c
@@ -315,6 +315,7 @@
     {
       ip4_header_t *oh4;
       ip6_header_t *oh6;
+      u64 n_lost = 0;
 
       if (next[0] < AH_DECRYPT_N_NEXT)
 	goto trace;
@@ -323,7 +324,7 @@
 
       if (PREDICT_TRUE (sa0->integ_alg != IPSEC_INTEG_ALG_NONE))
 	{
-	  /* redo the anit-reply check. see esp_decrypt for details */
+	  /* redo the anti-reply check. see esp_decrypt for details */
 	  if (ipsec_sa_anti_replay_and_sn_advance (sa0, pd->seq, pd->seq_hi,
 						   true, NULL))
 	    {
@@ -331,7 +332,10 @@
 	      next[0] = AH_DECRYPT_NEXT_DROP;
 	      goto trace;
 	    }
-	  ipsec_sa_anti_replay_advance (sa0, pd->seq, pd->seq_hi);
+	  n_lost = ipsec_sa_anti_replay_advance (sa0, thread_index, pd->seq,
+						 pd->seq_hi);
+	  vlib_prefetch_simple_counter (&ipsec_sa_lost_counters, thread_index,
+					pd->sa_index);
 	}
 
       u16 ah_hdr_len = sizeof (ah_header_t) + pd->icv_size
@@ -398,6 +402,10 @@
 	    }
 	}
 
+      if (PREDICT_FALSE (n_lost))
+	vlib_increment_simple_counter (&ipsec_sa_lost_counters, thread_index,
+				       pd->sa_index, n_lost);
+
       vnet_buffer (b[0])->sw_if_index[VLIB_TX] = (u32) ~ 0;
     trace:
       if (PREDICT_FALSE (b[0]->flags & VLIB_BUFFER_IS_TRACED))
diff --git a/src/vnet/ipsec/esp_decrypt.c b/src/vnet/ipsec/esp_decrypt.c
index e30fc9e..f1e8065 100644
--- a/src/vnet/ipsec/esp_decrypt.c
+++ b/src/vnet/ipsec/esp_decrypt.c
@@ -748,10 +748,11 @@
 }
 
 static_always_inline void
-esp_decrypt_post_crypto (vlib_main_t * vm, vlib_node_runtime_t * node,
-			 esp_decrypt_packet_data_t * pd,
-			 esp_decrypt_packet_data2_t * pd2, vlib_buffer_t * b,
-			 u16 * next, int is_ip6, int is_tun, int is_async)
+esp_decrypt_post_crypto (vlib_main_t *vm, const vlib_node_runtime_t *node,
+			 const esp_decrypt_packet_data_t *pd,
+			 const esp_decrypt_packet_data2_t *pd2,
+			 vlib_buffer_t *b, u16 *next, int is_ip6, int is_tun,
+			 int is_async)
 {
   ipsec_sa_t *sa0 = ipsec_sa_get (pd->sa_index);
   vlib_buffer_t *lb = b;
@@ -790,7 +791,11 @@
       return;
     }
 
-  ipsec_sa_anti_replay_advance (sa0, pd->seq, pd->seq_hi);
+  u64 n_lost =
+    ipsec_sa_anti_replay_advance (sa0, vm->thread_index, pd->seq, pd->seq_hi);
+
+  vlib_prefetch_simple_counter (&ipsec_sa_lost_counters, vm->thread_index,
+				pd->sa_index);
 
   if (pd->is_chain)
     {
@@ -1011,6 +1016,10 @@
 	    }
 	}
     }
+
+  if (PREDICT_FALSE (n_lost))
+    vlib_increment_simple_counter (&ipsec_sa_lost_counters, vm->thread_index,
+				   pd->sa_index, n_lost);
 }
 
 always_inline uword
diff --git a/src/vnet/ipsec/ipsec_cli.c b/src/vnet/ipsec/ipsec_cli.c
index 3a3e53b..bdb9c7b 100644
--- a/src/vnet/ipsec/ipsec_cli.c
+++ b/src/vnet/ipsec/ipsec_cli.c
@@ -759,6 +759,7 @@
 {
   vlib_clear_combined_counters (&ipsec_spd_policy_counters);
   vlib_clear_combined_counters (&ipsec_sa_counters);
+  vlib_clear_simple_counters (&ipsec_sa_lost_counters);
 
   return (NULL);
 }
diff --git a/src/vnet/ipsec/ipsec_format.c b/src/vnet/ipsec/ipsec_format.c
index 5f7caab..ec644a7 100644
--- a/src/vnet/ipsec/ipsec_format.c
+++ b/src/vnet/ipsec/ipsec_format.c
@@ -272,6 +272,7 @@
   u32 sai = va_arg (*args, u32);
   ipsec_format_flags_t flags = va_arg (*args, ipsec_format_flags_t);
   vlib_counter_t counts;
+  counter_t lost;
   ipsec_sa_t *sa;
 
   if (pool_is_free_index (ipsec_sa_pool, sai))
@@ -312,7 +313,9 @@
 	      clib_host_to_net_u16 (sa->udp_hdr.dst_port));
 
   vlib_get_combined_counter (&ipsec_sa_counters, sai, &counts);
-  s = format (s, "\n   packets %u bytes %u", counts.packets, counts.bytes);
+  lost = vlib_get_simple_counter (&ipsec_sa_lost_counters, sai);
+  s = format (s, "\n   tx/rx:[packets:%Ld bytes:%Ld], lost:[packets:%Ld]",
+	      counts.packets, counts.bytes, lost);
 
   if (ipsec_sa_is_set_IS_TUNNEL (sa))
     s = format (s, "\n%U", format_tunnel, &sa->tunnel, 3);
diff --git a/src/vnet/ipsec/ipsec_sa.c b/src/vnet/ipsec/ipsec_sa.c
index b5d58d0..387d8a7 100644
--- a/src/vnet/ipsec/ipsec_sa.c
+++ b/src/vnet/ipsec/ipsec_sa.c
@@ -28,6 +28,10 @@
   .name = "SA",
   .stat_segment_name = "/net/ipsec/sa",
 };
+vlib_simple_counter_main_t ipsec_sa_lost_counters = {
+  .name = "SA-lost",
+  .stat_segment_name = "/net/ipsec/sa/lost",
+};
 
 ipsec_sa_t *ipsec_sa_pool;
 
@@ -193,6 +197,8 @@
 
   vlib_validate_combined_counter (&ipsec_sa_counters, sa_index);
   vlib_zero_combined_counter (&ipsec_sa_counters, sa_index);
+  vlib_validate_simple_counter (&ipsec_sa_lost_counters, sa_index);
+  vlib_zero_simple_counter (&ipsec_sa_lost_counters, sa_index);
 
   tunnel_copy (tun, &sa->tunnel);
   sa->id = id;
@@ -422,6 +428,7 @@
 ipsec_sa_clear (index_t sai)
 {
   vlib_zero_combined_counter (&ipsec_sa_counters, sai);
+  vlib_zero_simple_counter (&ipsec_sa_lost_counters, sai);
 }
 
 void
diff --git a/src/vnet/ipsec/ipsec_sa.h b/src/vnet/ipsec/ipsec_sa.h
index 14461ad..2cc64e1 100644
--- a/src/vnet/ipsec/ipsec_sa.h
+++ b/src/vnet/ipsec/ipsec_sa.h
@@ -261,6 +261,7 @@
  * SA packet & bytes counters
  */
 extern vlib_combined_counter_main_t ipsec_sa_counters;
+extern vlib_simple_counter_main_t ipsec_sa_lost_counters;
 
 extern void ipsec_mk_key (ipsec_key_t * key, const u8 * data, u8 len);
 
@@ -522,6 +523,48 @@
   return 0;
 }
 
+always_inline u32
+ipsec_sa_anti_replay_window_shift (ipsec_sa_t *sa, u32 inc)
+{
+  u32 n_lost = 0;
+
+  if (inc < IPSEC_SA_ANTI_REPLAY_WINDOW_SIZE)
+    {
+      if (sa->seq > IPSEC_SA_ANTI_REPLAY_WINDOW_SIZE)
+	{
+	  /*
+	   * count how many holes there are in the portion
+	   * of the window that we will right shift of the end
+	   * as a result of this increments
+	   */
+	  u64 mask = (((u64) 1 << inc) - 1) << (BITS (u64) - inc);
+	  u64 old = sa->replay_window & mask;
+	  /* the number of packets we saw in this section of the window */
+	  u64 seen = count_set_bits (old);
+
+	  /*
+	   * the number we missed is the size of the window section
+	   * minus the number we saw.
+	   */
+	  n_lost = inc - seen;
+	}
+      sa->replay_window = ((sa->replay_window) << inc) | 1;
+    }
+  else
+    {
+      /* holes in the replay window are lost packets */
+      n_lost = BITS (u64) - count_set_bits (sa->replay_window);
+
+      /* any sequence numbers that now fall outside the window
+       * are forever lost */
+      n_lost += inc - IPSEC_SA_ANTI_REPLAY_WINDOW_SIZE;
+
+      sa->replay_window = 1;
+    }
+
+  return (n_lost);
+}
+
 /*
  * Anti replay window advance
  *  inputs need to be in host byte order.
@@ -531,9 +574,11 @@
  * However, updating the window is trivial, so we do it anyway to save
  * the branch cost.
  */
-always_inline void
-ipsec_sa_anti_replay_advance (ipsec_sa_t *sa, u32 seq, u32 hi_seq)
+always_inline u64
+ipsec_sa_anti_replay_advance (ipsec_sa_t *sa, u32 thread_index, u32 seq,
+			      u32 hi_seq)
 {
+  u64 n_lost = 0;
   u32 pos;
 
   if (ipsec_sa_is_set_USE_ESN (sa))
@@ -543,19 +588,13 @@
       if (wrap == 0 && seq > sa->seq)
 	{
 	  pos = seq - sa->seq;
-	  if (pos < IPSEC_SA_ANTI_REPLAY_WINDOW_SIZE)
-	    sa->replay_window = ((sa->replay_window) << pos) | 1;
-	  else
-	    sa->replay_window = 1;
+	  n_lost = ipsec_sa_anti_replay_window_shift (sa, pos);
 	  sa->seq = seq;
 	}
       else if (wrap > 0)
 	{
 	  pos = ~seq + sa->seq + 1;
-	  if (pos < IPSEC_SA_ANTI_REPLAY_WINDOW_SIZE)
-	    sa->replay_window = ((sa->replay_window) << pos) | 1;
-	  else
-	    sa->replay_window = 1;
+	  n_lost = ipsec_sa_anti_replay_window_shift (sa, pos);
 	  sa->seq = seq;
 	  sa->seq_hi = hi_seq;
 	}
@@ -575,10 +614,7 @@
       if (seq > sa->seq)
 	{
 	  pos = seq - sa->seq;
-	  if (pos < IPSEC_SA_ANTI_REPLAY_WINDOW_SIZE)
-	    sa->replay_window = ((sa->replay_window) << pos) | 1;
-	  else
-	    sa->replay_window = 1;
+	  n_lost = ipsec_sa_anti_replay_window_shift (sa, pos);
 	  sa->seq = seq;
 	}
       else
@@ -587,6 +623,8 @@
 	  sa->replay_window |= (1ULL << pos);
 	}
     }
+
+  return n_lost;
 }