diff options
author | Alexander Chernavin <achernavin@netgate.com> | 2022-07-20 13:01:42 +0000 |
---|---|---|
committer | Matthew Smith <mgsmith@netgate.com> | 2022-08-09 15:42:43 +0000 |
commit | a6328e51e0c831ba3f0f4977f776491ac44eaec5 (patch) | |
tree | 2824c830f35554ed16887be32065d48f66809c34 /src | |
parent | fd2417b2a42e34062e3d07875e5c4e11922513d5 (diff) |
wireguard: add handshake rate limiting support
Type: feature
With this change, if being under load a handshake message with both
valid mac1 and mac2 is received, the peer will be rate limited. Cover
this with tests.
Signed-off-by: Alexander Chernavin <achernavin@netgate.com>
Change-Id: Id8d58bb293a7975c3d922c48b4948fd25e20af4b
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/wireguard/FEATURE.yaml | 3 | ||||
-rw-r--r-- | src/plugins/wireguard/README.rst | 3 | ||||
-rw-r--r-- | src/plugins/wireguard/wireguard_cookie.c | 147 | ||||
-rw-r--r-- | src/plugins/wireguard/wireguard_cookie.h | 23 | ||||
-rw-r--r-- | src/plugins/wireguard/wireguard_if.c | 6 | ||||
-rw-r--r-- | src/plugins/wireguard/wireguard_input.c | 3 |
6 files changed, 178 insertions, 7 deletions
diff --git a/src/plugins/wireguard/FEATURE.yaml b/src/plugins/wireguard/FEATURE.yaml index cf8b6d7f3c4..4c6946d2a33 100644 --- a/src/plugins/wireguard/FEATURE.yaml +++ b/src/plugins/wireguard/FEATURE.yaml @@ -8,5 +8,4 @@ description: "Wireguard protocol implementation" state: development properties: [API, CLI] missing: - - IPv6 support - - DoS protection as in the original protocol + - Peers roaming between different external IPs diff --git a/src/plugins/wireguard/README.rst b/src/plugins/wireguard/README.rst index cb7a024fdf9..ead412519b7 100644 --- a/src/plugins/wireguard/README.rst +++ b/src/plugins/wireguard/README.rst @@ -77,5 +77,4 @@ Main next steps for improving this implementation ------------------------------------------------- 1. Use all benefits of VPP-engine. -2. Add IPv6 support (currently only supports IPv4) -3. Add DoS protection as in original protocol (using cookie) +2. Add peers roaming support diff --git a/src/plugins/wireguard/wireguard_cookie.c b/src/plugins/wireguard/wireguard_cookie.c index 595b8770e56..4ebbfa0fa63 100644 --- a/src/plugins/wireguard/wireguard_cookie.c +++ b/src/plugins/wireguard/wireguard_cookie.c @@ -34,6 +34,11 @@ static void cookie_checker_make_cookie (vlib_main_t *vm, cookie_checker_t *, uint8_t[COOKIE_COOKIE_SIZE], ip46_address_t *ip, u16 udp_port); +static void ratelimit_init (ratelimit_t *, ratelimit_entry_t *); +static void ratelimit_deinit (ratelimit_t *); +static void ratelimit_gc (ratelimit_t *, bool); +static bool ratelimit_allow (ratelimit_t *, ip46_address_t *); + /* Public Functions */ void cookie_maker_init (cookie_maker_t * cp, const uint8_t key[COOKIE_INPUT_SIZE]) @@ -44,6 +49,14 @@ cookie_maker_init (cookie_maker_t * cp, const uint8_t key[COOKIE_INPUT_SIZE]) } void +cookie_checker_init (cookie_checker_t *cc, ratelimit_entry_t *pool) +{ + clib_memset (cc, 0, sizeof (*cc)); + ratelimit_init (&cc->cc_ratelimit_v4, pool); + ratelimit_init (&cc->cc_ratelimit_v6, pool); +} + +void cookie_checker_update (cookie_checker_t * cc, uint8_t key[COOKIE_INPUT_SIZE]) { if (key) @@ -59,6 +72,13 @@ cookie_checker_update (cookie_checker_t * cc, uint8_t key[COOKIE_INPUT_SIZE]) } void +cookie_checker_deinit (cookie_checker_t *cc) +{ + ratelimit_deinit (&cc->cc_ratelimit_v4); + ratelimit_deinit (&cc->cc_ratelimit_v6); +} + +void cookie_checker_create_payload (vlib_main_t *vm, cookie_checker_t *cc, message_macs_t *cm, uint8_t nonce[COOKIE_NONCE_SIZE], @@ -146,6 +166,13 @@ cookie_checker_validate_macs (vlib_main_t *vm, cookie_checker_t *cc, if (clib_memcmp (our_cm.mac2, cm->mac2, COOKIE_MAC_SIZE) != 0) return VALID_MAC_BUT_NO_COOKIE; + /* If the mac2 is valid, we may want to rate limit the peer */ + ratelimit_t *rl; + rl = ip46_address_is_ip4 (ip) ? &cc->cc_ratelimit_v4 : &cc->cc_ratelimit_v6; + + if (!ratelimit_allow (rl, ip)) + return VALID_MAC_WITH_COOKIE_BUT_RATELIMITED; + return VALID_MAC_WITH_COOKIE; } @@ -213,6 +240,126 @@ cookie_checker_make_cookie (vlib_main_t *vm, cookie_checker_t *cc, blake2s_final (&state, cookie, COOKIE_COOKIE_SIZE); } +static void +ratelimit_init (ratelimit_t *rl, ratelimit_entry_t *pool) +{ + rl->rl_pool = pool; +} + +static void +ratelimit_deinit (ratelimit_t *rl) +{ + ratelimit_gc (rl, /* force */ true); + hash_free (rl->rl_table); +} + +static void +ratelimit_gc (ratelimit_t *rl, bool force) +{ + u32 r_key; + u32 r_idx; + ratelimit_entry_t *r; + + if (force) + { + /* clang-format off */ + hash_foreach (r_key, r_idx, rl->rl_table, { + r = pool_elt_at_index (rl->rl_pool, r_idx); + pool_put (rl->rl_pool, r); + }); + /* clang-format on */ + return; + } + + f64 now = vlib_time_now (vlib_get_main ()); + + if ((rl->rl_last_gc + ELEMENT_TIMEOUT) < now) + { + u32 *r_key_to_del = NULL; + u32 *pr_key; + + rl->rl_last_gc = now; + + /* clang-format off */ + hash_foreach (r_key, r_idx, rl->rl_table, { + r = pool_elt_at_index (rl->rl_pool, r_idx); + if ((r->r_last_time + ELEMENT_TIMEOUT) < now) + { + vec_add1 (r_key_to_del, r_key); + pool_put (rl->rl_pool, r); + } + }); + /* clang-format on */ + + vec_foreach (pr_key, r_key_to_del) + { + hash_unset (rl->rl_table, *pr_key); + } + + vec_free (r_key_to_del); + } +} + +static bool +ratelimit_allow (ratelimit_t *rl, ip46_address_t *ip) +{ + u32 r_key; + uword *p; + u32 r_idx; + ratelimit_entry_t *r; + f64 now = vlib_time_now (vlib_get_main ()); + + if (ip46_address_is_ip4 (ip)) + /* Use all 4 bytes of IPv4 address */ + r_key = ip->ip4.as_u32; + else + /* Use top 8 bytes (/64) of IPv6 address */ + r_key = ip->ip6.as_u32[0] ^ ip->ip6.as_u32[1]; + + /* Check if there is already an entry for the IP address */ + p = hash_get (rl->rl_table, r_key); + if (p) + { + u64 tokens; + f64 diff; + + r_idx = p[0]; + r = pool_elt_at_index (rl->rl_pool, r_idx); + + diff = now - r->r_last_time; + r->r_last_time = now; + + tokens = r->r_tokens + diff * NSEC_PER_SEC; + + if (tokens > TOKEN_MAX) + tokens = TOKEN_MAX; + + if (tokens >= INITIATION_COST) + { + r->r_tokens = tokens - INITIATION_COST; + return true; + } + + r->r_tokens = tokens; + return false; + } + + /* No entry for the IP address */ + ratelimit_gc (rl, /* force */ false); + + if (hash_elts (rl->rl_table) >= RATELIMIT_SIZE_MAX) + return false; + + pool_get (rl->rl_pool, r); + r_idx = r - rl->rl_pool; + hash_set (rl->rl_table, r_key, r_idx); + + r->r_last_time = now; + r->r_tokens = TOKEN_MAX - INITIATION_COST; + + return true; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/plugins/wireguard/wireguard_cookie.h b/src/plugins/wireguard/wireguard_cookie.h index 9298ece8db5..7467cf2ed4a 100644 --- a/src/plugins/wireguard/wireguard_cookie.h +++ b/src/plugins/wireguard/wireguard_cookie.h @@ -25,7 +25,8 @@ enum cookie_mac_state { INVALID_MAC, VALID_MAC_BUT_NO_COOKIE, - VALID_MAC_WITH_COOKIE + VALID_MAC_WITH_COOKIE, + VALID_MAC_WITH_COOKIE_BUT_RATELIMITED, }; #define COOKIE_MAC_SIZE 16 @@ -50,8 +51,6 @@ enum cookie_mac_state #define INITIATION_COST (NSEC_PER_SEC / INITIATIONS_PER_SECOND) #define TOKEN_MAX (INITIATION_COST * INITIATIONS_BURSTABLE) #define ELEMENT_TIMEOUT 1 -#define IPV4_MASK_SIZE 4 /* Use all 4 bytes of IPv4 address */ -#define IPV6_MASK_SIZE 8 /* Use top 8 bytes (/64) of IPv6 address */ typedef struct cookie_macs { @@ -59,6 +58,19 @@ typedef struct cookie_macs uint8_t mac2[COOKIE_MAC_SIZE]; } message_macs_t; +typedef struct ratelimit_entry +{ + f64 r_last_time; + u64 r_tokens; +} ratelimit_entry_t; + +typedef struct ratelimit +{ + ratelimit_entry_t *rl_pool; + uword *rl_table; + f64 rl_last_gc; +} ratelimit_t; + typedef struct cookie_maker { uint8_t cp_mac1_key[COOKIE_KEY_SIZE]; @@ -72,6 +84,9 @@ typedef struct cookie_maker typedef struct cookie_checker { + ratelimit_t cc_ratelimit_v4; + ratelimit_t cc_ratelimit_v6; + uint8_t cc_mac1_key[COOKIE_KEY_SIZE]; uint8_t cc_cookie_key[COOKIE_KEY_SIZE]; @@ -81,7 +96,9 @@ typedef struct cookie_checker void cookie_maker_init (cookie_maker_t *, const uint8_t[COOKIE_INPUT_SIZE]); +void cookie_checker_init (cookie_checker_t *, ratelimit_entry_t *); void cookie_checker_update (cookie_checker_t *, uint8_t[COOKIE_INPUT_SIZE]); +void cookie_checker_deinit (cookie_checker_t *); void cookie_checker_create_payload (vlib_main_t *vm, cookie_checker_t *cc, message_macs_t *cm, uint8_t nonce[COOKIE_NONCE_SIZE], diff --git a/src/plugins/wireguard/wireguard_if.c b/src/plugins/wireguard/wireguard_if.c index c4199d23354..a869df08ce2 100644 --- a/src/plugins/wireguard/wireguard_if.c +++ b/src/plugins/wireguard/wireguard_if.c @@ -34,6 +34,9 @@ static index_t *wg_if_index_by_sw_if_index; /* vector of interfaces key'd on their UDP port (in network order) */ index_t **wg_if_indexes_by_port; +/* pool of ratelimit entries */ +static ratelimit_entry_t *wg_ratelimit_pool; + static u8 * format_wg_if_name (u8 * s, va_list * args) { @@ -309,6 +312,7 @@ wg_if_create (u32 user_instance, wg_if->port = port; wg_if->local_idx = local - noise_local_pool; + cookie_checker_init (&wg_if->cookie_checker, wg_ratelimit_pool); cookie_checker_update (&wg_if->cookie_checker, local->l_public); hw_if_index = vnet_register_interface (vnm, @@ -372,6 +376,8 @@ wg_if_delete (u32 sw_if_index) udp_unregister_dst_port (vlib_get_main (), wg_if->port, 0); } + cookie_checker_deinit (&wg_if->cookie_checker); + vnet_reset_interface_l3_output_node (vnm->vlib_main, sw_if_index); vnet_delete_hw_interface (vnm, hw->hw_if_index); pool_put_index (noise_local_pool, wg_if->local_idx); diff --git a/src/plugins/wireguard/wireguard_input.c b/src/plugins/wireguard/wireguard_input.c index 3f546cc494f..b85cdc610e4 100644 --- a/src/plugins/wireguard/wireguard_input.c +++ b/src/plugins/wireguard/wireguard_input.c @@ -25,6 +25,7 @@ #define foreach_wg_input_error \ _ (NONE, "No error") \ _ (HANDSHAKE_MAC, "Invalid MAC handshake") \ + _ (HANDSHAKE_RATELIMITED, "Handshake ratelimited") \ _ (PEER, "Peer error") \ _ (INTERFACE, "Interface error") \ _ (DECRYPTION, "Failed during decryption") \ @@ -232,6 +233,8 @@ wg_handshake_process (vlib_main_t *vm, wg_main_t *wmp, vlib_buffer_t *b, packet_needs_cookie = false; else if (under_load && mac_state == VALID_MAC_BUT_NO_COOKIE) packet_needs_cookie = true; + else if (mac_state == VALID_MAC_WITH_COOKIE_BUT_RATELIMITED) + return WG_INPUT_ERROR_HANDSHAKE_RATELIMITED; else return WG_INPUT_ERROR_HANDSHAKE_MAC; |