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 <achernavin@netgate.com> Change-Id: I3003570a9cf807cfb0b5145b89a085455c30e717
This commit is contained in:
Alexander Chernavin
committed by
Fan Zhang
parent
03aae96379
commit
ce91af8ad2
@ -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,
|
||||
|
@ -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],
|
||||
|
@ -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],
|
||||
|
@ -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]);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -19,10 +19,17 @@
|
||||
|
||||
#include <wireguard/wireguard_peer.h>
|
||||
|
||||
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)
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user