From ce91af8ad27e5ddef1e1f8316129bfcaa3de9ef6 Mon Sep 17 00:00:00 2001 From: Alexander Chernavin Date: Wed, 20 Jul 2022 12:43:42 +0000 Subject: [PATCH] wireguard: add dos mitigation support Type: feature With this change: - if the number of received handshake messages exceeds the limit calculated based on the peers number, under load state will activate; - if being under load a handshake message with a valid mac1 is received, but mac2 is invalid, a cookie reply will be sent. Also, cover these with tests. Signed-off-by: Alexander Chernavin Change-Id: I3003570a9cf807cfb0b5145b89a085455c30e717 --- src/plugins/wireguard/wireguard_chachapoly.c | 30 +++++ src/plugins/wireguard/wireguard_chachapoly.h | 5 + src/plugins/wireguard/wireguard_cookie.c | 19 +++ src/plugins/wireguard/wireguard_cookie.h | 5 + src/plugins/wireguard/wireguard_if.c | 4 +- src/plugins/wireguard/wireguard_if.h | 42 ++++++ src/plugins/wireguard/wireguard_input.c | 30 ++++- src/plugins/wireguard/wireguard_peer.c | 48 +------ src/plugins/wireguard/wireguard_send.c | 98 ++++++++++++-- src/plugins/wireguard/wireguard_send.h | 7 + test/test_wireguard.py | 191 ++++++++++++++++++++++++++- 11 files changed, 411 insertions(+), 68 deletions(-) diff --git a/src/plugins/wireguard/wireguard_chachapoly.c b/src/plugins/wireguard/wireguard_chachapoly.c index 961b43f100d..0dd7908d2e2 100644 --- a/src/plugins/wireguard/wireguard_chachapoly.c +++ b/src/plugins/wireguard/wireguard_chachapoly.c @@ -61,6 +61,36 @@ wg_chacha20poly1305_calc (vlib_main_t *vm, u8 *src, u32 src_len, u8 *dst, return (op->status == VNET_CRYPTO_OP_STATUS_COMPLETED); } +void +wg_xchacha20poly1305_encrypt (vlib_main_t *vm, u8 *src, u32 src_len, u8 *dst, + u8 *aad, u32 aad_len, + u8 nonce[XCHACHA20POLY1305_NONCE_SIZE], + u8 key[CHACHA20POLY1305_KEY_SIZE]) +{ + int i; + u32 derived_key[CHACHA20POLY1305_KEY_SIZE / sizeof (u32)]; + u64 h_nonce; + + clib_memcpy (&h_nonce, nonce + 16, sizeof (h_nonce)); + h_nonce = le64toh (h_nonce); + hchacha20 (derived_key, nonce, key); + + for (i = 0; i < (sizeof (derived_key) / sizeof (derived_key[0])); i++) + (derived_key[i]) = htole32 ((derived_key[i])); + + uint32_t key_idx; + + key_idx = + vnet_crypto_key_add (vm, VNET_CRYPTO_ALG_CHACHA20_POLY1305, + (uint8_t *) derived_key, CHACHA20POLY1305_KEY_SIZE); + + wg_chacha20poly1305_calc (vm, src, src_len, dst, aad, aad_len, h_nonce, + VNET_CRYPTO_OP_CHACHA20_POLY1305_ENC, key_idx); + + vnet_crypto_key_del (vm, key_idx); + wg_secure_zero_memory (derived_key, CHACHA20POLY1305_KEY_SIZE); +} + bool wg_xchacha20poly1305_decrypt (vlib_main_t *vm, u8 *src, u32 src_len, u8 *dst, u8 *aad, u32 aad_len, diff --git a/src/plugins/wireguard/wireguard_chachapoly.h b/src/plugins/wireguard/wireguard_chachapoly.h index 803774cafe1..f09b2c8dd9d 100644 --- a/src/plugins/wireguard/wireguard_chachapoly.h +++ b/src/plugins/wireguard/wireguard_chachapoly.h @@ -27,6 +27,11 @@ bool wg_chacha20poly1305_calc (vlib_main_t *vm, u8 *src, u32 src_len, u8 *dst, vnet_crypto_op_id_t op_id, vnet_crypto_key_index_t key_index); +void wg_xchacha20poly1305_encrypt (vlib_main_t *vm, u8 *src, u32 src_len, + u8 *dst, u8 *aad, u32 aad_len, + u8 nonce[XCHACHA20POLY1305_NONCE_SIZE], + u8 key[CHACHA20POLY1305_KEY_SIZE]); + bool wg_xchacha20poly1305_decrypt (vlib_main_t *vm, u8 *src, u32 src_len, u8 *dst, u8 *aad, u32 aad_len, u8 nonce[XCHACHA20POLY1305_NONCE_SIZE], diff --git a/src/plugins/wireguard/wireguard_cookie.c b/src/plugins/wireguard/wireguard_cookie.c index 47e8784566f..595b8770e56 100644 --- a/src/plugins/wireguard/wireguard_cookie.c +++ b/src/plugins/wireguard/wireguard_cookie.c @@ -58,6 +58,25 @@ cookie_checker_update (cookie_checker_t * cc, uint8_t key[COOKIE_INPUT_SIZE]) } } +void +cookie_checker_create_payload (vlib_main_t *vm, cookie_checker_t *cc, + message_macs_t *cm, + uint8_t nonce[COOKIE_NONCE_SIZE], + uint8_t ecookie[COOKIE_ENCRYPTED_SIZE], + ip46_address_t *ip, u16 udp_port) +{ + uint8_t cookie[COOKIE_COOKIE_SIZE]; + + cookie_checker_make_cookie (vm, cc, cookie, ip, udp_port); + RAND_bytes (nonce, COOKIE_NONCE_SIZE); + + wg_xchacha20poly1305_encrypt (vm, cookie, COOKIE_COOKIE_SIZE, ecookie, + cm->mac1, COOKIE_MAC_SIZE, nonce, + cc->cc_cookie_key); + + wg_secure_zero_memory (cookie, sizeof (cookie)); +} + bool cookie_maker_consume_payload (vlib_main_t *vm, cookie_maker_t *cp, uint8_t nonce[COOKIE_NONCE_SIZE], diff --git a/src/plugins/wireguard/wireguard_cookie.h b/src/plugins/wireguard/wireguard_cookie.h index e4bea90f854..9298ece8db5 100644 --- a/src/plugins/wireguard/wireguard_cookie.h +++ b/src/plugins/wireguard/wireguard_cookie.h @@ -82,6 +82,11 @@ typedef struct cookie_checker void cookie_maker_init (cookie_maker_t *, const uint8_t[COOKIE_INPUT_SIZE]); void cookie_checker_update (cookie_checker_t *, uint8_t[COOKIE_INPUT_SIZE]); +void cookie_checker_create_payload (vlib_main_t *vm, cookie_checker_t *cc, + message_macs_t *cm, + uint8_t nonce[COOKIE_NONCE_SIZE], + uint8_t ecookie[COOKIE_ENCRYPTED_SIZE], + ip46_address_t *ip, u16 udp_port); bool cookie_maker_consume_payload (vlib_main_t *vm, cookie_maker_t *cp, uint8_t nonce[COOKIE_NONCE_SIZE], uint8_t ecookie[COOKIE_ENCRYPTED_SIZE]); diff --git a/src/plugins/wireguard/wireguard_if.c b/src/plugins/wireguard/wireguard_if.c index fd123471a8c..c4199d23354 100644 --- a/src/plugins/wireguard/wireguard_if.c +++ b/src/plugins/wireguard/wireguard_if.c @@ -287,7 +287,7 @@ wg_if_create (u32 user_instance, return VNET_API_ERROR_INVALID_REGISTRATION; } - pool_get (wg_if_pool, wg_if); + pool_get_zero (wg_if_pool, wg_if); /* tunnel index (or instance) */ u32 t_idx = wg_if - wg_if_pool; @@ -354,6 +354,8 @@ wg_if_delete (u32 sw_if_index) // Remove peers before interface deletion wg_if_peer_walk (wg_if, wg_peer_if_delete, NULL); + hash_free (wg_if->peers); + index_t *ii; index_t *ifs = wg_if_indexes_get_by_port (wg_if->port); vec_foreach (ii, ifs) diff --git a/src/plugins/wireguard/wireguard_if.h b/src/plugins/wireguard/wireguard_if.h index 0a042cb2d9b..2a6ab8e4be5 100644 --- a/src/plugins/wireguard/wireguard_if.h +++ b/src/plugins/wireguard/wireguard_if.h @@ -36,6 +36,10 @@ typedef struct wg_if_t_ /* hash table of peers on this link */ uword *peers; + + /* Under load params */ + f64 handshake_counting_end; + u32 handshake_num; } wg_if_t; @@ -81,6 +85,44 @@ wg_if_indexes_get_by_port (u16 port) return (wg_if_indexes_by_port[port]); } +#define HANDSHAKE_COUNTING_INTERVAL 0.5 +#define UNDER_LOAD_INTERVAL 1.0 +#define HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD 40 + +static_always_inline bool +wg_if_is_under_load (vlib_main_t *vm, wg_if_t *wgi) +{ + static f64 wg_under_load_end; + f64 now = vlib_time_now (vm); + u32 num_until_under_load = + hash_elts (wgi->peers) * HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD; + + if (wgi->handshake_counting_end < now) + { + wgi->handshake_counting_end = now + HANDSHAKE_COUNTING_INTERVAL; + wgi->handshake_num = 0; + } + wgi->handshake_num++; + + if (wgi->handshake_num >= num_until_under_load) + { + wg_under_load_end = now + UNDER_LOAD_INTERVAL; + return true; + } + + if (wg_under_load_end > now) + { + return true; + } + + return false; +} + +static_always_inline void +wg_if_dec_handshake_num (wg_if_t *wgi) +{ + wgi->handshake_num--; +} #endif diff --git a/src/plugins/wireguard/wireguard_input.c b/src/plugins/wireguard/wireguard_input.c index ef60d50c3da..3f546cc494f 100644 --- a/src/plugins/wireguard/wireguard_input.c +++ b/src/plugins/wireguard/wireguard_input.c @@ -32,6 +32,7 @@ _ (HANDSHAKE_SEND, "Failed while sending Handshake") \ _ (HANDSHAKE_RECEIVE, "Failed while receiving Handshake") \ _ (COOKIE_DECRYPTION, "Failed during Cookie decryption") \ + _ (COOKIE_SEND, "Failed during sending Cookie") \ _ (TOO_BIG, "Packet too big") \ _ (UNDEFINED, "Undefined error") \ _ (CRYPTO_ENGINE_ERROR, "crypto engine error (packet dropped)") @@ -173,7 +174,6 @@ wg_handshake_process (vlib_main_t *vm, wg_main_t *wmp, vlib_buffer_t *b, u16 udp_dst_port = clib_host_to_net_u16 (uhd->dst_port);; message_header_t *header = current_b_data; - under_load = false; if (PREDICT_FALSE (header->type == MESSAGE_HANDSHAKE_COOKIE)) { @@ -211,11 +211,13 @@ wg_handshake_process (vlib_main_t *vm, wg_main_t *wmp, vlib_buffer_t *b, if (NULL == wg_if) continue; + under_load = wg_if_is_under_load (vm, wg_if); mac_state = cookie_checker_validate_macs ( vm, &wg_if->cookie_checker, macs, current_b_data, len, under_load, &src_ip, udp_src_port); if (mac_state == INVALID_MAC) { + wg_if_dec_handshake_num (wg_if); wg_if = NULL; continue; } @@ -241,8 +243,16 @@ wg_handshake_process (vlib_main_t *vm, wg_main_t *wmp, vlib_buffer_t *b, if (packet_needs_cookie) { - // TODO: Add processing + + if (!wg_send_handshake_cookie (vm, message->sender_index, + &wg_if->cookie_checker, macs, + &ip_addr_46 (&wg_if->src_ip), + wg_if->port, &src_ip, udp_src_port)) + return WG_INPUT_ERROR_COOKIE_SEND; + + return WG_INPUT_ERROR_NONE; } + noise_remote_t *rp; if (noise_consume_initiation (vm, noise_local_get (wg_if->local_idx), &rp, @@ -271,6 +281,18 @@ wg_handshake_process (vlib_main_t *vm, wg_main_t *wmp, vlib_buffer_t *b, case MESSAGE_HANDSHAKE_RESPONSE: { message_handshake_response_t *resp = current_b_data; + + if (packet_needs_cookie) + { + if (!wg_send_handshake_cookie (vm, resp->sender_index, + &wg_if->cookie_checker, macs, + &ip_addr_46 (&wg_if->src_ip), + wg_if->port, &src_ip, udp_src_port)) + return WG_INPUT_ERROR_COOKIE_SEND; + + return WG_INPUT_ERROR_NONE; + } + index_t peeri = INDEX_INVALID; u32 *entry = wg_index_table_lookup (&wmp->index_table, resp->receiver_index); @@ -292,10 +314,6 @@ wg_handshake_process (vlib_main_t *vm, wg_main_t *wmp, vlib_buffer_t *b, { return WG_INPUT_ERROR_PEER; } - if (packet_needs_cookie) - { - // TODO: Add processing - } // set_peer_address (peer, ip4_src, udp_src_port); if (noise_remote_begin_session (vm, &peer->remote)) diff --git a/src/plugins/wireguard/wireguard_peer.c b/src/plugins/wireguard/wireguard_peer.c index f5fbc3c178c..589f71272f6 100644 --- a/src/plugins/wireguard/wireguard_peer.c +++ b/src/plugins/wireguard/wireguard_peer.c @@ -95,51 +95,6 @@ wg_peer_init (vlib_main_t * vm, wg_peer_t * peer) wg_peer_clear (vm, peer); } -static u8 * -wg_peer_build_rewrite (const wg_peer_t *peer, u8 is_ip4) -{ - u8 *rewrite = NULL; - if (is_ip4) - { - ip4_udp_header_t *hdr; - - /* reserve space for ip4, udp and wireguard headers */ - vec_validate (rewrite, sizeof (ip4_udp_wg_header_t) - 1); - hdr = (ip4_udp_header_t *) rewrite; - - hdr->ip4.ip_version_and_header_length = 0x45; - hdr->ip4.ttl = 64; - hdr->ip4.src_address = peer->src.addr.ip4; - hdr->ip4.dst_address = peer->dst.addr.ip4; - hdr->ip4.protocol = IP_PROTOCOL_UDP; - hdr->ip4.checksum = ip4_header_checksum (&hdr->ip4); - - hdr->udp.src_port = clib_host_to_net_u16 (peer->src.port); - hdr->udp.dst_port = clib_host_to_net_u16 (peer->dst.port); - hdr->udp.checksum = 0; - } - else - { - ip6_udp_header_t *hdr; - - /* reserve space for ip6, udp and wireguard headers */ - vec_validate (rewrite, sizeof (ip6_udp_wg_header_t) - 1); - hdr = (ip6_udp_header_t *) rewrite; - - hdr->ip6.ip_version_traffic_class_and_flow_label = 0x60; - ip6_address_copy (&hdr->ip6.src_address, &peer->src.addr.ip6); - ip6_address_copy (&hdr->ip6.dst_address, &peer->dst.addr.ip6); - hdr->ip6.protocol = IP_PROTOCOL_UDP; - hdr->ip6.hop_limit = 64; - - hdr->udp.src_port = clib_host_to_net_u16 (peer->src.port); - hdr->udp.dst_port = clib_host_to_net_u16 (peer->dst.port); - hdr->udp.checksum = 0; - } - - return (rewrite); -} - static void wg_peer_adj_stack (wg_peer_t *peer, adj_index_t ai) { @@ -327,7 +282,8 @@ wg_peer_fill (vlib_main_t *vm, wg_peer_t *peer, u32 table_id, peer->src.port = wgi->port; u8 is_ip4 = ip46_address_is_ip4 (&peer->dst.addr); - peer->rewrite = wg_peer_build_rewrite (peer, is_ip4); + peer->rewrite = wg_build_rewrite (&peer->src.addr, peer->src.port, + &peer->dst.addr, peer->dst.port, is_ip4); u32 ii; vec_validate (peer->allowed_ips, vec_len (allowed_ips) - 1); diff --git a/src/plugins/wireguard/wireguard_send.c b/src/plugins/wireguard/wireguard_send.c index 53692f074da..509fe70c777 100644 --- a/src/plugins/wireguard/wireguard_send.c +++ b/src/plugins/wireguard/wireguard_send.c @@ -41,7 +41,7 @@ ip46_enqueue_packet (vlib_main_t *vm, u32 bi0, int is_ip4) } static void -wg_buffer_prepend_rewrite (vlib_buffer_t *b0, const wg_peer_t *peer, u8 is_ip4) +wg_buffer_prepend_rewrite (vlib_buffer_t *b0, const u8 *rewrite, u8 is_ip4) { if (is_ip4) { @@ -52,7 +52,7 @@ wg_buffer_prepend_rewrite (vlib_buffer_t *b0, const wg_peer_t *peer, u8 is_ip4) hdr4 = vlib_buffer_get_current (b0); /* copy only ip4 and udp header; wireguard header not needed */ - clib_memcpy (hdr4, peer->rewrite, sizeof (ip4_udp_header_t)); + clib_memcpy (hdr4, rewrite, sizeof (ip4_udp_header_t)); hdr4->udp.length = clib_host_to_net_u16 (b0->current_length - sizeof (ip4_header_t)); @@ -68,7 +68,7 @@ wg_buffer_prepend_rewrite (vlib_buffer_t *b0, const wg_peer_t *peer, u8 is_ip4) hdr6 = vlib_buffer_get_current (b0); /* copy only ip6 and udp header; wireguard header not needed */ - clib_memcpy (hdr6, peer->rewrite, sizeof (ip6_udp_header_t)); + clib_memcpy (hdr6, rewrite, sizeof (ip6_udp_header_t)); hdr6->udp.length = clib_host_to_net_u16 (b0->current_length - sizeof (ip6_header_t)); @@ -78,7 +78,7 @@ wg_buffer_prepend_rewrite (vlib_buffer_t *b0, const wg_peer_t *peer, u8 is_ip4) } static bool -wg_create_buffer (vlib_main_t *vm, const wg_peer_t *peer, const u8 *packet, +wg_create_buffer (vlib_main_t *vm, const u8 *rewrite, const u8 *packet, u32 packet_len, u32 *bi, u8 is_ip4) { u32 n_buf0 = 0; @@ -95,11 +95,57 @@ wg_create_buffer (vlib_main_t *vm, const wg_peer_t *peer, const u8 *packet, b0->current_length = packet_len; - wg_buffer_prepend_rewrite (b0, peer, is_ip4); + wg_buffer_prepend_rewrite (b0, rewrite, is_ip4); return true; } +u8 * +wg_build_rewrite (ip46_address_t *src_addr, u16 src_port, + ip46_address_t *dst_addr, u16 dst_port, u8 is_ip4) +{ + u8 *rewrite = NULL; + if (is_ip4) + { + ip4_udp_header_t *hdr; + + /* reserve space for ip4, udp and wireguard headers */ + vec_validate (rewrite, sizeof (ip4_udp_wg_header_t) - 1); + hdr = (ip4_udp_header_t *) rewrite; + + hdr->ip4.ip_version_and_header_length = 0x45; + hdr->ip4.ttl = 64; + hdr->ip4.src_address = src_addr->ip4; + hdr->ip4.dst_address = dst_addr->ip4; + hdr->ip4.protocol = IP_PROTOCOL_UDP; + hdr->ip4.checksum = ip4_header_checksum (&hdr->ip4); + + hdr->udp.src_port = clib_host_to_net_u16 (src_port); + hdr->udp.dst_port = clib_host_to_net_u16 (dst_port); + hdr->udp.checksum = 0; + } + else + { + ip6_udp_header_t *hdr; + + /* reserve space for ip6, udp and wireguard headers */ + vec_validate (rewrite, sizeof (ip6_udp_wg_header_t) - 1); + hdr = (ip6_udp_header_t *) rewrite; + + hdr->ip6.ip_version_traffic_class_and_flow_label = 0x60; + ip6_address_copy (&hdr->ip6.src_address, &src_addr->ip6); + ip6_address_copy (&hdr->ip6.dst_address, &dst_addr->ip6); + hdr->ip6.protocol = IP_PROTOCOL_UDP; + hdr->ip6.hop_limit = 64; + + hdr->udp.src_port = clib_host_to_net_u16 (src_port); + hdr->udp.dst_port = clib_host_to_net_u16 (dst_port); + hdr->udp.checksum = 0; + } + + return (rewrite); +} + bool wg_send_handshake (vlib_main_t * vm, wg_peer_t * peer, bool is_retry) { @@ -135,8 +181,8 @@ wg_send_handshake (vlib_main_t * vm, wg_peer_t * peer, bool is_retry) u8 is_ip4 = ip46_address_is_ip4 (&peer->dst.addr); u32 bi0 = 0; - if (!wg_create_buffer (vm, peer, (u8 *) &packet, sizeof (packet), &bi0, - is_ip4)) + if (!wg_create_buffer (vm, peer->rewrite, (u8 *) &packet, sizeof (packet), + &bi0, is_ip4)) return false; ip46_enqueue_packet (vm, bi0, is_ip4); @@ -211,8 +257,8 @@ wg_send_keepalive (vlib_main_t * vm, wg_peer_t * peer) u8 is_ip4 = ip46_address_is_ip4 (&peer->dst.addr); packet->header.type = MESSAGE_DATA; - if (!wg_create_buffer (vm, peer, (u8 *) packet, size_of_packet, &bi0, - is_ip4)) + if (!wg_create_buffer (vm, peer->rewrite, (u8 *) packet, size_of_packet, + &bi0, is_ip4)) { ret = false; goto out; @@ -252,8 +298,8 @@ wg_send_handshake_response (vlib_main_t * vm, wg_peer_t * peer) u32 bi0 = 0; u8 is_ip4 = ip46_address_is_ip4 (&peer->dst.addr); - if (!wg_create_buffer (vm, peer, (u8 *) &packet, sizeof (packet), - &bi0, is_ip4)) + if (!wg_create_buffer (vm, peer->rewrite, (u8 *) &packet, + sizeof (packet), &bi0, is_ip4)) return false; ip46_enqueue_packet (vm, bi0, is_ip4); @@ -264,6 +310,36 @@ wg_send_handshake_response (vlib_main_t * vm, wg_peer_t * peer) return false; } +bool +wg_send_handshake_cookie (vlib_main_t *vm, u32 sender_index, + cookie_checker_t *cookie_checker, + message_macs_t *macs, ip46_address_t *wg_if_addr, + u16 wg_if_port, ip46_address_t *remote_addr, + u16 remote_port) +{ + message_handshake_cookie_t packet; + u8 *rewrite; + + packet.header.type = MESSAGE_HANDSHAKE_COOKIE; + packet.receiver_index = sender_index; + + cookie_checker_create_payload (vm, cookie_checker, macs, packet.nonce, + packet.encrypted_cookie, remote_addr, + remote_port); + + u32 bi0 = 0; + u8 is_ip4 = ip46_address_is_ip4 (remote_addr); + rewrite = wg_build_rewrite (wg_if_addr, wg_if_port, remote_addr, remote_port, + is_ip4); + if (!wg_create_buffer (vm, rewrite, (u8 *) &packet, sizeof (packet), &bi0, + is_ip4)) + return false; + + ip46_enqueue_packet (vm, bi0, is_ip4); + + return true; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/plugins/wireguard/wireguard_send.h b/src/plugins/wireguard/wireguard_send.h index 9575b84b659..419783a5db2 100644 --- a/src/plugins/wireguard/wireguard_send.h +++ b/src/plugins/wireguard/wireguard_send.h @@ -19,10 +19,17 @@ #include +u8 *wg_build_rewrite (ip46_address_t *src_addr, u16 src_port, + ip46_address_t *dst_addr, u16 dst_port, u8 is_ip4); bool wg_send_keepalive (vlib_main_t * vm, wg_peer_t * peer); bool wg_send_handshake (vlib_main_t * vm, wg_peer_t * peer, bool is_retry); void wg_send_handshake_from_mt (u32 peer_index, bool is_retry); bool wg_send_handshake_response (vlib_main_t * vm, wg_peer_t * peer); +bool wg_send_handshake_cookie (vlib_main_t *vm, u32 sender_index, + cookie_checker_t *cookie_checker, + message_macs_t *macs, + ip46_address_t *wg_if_addr, u16 wg_if_port, + ip46_address_t *remote_addr, u16 remote_port); always_inline void ip4_header_set_len_w_chksum (ip4_header_t * ip4, u16 len) diff --git a/test/test_wireguard.py b/test/test_wireguard.py index 7395402e27f..564dee2fc2e 100644 --- a/test/test_wireguard.py +++ b/test/test_wireguard.py @@ -78,6 +78,9 @@ class VppWgInterface(VppInterface): self.private_key = X25519PrivateKey.generate() self.public_key = self.private_key.public_key() + # cookie related params + self.cookie_key = blake2s(b"cookie--" + self.public_key_bytes()).digest() + def public_key_bytes(self): return public_key_bytes(self.public_key) @@ -146,6 +149,10 @@ def find_route(test, prefix, is_ip6, table_id=0): NOISE_HANDSHAKE_NAME = b"Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s" NOISE_IDENTIFIER_NAME = b"WireGuard v1 zx2c4 Jason@zx2c4.com" +HANDSHAKE_COUNTING_INTERVAL = 0.5 +UNDER_LOAD_INTERVAL = 1.0 +HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD = 40 + class VppWgPeer(VppObject): def __init__(self, test, itf, endpoint, port, allowed_ips, persistent_keepalive=15): @@ -163,6 +170,8 @@ class VppWgPeer(VppObject): # cookie related params self.cookie_key = blake2s(b"cookie--" + self.public_key_bytes()).digest() self.last_sent_cookie = None + self.last_mac1 = None + self.last_received_cookie = None self.noise = NoiseConnection.from_name(NOISE_HANDSHAKE_NAME) @@ -226,6 +235,9 @@ class VppWgPeer(VppObject): / UDP(sport=self.port, dport=self.itf.port) ) + def noise_reset(self): + self.noise = NoiseConnection.from_name(NOISE_HANDSHAKE_NAME) + def noise_init(self, public_key=None): self.noise.set_prologue(NOISE_IDENTIFIER_NAME) self.noise.set_psks(psk=bytes(bytearray(32))) @@ -293,6 +305,27 @@ class VppWgPeer(VppObject): return cookie_reply + def consume_cookie(self, p, is_ip6=False): + self.verify_header(p, is_ip6) + + cookie_reply = Wireguard(p[Raw]) + + self._test.assertEqual(cookie_reply[Wireguard].message_type, 3) + self._test.assertEqual(cookie_reply[Wireguard].reserved_zero, 0) + self._test.assertEqual( + cookie_reply[WireguardCookieReply].receiver_index, self.receiver_index + ) + + # collect info from cookie reply + nonce = cookie_reply[WireguardCookieReply].nonce + encrypted_cookie = cookie_reply[WireguardCookieReply].encrypted_cookie + ciphertext, tag = encrypted_cookie[:16], encrypted_cookie[16:] + + # decrypt cookie data + cipher = ChaCha20_Poly1305.new(key=self.itf.cookie_key, nonce=nonce) + cipher.update(self.last_mac1) + self.last_received_cookie = cipher.decrypt_and_verify(ciphertext, tag) + def mk_handshake(self, tx_itf, is_ip6=False, public_key=None): self.noise.set_as_initiator() self.noise_init(public_key) @@ -320,10 +353,19 @@ class VppWgPeer(VppObject): # generate the mac1 hash mac_key = blake2s(b"mac1----" + self.itf.public_key_bytes()).digest() - p[WireguardInitiation].mac1 = blake2s( - bytes(p)[0:116], digest_size=16, key=mac_key - ).digest() - p[WireguardInitiation].mac2 = bytearray(16) + mac1 = blake2s(bytes(p)[0:116], digest_size=16, key=mac_key).digest() + p[WireguardInitiation].mac1 = mac1 + self.last_mac1 = mac1 + + # generate the mac2 hash + if self.last_received_cookie: + mac2 = blake2s( + bytes(p)[0:132], digest_size=16, key=self.last_received_cookie + ).digest() + p[WireguardInitiation].mac2 = mac2 + self.last_received_cookie = None + else: + p[WireguardInitiation].mac2 = bytearray(16) p = self.mk_tunnel_header(tx_itf, is_ip6) / p @@ -384,6 +426,7 @@ class VppWgPeer(VppObject): ) mac1 = blake2s(bytes(resp)[:-32], digest_size=16, key=mac_key).digest() resp[WireguardResponse].mac1 = mac1 + self.last_mac1 = mac1 resp = self.mk_tunnel_header(tx_itf, is_ip6) / resp self._test.assertTrue(self.noise.handshake_finished) @@ -646,6 +689,146 @@ class TestWg(VppTestCase): """Send cookie on handshake response (v6)""" self._test_wg_send_cookie_tmpl(is_resp=True, is_ip6=True) + def _test_wg_receive_cookie_tmpl(self, is_resp, is_ip6): + port = 12323 + + # create wg interface + if is_ip6: + wg0 = VppWgInterface(self, self.pg1.local_ip6, port).add_vpp_config() + wg0.admin_up() + wg0.config_ip6() + else: + 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 + if is_ip6: + peer_1 = VppWgPeer( + self, wg0, self.pg1.remote_ip6, port + 1, ["1::3:0/112"] + ).add_vpp_config() + else: + 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) + + if is_resp: + # wait for the peer to send a handshake initiation + rxs = self.pg1.get_capture(1, timeout=2) + # prepare and send a bunch of handshake responses + # expect to switch to under load state + resp = peer_1.consume_init(rxs[0], self.pg1, is_ip6=is_ip6) + txs = [resp] * HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD + rxs = self.send_and_expect_some(self.pg1, txs, self.pg1) + # reset noise to be able to turn into initiator later + peer_1.noise_reset() + else: + # prepare and send a bunch of handshake initiations + # expect to switch to under load state + init = peer_1.mk_handshake(self.pg1, is_ip6=is_ip6) + txs = [init] * HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD + rxs = self.send_and_expect_some(self.pg1, txs, self.pg1) + + # expect the peer to send a cookie reply + peer_1.consume_cookie(rxs[-1], is_ip6=is_ip6) + + # prepare and send a handshake initiation with wrong mac2 + # expect a cookie reply + init = peer_1.mk_handshake(self.pg1, is_ip6=is_ip6) + init.mac2 = b"1234567890" + rxs = self.send_and_expect(self.pg1, [init], self.pg1) + peer_1.consume_cookie(rxs[0], is_ip6=is_ip6) + + # prepare and send a handshake initiation with correct mac2 + # expect a handshake response + init = peer_1.mk_handshake(self.pg1, is_ip6=is_ip6) + rxs = self.send_and_expect(self.pg1, [init], self.pg1) + + # verify the response + peer_1.consume_response(rxs[0], is_ip6=is_ip6) + + # clear up under load state + self.sleep(UNDER_LOAD_INTERVAL) + + # remove configs + peer_1.remove_vpp_config() + wg0.remove_vpp_config() + + def test_wg_receive_cookie_on_init_v4(self): + """Receive cookie on handshake initiation (v4)""" + self._test_wg_receive_cookie_tmpl(is_resp=False, is_ip6=False) + + def test_wg_receive_cookie_on_init_v6(self): + """Receive cookie on handshake initiation (v6)""" + self._test_wg_receive_cookie_tmpl(is_resp=False, is_ip6=True) + + def test_wg_receive_cookie_on_resp_v4(self): + """Receive cookie on handshake response (v4)""" + self._test_wg_receive_cookie_tmpl(is_resp=True, is_ip6=False) + + def test_wg_receive_cookie_on_resp_v6(self): + """Receive cookie on handshake response (v6)""" + self._test_wg_receive_cookie_tmpl(is_resp=True, is_ip6=True) + + def test_wg_under_load_interval(self): + """Under load interval""" + 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) + + # prepare and send a bunch of handshake initiations + # expect to switch to under load state + init = peer_1.mk_handshake(self.pg1) + txs = [init] * HANDSHAKE_NUM_PER_PEER_UNTIL_UNDER_LOAD + rxs = self.send_and_expect_some(self.pg1, txs, self.pg1) + + # expect the peer to send a cookie reply + peer_1.consume_cookie(rxs[-1]) + + # sleep till the next counting interval + # expect under load state is still active + self.sleep(HANDSHAKE_COUNTING_INTERVAL) + + # prepare and send a handshake initiation with wrong mac2 + # expect a cookie reply + init = peer_1.mk_handshake(self.pg1) + init.mac2 = b"1234567890" + rxs = self.send_and_expect(self.pg1, [init], self.pg1) + peer_1.consume_cookie(rxs[0]) + + # sleep till the end of being under load + # expect under load state is over + self.sleep(UNDER_LOAD_INTERVAL - HANDSHAKE_COUNTING_INTERVAL) + + # prepare and send a handshake initiation with wrong mac2 + # expect a handshake response + init = peer_1.mk_handshake(self.pg1) + init.mac2 = b"1234567890" + rxs = self.send_and_expect(self.pg1, [init], self.pg1) + + # verify the response + peer_1.consume_response(rxs[0]) + + # remove configs + peer_1.remove_vpp_config() + wg0.remove_vpp_config() + def test_wg_peer_resp(self): """Send handshake response""" port = 12323 -- 2.16.6