Files
vpp/src/plugins/wireguard/wireguard_api.c
Alexander Chernavin d76c029a85 wireguard: fix sending peer events from worker threads
Type: fix

API clients can register for peer events (e.g. to be notified when
connection is established). In a multi-worker setup, peer events might
be triggered from a worker thread. In order to send a peer event to the
clients, an API message needs to be allocated and populated.

API messages allocation is only allowed from the main thread. Currently,
the code does not handle the case when a peer event is trying to be sent
from a worker thread. In debug builds, when this happens, it causes
SIGABRT in vl_msg_api_alloc_internal() because assertion "pool == 0 ||
vlib_get_thread_index () == 0" fails. In production builds, when this
happens, it might cause unexplained behavior.

There is a test that is supposed to catch this but all multi-worker
Wireguard tests are currently disabled. This problem is likely to be one
of the reasons they were disabled.

With this fix, when a peer event is triggered from a worker thread,
allocate and send corresponding API message from the main thread using
RPC.

Signed-off-by: Alexander Chernavin <achernavin@netgate.com>
Change-Id: Ib3fe19f8070563b35732afd16c017411c089437e
2023-03-20 16:41:14 +00:00

429 lines
11 KiB
C

/*
* Copyright (c) 2020 Cisco and/or its affiliates.
* Copyright (c) 2020 Doc.ai 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/vnet.h>
#include <vlibmemory/api.h>
#include <vnet/format_fns.h>
#include <vnet/ip/ip_types_api.h>
#include <vlibapi/api.h>
#include <wireguard/wireguard.api_enum.h>
#include <wireguard/wireguard.api_types.h>
#include <wireguard/wireguard_key.h>
#include <wireguard/wireguard.h>
#include <wireguard/wireguard_if.h>
#define REPLY_MSG_ID_BASE wmp->msg_id_base
#include <wireguard/wireguard_peer.h>
#include <vlibapi/api_helper_macros.h>
static void
vl_api_wireguard_interface_create_t_handler
(vl_api_wireguard_interface_create_t * mp)
{
vl_api_wireguard_interface_create_reply_t *rmp;
wg_main_t *wmp = &wg_main;
u8 private_key[NOISE_PUBLIC_KEY_LEN];
ip_address_t src;
u32 sw_if_index = ~0;
int rv = 0;
wg_feature_init (wmp);
ip_address_decode2 (&mp->interface.src_ip, &src);
if (mp->generate_key)
curve25519_gen_secret (private_key);
else
clib_memcpy (private_key, mp->interface.private_key, NOISE_PUBLIC_KEY_LEN);
rv = wg_if_create (ntohl (mp->interface.user_instance), private_key,
ntohs (mp->interface.port), &src, &sw_if_index);
REPLY_MACRO2(VL_API_WIREGUARD_INTERFACE_CREATE_REPLY,
{
rmp->sw_if_index = htonl(sw_if_index);
});
}
static void
vl_api_wireguard_interface_delete_t_handler
(vl_api_wireguard_interface_delete_t * mp)
{
vl_api_wireguard_interface_delete_reply_t *rmp;
wg_main_t *wmp = &wg_main;
int rv = 0;
wg_feature_init (wmp);
VALIDATE_SW_IF_INDEX (mp);
rv = wg_if_delete (ntohl (mp->sw_if_index));
BAD_SW_IF_INDEX_LABEL;
REPLY_MACRO(VL_API_WIREGUARD_INTERFACE_DELETE_REPLY);
}
typedef struct wg_deatils_walk_t_
{
vl_api_registration_t *reg;
u32 context;
u8 show_private_key;
} wg_deatils_walk_t;
static walk_rc_t
wireguard_if_send_details (index_t wgii, void *data)
{
vl_api_wireguard_interface_details_t *rmp;
wg_deatils_walk_t *ctx = data;
const wg_if_t *wgi;
const noise_local_t *local;
wgi = wg_if_get (wgii);
local = noise_local_get (wgi->local_idx);
rmp = vl_msg_api_alloc_zero (sizeof (*rmp));
rmp->_vl_msg_id = htons (VL_API_WIREGUARD_INTERFACE_DETAILS +
wg_main.msg_id_base);
if (ctx->show_private_key)
clib_memcpy (rmp->interface.private_key,
local->l_private, NOISE_PUBLIC_KEY_LEN);
clib_memcpy (rmp->interface.public_key,
local->l_public, NOISE_PUBLIC_KEY_LEN);
rmp->interface.sw_if_index = htonl (wgi->sw_if_index);
rmp->interface.port = htons (wgi->port);
rmp->interface.user_instance = htonl (wgi->user_instance);
ip_address_encode2 (&wgi->src_ip, &rmp->interface.src_ip);
rmp->context = ctx->context;
vl_api_send_msg (ctx->reg, (u8 *) rmp);
return (WALK_CONTINUE);
}
static void
vl_api_wireguard_interface_dump_t_handler (vl_api_wireguard_interface_dump_t *
mp)
{
vl_api_registration_t *reg;
wg_main_t *wmp = &wg_main;
wg_feature_init (wmp);
reg = vl_api_client_index_to_registration (mp->client_index);
if (reg == 0)
return;
wg_deatils_walk_t ctx = {
.reg = reg,
.context = mp->context,
.show_private_key = mp->show_private_key,
};
u32 sw_if_index = ntohl (mp->sw_if_index);
if (sw_if_index == ~0)
wg_if_walk (wireguard_if_send_details, &ctx);
else
{
index_t wgii = wg_if_find_by_sw_if_index (sw_if_index);
if (wgii != INDEX_INVALID)
wireguard_if_send_details (wgii, &ctx);
}
}
static void
vl_api_wireguard_peer_add_t_handler (vl_api_wireguard_peer_add_t * mp)
{
vl_api_wireguard_peer_add_reply_t *rmp;
wg_main_t *wmp = &wg_main;
index_t peeri = INDEX_INVALID;
int ii, rv = 0;
ip_address_t endpoint;
fib_prefix_t *allowed_ips = NULL;
VALIDATE_SW_IF_INDEX (&(mp->peer));
if (0 == mp->peer.n_allowed_ips)
{
rv = VNET_API_ERROR_INVALID_VALUE;
goto done;
}
wg_feature_init (wmp);
vec_validate (allowed_ips, mp->peer.n_allowed_ips - 1);
ip_address_decode2 (&mp->peer.endpoint, &endpoint);
for (ii = 0; ii < mp->peer.n_allowed_ips; ii++)
ip_prefix_decode (&mp->peer.allowed_ips[ii], &allowed_ips[ii]);
rv = wg_peer_add (ntohl (mp->peer.sw_if_index), mp->peer.public_key,
ntohl (mp->peer.table_id), &ip_addr_46 (&endpoint),
allowed_ips, ntohs (mp->peer.port),
ntohs (mp->peer.persistent_keepalive), &peeri);
vec_free (allowed_ips);
done:
BAD_SW_IF_INDEX_LABEL;
REPLY_MACRO2(VL_API_WIREGUARD_PEER_ADD_REPLY,
{
rmp->peer_index = ntohl (peeri);
});
}
static void
vl_api_wireguard_peer_remove_t_handler (vl_api_wireguard_peer_remove_t * mp)
{
vl_api_wireguard_peer_remove_reply_t *rmp;
wg_main_t *wmp = &wg_main;
int rv = 0;
wg_feature_init (wmp);
rv = wg_peer_remove (ntohl (mp->peer_index));
REPLY_MACRO(VL_API_WIREGUARD_PEER_REMOVE_REPLY);
}
static walk_rc_t
wg_api_send_peers_details (index_t peeri, void *data)
{
vl_api_wireguard_peers_details_t *rmp;
wg_deatils_walk_t *ctx = data;
const wg_peer_t *peer;
u8 n_allowed_ips;
size_t ss;
if (pool_is_free_index (wg_peer_pool, peeri))
return (WALK_CONTINUE);
peer = wg_peer_get (peeri);
n_allowed_ips = vec_len (peer->allowed_ips);
ss = (sizeof (*rmp) + (n_allowed_ips * sizeof (rmp->peer.allowed_ips[0])));
rmp = vl_msg_api_alloc_zero (ss);
rmp->_vl_msg_id = htons (VL_API_WIREGUARD_PEERS_DETAILS +
wg_main.msg_id_base);
rmp->peer.peer_index = htonl (peeri);
rmp->peer.flags = peer->flags;
clib_memcpy (rmp->peer.public_key,
peer->remote.r_public, NOISE_PUBLIC_KEY_LEN);
ip_address_encode (&peer->dst.addr, IP46_TYPE_ANY, &rmp->peer.endpoint);
rmp->peer.port = htons (peer->dst.port);
rmp->peer.n_allowed_ips = n_allowed_ips;
rmp->peer.sw_if_index = htonl (peer->wg_sw_if_index);
rmp->peer.persistent_keepalive = htons (peer->persistent_keepalive_interval);
rmp->peer.table_id = htonl (peer->table_id);
int ii;
for (ii = 0; ii < n_allowed_ips; ii++)
ip_prefix_encode (&peer->allowed_ips[ii], &rmp->peer.allowed_ips[ii]);
rmp->context = ctx->context;
vl_api_send_msg (ctx->reg, (u8 *) rmp);
return (WALK_CONTINUE);
}
static void
vl_api_wireguard_peers_dump_t_handler (vl_api_wireguard_peers_dump_t * mp)
{
vl_api_registration_t *reg;
wg_main_t *wmp = &wg_main;
wg_feature_init (wmp);
reg = vl_api_client_index_to_registration (mp->client_index);
if (reg == NULL)
return;
wg_deatils_walk_t ctx = {
.reg = reg,
.context = mp->context,
};
if (mp->peer_index == ~0)
wg_peer_walk (wg_api_send_peers_details, &ctx);
else
wg_api_send_peers_details (ntohl (mp->peer_index), &ctx);
}
static vpe_client_registration_t *
wg_api_client_lookup (wg_peer_t *peer, u32 client_index)
{
uword *p;
vpe_client_registration_t *api_client = NULL;
p = hash_get (peer->api_client_by_client_index, client_index);
if (p)
api_client = vec_elt_at_index (peer->api_clients, p[0]);
return api_client;
}
static walk_rc_t
wg_api_update_peer_api_client (index_t peeri, void *data)
{
if (pool_is_free_index (wg_peer_pool, peeri))
return (WALK_CONTINUE);
vl_api_want_wireguard_peer_events_t *mp = data;
wg_peer_t *peer = wg_peer_get (peeri);
if (ntohl (mp->sw_if_index) != ~0 &&
ntohl (mp->sw_if_index) != peer->wg_sw_if_index)
{
return (WALK_CONTINUE);
}
vpe_client_registration_t *api_client;
api_client = wg_api_client_lookup (peer, mp->client_index);
if (api_client)
{
if (mp->enable_disable)
{
return (WALK_CONTINUE);
}
hash_unset (peer->api_client_by_client_index, api_client->client_index);
pool_put (peer->api_clients, api_client);
}
if (mp->enable_disable)
{
pool_get (peer->api_clients, api_client);
clib_memset (api_client, 0, sizeof (vpe_client_registration_t));
api_client->client_index = mp->client_index;
api_client->client_pid = mp->pid;
hash_set (peer->api_client_by_client_index, mp->client_index,
api_client - peer->api_clients);
}
return (WALK_CONTINUE);
}
static void
vl_api_want_wireguard_peer_events_t_handler (
vl_api_want_wireguard_peer_events_t *mp)
{
wg_main_t *wmp = &wg_main;
vl_api_want_wireguard_peer_events_reply_t *rmp;
int rv = 0;
wg_feature_init (wmp);
if (mp->peer_index == ~0)
wg_peer_walk (wg_api_update_peer_api_client, mp);
else
wg_api_update_peer_api_client (ntohl (mp->peer_index), mp);
REPLY_MACRO (VL_API_WANT_WIREGUARD_PEER_EVENTS_REPLY);
}
static void
wg_api_send_peer_event (vl_api_registration_t *rp, index_t peer_index,
wg_peer_flags flags)
{
vl_api_wireguard_peer_event_t *mp = vl_msg_api_alloc (sizeof (*mp));
clib_memset (mp, 0, sizeof (*mp));
mp->_vl_msg_id = htons (VL_API_WIREGUARD_PEER_EVENT + wg_main.msg_id_base);
mp->peer_index = htonl (peer_index);
mp->flags = flags;
vl_api_send_msg (rp, (u8 *) mp);
}
typedef struct
{
index_t peeri;
wg_peer_flags flags;
} wg_api_peer_event_args_t;
static void
wg_api_peer_event_cb (wg_api_peer_event_args_t *args)
{
wg_peer_t *peer = wg_peer_get (args->peeri);
vpe_client_registration_t *api_client;
vl_api_registration_t *rp;
pool_foreach (api_client, peer->api_clients)
{
rp = vl_api_client_index_to_registration (api_client->client_index);
if (rp)
{
wg_api_send_peer_event (rp, args->peeri, args->flags);
}
};
}
void
wg_api_peer_event (index_t peeri, wg_peer_flags flags)
{
wg_api_peer_event_args_t args = {
.peeri = peeri,
.flags = flags,
};
vl_api_rpc_call_main_thread (wg_api_peer_event_cb, (u8 *) &args,
sizeof (args));
}
static void
vl_api_wg_set_async_mode_t_handler (vl_api_wg_set_async_mode_t *mp)
{
wg_main_t *wmp = &wg_main;
vl_api_wg_set_async_mode_reply_t *rmp;
int rv = 0;
wg_set_async_mode (mp->async_enable);
REPLY_MACRO (VL_API_WG_SET_ASYNC_MODE_REPLY);
}
/* set tup the API message handling tables */
#include <wireguard/wireguard.api.c>
static clib_error_t *
wg_api_hookup (vlib_main_t * vm)
{
wg_main_t *wmp = &wg_main;
wmp->msg_id_base = setup_message_id_table ();
return 0;
}
VLIB_API_INIT_FUNCTION (wg_api_hookup);
/*
* fd.io coding-style-patch-verification: ON
*
* Local Variables:
* eval: (c-set-style "gnu")
* End:
*/