Files
vpp/src/plugins/wireguard/wireguard_peer.c
T
Alexander Chernavin ae60538925 wireguard: fix fib entry tracking
Type: fix

After peers roaming support addition, FIB entry tracking stopped
working. For example, it can be observed when an adjacency is stacked on
a FIB entry by the plugin and the FIB entry hasn't got ARP resolution
yet. Once the FIB entry gets ARP resolution, the adjacency is not
re-stacked as it used to. This results in endless ARP requests when a
traffic is sent via the adjacency.

This is broken because the plugin stopped using "midchain delegate" with
peers roaming support addition. The reason is that "midchain delegate"
didn't support stacking on a different FIB entry which is needed when
peer's endpoint changes. Now it is supported there (added in 36892).

With this fix, start using "midchane delegate" again and thus, fix FIB
entry tracking. Also, cover this in tests.

Signed-off-by: Alexander Chernavin <achernavin@netgate.com>
Change-Id: Iea91f38739ab129e601fd6567b52565dbd649371
2022-08-17 09:04:27 +00:00

536 lines
13 KiB
C

/*
* Copyright (c) 2020 Doc.ai and/or its affiliates.
* Copyright (c) 2020 Cisco and/or its affiliates.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <vnet/adj/adj_midchain.h>
#include <vnet/fib/fib_table.h>
#include <wireguard/wireguard_peer.h>
#include <wireguard/wireguard_if.h>
#include <wireguard/wireguard_messages.h>
#include <wireguard/wireguard_key.h>
#include <wireguard/wireguard_send.h>
#include <wireguard/wireguard.h>
#include <vnet/tunnel/tunnel_dp.h>
wg_peer_t *wg_peer_pool;
index_t *wg_peer_by_adj_index;
static void
wg_peer_endpoint_reset (wg_peer_endpoint_t * ep)
{
ip46_address_reset (&ep->addr);
ep->port = 0;
}
static void
wg_peer_endpoint_init (wg_peer_endpoint_t *ep, const ip46_address_t *addr,
u16 port)
{
ip46_address_copy (&ep->addr, addr);
ep->port = port;
}
static void
wg_peer_clear (vlib_main_t * vm, wg_peer_t * peer)
{
index_t perri = peer - wg_peer_pool;
wg_timers_stop (peer);
wg_peer_update_flags (perri, WG_PEER_ESTABLISHED, false);
wg_peer_update_flags (perri, WG_PEER_STATUS_DEAD, true);
for (int i = 0; i < WG_N_TIMERS; i++)
{
peer->timers[i] = ~0;
peer->timers_dispatched[i] = 0;
}
peer->last_sent_handshake = vlib_time_now (vm) - (REKEY_TIMEOUT + 1);
clib_memset (&peer->cookie_maker, 0, sizeof (peer->cookie_maker));
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_by_adj_index[*adj_index] = INDEX_INVALID;
if (adj_is_valid (*adj_index))
adj_midchain_delegate_unstack (*adj_index);
}
peer->input_thread_index = ~0;
peer->output_thread_index = ~0;
peer->timer_wheel = 0;
peer->persistent_keepalive_interval = 0;
peer->timer_handshake_attempts = 0;
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;
vec_free (peer->rewrite);
vec_free (peer->allowed_ips);
vec_free (peer->adj_indices);
}
static void
wg_peer_init (vlib_main_t * vm, wg_peer_t * peer)
{
peer->api_client_by_client_index = hash_create (0, sizeof (u32));
peer->api_clients = NULL;
wg_peer_clear (vm, peer);
}
static void
wg_peer_adj_stack (wg_peer_t *peer, adj_index_t ai)
{
ip_adjacency_t *adj;
u32 sw_if_index;
wg_if_t *wgi;
fib_protocol_t fib_proto;
if (!adj_is_valid (ai))
return;
adj = adj_get (ai);
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;
wgi = wg_if_get (wg_if_find_by_sw_if_index (sw_if_index));
if (!wgi)
return;
if (!vnet_sw_interface_is_admin_up (vnet_get_main (), wgi->sw_if_index) ||
!wg_peer_can_send (peer))
{
adj_midchain_delegate_unstack (ai);
}
else
{
/* *INDENT-OFF* */
fib_prefix_t dst = {
.fp_len = is_ip4 ? 32 : 128,
.fp_proto = fib_proto,
.fp_addr = peer->dst.addr,
};
/* *INDENT-ON* */
u32 fib_index;
fib_index = fib_table_find (fib_proto, peer->table_id);
adj_midchain_delegate_stack (ai, fib_index, &dst);
}
}
static void
wg_peer_adj_reset_stacking (adj_index_t ai)
{
adj_midchain_delegate_remove (ai);
}
static void
wg_peer_66_fixup (vlib_main_t *vm, const ip_adjacency_t *adj, vlib_buffer_t *b,
const void *data)
{
u8 iph_offset = 0;
ip6_header_t *ip6_out;
ip6_header_t *ip6_in;
/* Must set locally originated otherwise we're not allowed to
fragment the packet later */
b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
ip6_out = vlib_buffer_get_current (b);
iph_offset = vnet_buffer (b)->ip.save_rewrite_length;
ip6_in = vlib_buffer_get_current (b) + iph_offset;
ip6_out->ip_version_traffic_class_and_flow_label =
ip6_in->ip_version_traffic_class_and_flow_label;
}
static void
wg_peer_46_fixup (vlib_main_t *vm, const ip_adjacency_t *adj, vlib_buffer_t *b,
const void *data)
{
u8 iph_offset = 0;
ip6_header_t *ip6_out;
ip4_header_t *ip4_in;
/* Must set locally originated otherwise we're not allowed to
fragment the packet later */
b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
ip6_out = vlib_buffer_get_current (b);
iph_offset = vnet_buffer (b)->ip.save_rewrite_length;
ip4_in = vlib_buffer_get_current (b) + iph_offset;
u32 vtcfl = 0x6 << 28;
vtcfl |= ip4_in->tos << 20;
vtcfl |= vnet_buffer (b)->ip.flow_hash & 0x000fffff;
ip6_out->ip_version_traffic_class_and_flow_label =
clib_host_to_net_u32 (vtcfl);
}
static adj_midchain_fixup_t
wg_peer_get_fixup (wg_peer_t *peer, vnet_link_t lt)
{
if (!ip46_address_is_ip4 (&peer->dst.addr))
{
if (lt == VNET_LINK_IP4)
return (wg_peer_46_fixup);
if (lt == VNET_LINK_IP6)
return (wg_peer_66_fixup);
}
return (NULL);
}
walk_rc_t
wg_peer_if_admin_state_change (index_t peeri, void *data)
{
wg_peer_t *peer;
adj_index_t *adj_index;
peer = wg_peer_get (peeri);
vec_foreach (adj_index, peer->adj_indices)
{
wg_peer_adj_stack (peer, *adj_index);
}
return (WALK_CONTINUE);
}
walk_rc_t
wg_peer_if_adj_change (index_t peeri, void *data)
{
adj_index_t *adj_index = data;
adj_midchain_fixup_t fixup;
ip_adjacency_t *adj;
wg_peer_t *peer;
fib_prefix_t *allowed_ip;
adj = adj_get (*adj_index);
peer = wg_peer_get (peeri);
vec_foreach (allowed_ip, peer->allowed_ips)
{
if (fib_prefix_is_cover_addr_46 (allowed_ip,
&adj->sub_type.nbr.next_hop))
{
vec_add1 (peer->adj_indices, *adj_index);
vec_validate_init_empty (wg_peer_by_adj_index, *adj_index,
INDEX_INVALID);
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);
return (WALK_STOP);
}
}
return (WALK_CONTINUE);
}
adj_walk_rc_t
wg_peer_adj_walk (adj_index_t ai, void *data)
{
return wg_peer_if_adj_change ((*(index_t *) (data)), &ai) == WALK_CONTINUE ?
ADJ_WALK_RC_CONTINUE :
ADJ_WALK_RC_STOP;
}
walk_rc_t
wg_peer_if_delete (index_t peeri, void *data)
{
wg_peer_remove (peeri);
return (WALK_CONTINUE);
}
static int
wg_peer_fill (vlib_main_t *vm, wg_peer_t *peer, u32 table_id,
const ip46_address_t *dst, u16 port,
u16 persistent_keepalive_interval,
const fib_prefix_t *allowed_ips, u32 wg_sw_if_index)
{
index_t perri = peer - wg_peer_pool;
wg_peer_endpoint_init (&peer->dst, dst, port);
peer->table_id = table_id;
peer->wg_sw_if_index = wg_sw_if_index;
peer->timer_wheel = &wg_main.timer_wheel;
peer->persistent_keepalive_interval = persistent_keepalive_interval;
peer->last_sent_handshake = vlib_time_now (vm) - (REKEY_TIMEOUT + 1);
wg_peer_update_flags (perri, WG_PEER_STATUS_DEAD, false);
const wg_if_t *wgi = wg_if_get (wg_if_find_by_sw_if_index (wg_sw_if_index));
if (NULL == wgi)
return (VNET_API_ERROR_INVALID_INTERFACE);
ip_address_to_46 (&wgi->src_ip, &peer->src.addr);
peer->src.port = wgi->port;
u8 is_ip4 = ip46_address_is_ip4 (&peer->dst.addr);
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);
vec_foreach_index (ii, allowed_ips)
{
peer->allowed_ips[ii] = allowed_ips[ii];
}
fib_protocol_t proto;
FOR_EACH_FIB_IP_PROTOCOL (proto)
{
adj_nbr_walk (wg_sw_if_index, proto, wg_peer_adj_walk, &perri);
}
return (0);
}
void
wg_peer_update_flags (index_t peeri, wg_peer_flags flag, bool add_del)
{
wg_peer_t *peer = wg_peer_get (peeri);
if ((add_del && (peer->flags & flag)) || (!add_del && !(peer->flags & flag)))
{
return;
}
peer->flags ^= flag;
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);
adj_index_t *adj_index;
vec_foreach (adj_index, peer->adj_indices)
{
if (adj_is_valid (*adj_index))
{
adj_midchain_fixup_t 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_reset_stacking (*adj_index);
wg_peer_adj_stack (peer, *adj_index);
}
}
}
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,
const fib_prefix_t *allowed_ips, u16 port,
u16 persistent_keepalive, u32 *peer_index)
{
wg_if_t *wg_if;
wg_peer_t *peer;
int rv;
vlib_main_t *vm = vlib_get_main ();
if (tun_sw_if_index == ~0)
return (VNET_API_ERROR_INVALID_SW_IF_INDEX);
wg_if = wg_if_get (wg_if_find_by_sw_if_index (tun_sw_if_index));
if (!wg_if)
return (VNET_API_ERROR_INVALID_SW_IF_INDEX);
/* *INDENT-OFF* */
pool_foreach (peer, wg_peer_pool)
{
if (!memcmp (peer->remote.r_public, public_key, NOISE_PUBLIC_KEY_LEN))
{
return (VNET_API_ERROR_ENTRY_ALREADY_EXISTS);
}
}
/* *INDENT-ON* */
if (pool_elts (wg_peer_pool) > MAX_PEERS)
return (VNET_API_ERROR_LIMIT_EXCEEDED);
pool_get_zero (wg_peer_pool, peer);
wg_peer_init (vm, peer);
rv = wg_peer_fill (vm, peer, table_id, endpoint, (u16) port,
persistent_keepalive, allowed_ips, tun_sw_if_index);
if (rv)
{
wg_peer_clear (vm, peer);
pool_put (wg_peer_pool, peer);
return (rv);
}
noise_remote_init (&peer->remote, peer - wg_peer_pool, public_key,
wg_if->local_idx);
cookie_maker_init (&peer->cookie_maker, public_key);
wg_send_handshake (vm, peer, false);
if (peer->persistent_keepalive_interval != 0)
{
wg_send_keepalive (vm, peer);
}
*peer_index = peer - wg_peer_pool;
wg_if_peer_add (wg_if, *peer_index);
return (0);
}
int
wg_peer_remove (index_t peeri)
{
wg_main_t *wmp = &wg_main;
wg_peer_t *peer = NULL;
wg_if_t *wgi;
if (pool_is_free_index (wg_peer_pool, peeri))
return VNET_API_ERROR_NO_SUCH_ENTRY;
peer = pool_elt_at_index (wg_peer_pool, peeri);
wgi = wg_if_get (wg_if_find_by_sw_if_index (peer->wg_sw_if_index));
wg_if_peer_remove (wgi, peeri);
noise_remote_clear (wmp->vlib_main, &peer->remote);
wg_peer_clear (wmp->vlib_main, peer);
pool_put (wg_peer_pool, peer);
return (0);
}
index_t
wg_peer_walk (wg_peer_walk_cb_t fn, void *data)
{
index_t peeri;
/* *INDENT-OFF* */
pool_foreach_index (peeri, wg_peer_pool)
{
if (WALK_STOP == fn(peeri, data))
return peeri;
}
/* *INDENT-ON* */
return INDEX_INVALID;
}
static u8 *
format_wg_peer_endpoint (u8 * s, va_list * args)
{
wg_peer_endpoint_t *ep = va_arg (*args, wg_peer_endpoint_t *);
s = format (s, "%U:%d", format_ip46_address, &ep->addr, IP46_TYPE_ANY,
ep->port);
return (s);
}
u8 *
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;
peer = wg_peer_get (peeri);
key_to_base64 (peer->remote.r_public, NOISE_PUBLIC_KEY_LEN, key);
s = format (
s,
"[%d] endpoint:[%U->%U] %U keep-alive:%d flags: %d, api-clients count: %d",
peeri, format_wg_peer_endpoint, &peer->src, format_wg_peer_endpoint,
&peer->dst, format_vnet_sw_if_index_name, vnet_get_main (),
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)
{
s = format (s, " %d", *adj_index);
}
s = format (s, "\n key:%=s %U", key, format_hex_bytes,
peer->remote.r_public, NOISE_PUBLIC_KEY_LEN);
s = format (s, "\n allowed-ips:");
vec_foreach (allowed_ip, peer->allowed_ips)
{
s = format (s, " %U", format_fib_prefix, allowed_ip);
}
return s;
}
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/