ipsec: Fix setting the hi-sequence number for decrypt

Type: fix

two problems;
 1 - just because anti-reply is not enabled doesn't mean the high sequence
number should not be used.
   - fix, there needs to be some means to detect a wrapped packet, so we
use a window size of 2^30.
 2 - The SA object was used as a scratch pad for the high-sequence
number used during decryption. That means that once the batch has been
processed the high-sequence number used is lost. This means it is not
possible to distinguish this case:
      if (seq < IPSEC_SA_ANTI_REPLAY_WINDOW_LOWER_BOUND (tl))
	{
	  ...
	  if (post_decrypt)
	    {
	      if (hi_seq_used == sa->seq_hi)
		/* the high sequence number used to succesfully decrypt this
		 * packet is the same as the last-sequnence number of the SA.
		 * that means this packet did not cause a wrap.
		 * this packet is thus out of window and should be dropped */
		return 1;
	      else
		/* The packet decrypted with a different high sequence number
		 * to the SA, that means it is the wrap packet and should be
		 * accepted */
		return 0;
	    }
  - fix: don't use the SA as a scratch pad, use the 'packet_data' - the
same place that is used as the scratch pad for the low sequence number.

other consequences:
 - An SA doesn't have seq and last_seq, it has only seq; the sequence
numnber of the last packet tx'd or rx'd.
 - there's 64bits of space available on the SA's first cache line. move
the AES CTR mode IV there.
 - test the ESN/AR combinations to catch the bugs this fixes. This
doubles the amount of tests, but without AR on they only run for 2
seconds. In the AR tests, the time taken to wait for packets that won't
arrive is dropped from 1 to 0.2 seconds thus reducing the runtime of
these tests from 10-15 to about 5 sceonds.

Signed-off-by: Neale Ranns <neale@graphiant.com>
Change-Id: Iaac78905289a272dc01930d70decd8109cf5e7a5
diff --git a/test/template_ipsec.py b/test/template_ipsec.py
index fd9d91f..57c8434 100644
--- a/test/template_ipsec.py
+++ b/test/template_ipsec.py
@@ -324,6 +324,199 @@
 
         return count
 
+    def verify_hi_seq_num(self):
+        p = self.params[socket.AF_INET]
+        saf = VppEnum.vl_api_ipsec_sad_flags_t
+        esn_on = p.vpp_tra_sa.esn_en
+        ar_on = p.flags & saf.IPSEC_API_SAD_FLAG_USE_ANTI_REPLAY
+
+        seq_cycle_node_name = \
+            ('/err/%s/sequence number cycled (packet dropped)' %
+             self.tra4_encrypt_node_name)
+        replay_count = self.get_replay_counts(p)
+        hash_failed_count = self.get_hash_failed_counts(p)
+        seq_cycle_count = self.statistics.get_err_counter(seq_cycle_node_name)
+
+        # a few packets so we get the rx seq number above the window size and
+        # thus can simulate a wrap with an out of window packet
+        pkts = [(Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=seq))
+                for seq in range(63, 80)]
+        recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
+        # these 4 packets will all choose seq-num 0 to decrpyt since none
+        # are out of window when first checked. however, once #200 has
+        # decrypted it will move the window to 200 and has #81 is out of
+        # window. this packet should be dropped.
+        pkts = [(Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=200)),
+                (Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=81)),
+                (Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=201)),
+                (Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=202))]
+
+        # if anti-replay is off then we won't drop #81
+        n_rx = 3 if ar_on else 4
+        self.send_and_expect(self.tra_if, pkts, self.tra_if, n_rx=n_rx)
+        # this packet is one before the wrap
+        pkts = [(Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=203))]
+        recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
+        # move the window over half way to a wrap
+        pkts = [(Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=0x80000001))]
+        recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
+        # anti-replay will drop old packets, no anti-replay will not
+        pkts = [(Ether(src=self.tra_if.remote_mac,
+                       dst=self.tra_if.local_mac) /
+                 p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                           dst=self.tra_if.local_ip4) /
+                                        ICMP(),
+                                        seq_num=0x44000001))]
+
+        if ar_on:
+            self.send_and_assert_no_replies(self.tra_if, pkts)
+        else:
+            recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
+        if esn_on:
+            #
+            # validate wrapping the ESN
+            #
+
+            # wrap scapy's TX SA SN
+            p.scapy_tra_sa.seq_num = 0x100000005
+
+            # send a packet that wraps the window for both AR and no AR
+            pkts = [(Ether(src=self.tra_if.remote_mac,
+                           dst=self.tra_if.local_mac) /
+                     p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                               dst=self.tra_if.local_ip4) /
+                                            ICMP(),
+                                            seq_num=0x100000005))]
+
+            rxs = self.send_and_expect(self.tra_if, pkts, self.tra_if)
+            for rx in rxs:
+                decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
+
+            # move the window forward to half way to the next wrap
+            pkts = [(Ether(src=self.tra_if.remote_mac,
+                           dst=self.tra_if.local_mac) /
+                     p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                               dst=self.tra_if.local_ip4) /
+                                            ICMP(),
+                                            seq_num=0x180000005))]
+
+            rxs = self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
+            # a packet less than 2^30 from the current position is:
+            #  - AR: out of window and dropped
+            #  - non-AR: accepted
+            pkts = [(Ether(src=self.tra_if.remote_mac,
+                           dst=self.tra_if.local_mac) /
+                     p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                               dst=self.tra_if.local_ip4) /
+                                            ICMP(),
+                                            seq_num=0x170000005))]
+
+            if ar_on:
+                self.send_and_assert_no_replies(self.tra_if, pkts)
+            else:
+                self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
+            # a packet more than 2^30 from the current position is:
+            #  - AR: out of window and dropped
+            #  - non-AR: considered a wrap, but since it's not a wrap
+            #    it won't decrpyt and so will be dropped
+            pkts = [(Ether(src=self.tra_if.remote_mac,
+                           dst=self.tra_if.local_mac) /
+                     p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                               dst=self.tra_if.local_ip4) /
+                                            ICMP(),
+                                            seq_num=0x130000005))]
+
+            self.send_and_assert_no_replies(self.tra_if, pkts)
+
+            # a packet less than 2^30 from the current position and is a
+            # wrap; (the seq is currently at 0x180000005).
+            #  - AR: out of window so considered a wrap, so accepted
+            #  - non-AR: not considered a wrap, so won't decrypt
+            p.scapy_tra_sa.seq_num = 0x260000005
+            pkts = [(Ether(src=self.tra_if.remote_mac,
+                           dst=self.tra_if.local_mac) /
+                     p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                               dst=self.tra_if.local_ip4) /
+                                            ICMP(),
+                                            seq_num=0x260000005))]
+            if ar_on:
+                self.send_and_expect(self.tra_if, pkts, self.tra_if)
+            else:
+                self.send_and_assert_no_replies(self.tra_if, pkts)
+
+            #
+            # window positions are different now for AR/non-AR
+            #  move non-AR forward
+            #
+            if not ar_on:
+                # a packet more than 2^30 from the current position and is a
+                # wrap; (the seq is currently at 0x180000005).
+                #  - AR: accepted
+                #  - non-AR: not considered a wrap, so won't decrypt
+
+                pkts = [(Ether(src=self.tra_if.remote_mac,
+                               dst=self.tra_if.local_mac) /
+                         p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                                   dst=self.tra_if.local_ip4) /
+                                                ICMP(),
+                                                seq_num=0x200000005)),
+                        (Ether(src=self.tra_if.remote_mac,
+                               dst=self.tra_if.local_mac) /
+                         p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                                   dst=self.tra_if.local_ip4) /
+                                                ICMP(),
+                                                seq_num=0x200000006))]
+                self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
+                pkts = [(Ether(src=self.tra_if.remote_mac,
+                               dst=self.tra_if.local_mac) /
+                         p.scapy_tra_sa.encrypt(IP(src=self.tra_if.remote_ip4,
+                                                   dst=self.tra_if.local_ip4) /
+                                                ICMP(),
+                                                seq_num=0x260000005))]
+                self.send_and_expect(self.tra_if, pkts, self.tra_if)
+
     def verify_tra_anti_replay(self):
         p = self.params[socket.AF_INET]
         esn_en = p.vpp_tra_sa.esn_en
