wireguard: stop sending handshakes when wg intf is down 61/37061/12
authorAlexander Chernavin <achernavin@netgate.com>
Thu, 1 Sep 2022 13:42:56 +0000 (13:42 +0000)
committerMatthew Smith <mgsmith@netgate.com>
Tue, 27 Sep 2022 16:15:54 +0000 (16:15 +0000)
Type: fix

Currently, when a wg interface is administratively disabled initially or
during operation, handshake packets continue to be sent. Data packets
stop being sent because routes pointing to the wg interface will not be
used. But data keys remain.

With this fix, when a wg interface is administratively disabled during
peer creation, avoid connection initialization to the peer. Data keys
and timers should be empty at this point. When a wg interface is
disabled during operation, disable all peers (i.e. stop all timers,
clear data keys, etc.). Thus, state should be identical in both cases.
When a wg interface is administratively enabled, enable all peers (i.e.
get ready to exchange data packets and initiate a connection). Also,
cover these scenarios with tests.

Signed-off-by: Alexander Chernavin <achernavin@netgate.com>
Change-Id: Ie9a620077e55d519d21b0abc8c0d3c87b378bca3

src/plugins/wireguard/wireguard_peer.c
test/test_wireguard.py

index b22110a..ef791c6 100644 (file)
@@ -202,16 +202,76 @@ wg_peer_get_fixup (wg_peer_t *peer, vnet_link_t lt)
   return (NULL);
 }
 
+static void
+wg_peer_disable (vlib_main_t *vm, wg_peer_t *peer)
+{
+  index_t peeri = peer - wg_peer_pool;
+
+  wg_timers_stop (peer);
+  wg_peer_update_flags (peeri, WG_PEER_ESTABLISHED, false);
+
+  for (int i = 0; i < WG_N_TIMERS; i++)
+    {
+      peer->timers[i] = ~0;
+      peer->timers_dispatched[i] = 0;
+    }
+  peer->timer_handshake_attempts = 0;
+
+  peer->last_sent_handshake = vlib_time_now (vm) - (REKEY_TIMEOUT + 1);
+  peer->last_sent_packet = 0;
+  peer->last_received_packet = 0;
+  peer->session_derived = 0;
+  peer->rehandshake_started = 0;
+
+  peer->new_handshake_interval_tick = 0;
+  peer->rehandshake_interval_tick = 0;
+
+  peer->timer_need_another_keepalive = false;
+
+  noise_remote_clear (vm, &peer->remote);
+}
+
+static void
+wg_peer_enable (vlib_main_t *vm, wg_peer_t *peer)
+{
+  index_t peeri = peer - wg_peer_pool;
+  wg_if_t *wg_if;
+  u8 public_key[NOISE_PUBLIC_KEY_LEN];
+
+  wg_if = wg_if_get (wg_if_find_by_sw_if_index (peer->wg_sw_if_index));
+  clib_memcpy (public_key, peer->remote.r_public, NOISE_PUBLIC_KEY_LEN);
+
+  noise_remote_init (&peer->remote, peeri, public_key, wg_if->local_idx);
+
+  wg_send_handshake (vm, peer, false);
+  if (peer->persistent_keepalive_interval != 0)
+    {
+      wg_send_keepalive (vm, peer);
+    }
+}
+
 walk_rc_t
 wg_peer_if_admin_state_change (index_t peeri, void *data)
 {
   wg_peer_t *peer;
   adj_index_t *adj_index;
+  vlib_main_t *vm = vlib_get_main ();
+
   peer = wg_peer_get (peeri);
   vec_foreach (adj_index, peer->adj_indices)
     {
       wg_peer_adj_stack (peer, *adj_index);
     }
+
+  if (vnet_sw_interface_is_admin_up (vnet_get_main (), peer->wg_sw_if_index))
+    {
+      wg_peer_enable (vm, peer);
+    }
+  else
+    {
+      wg_peer_disable (vm, peer);
+    }
+
   return (WALK_CONTINUE);
 }
 
@@ -431,10 +491,13 @@ wg_peer_add (u32 tun_sw_if_index, const u8 public_key[NOISE_PUBLIC_KEY_LEN],
                     wg_if->local_idx);
   cookie_maker_init (&peer->cookie_maker, public_key);
 
-  wg_send_handshake (vm, peer, false);
-  if (peer->persistent_keepalive_interval != 0)
+  if (vnet_sw_interface_is_admin_up (vnet_get_main (), tun_sw_if_index))
     {
-      wg_send_keepalive (vm, peer);
+      wg_send_handshake (vm, peer, false);
+      if (peer->persistent_keepalive_interval != 0)
+       {
+         wg_send_keepalive (vm, peer);
+       }
     }
 
   *peer_index = peer - wg_peer_pool;
