From fee9853a4f5d9a180ef6309cc37bd4060d27a51e Mon Sep 17 00:00:00 2001 From: Alexander Chernavin Date: Thu, 4 Aug 2022 08:11:57 +0000 Subject: wireguard: add peers roaming support Type: feature With this change, peers are able to roam between different external endpoints. Successfully authenticated handshake or data packet that is received from a new endpoint will cause the peer's endpoint to be updated accordingly. Signed-off-by: Alexander Chernavin Change-Id: Ib4eb7dfa3403f3fb9e8bbe19ba6237c4960c764c --- src/plugins/wireguard/wireguard_peer.c | 125 +++++++++++++++++++++++++++------ 1 file changed, 102 insertions(+), 23 deletions(-) (limited to 'src/plugins/wireguard/wireguard_peer.c') diff --git a/src/plugins/wireguard/wireguard_peer.c b/src/plugins/wireguard/wireguard_peer.c index 589f71272f6..922ca8cdae5 100644 --- a/src/plugins/wireguard/wireguard_peer.c +++ b/src/plugins/wireguard/wireguard_peer.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -63,13 +64,14 @@ wg_peer_clear (vlib_main_t * vm, wg_peer_t * peer) wg_peer_endpoint_reset (&peer->src); wg_peer_endpoint_reset (&peer->dst); - adj_index_t *adj_index; - vec_foreach (adj_index, peer->adj_indices) + wg_peer_adj_t *peer_adj; + vec_foreach (peer_adj, peer->adjs) { - if (INDEX_INVALID != *adj_index) - { - wg_peer_by_adj_index[*adj_index] = INDEX_INVALID; - } + wg_peer_by_adj_index[peer_adj->adj_index] = INDEX_INVALID; + if (FIB_NODE_INDEX_INVALID != peer_adj->fib_entry_index) + fib_entry_untrack (peer_adj->fib_entry_index, peer_adj->sibling_index); + if (adj_is_valid (peer_adj->adj_index)) + adj_nbr_midchain_unstack (peer_adj->adj_index); } peer->input_thread_index = ~0; peer->output_thread_index = ~0; @@ -83,8 +85,9 @@ wg_peer_clear (vlib_main_t * vm, wg_peer_t * peer) peer->new_handshake_interval_tick = 0; peer->rehandshake_interval_tick = 0; peer->timer_need_another_keepalive = false; + vec_free (peer->rewrite); vec_free (peer->allowed_ips); - vec_free (peer->adj_indices); + vec_free (peer->adjs); } static void @@ -96,17 +99,17 @@ wg_peer_init (vlib_main_t * vm, wg_peer_t * peer) } static void -wg_peer_adj_stack (wg_peer_t *peer, adj_index_t ai) +wg_peer_adj_stack (wg_peer_t *peer, wg_peer_adj_t *peer_adj) { ip_adjacency_t *adj; u32 sw_if_index; wg_if_t *wgi; fib_protocol_t fib_proto; - if (!adj_is_valid (ai)) + if (!adj_is_valid (peer_adj->adj_index)) return; - adj = adj_get (ai); + adj = adj_get (peer_adj->adj_index); sw_if_index = adj->rewrite_header.sw_if_index; u8 is_ip4 = ip46_address_is_ip4 (&peer->src.addr); fib_proto = is_ip4 ? FIB_PROTOCOL_IP4 : FIB_PROTOCOL_IP6; @@ -116,9 +119,10 @@ wg_peer_adj_stack (wg_peer_t *peer, adj_index_t ai) if (!wgi) return; - if (!vnet_sw_interface_is_admin_up (vnet_get_main (), wgi->sw_if_index)) + if (!vnet_sw_interface_is_admin_up (vnet_get_main (), wgi->sw_if_index) || + !wg_peer_can_send (peer)) { - adj_midchain_delegate_unstack (ai); + adj_nbr_midchain_unstack (peer_adj->adj_index); } else { @@ -132,8 +136,13 @@ wg_peer_adj_stack (wg_peer_t *peer, adj_index_t ai) u32 fib_index; fib_index = fib_table_find (fib_proto, peer->table_id); + peer_adj->fib_entry_index = + fib_entry_track (fib_index, &dst, FIB_NODE_TYPE_ADJ, + peer_adj->adj_index, &peer_adj->sibling_index); - adj_midchain_delegate_stack (ai, fib_index, &dst); + adj_nbr_midchain_stack_on_fib_entry ( + peer_adj->adj_index, peer_adj->fib_entry_index, + fib_forw_chain_type_from_fib_proto (dst.fp_proto)); } } @@ -198,11 +207,11 @@ walk_rc_t wg_peer_if_admin_state_change (index_t peeri, void *data) { wg_peer_t *peer; - adj_index_t *adj_index; + wg_peer_adj_t *peer_adj; peer = wg_peer_get (peeri); - vec_foreach (adj_index, peer->adj_indices) + vec_foreach (peer_adj, peer->adjs) { - wg_peer_adj_stack (peer, *adj_index); + wg_peer_adj_stack (peer, peer_adj); } return (WALK_CONTINUE); } @@ -215,6 +224,7 @@ wg_peer_if_adj_change (index_t peeri, void *data) ip_adjacency_t *adj; wg_peer_t *peer; fib_prefix_t *allowed_ip; + wg_peer_adj_t *peer_adj; adj = adj_get (*adj_index); @@ -224,17 +234,21 @@ wg_peer_if_adj_change (index_t peeri, void *data) if (fib_prefix_is_cover_addr_46 (allowed_ip, &adj->sub_type.nbr.next_hop)) { - vec_add1 (peer->adj_indices, *adj_index); + vec_add2 (peer->adjs, peer_adj, 1); + peer_adj->adj_index = *adj_index; + peer_adj->fib_entry_index = FIB_NODE_INDEX_INVALID; + peer_adj->sibling_index = ~0; + vec_validate_init_empty (wg_peer_by_adj_index, *adj_index, INDEX_INVALID); - wg_peer_by_adj_index[*adj_index] = peer - wg_peer_pool; + wg_peer_by_adj_index[*adj_index] = peeri; fixup = wg_peer_get_fixup (peer, adj_get_link_type (*adj_index)); adj_nbr_midchain_update_rewrite (*adj_index, fixup, NULL, ADJ_FLAG_MIDCHAIN_IP_STACK, vec_dup (peer->rewrite)); - wg_peer_adj_stack (peer, *adj_index); + wg_peer_adj_stack (peer, peer_adj); return (WALK_STOP); } } @@ -313,6 +327,71 @@ wg_peer_update_flags (index_t peeri, wg_peer_flags flag, bool add_del) wg_api_peer_event (peeri, peer->flags); } +void +wg_peer_update_endpoint (index_t peeri, const ip46_address_t *addr, u16 port) +{ + wg_peer_t *peer = wg_peer_get (peeri); + + if (ip46_address_is_equal (&peer->dst.addr, addr) && peer->dst.port == port) + return; + + wg_peer_endpoint_init (&peer->dst, addr, port); + + u8 is_ip4 = ip46_address_is_ip4 (&peer->dst.addr); + vec_free (peer->rewrite); + peer->rewrite = wg_build_rewrite (&peer->src.addr, peer->src.port, + &peer->dst.addr, peer->dst.port, is_ip4); + + wg_peer_adj_t *peer_adj; + vec_foreach (peer_adj, peer->adjs) + { + if (FIB_NODE_INDEX_INVALID != peer_adj->fib_entry_index) + { + fib_entry_untrack (peer_adj->fib_entry_index, + peer_adj->sibling_index); + peer_adj->fib_entry_index = FIB_NODE_INDEX_INVALID; + peer_adj->sibling_index = ~0; + } + + if (adj_is_valid (peer_adj->adj_index)) + { + adj_midchain_fixup_t fixup = + wg_peer_get_fixup (peer, adj_get_link_type (peer_adj->adj_index)); + adj_nbr_midchain_update_rewrite (peer_adj->adj_index, fixup, NULL, + ADJ_FLAG_MIDCHAIN_IP_STACK, + vec_dup (peer->rewrite)); + wg_peer_adj_stack (peer, peer_adj); + } + } +} + +typedef struct wg_peer_upd_ep_args_t_ +{ + index_t peeri; + ip46_address_t addr; + u16 port; +} wg_peer_upd_ep_args_t; + +static void +wg_peer_update_endpoint_thread_fn (wg_peer_upd_ep_args_t *args) +{ + wg_peer_update_endpoint (args->peeri, &args->addr, args->port); +} + +void +wg_peer_update_endpoint_from_mt (index_t peeri, const ip46_address_t *addr, + u16 port) +{ + wg_peer_upd_ep_args_t args = { + .peeri = peeri, + .port = port, + }; + + ip46_address_copy (&args.addr, addr); + vlib_rpc_call_main_thread (wg_peer_update_endpoint_thread_fn, (u8 *) &args, + sizeof (args)); +} + int wg_peer_add (u32 tun_sw_if_index, const u8 public_key[NOISE_PUBLIC_KEY_LEN], u32 table_id, const ip46_address_t *endpoint, @@ -345,7 +424,7 @@ wg_peer_add (u32 tun_sw_if_index, const u8 public_key[NOISE_PUBLIC_KEY_LEN], if (pool_elts (wg_peer_pool) > MAX_PEERS) return (VNET_API_ERROR_LIMIT_EXCEEDED); - pool_get (wg_peer_pool, peer); + pool_get_zero (wg_peer_pool, peer); wg_peer_init (vm, peer); @@ -428,9 +507,9 @@ format_wg_peer (u8 * s, va_list * va) { index_t peeri = va_arg (*va, index_t); fib_prefix_t *allowed_ip; - adj_index_t *adj_index; u8 key[NOISE_KEY_LEN_BASE64]; wg_peer_t *peer; + wg_peer_adj_t *peer_adj; peer = wg_peer_get (peeri); key_to_base64 (peer->remote.r_public, NOISE_PUBLIC_KEY_LEN, key); @@ -443,9 +522,9 @@ format_wg_peer (u8 * s, va_list * va) peer->wg_sw_if_index, peer->persistent_keepalive_interval, peer->flags, pool_elts (peer->api_clients)); s = format (s, "\n adj:"); - vec_foreach (adj_index, peer->adj_indices) + vec_foreach (peer_adj, peer->adjs) { - s = format (s, " %d", *adj_index); + s = format (s, " %d", peer_adj->adj_index); } s = format (s, "\n key:%=s %U", key, format_hex_bytes, peer->remote.r_public, NOISE_PUBLIC_KEY_LEN); -- cgit 1.2.3-korg