@@ -359,7 +552,7 @@
         recv_pkts = self.send_and_expect(self.tra_if, pkts, self.tra_if)
 
         # replayed packets are dropped
-        self.send_and_assert_no_replies(self.tra_if, pkts)
+        self.send_and_assert_no_replies(self.tra_if, pkts, timeout=0.2)
         replay_count += len(pkts)
         self.assertEqual(self.get_replay_counts(p), replay_count)
 
@@ -393,7 +586,7 @@
         recv_pkts = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
 
         # replayed packets are dropped
-        self.send_and_assert_no_replies(self.tra_if, pkt * 3)
+        self.send_and_assert_no_replies(self.tra_if, pkt * 3, timeout=0.2)
         replay_count += 3
         self.assertEqual(self.get_replay_counts(p), replay_count)
 
@@ -420,7 +613,7 @@
                                    dst=self.tra_if.local_ip4) /
                                 ICMP(),
                                 seq_num=350))
-        self.send_and_assert_no_replies(self.tra_if, pkt * 17)
+        self.send_and_assert_no_replies(self.tra_if, pkt * 17, timeout=0.2)
 
         hash_failed_count += 17
         self.assertEqual(self.get_hash_failed_counts(p), hash_failed_count)
@@ -436,7 +629,7 @@
                                        dst=self.tra_if.local_ip4) /
                                     ICMP(),
                                     seq_num=350))
