ipsec: manually binding an SA to a worker

An SA is normally bound to the first thread using it. However, one
could want to manually bind an SA to a specific worker.

Type: improvement
Signed-off-by: Maxime Peim <mpeim@cisco.com>
Change-Id: I05cbbf753e44a01d9964ee47812c964db9bbb488
This commit is contained in:
Maxime Peim
2023-03-20 14:13:56 +00:00
committed by Neale Ranns
parent 601972bb20
commit 1271e3a2a1
7 changed files with 310 additions and 6 deletions

View File

@ -201,6 +201,29 @@ autoreply define ipsec_sad_entry_del
u32 id; u32 id;
}; };
/** \brief An API to bind an SAD entry to a specific worker
@param client_index - opaque cookie to identify the sender
@param context - sender context, to match reply w/ request
@param sa_id - the id of the SA to bind
@param worker - the worker's index to which the SA will be bound to
*/
autoreply define ipsec_sad_bind
{
u32 client_index;
u32 context;
u32 sa_id;
u32 worker;
};
autoreply define ipsec_sad_unbind
{
u32 client_index;
u32 context;
u32 sa_id;
};
/** \brief An API to update the tunnel parameters and the ports associated with an SA /** \brief An API to update the tunnel parameters and the ports associated with an SA
Used in the NAT-T case when the NAT data changes Used in the NAT-T case when the NAT data changes
@ -430,6 +453,12 @@ define ipsec_sa_v3_dump
u32 context; u32 context;
u32 sa_id; u32 sa_id;
}; };
define ipsec_sa_v4_dump
{
u32 client_index;
u32 context;
u32 sa_id;
};
/** \brief IPsec security association database response /** \brief IPsec security association database response
@param context - sender context which was passed in the request @param context - sender context which was passed in the request
@ -479,6 +508,18 @@ define ipsec_sa_v3_details {
u32 stat_index; u32 stat_index;
}; };
define ipsec_sa_v4_details {
u32 context;
vl_api_ipsec_sad_entry_v3_t entry;
vl_api_interface_index_t sw_if_index;
u64 seq_outbound;
u64 last_seq_inbound;
u64 replay_window;
u32 thread_index;
u32 stat_index;
};
/** \brief Dump IPsec backends /** \brief Dump IPsec backends
@param client_index - opaque cookie to identify the sender @param client_index - opaque cookie to identify the sender

View File

@ -592,6 +592,36 @@ out:
REPLY_MACRO (VL_API_IPSEC_SAD_ENTRY_UPDATE_REPLY); REPLY_MACRO (VL_API_IPSEC_SAD_ENTRY_UPDATE_REPLY);
} }
static void
vl_api_ipsec_sad_bind_t_handler (vl_api_ipsec_sad_bind_t *mp)
{
vl_api_ipsec_sad_bind_reply_t *rmp;
u32 sa_id;
u32 worker;
int rv;
sa_id = ntohl (mp->sa_id);
worker = ntohl (mp->worker);
rv = ipsec_sa_bind (sa_id, worker, true /* bind */);
REPLY_MACRO (VL_API_IPSEC_SAD_BIND_REPLY);
}
static void
vl_api_ipsec_sad_unbind_t_handler (vl_api_ipsec_sad_unbind_t *mp)
{
vl_api_ipsec_sad_unbind_reply_t *rmp;
u32 sa_id;
int rv;
sa_id = ntohl (mp->sa_id);
rv = ipsec_sa_bind (sa_id, ~0, false /* bind */);
REPLY_MACRO (VL_API_IPSEC_SAD_UNBIND_REPLY);
}
static void static void
send_ipsec_spds_details (ipsec_spd_t * spd, vl_api_registration_t * reg, send_ipsec_spds_details (ipsec_spd_t * spd, vl_api_registration_t * reg,
u32 context) u32 context)
@ -1116,6 +1146,87 @@ vl_api_ipsec_sa_v3_dump_t_handler (vl_api_ipsec_sa_v3_dump_t *mp)
ipsec_sa_walk (send_ipsec_sa_v3_details, &ctx); ipsec_sa_walk (send_ipsec_sa_v3_details, &ctx);
} }
static walk_rc_t
send_ipsec_sa_v4_details (ipsec_sa_t *sa, void *arg)
{
ipsec_dump_walk_ctx_t *ctx = arg;
vl_api_ipsec_sa_v4_details_t *mp;
mp = vl_msg_api_alloc (sizeof (*mp));
clib_memset (mp, 0, sizeof (*mp));
mp->_vl_msg_id = ntohs (REPLY_MSG_ID_BASE + VL_API_IPSEC_SA_V4_DETAILS);
mp->context = ctx->context;
mp->entry.sad_id = htonl (sa->id);
mp->entry.spi = htonl (sa->spi);
mp->entry.protocol = ipsec_proto_encode (sa->protocol);
mp->entry.crypto_algorithm = ipsec_crypto_algo_encode (sa->crypto_alg);
ipsec_key_encode (&sa->crypto_key, &mp->entry.crypto_key);
mp->entry.integrity_algorithm = ipsec_integ_algo_encode (sa->integ_alg);
ipsec_key_encode (&sa->integ_key, &mp->entry.integrity_key);
mp->entry.flags = ipsec_sad_flags_encode (sa);
mp->entry.salt = clib_host_to_net_u32 (sa->salt);
if (ipsec_sa_is_set_IS_PROTECT (sa))
{
ipsec_sa_dump_match_ctx_t ctx = {
.sai = sa - ipsec_sa_pool,
.sw_if_index = ~0,
};
ipsec_tun_protect_walk (ipsec_sa_dump_match_sa, &ctx);
mp->sw_if_index = htonl (ctx.sw_if_index);
}
else
mp->sw_if_index = ~0;
if (ipsec_sa_is_set_IS_TUNNEL (sa))
tunnel_encode (&sa->tunnel, &mp->entry.tunnel);
if (ipsec_sa_is_set_UDP_ENCAP (sa))
{
mp->entry.udp_src_port = sa->udp_hdr.src_port;
mp->entry.udp_dst_port = sa->udp_hdr.dst_port;
}
mp->seq_outbound = clib_host_to_net_u64 (((u64) sa->seq));
mp->last_seq_inbound = clib_host_to_net_u64 (((u64) sa->seq));
if (ipsec_sa_is_set_USE_ESN (sa))
{
mp->seq_outbound |= (u64) (clib_host_to_net_u32 (sa->seq_hi));
mp->last_seq_inbound |= (u64) (clib_host_to_net_u32 (sa->seq_hi));
}
if (ipsec_sa_is_set_USE_ANTI_REPLAY (sa))
mp->replay_window = clib_host_to_net_u64 (sa->replay_window);
mp->thread_index = clib_host_to_net_u32 (sa->thread_index);
mp->stat_index = clib_host_to_net_u32 (sa->stat_index);
vl_api_send_msg (ctx->reg, (u8 *) mp);
return (WALK_CONTINUE);
}
static void
vl_api_ipsec_sa_v4_dump_t_handler (vl_api_ipsec_sa_v4_dump_t *mp)
{
vl_api_registration_t *reg;
reg = vl_api_client_index_to_registration (mp->client_index);
if (!reg)
return;
ipsec_dump_walk_ctx_t ctx = {
.reg = reg,
.context = mp->context,
};
ipsec_sa_walk (send_ipsec_sa_v4_details, &ctx);
}
static void static void
vl_api_ipsec_backend_dump_t_handler (vl_api_ipsec_backend_dump_t * mp) vl_api_ipsec_backend_dump_t_handler (vl_api_ipsec_backend_dump_t * mp)
{ {

View File

@ -211,6 +211,71 @@ VLIB_CLI_COMMAND (ipsec_sa_add_del_command, static) = {
}; };
/* *INDENT-ON* */ /* *INDENT-ON* */
static clib_error_t *
ipsec_sa_bind_cli (vlib_main_t *vm, unformat_input_t *input,
vlib_cli_command_t *cmd)
{
unformat_input_t _line_input, *line_input = &_line_input;
u32 id = ~0;
u32 worker = ~0;
bool bind = 1;
int rv;
clib_error_t *error = NULL;
if (!unformat_user (input, unformat_line_input, line_input))
return 0;
while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
{
if (unformat (line_input, "unbind"))
bind = 0;
else if (id != ~0 && unformat (line_input, "%u", &id))
;
else if (unformat (line_input, "%u", &worker))
;
else
{
error = clib_error_return (0, "parse error: '%U'",
format_unformat_error, line_input);
goto done;
}
}
if (id == ~0)
{
error = clib_error_return (0, "please specify SA ID");
goto done;
}
if (bind && ~0 == worker)
{
error = clib_error_return (0, "please specify worker to bind to");
goto done;
}
rv = ipsec_sa_bind (id, worker, bind);
switch (rv)
{
case VNET_API_ERROR_INVALID_VALUE:
error = clib_error_return (0, "please specify a valid SA ID");
break;
case VNET_API_ERROR_INVALID_WORKER:
error = clib_error_return (0, "please specify a valid worker index");
break;
}
done:
unformat_free (line_input);
return error;
}
VLIB_CLI_COMMAND (ipsec_sa_bind_cmd, static) = {
.path = "ipsec sa bind",
.short_help = "ipsec sa [unbind] <sa-id> <worker>",
.function = ipsec_sa_bind_cli,
};
static clib_error_t * static clib_error_t *
ipsec_spd_add_del_command_fn (vlib_main_t * vm, ipsec_spd_add_del_command_fn (vlib_main_t * vm,
unformat_input_t * input, unformat_input_t * input,

View File

@ -521,6 +521,32 @@ ipsec_sa_del (ipsec_sa_t * sa)
pool_put (ipsec_sa_pool, sa); pool_put (ipsec_sa_pool, sa);
} }
int
ipsec_sa_bind (u32 id, u32 worker, bool bind)
{
ipsec_main_t *im = &ipsec_main;
uword *p;
ipsec_sa_t *sa;
p = hash_get (im->sa_index_by_sa_id, id);
if (!p)
return VNET_API_ERROR_INVALID_VALUE;
sa = ipsec_sa_get (p[0]);
if (!bind)
{
sa->thread_index = ~0;
return 0;
}
if (worker >= vlib_num_workers ())
return VNET_API_ERROR_INVALID_WORKER;
sa->thread_index = vlib_get_worker_thread_index (worker);
return 0;
}
void void
ipsec_sa_unlock (index_t sai) ipsec_sa_unlock (index_t sai)
{ {
@ -618,19 +644,18 @@ ipsec_sa_fib_node_get (fib_node_index_t index)
} }
static ipsec_sa_t * static ipsec_sa_t *
ipsec_sa_from_fib_node (fib_node_t * node) ipsec_sa_from_fib_node (fib_node_t *node)
{ {
ASSERT (FIB_NODE_TYPE_IPSEC_SA == node->fn_type); ASSERT (FIB_NODE_TYPE_IPSEC_SA == node->fn_type);
return ((ipsec_sa_t *) (((char *) node) - return (
STRUCT_OFFSET_OF (ipsec_sa_t, node))); (ipsec_sa_t *) (((char *) node) - STRUCT_OFFSET_OF (ipsec_sa_t, node)));
} }
/** /**
* Function definition to inform the FIB node that its last lock has gone. * Function definition to inform the FIB node that its last lock has gone.
*/ */
static void static void
ipsec_sa_last_lock_gone (fib_node_t * node) ipsec_sa_last_lock_gone (fib_node_t *node)
{ {
/* /*
* The ipsec SA is a root of the graph. As such * The ipsec SA is a root of the graph. As such
@ -643,7 +668,7 @@ ipsec_sa_last_lock_gone (fib_node_t * node)
* Function definition to backwalk a FIB node * Function definition to backwalk a FIB node
*/ */
static fib_node_back_walk_rc_t static fib_node_back_walk_rc_t
ipsec_sa_back_walk (fib_node_t * node, fib_node_back_walk_ctx_t * ctx) ipsec_sa_back_walk (fib_node_t *node, fib_node_back_walk_ctx_t *ctx)
{ {
ipsec_sa_stack (ipsec_sa_from_fib_node (node)); ipsec_sa_stack (ipsec_sa_from_fib_node (node));

View File

@ -283,6 +283,7 @@ ipsec_sa_add_and_lock (u32 id, u32 spi, ipsec_protocol_t proto,
ipsec_integ_alg_t integ_alg, const ipsec_key_t *ik, ipsec_integ_alg_t integ_alg, const ipsec_key_t *ik,
ipsec_sa_flags_t flags, u32 salt, u16 src_port, ipsec_sa_flags_t flags, u32 salt, u16 src_port,
u16 dst_port, const tunnel_t *tun, u32 *sa_out_index); u16 dst_port, const tunnel_t *tun, u32 *sa_out_index);
extern int ipsec_sa_bind (u32 id, u32 worker, bool bind);
extern index_t ipsec_sa_find_and_lock (u32 id); extern index_t ipsec_sa_find_and_lock (u32 id);
extern int ipsec_sa_unlock_id (u32 id); extern int ipsec_sa_unlock_id (u32 id);
extern void ipsec_sa_unlock (index_t sai); extern void ipsec_sa_unlock (index_t sai);

View File

@ -288,6 +288,18 @@ api_ipsec_sad_entry_del (vat_main_t *vat)
return -1; return -1;
} }
static int
api_ipsec_sad_bind (vat_main_t *vat)
{
return -1;
}
static int
api_ipsec_sad_unbind (vat_main_t *vat)
{
return -1;
}
static void static void
vl_api_ipsec_sad_entry_add_del_v2_reply_t_handler ( vl_api_ipsec_sad_entry_add_del_v2_reply_t_handler (
vl_api_ipsec_sad_entry_add_del_v2_reply_t *mp) vl_api_ipsec_sad_entry_add_del_v2_reply_t *mp)
@ -433,6 +445,17 @@ api_ipsec_sa_dump (vat_main_t *vam)
return ret; return ret;
} }
static void
vl_api_ipsec_sa_v4_details_t_handler (vl_api_ipsec_sa_v4_details_t *mp)
{
}
static int
api_ipsec_sa_v4_dump (vat_main_t *mp)
{
return -1;
}
static void static void
vl_api_ipsec_sa_details_t_handler (vl_api_ipsec_sa_details_t *mp) vl_api_ipsec_sa_details_t_handler (vl_api_ipsec_sa_details_t *mp)
{ {

View File

@ -4,10 +4,14 @@ from framework import VppTestCase, VppTestRunner
from template_ipsec import TemplateIpsec, IPsecIPv4Params from template_ipsec import TemplateIpsec, IPsecIPv4Params
from vpp_papi import VppEnum from vpp_papi import VppEnum
from vpp_ipsec import VppIpsecSA
class IpsecApiTestCase(VppTestCase): class IpsecApiTestCase(VppTestCase):
"""IPSec API tests""" """IPSec API tests"""
vpp_worker_count = 2
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(IpsecApiTestCase, cls).setUpClass() super(IpsecApiTestCase, cls).setUpClass()
@ -115,6 +119,40 @@ class IpsecApiTestCase(VppTestCase):
) )
self.vapi.ipsec_select_backend(protocol=self.vpp_ah_protocol, index=0) self.vapi.ipsec_select_backend(protocol=self.vpp_ah_protocol, index=0)
def __check_sa_binding(self, sa_id, thread_index):
found_sa = False
sa_dumps = self.vapi.ipsec_sa_v4_dump()
for dump in sa_dumps:
if dump.entry.sad_id == sa_id:
self.assertEqual(dump.thread_index, thread_index)
found_sa = True
break
if not found_sa:
self.fail("SA not found in VPP")
def test_sa_worker_bind(self):
"""Bind an SA to a worker"""
sa = VppIpsecSA(
self,
self.ipv4_params.scapy_tun_sa_id,
self.ipv4_params.scapy_tun_spi,
self.ipv4_params.auth_algo_vpp_id,
self.ipv4_params.auth_key,
self.ipv4_params.crypt_algo_vpp_id,
self.ipv4_params.crypt_key,
VppEnum.vl_api_ipsec_proto_t.IPSEC_API_PROTO_ESP,
)
sa.add_vpp_config()
self.__check_sa_binding(sa.id, 0xFFFF)
self.vapi.ipsec_sad_bind(sa_id=sa.id, worker=1)
self.__check_sa_binding(sa.id, 2)
sa.remove_vpp_config()
if __name__ == "__main__": if __name__ == "__main__":
unittest.main(testRunner=VppTestRunner) unittest.main(testRunner=VppTestRunner)