index afa6d70..7e5ef5b 100644 (file)
@@ -2260,6 +2260,138 @@ class TestWg(VppTestCase):
         wg0.remove_vpp_config()
         wg1.remove_vpp_config()
 
+    def test_wg_sending_handshake_when_admin_down(self):
+        """Sending handshake when admin down"""
+        port = 12323
+
+        # create wg interface
+        wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
+        wg0.config_ip4()
+
+        # create a peer
+        peer_1 = VppWgPeer(
+            self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.3.0/24"]
+        ).add_vpp_config()
+        self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        # wait for the peer to send a handshake initiation
+        # expect no handshakes
+        for i in range(2):
+            self.pg1.assert_nothing_captured(remark="handshake packet(s) sent")
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        # administratively enable the wg interface
+        # expect the peer to send a handshake initiation
+        wg0.admin_up()
+        rxs = self.pg1.get_capture(1, timeout=2)
+        peer_1.consume_init(rxs[0], self.pg1)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        # administratively disable the wg interface
+        # expect no handshakes
+        wg0.admin_down()
+        for i in range(6):
+            self.pg1.assert_nothing_captured(remark="handshake packet(s) sent")
+
+        # remove configs
+        peer_1.remove_vpp_config()
+        wg0.remove_vpp_config()
+
+    def test_wg_sending_data_when_admin_down(self):
+        """Sending data when admin down"""
+        port = 12323
+
+        # create wg interface
+        wg0 = VppWgInterface(self, self.pg1.local_ip4, port).add_vpp_config()
+        wg0.admin_up()
+        wg0.config_ip4()
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        # create a peer
+        peer_1 = VppWgPeer(
+            self, wg0, self.pg1.remote_ip4, port + 1, ["10.11.3.0/24"]
+        ).add_vpp_config()
+        self.assertEqual(len(self.vapi.wireguard_peers_dump()), 1)
+
+        # create a route to rewrite traffic into the wg interface
+        r1 = VppIpRoute(
+            self, "10.11.3.0", 24, [VppRoutePath("10.11.3.1", wg0.sw_if_index)]
+        ).add_vpp_config()
+
+        # wait for the peer to send a handshake initiation
+        rxs = self.pg1.get_capture(1, timeout=2)
+
+        # prepare and send a handshake response
+        # expect a keepalive message
+        resp = peer_1.consume_init(rxs[0], self.pg1)
+        rxs = self.send_and_expect(self.pg1, [resp], self.pg1)
+
+        # verify the keepalive message
+        b = peer_1.decrypt_transport(rxs[0])
+        self.assertEqual(0, len(b))
+
+        # prepare and send a packet that will be rewritten into the wg interface
+        # expect a data packet sent
+        p = (
+            Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
+            / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")
+            / UDP(sport=555, dport=556)
+            / Raw()
+        )
+        rxs = self.send_and_expect(self.pg0, [p], self.pg1)
+
+        # verify the data packet
+        peer_1.validate_encapped(rxs, p)
+
+        # administratively disable the wg interface
+        wg0.admin_down()
+
+        # send a packet that will be rewritten into the wg interface
+        # expect no data packets sent
+        self.send_and_assert_no_replies(self.pg0, [p])
+
+        # administratively enable the wg interface
+        # expect the peer to send a handshake initiation
+        wg0.admin_up()
+        peer_1.noise_reset()
+        rxs = self.pg1.get_capture(1, timeout=2)
+        resp = peer_1.consume_init(rxs[0], self.pg1)
+
+        # send a packet that will be rewritten into the wg interface
+        # expect no data packets sent because the peer is not initiated
+        self.send_and_assert_no_replies(self.pg0, [p])
+        self.assertEqual(
+            self.base_kp4_err + 1, self.statistics.get_err_counter(self.kp4_error)
+        )
+
+        # send a handshake response and expect a keepalive message
+        rxs = self.send_and_expect(self.pg1, [resp], self.pg1)
+
+        # verify the keepalive message
+        b = peer_1.decrypt_transport(rxs[0])
+        self.assertEqual(0, len(b))
+
+        # send a packet that will be rewritten into the wg interface
+        # expect a data packet sent
+        rxs = self.send_and_expect(self.pg0, [p], self.pg1)
+
+        # verify the data packet
+        peer_1.validate_encapped(rxs, p)
+
+        # remove configs
+        r1.remove_vpp_config()
+        peer_1.remove_vpp_config()
+        wg0.remove_vpp_config()
+
 
 @tag_fixme_ubuntu2204
 @tag_fixme_debian11
@@ -2431,7 +2563,7 @@ class TestWgFIB(VppTestCase):
         self.assertEqual(0, len(b))
 
         # prepare and send a packet that will be rewritten into the wg interface
-        # expect a data packet sent to the new endpoint
+        # expect a data packet sent
         p = (
             Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac)
             / IP(src=self.pg0.remote_ip4, dst="10.11.3.2")