-            self.send_and_assert_no_replies(self.tra_if, pkt * 17)
+            self.send_and_assert_no_replies(self.tra_if, pkt * 17, timeout=0.2)
 
             undersize_count += 17
             self.assert_error_counter_equal(undersize_node_name,
@@ -462,7 +655,7 @@
                                          dst=self.tra_if.local_ip4) /
                                       ICMP(),
                                       seq_num=17))
-        self.send_and_assert_no_replies(self.tra_if, pkt * 17)
+        self.send_and_assert_no_replies(self.tra_if, pkt * 17, timeout=0.2)
 
         if esn_en:
             # an out of window error with ESN looks like a high sequence
@@ -526,6 +719,7 @@
                                           ICMP(),
                                           seq_num=0x100000005))
             rx = self.send_and_expect(self.tra_if, [pkt], self.tra_if)
+
             decrypted = p.vpp_tra_sa.decrypt(rx[0][IP])
 
             #
@@ -544,7 +738,7 @@
 
             #
             # While in case A we cannot wrap the high sequence number again
-            # becuase VPP will consider this packet to be one that moves the
+            # because VPP will consider this packet to be one that moves the
             # window forward
             #
             pkt = (Ether(src=self.tra_if.remote_mac,
@@ -553,13 +747,14 @@
                                              dst=self.tra_if.local_ip4) /
                                           ICMP(),
                                           seq_num=0x200000999))
-            self.send_and_assert_no_replies(self.tra_if, [pkt], self.tra_if)
+            self.send_and_assert_no_replies(self.tra_if, [pkt], self.tra_if,
+                                            timeout=0.2)
 
             hash_failed_count += 1
             self.assertEqual(self.get_hash_failed_counts(p), hash_failed_count)
 
             #
-            # but if we move the wondow forward to case B, then we can wrap
+            # but if we move the window forward to case B, then we can wrap
             # again
             #
             p.scapy_tra_sa.seq_num = 0x100000555
@@ -587,7 +782,7 @@
             # without ESN TX sequence numbers can't wrap and packets are
             # dropped from here on out.
             #
-            self.send_and_assert_no_replies(self.tra_if, pkts)
+            self.send_and_assert_no_replies(self.tra_if, pkts, timeout=0.2)
             seq_cycle_count += len(pkts)
             self.assert_error_counter_equal(seq_cycle_node_name,
                                             seq_cycle_count)