1826 lines
42 KiB
C
1826 lines
42 KiB
C
/* SPDX-License-Identifier: BSD-3-Clause
|
|
* Copyright(c) 2022 Intel Corporation
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <rte_common.h>
|
|
#include <rte_ip.h>
|
|
#include <rte_tailq.h>
|
|
#include <rte_eal_memconfig.h>
|
|
#include <rte_ring.h>
|
|
#include <rte_mbuf.h>
|
|
#include <rte_cryptodev.h>
|
|
#include <rte_ipsec.h>
|
|
|
|
#include "rte_swx_ipsec.h"
|
|
|
|
#ifndef RTE_SWX_IPSEC_HUGE_PAGES_DISABLE
|
|
|
|
#include <rte_malloc.h>
|
|
|
|
static void *
|
|
env_calloc(size_t size, size_t alignment, int numa_node)
|
|
{
|
|
return rte_zmalloc_socket(NULL, size, alignment, numa_node);
|
|
}
|
|
|
|
static void
|
|
env_free(void *start, size_t size __rte_unused)
|
|
{
|
|
rte_free(start);
|
|
}
|
|
|
|
#else
|
|
|
|
#include <numa.h>
|
|
|
|
static void *
|
|
env_calloc(size_t size, size_t alignment __rte_unused, int numa_node)
|
|
{
|
|
void *start;
|
|
|
|
if (numa_available() == -1)
|
|
return NULL;
|
|
|
|
start = numa_alloc_onnode(size, numa_node);
|
|
if (!start)
|
|
return NULL;
|
|
|
|
memset(start, 0, size);
|
|
return start;
|
|
}
|
|
|
|
static void
|
|
env_free(void *start, size_t size)
|
|
{
|
|
if ((numa_available() == -1) || !start)
|
|
return;
|
|
|
|
numa_free(start, size);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifndef RTE_SWX_IPSEC_POOL_CACHE_SIZE
|
|
#define RTE_SWX_IPSEC_POOL_CACHE_SIZE 256
|
|
#endif
|
|
|
|
/* The two crypto device mempools have their size set to the number of SAs. The mempool API requires
|
|
* the mempool size to be at least 1.5 times the size of the mempool cache.
|
|
*/
|
|
#define N_SA_MIN (RTE_SWX_IPSEC_POOL_CACHE_SIZE * 1.5)
|
|
|
|
struct ipsec_sa {
|
|
struct rte_ipsec_session s;
|
|
int valid;
|
|
};
|
|
|
|
struct ipsec_pkts_in {
|
|
struct rte_mbuf *pkts[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
struct ipsec_sa *sa[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
struct rte_ipsec_group groups[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
struct rte_crypto_op *group_cops[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
struct rte_crypto_op *cops[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
uint32_t n_cops;
|
|
};
|
|
|
|
struct ipsec_pkts_out {
|
|
struct rte_crypto_op *cops[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
struct rte_mbuf *group_pkts[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
struct rte_ipsec_group groups[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
struct rte_mbuf *pkts[RTE_SWX_IPSEC_BURST_SIZE_MAX];
|
|
uint32_t n_pkts;
|
|
};
|
|
|
|
struct rte_swx_ipsec {
|
|
/*
|
|
* Parameters.
|
|
*/
|
|
|
|
/* IPsec instance name. */
|
|
char name[RTE_SWX_IPSEC_NAME_SIZE];
|
|
|
|
/* Input packet queue. */
|
|
struct rte_ring *ring_in;
|
|
|
|
/* Output packet queue. */
|
|
struct rte_ring *ring_out;
|
|
|
|
/* Crypto device ID. */
|
|
uint8_t dev_id;
|
|
|
|
/* Crypto device queue pair ID. */
|
|
uint16_t qp_id;
|
|
|
|
/* Burst sizes. */
|
|
struct rte_swx_ipsec_burst_size bsz;
|
|
|
|
/* SA table size. */
|
|
size_t n_sa_max;
|
|
|
|
/*
|
|
* Internals.
|
|
*/
|
|
/* Crypto device buffer pool for sessions. */
|
|
struct rte_mempool *mp_session;
|
|
|
|
/* Pre-crypto packets. */
|
|
struct ipsec_pkts_in in;
|
|
|
|
/* Post-crypto packets. */
|
|
struct ipsec_pkts_out out;
|
|
|
|
/* Crypto device enqueue threshold. */
|
|
uint32_t crypto_wr_threshold;
|
|
|
|
/* Packets currently under crypto device processing. */
|
|
uint32_t n_pkts_crypto;
|
|
|
|
/* List of free SADB positions. */
|
|
uint32_t *sa_free_id;
|
|
|
|
/* Number of elements in the SADB list of free positions. */
|
|
size_t n_sa_free_id;
|
|
|
|
/* Allocated memory total size in bytes. */
|
|
size_t total_size;
|
|
|
|
/* Flag for registration to the global list of instances. */
|
|
int registered;
|
|
|
|
/*
|
|
* Table memory.
|
|
*/
|
|
uint8_t memory[] __rte_cache_aligned;
|
|
};
|
|
|
|
static inline struct ipsec_sa *
|
|
ipsec_sa_get(struct rte_swx_ipsec *ipsec, uint32_t sa_id)
|
|
{
|
|
struct ipsec_sa *sadb = (struct ipsec_sa *)ipsec->memory;
|
|
|
|
return &sadb[sa_id & (ipsec->n_sa_max - 1)];
|
|
}
|
|
|
|
/* Global list of instances. */
|
|
TAILQ_HEAD(rte_swx_ipsec_list, rte_tailq_entry);
|
|
|
|
static struct rte_tailq_elem rte_swx_ipsec_tailq = {
|
|
.name = "RTE_SWX_IPSEC",
|
|
};
|
|
|
|
EAL_REGISTER_TAILQ(rte_swx_ipsec_tailq)
|
|
|
|
struct rte_swx_ipsec *
|
|
rte_swx_ipsec_find(const char *name)
|
|
{
|
|
struct rte_swx_ipsec_list *ipsec_list;
|
|
struct rte_tailq_entry *te = NULL;
|
|
|
|
if (!name ||
|
|
!name[0] ||
|
|
(strnlen(name, RTE_SWX_IPSEC_NAME_SIZE) == RTE_SWX_IPSEC_NAME_SIZE))
|
|
return NULL;
|
|
|
|
ipsec_list = RTE_TAILQ_CAST(rte_swx_ipsec_tailq.head, rte_swx_ipsec_list);
|
|
|
|
rte_mcfg_tailq_read_lock();
|
|
|
|
TAILQ_FOREACH(te, ipsec_list, next) {
|
|
struct rte_swx_ipsec *ipsec = (struct rte_swx_ipsec *)te->data;
|
|
|
|
if (!strncmp(name, ipsec->name, sizeof(ipsec->name))) {
|
|
rte_mcfg_tailq_read_unlock();
|
|
return ipsec;
|
|
}
|
|
}
|
|
|
|
rte_mcfg_tailq_read_unlock();
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
ipsec_register(struct rte_swx_ipsec *ipsec)
|
|
{
|
|
struct rte_swx_ipsec_list *ipsec_list;
|
|
struct rte_tailq_entry *te = NULL;
|
|
|
|
ipsec_list = RTE_TAILQ_CAST(rte_swx_ipsec_tailq.head, rte_swx_ipsec_list);
|
|
|
|
rte_mcfg_tailq_write_lock();
|
|
|
|
TAILQ_FOREACH(te, ipsec_list, next) {
|
|
struct rte_swx_ipsec *elem = (struct rte_swx_ipsec *)te->data;
|
|
|
|
if (!strncmp(ipsec->name, elem->name, sizeof(ipsec->name))) {
|
|
rte_mcfg_tailq_write_unlock();
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
te = calloc(1, sizeof(struct rte_tailq_entry));
|
|
if (!te) {
|
|
rte_mcfg_tailq_write_unlock();
|
|
return -ENOMEM;
|
|
}
|
|
|
|
te->data = (void *)ipsec;
|
|
TAILQ_INSERT_TAIL(ipsec_list, te, next);
|
|
rte_mcfg_tailq_write_unlock();
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
ipsec_unregister(struct rte_swx_ipsec *ipsec)
|
|
{
|
|
struct rte_swx_ipsec_list *ipsec_list;
|
|
struct rte_tailq_entry *te = NULL;
|
|
|
|
ipsec_list = RTE_TAILQ_CAST(rte_swx_ipsec_tailq.head, rte_swx_ipsec_list);
|
|
|
|
rte_mcfg_tailq_write_lock();
|
|
|
|
TAILQ_FOREACH(te, ipsec_list, next) {
|
|
if (te->data == (void *)ipsec) {
|
|
TAILQ_REMOVE(ipsec_list, te, next);
|
|
rte_mcfg_tailq_write_unlock();
|
|
free(te);
|
|
return;
|
|
}
|
|
}
|
|
|
|
rte_mcfg_tailq_write_unlock();
|
|
}
|
|
|
|
static void
|
|
ipsec_session_free(struct rte_swx_ipsec *ipsec, struct rte_ipsec_session *s);
|
|
|
|
void
|
|
rte_swx_ipsec_free(struct rte_swx_ipsec *ipsec)
|
|
{
|
|
size_t i;
|
|
|
|
if (!ipsec)
|
|
return;
|
|
|
|
/* Remove the current instance from the global list. */
|
|
if (ipsec->registered)
|
|
ipsec_unregister(ipsec);
|
|
|
|
/* SADB. */
|
|
for (i = 0; i < ipsec->n_sa_max; i++) {
|
|
struct ipsec_sa *sa = ipsec_sa_get(ipsec, i);
|
|
|
|
if (!sa->valid)
|
|
continue;
|
|
|
|
/* SA session. */
|
|
ipsec_session_free(ipsec, &sa->s);
|
|
}
|
|
|
|
/* Crypto device buffer pools. */
|
|
rte_mempool_free(ipsec->mp_session);
|
|
|
|
/* IPsec object memory. */
|
|
env_free(ipsec, ipsec->total_size);
|
|
}
|
|
|
|
int
|
|
rte_swx_ipsec_create(struct rte_swx_ipsec **ipsec_out,
|
|
const char *name,
|
|
struct rte_swx_ipsec_params *params,
|
|
int numa_node)
|
|
{
|
|
char resource_name[RTE_SWX_IPSEC_NAME_SIZE];
|
|
struct rte_swx_ipsec *ipsec = NULL;
|
|
struct rte_ring *ring_in, *ring_out;
|
|
struct rte_cryptodev_info dev_info;
|
|
size_t n_sa_max, sadb_offset, sadb_size, sa_free_id_offset, sa_free_id_size, total_size, i;
|
|
uint32_t dev_session_size;
|
|
int dev_id, status = 0;
|
|
|
|
/* Check input parameters. */
|
|
if (!ipsec_out ||
|
|
!name ||
|
|
!name[0] ||
|
|
(strnlen((name), RTE_SWX_IPSEC_NAME_SIZE) == RTE_SWX_IPSEC_NAME_SIZE) ||
|
|
!params ||
|
|
(params->bsz.ring_rd > RTE_SWX_IPSEC_BURST_SIZE_MAX) ||
|
|
(params->bsz.ring_wr > RTE_SWX_IPSEC_BURST_SIZE_MAX) ||
|
|
(params->bsz.crypto_wr > RTE_SWX_IPSEC_BURST_SIZE_MAX) ||
|
|
(params->bsz.crypto_rd > RTE_SWX_IPSEC_BURST_SIZE_MAX) ||
|
|
!params->n_sa_max) {
|
|
status = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
ring_in = rte_ring_lookup(params->ring_in_name);
|
|
if (!ring_in) {
|
|
status = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
ring_out = rte_ring_lookup(params->ring_out_name);
|
|
if (!ring_out) {
|
|
status = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
dev_id = rte_cryptodev_get_dev_id(params->crypto_dev_name);
|
|
if (dev_id == -1) {
|
|
status = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
rte_cryptodev_info_get(dev_id, &dev_info);
|
|
if (params->crypto_dev_queue_pair_id >= dev_info.max_nb_queue_pairs) {
|
|
status = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
/* Memory allocation. */
|
|
n_sa_max = rte_align64pow2(RTE_MAX(params->n_sa_max, N_SA_MIN));
|
|
|
|
sadb_offset = sizeof(struct rte_swx_ipsec);
|
|
sadb_size = RTE_CACHE_LINE_ROUNDUP(n_sa_max * sizeof(struct ipsec_sa));
|
|
|
|
sa_free_id_offset = sadb_offset + sadb_size;
|
|
sa_free_id_size = RTE_CACHE_LINE_ROUNDUP(n_sa_max * sizeof(uint32_t));
|
|
|
|
total_size = sa_free_id_offset + sa_free_id_size;
|
|
ipsec = env_calloc(total_size, RTE_CACHE_LINE_SIZE, numa_node);
|
|
if (!ipsec) {
|
|
status = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
/* Initialization. */
|
|
strcpy(ipsec->name, name);
|
|
ipsec->ring_in = ring_in;
|
|
ipsec->ring_out = ring_out;
|
|
ipsec->dev_id = (uint8_t)dev_id;
|
|
ipsec->qp_id = params->crypto_dev_queue_pair_id;
|
|
memcpy(&ipsec->bsz, ¶ms->bsz, sizeof(struct rte_swx_ipsec_burst_size));
|
|
ipsec->n_sa_max = n_sa_max;
|
|
|
|
ipsec->crypto_wr_threshold = params->bsz.crypto_wr * 3 / 4;
|
|
|
|
ipsec->sa_free_id = (uint32_t *)&ipsec->memory[sa_free_id_offset];
|
|
for (i = 0; i < n_sa_max; i++)
|
|
ipsec->sa_free_id[i] = n_sa_max - 1 - i;
|
|
ipsec->n_sa_free_id = n_sa_max;
|
|
|
|
ipsec->total_size = total_size;
|
|
|
|
/* Crypto device memory pools. */
|
|
dev_session_size = rte_cryptodev_sym_get_private_session_size((uint8_t)dev_id);
|
|
|
|
snprintf(resource_name, sizeof(resource_name), "%s_mp", name);
|
|
ipsec->mp_session = rte_cryptodev_sym_session_pool_create(resource_name,
|
|
n_sa_max, /* number of pool elements */
|
|
dev_session_size, /* pool element size */
|
|
RTE_SWX_IPSEC_POOL_CACHE_SIZE, /* pool cache size */
|
|
0, /* pool element private data size */
|
|
numa_node);
|
|
if (!ipsec->mp_session) {
|
|
status = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
/* Add the current instance to the global list. */
|
|
status = ipsec_register(ipsec);
|
|
if (status)
|
|
goto error;
|
|
|
|
ipsec->registered = 1;
|
|
|
|
*ipsec_out = ipsec;
|
|
return 0;
|
|
|
|
error:
|
|
rte_swx_ipsec_free(ipsec);
|
|
return status;
|
|
}
|
|
|
|
static inline int
|
|
ipsec_sa_group(struct rte_swx_ipsec *ipsec, int n_pkts)
|
|
{
|
|
struct ipsec_sa *sa;
|
|
struct rte_ipsec_group *g;
|
|
int n_groups, n_pkts_in_group, i;
|
|
|
|
sa = ipsec->in.sa[0];
|
|
|
|
g = &ipsec->in.groups[0];
|
|
g->id.ptr = sa;
|
|
g->m = &ipsec->in.pkts[0];
|
|
n_pkts_in_group = 1;
|
|
n_groups = 1;
|
|
|
|
for (i = 1; i < n_pkts; i++) {
|
|
struct ipsec_sa *sa_new = ipsec->in.sa[i];
|
|
|
|
/* Same SA => Add the current pkt to the same group. */
|
|
if (sa_new == sa) {
|
|
n_pkts_in_group++;
|
|
continue;
|
|
}
|
|
|
|
/* Different SA => Close the current group & add the current pkt to a new group. */
|
|
g->cnt = n_pkts_in_group;
|
|
sa = sa_new;
|
|
|
|
g++;
|
|
g->id.ptr = sa;
|
|
g->m = &ipsec->in.pkts[i];
|
|
n_pkts_in_group = 1;
|
|
n_groups++;
|
|
}
|
|
|
|
/* Close the last group. */
|
|
g->cnt = n_pkts_in_group;
|
|
|
|
return n_groups;
|
|
}
|
|
|
|
static inline void
|
|
ipsec_crypto_enqueue(struct rte_swx_ipsec *ipsec, uint16_t n_cops)
|
|
{
|
|
struct rte_crypto_op **dst0 = ipsec->in.cops, **dst;
|
|
struct rte_crypto_op **src = ipsec->in.group_cops;
|
|
|
|
uint32_t n_pkts_crypto = ipsec->n_pkts_crypto;
|
|
uint32_t n_dst = ipsec->in.n_cops;
|
|
uint32_t n_dst_max = ipsec->bsz.crypto_wr;
|
|
uint32_t n_dst_avail = n_dst_max - n_dst;
|
|
uint32_t n_src = n_cops;
|
|
uint32_t i;
|
|
|
|
dst = &dst0[n_dst];
|
|
|
|
/* Shortcut: If no elements in DST and enough elements in SRC, then simply use SRC directly
|
|
* instead of moving the SRC to DST first and then using DST.
|
|
*/
|
|
if (!n_dst && n_src >= ipsec->crypto_wr_threshold) {
|
|
uint16_t n_ok;
|
|
|
|
n_ok = rte_cryptodev_enqueue_burst(ipsec->dev_id, ipsec->qp_id, src, n_src);
|
|
ipsec->n_pkts_crypto = n_pkts_crypto + n_ok;
|
|
|
|
for (i = n_ok; i < n_src; i++) {
|
|
struct rte_crypto_op *cop = src[i];
|
|
struct rte_mbuf *m = cop->sym->m_src;
|
|
|
|
rte_pktmbuf_free(m);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Move from SRC to DST. Every time DST gets full, send burst from DST. */
|
|
for ( ; n_src >= n_dst_avail; ) {
|
|
uint32_t n_ok;
|
|
|
|
/* Move from SRC to DST. */
|
|
for (i = 0; i < n_dst_avail; i++)
|
|
*dst++ = *src++;
|
|
|
|
n_src -= n_dst_avail;
|
|
|
|
/* DST full: send burst from DST. */
|
|
n_ok = rte_cryptodev_enqueue_burst(ipsec->dev_id, ipsec->qp_id, dst0, n_dst_max);
|
|
n_pkts_crypto += n_ok;
|
|
|
|
for (i = n_ok ; i < n_dst_max; i++) {
|
|
struct rte_crypto_op *cop = dst0[i];
|
|
struct rte_mbuf *m = cop->sym->m_src;
|
|
|
|
rte_pktmbuf_free(m);
|
|
}
|
|
|
|
/* Next iteration. */
|
|
dst = dst0;
|
|
n_dst = 0;
|
|
n_dst_avail = n_dst_max;
|
|
}
|
|
|
|
ipsec->n_pkts_crypto = n_pkts_crypto;
|
|
|
|
/* Move from SRC to DST. Not enough elements in SRC to get DST full. */
|
|
for (i = 0; i < n_src; i++)
|
|
*dst++ = *src++;
|
|
|
|
n_dst += n_src;
|
|
|
|
ipsec->in.n_cops = n_dst;
|
|
}
|
|
|
|
/**
|
|
* Packet buffer anatomy:
|
|
*
|
|
* +----------+---------+--------------------------------------------------------------------------+
|
|
* | Offset | Size | Description |
|
|
* | (Byte #) | (Bytes) | |
|
|
* +==========+=========+==========================================================================+
|
|
* | 0 | 128 | Meta-data: struct rte_mbuf. |
|
|
* | | | The buf_addr field points to the start of the packet section. |
|
|
* +----------+---------+--------------------------------------------------------------------------+
|
|
* | 128 | 128 | Meta-data: struct ipsec_mbuf (see below). |
|
|
* +----------+---------+--------------------------------------------------------------------------+
|
|
* | 256 | | Packet section. |
|
|
* | | | The first packet byte is placed at the offset indicated by the struct |
|
|
* | | | rte_mbuf::data_off field relative to the start of the packet section. |
|
|
* +----------+---------+--------------------------------------------------------------------------+
|
|
*/
|
|
struct ipsec_mbuf {
|
|
struct ipsec_sa *sa;
|
|
struct rte_crypto_op cop;
|
|
struct rte_crypto_sym_op sym_cop;
|
|
uint8_t buffer[32]; /* The crypto IV is placed here. */
|
|
};
|
|
|
|
/* Offset from the start of the struct ipsec_mbuf::cop where the crypto IV will be placed. */
|
|
#define IV_OFFSET (sizeof(struct rte_crypto_op) + sizeof(struct rte_crypto_sym_op))
|
|
|
|
#define META_LENGTH sizeof(struct rte_swx_ipsec_input_packet_metadata)
|
|
|
|
static inline void
|
|
rte_swx_ipsec_pre_crypto(struct rte_swx_ipsec *ipsec)
|
|
{
|
|
int n_pkts, n_groups, i;
|
|
|
|
/* Read packets from the input ring. */
|
|
n_pkts = rte_ring_sc_dequeue_burst(ipsec->ring_in,
|
|
(void **)ipsec->in.pkts,
|
|
ipsec->bsz.ring_rd,
|
|
NULL);
|
|
if (!n_pkts)
|
|
return;
|
|
|
|
/* Get the SA for each packet. */
|
|
for (i = 0; i < n_pkts; i++) {
|
|
struct rte_mbuf *m = ipsec->in.pkts[i];
|
|
struct rte_swx_ipsec_input_packet_metadata *meta;
|
|
struct rte_ipv4_hdr *ipv4_hdr;
|
|
uint32_t sa_id;
|
|
|
|
meta = rte_pktmbuf_mtod(m, struct rte_swx_ipsec_input_packet_metadata *);
|
|
ipv4_hdr = rte_pktmbuf_mtod_offset(m, struct rte_ipv4_hdr *, META_LENGTH);
|
|
|
|
/* Read the SA ID from the IPsec meta-data placed at the front of the IP packet. */
|
|
sa_id = ntohl(meta->sa_id);
|
|
|
|
/* Consume the IPsec meta-data. */
|
|
m->data_off += META_LENGTH;
|
|
m->data_len -= META_LENGTH;
|
|
m->pkt_len -= META_LENGTH;
|
|
|
|
/* Set the fields required by the IPsec library. */
|
|
m->l2_len = 0;
|
|
m->l3_len = (ipv4_hdr->version_ihl >> 4 == 4) ?
|
|
sizeof(struct rte_ipv4_hdr) :
|
|
sizeof(struct rte_ipv6_hdr);
|
|
|
|
/* Get the SA. */
|
|
ipsec->in.sa[i] = ipsec_sa_get(ipsec, sa_id);
|
|
}
|
|
|
|
/* Group packets that share the same SA. */
|
|
n_groups = ipsec_sa_group(ipsec, n_pkts);
|
|
|
|
/* Write each group of packets sharing the same SA to the crypto device. */
|
|
for (i = 0; i < n_groups; i++) {
|
|
struct rte_ipsec_group *g = &ipsec->in.groups[i];
|
|
struct ipsec_sa *sa = g->id.ptr;
|
|
struct rte_ipsec_session *s = &sa->s;
|
|
uint32_t j;
|
|
uint16_t n_pkts_ok;
|
|
|
|
/* Prepare the crypto ops for the current group. */
|
|
for (j = 0; j < g->cnt; j++) {
|
|
struct rte_mbuf *m = g->m[j];
|
|
struct ipsec_mbuf *priv = rte_mbuf_to_priv(m);
|
|
|
|
priv->sa = sa;
|
|
ipsec->in.group_cops[j] = &priv->cop;
|
|
}
|
|
|
|
n_pkts_ok = rte_ipsec_pkt_crypto_prepare(s, g->m, ipsec->in.group_cops, g->cnt);
|
|
|
|
for (j = n_pkts_ok; j < g->cnt; j++) {
|
|
struct rte_mbuf *m = g->m[j];
|
|
|
|
rte_pktmbuf_free(m);
|
|
}
|
|
|
|
/* Write the crypto ops of the current group to the crypto device. */
|
|
ipsec_crypto_enqueue(ipsec, n_pkts_ok);
|
|
}
|
|
}
|
|
|
|
static inline void
|
|
ipsec_ring_enqueue(struct rte_swx_ipsec *ipsec, struct rte_ipsec_group *g, uint32_t n_pkts)
|
|
{
|
|
struct rte_mbuf **dst0 = ipsec->out.pkts, **dst;
|
|
struct rte_mbuf **src = g->m;
|
|
|
|
uint32_t n_dst = ipsec->out.n_pkts;
|
|
uint32_t n_dst_max = ipsec->bsz.ring_wr;
|
|
uint32_t n_dst_avail = n_dst_max - n_dst;
|
|
uint32_t n_src = n_pkts;
|
|
uint32_t i;
|
|
|
|
dst = &dst0[n_dst];
|
|
|
|
/* Move from SRC to DST. Every time DST gets full, send burst from DST. */
|
|
for ( ; n_src >= n_dst_avail; ) {
|
|
uint32_t n_ok;
|
|
|
|
/* Move from SRC to DST. */
|
|
for (i = 0; i < n_dst_avail; i++)
|
|
*dst++ = *src++;
|
|
|
|
n_src -= n_dst_avail;
|
|
|
|
/* DST full: send burst from DST. */
|
|
n_ok = rte_ring_sp_enqueue_burst(ipsec->ring_out, (void **)dst0, n_dst_max, NULL);
|
|
|
|
for (i = n_ok ; i < n_dst_max; i++) {
|
|
struct rte_mbuf *m = dst[i];
|
|
|
|
rte_pktmbuf_free(m);
|
|
}
|
|
|
|
/* Next iteration. */
|
|
dst = dst0;
|
|
n_dst = 0;
|
|
n_dst_avail = n_dst_max;
|
|
}
|
|
|
|
/* Move from SRC to DST. Not enough elements in SRC to get DST full. */
|
|
for (i = 0; i < n_src; i++)
|
|
*dst++ = *src++;
|
|
|
|
n_dst += n_src;
|
|
|
|
ipsec->out.n_pkts = n_dst;
|
|
}
|
|
|
|
static inline void
|
|
rte_swx_ipsec_post_crypto(struct rte_swx_ipsec *ipsec)
|
|
{
|
|
uint32_t n_pkts_crypto = ipsec->n_pkts_crypto, n_pkts, ng, i;
|
|
|
|
/* Read the crypto ops from the crypto device. */
|
|
if (!n_pkts_crypto)
|
|
return;
|
|
|
|
n_pkts = rte_cryptodev_dequeue_burst(ipsec->dev_id,
|
|
ipsec->qp_id,
|
|
ipsec->out.cops,
|
|
ipsec->bsz.crypto_rd);
|
|
if (!n_pkts)
|
|
return;
|
|
|
|
ipsec->n_pkts_crypto = n_pkts_crypto - n_pkts;
|
|
|
|
/* Group packets that share the same SA. */
|
|
ng = rte_ipsec_pkt_crypto_group((const struct rte_crypto_op **)(uintptr_t)ipsec->out.cops,
|
|
ipsec->out.group_pkts,
|
|
ipsec->out.groups,
|
|
n_pkts);
|
|
|
|
/* Perform post-crypto IPsec processing for each group of packets that share the same SA.
|
|
* Write each group of packets to the output ring.
|
|
*/
|
|
for (i = 0, n_pkts = 0; i < ng; i++) {
|
|
struct rte_ipsec_group *g = &ipsec->out.groups[i];
|
|
struct rte_ipsec_session *s = g->id.ptr;
|
|
uint32_t n_pkts_ok, j;
|
|
|
|
/* Perform post-crypto IPsec processing for the current group. */
|
|
n_pkts_ok = rte_ipsec_pkt_process(s, g->m, g->cnt);
|
|
|
|
for (j = n_pkts_ok; j < g->cnt; j++) {
|
|
struct rte_mbuf *m = g->m[j];
|
|
|
|
rte_pktmbuf_free(m);
|
|
}
|
|
|
|
/* Write the packets of the current group to the output ring. */
|
|
ipsec_ring_enqueue(ipsec, g, n_pkts_ok);
|
|
}
|
|
}
|
|
|
|
void
|
|
rte_swx_ipsec_run(struct rte_swx_ipsec *ipsec)
|
|
{
|
|
rte_swx_ipsec_pre_crypto(ipsec);
|
|
rte_swx_ipsec_post_crypto(ipsec);
|
|
}
|
|
|
|
/**
|
|
* IPsec Control Plane API
|
|
*/
|
|
struct cipher_alg {
|
|
const char *name;
|
|
enum rte_crypto_cipher_algorithm alg;
|
|
uint32_t iv_size;
|
|
uint32_t block_size;
|
|
uint32_t key_size;
|
|
};
|
|
|
|
struct auth_alg {
|
|
const char *name;
|
|
enum rte_crypto_auth_algorithm alg;
|
|
uint32_t iv_size;
|
|
uint32_t digest_size;
|
|
uint32_t key_size;
|
|
};
|
|
|
|
struct aead_alg {
|
|
const char *name;
|
|
enum rte_crypto_aead_algorithm alg;
|
|
uint32_t iv_size;
|
|
uint32_t block_size;
|
|
uint32_t digest_size;
|
|
uint32_t key_size;
|
|
uint32_t aad_size;
|
|
};
|
|
|
|
static struct cipher_alg cipher_algs[] = {
|
|
[0] = {
|
|
.name = "null",
|
|
.alg = RTE_CRYPTO_CIPHER_NULL,
|
|
.iv_size = 0,
|
|
.block_size = 4,
|
|
.key_size = 0,
|
|
},
|
|
|
|
[1] = {
|
|
.name = "aes-cbc-128",
|
|
.alg = RTE_CRYPTO_CIPHER_AES_CBC,
|
|
.iv_size = 16,
|
|
.block_size = 16,
|
|
.key_size = 16,
|
|
},
|
|
|
|
[2] = {
|
|
.name = "aes-cbc-192",
|
|
.alg = RTE_CRYPTO_CIPHER_AES_CBC,
|
|
.iv_size = 16,
|
|
.block_size = 16,
|
|
.key_size = 24,
|
|
},
|
|
|
|
[3] = {
|
|
.name = "aes-cbc-256",
|
|
.alg = RTE_CRYPTO_CIPHER_AES_CBC,
|
|
.iv_size = 16,
|
|
.block_size = 16,
|
|
.key_size = 32,
|
|
},
|
|
|
|
[4] = {
|
|
.name = "aes-ctr-128",
|
|
.alg = RTE_CRYPTO_CIPHER_AES_CTR,
|
|
.iv_size = 8,
|
|
.block_size = 4,
|
|
.key_size = 20,
|
|
},
|
|
|
|
[5] = {
|
|
.name = "aes-ctr-192",
|
|
.alg = RTE_CRYPTO_CIPHER_AES_CTR,
|
|
.iv_size = 16,
|
|
.block_size = 16,
|
|
.key_size = 28,
|
|
},
|
|
|
|
[6] = {
|
|
.name = "aes-ctr-256",
|
|
.alg = RTE_CRYPTO_CIPHER_AES_CTR,
|
|
.iv_size = 16,
|
|
.block_size = 16,
|
|
.key_size = 36,
|
|
},
|
|
|
|
[7] = {
|
|
.name = "3des-cbc",
|
|
.alg = RTE_CRYPTO_CIPHER_3DES_CBC,
|
|
.iv_size = 8,
|
|
.block_size = 8,
|
|
.key_size = 24,
|
|
},
|
|
|
|
[8] = {
|
|
.name = "des-cbc",
|
|
.alg = RTE_CRYPTO_CIPHER_DES_CBC,
|
|
.iv_size = 8,
|
|
.block_size = 8,
|
|
.key_size = 8,
|
|
},
|
|
};
|
|
|
|
static struct auth_alg auth_algs[] = {
|
|
[0] = {
|
|
.name = "null",
|
|
.alg = RTE_CRYPTO_AUTH_NULL,
|
|
.iv_size = 0,
|
|
.digest_size = 0,
|
|
.key_size = 0,
|
|
},
|
|
|
|
[1] = {
|
|
.name = "sha1-hmac",
|
|
.alg = RTE_CRYPTO_AUTH_SHA1_HMAC,
|
|
.iv_size = 0,
|
|
.digest_size = 12,
|
|
.key_size = 20,
|
|
},
|
|
|
|
[2] = {
|
|
.name = "sha256-hmac",
|
|
.alg = RTE_CRYPTO_AUTH_SHA256_HMAC,
|
|
.iv_size = 0,
|
|
.digest_size = 16,
|
|
.key_size = 32,
|
|
},
|
|
|
|
[3] = {
|
|
.name = "sha384-hmac",
|
|
.alg = RTE_CRYPTO_AUTH_SHA384_HMAC,
|
|
.iv_size = 0,
|
|
.digest_size = 24,
|
|
.key_size = 48,
|
|
},
|
|
|
|
[4] = {
|
|
.name = "sha512-hmac",
|
|
.alg = RTE_CRYPTO_AUTH_SHA512_HMAC,
|
|
.iv_size = 0,
|
|
.digest_size = 32,
|
|
.key_size = 64,
|
|
},
|
|
|
|
[5] = {
|
|
.name = "aes-gmac",
|
|
.alg = RTE_CRYPTO_AUTH_AES_GMAC,
|
|
.iv_size = 8,
|
|
.digest_size = 16,
|
|
.key_size = 20,
|
|
},
|
|
|
|
[6] = {
|
|
.name = "aes-xcbc-mac-96",
|
|
.alg = RTE_CRYPTO_AUTH_AES_XCBC_MAC,
|
|
.iv_size = 0,
|
|
.digest_size = 12,
|
|
.key_size = 16,
|
|
},
|
|
};
|
|
|
|
static struct aead_alg aead_algs[] = {
|
|
[0] = {
|
|
.name = "aes-gcm-128",
|
|
.alg = RTE_CRYPTO_AEAD_AES_GCM,
|
|
.iv_size = 8,
|
|
.block_size = 4,
|
|
.key_size = 20,
|
|
.digest_size = 16,
|
|
.aad_size = 8,
|
|
},
|
|
|
|
[1] = {
|
|
.name = "aes-gcm-192",
|
|
.alg = RTE_CRYPTO_AEAD_AES_GCM,
|
|
.iv_size = 8,
|
|
.block_size = 4,
|
|
.key_size = 28,
|
|
.digest_size = 16,
|
|
.aad_size = 8,
|
|
},
|
|
|
|
[2] = {
|
|
.name = "aes-gcm-256",
|
|
.alg = RTE_CRYPTO_AEAD_AES_GCM,
|
|
.iv_size = 8,
|
|
.block_size = 4,
|
|
.key_size = 36,
|
|
.digest_size = 16,
|
|
.aad_size = 8,
|
|
},
|
|
|
|
[3] = {
|
|
.name = "aes-ccm-128",
|
|
.alg = RTE_CRYPTO_AEAD_AES_CCM,
|
|
.iv_size = 8,
|
|
.block_size = 4,
|
|
.key_size = 20,
|
|
.digest_size = 16,
|
|
.aad_size = 8,
|
|
},
|
|
|
|
[4] = {
|
|
.name = "aes-ccm-192",
|
|
.alg = RTE_CRYPTO_AEAD_AES_CCM,
|
|
.iv_size = 8,
|
|
.block_size = 4,
|
|
.key_size = 28,
|
|
.digest_size = 16,
|
|
.aad_size = 8,
|
|
},
|
|
|
|
[5] = {
|
|
.name = "aes-ccm-256",
|
|
.alg = RTE_CRYPTO_AEAD_AES_CCM,
|
|
.iv_size = 8,
|
|
.block_size = 4,
|
|
.key_size = 36,
|
|
.digest_size = 16,
|
|
.aad_size = 8,
|
|
},
|
|
|
|
[6] = {
|
|
.name = "chacha20-poly1305",
|
|
.alg = RTE_CRYPTO_AEAD_CHACHA20_POLY1305,
|
|
.iv_size = 12,
|
|
.block_size = 64,
|
|
.key_size = 36,
|
|
.digest_size = 16,
|
|
.aad_size = 8,
|
|
},
|
|
};
|
|
|
|
static struct cipher_alg *
|
|
cipher_alg_find(const char *name)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < RTE_DIM(cipher_algs); i++) {
|
|
struct cipher_alg *alg = &cipher_algs[i];
|
|
|
|
if (!strcmp(name, alg->name))
|
|
return alg;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct cipher_alg *
|
|
cipher_alg_find_by_id(enum rte_crypto_cipher_algorithm alg_id, uint32_t key_size)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < RTE_DIM(cipher_algs); i++) {
|
|
struct cipher_alg *alg = &cipher_algs[i];
|
|
|
|
if (alg->alg == alg_id && alg->key_size == key_size)
|
|
return alg;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct auth_alg *
|
|
auth_alg_find(const char *name)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < RTE_DIM(auth_algs); i++) {
|
|
struct auth_alg *alg = &auth_algs[i];
|
|
|
|
if (!strcmp(name, alg->name))
|
|
return alg;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct auth_alg *
|
|
auth_alg_find_by_id(enum rte_crypto_auth_algorithm alg_id, uint32_t key_size)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < RTE_DIM(auth_algs); i++) {
|
|
struct auth_alg *alg = &auth_algs[i];
|
|
|
|
if (alg->alg == alg_id && alg->key_size == key_size)
|
|
return alg;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct aead_alg *
|
|
aead_alg_find(const char *name)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < RTE_DIM(aead_algs); i++) {
|
|
struct aead_alg *alg = &aead_algs[i];
|
|
|
|
if (!strcmp(name, alg->name))
|
|
return alg;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct aead_alg *
|
|
aead_alg_find_by_id(enum rte_crypto_aead_algorithm alg_id, uint32_t key_size)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < RTE_DIM(aead_algs); i++) {
|
|
struct aead_alg *alg = &aead_algs[i];
|
|
|
|
if (alg->alg == alg_id && alg->key_size == key_size)
|
|
return alg;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
char_to_hex(char c, uint8_t *val)
|
|
{
|
|
if (c >= '0' && c <= '9') {
|
|
*val = c - '0';
|
|
return 0;
|
|
}
|
|
|
|
if (c >= 'A' && c <= 'F') {
|
|
*val = c - 'A' + 10;
|
|
return 0;
|
|
}
|
|
|
|
if (c >= 'a' && c <= 'f') {
|
|
*val = c - 'a' + 10;
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int
|
|
hex_string_parse(char *src, uint8_t *dst, uint32_t n_dst_bytes)
|
|
{
|
|
uint32_t i;
|
|
|
|
/* Check input arguments. */
|
|
if (!src || !src[0] || !dst || !n_dst_bytes)
|
|
return -EINVAL;
|
|
|
|
/* Skip any leading "0x" or "0X" in the src string. */
|
|
if ((src[0] == '0') && (src[1] == 'x' || src[1] == 'X'))
|
|
src += 2;
|
|
|
|
/* Convert each group of two hex characters in the src string to one byte in dst array. */
|
|
for (i = 0; i < n_dst_bytes; i++) {
|
|
uint8_t a, b;
|
|
int status;
|
|
|
|
status = char_to_hex(*src, &a);
|
|
if (status)
|
|
return status;
|
|
src++;
|
|
|
|
status = char_to_hex(*src, &b);
|
|
if (status)
|
|
return status;
|
|
src++;
|
|
|
|
dst[i] = a * 16 + b;
|
|
}
|
|
|
|
/* Check for the end of the src string. */
|
|
if (*src)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
token_is_comment(const char *token)
|
|
{
|
|
if ((token[0] == '#') ||
|
|
(token[0] == ';') ||
|
|
((token[0] == '/') && (token[1] == '/')))
|
|
return 1; /* TRUE. */
|
|
|
|
return 0; /* FALSE. */
|
|
}
|
|
|
|
#define MAX_TOKENS 64
|
|
|
|
#define CHECK(condition, msg) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
if (errmsg) \
|
|
*errmsg = msg; \
|
|
goto error; \
|
|
} \
|
|
} while (0)
|
|
|
|
struct rte_swx_ipsec_sa_params *
|
|
rte_swx_ipsec_sa_read(struct rte_swx_ipsec *ipsec __rte_unused,
|
|
const char *string,
|
|
int *is_blank_or_comment,
|
|
const char **errmsg)
|
|
{
|
|
char *token_array[MAX_TOKENS], **t;
|
|
struct rte_swx_ipsec_sa_params *p = NULL;
|
|
char *s0 = NULL, *s;
|
|
uint32_t n_tokens = 0;
|
|
int blank_or_comment = 0;
|
|
|
|
/* Check input arguments. */
|
|
CHECK(string && string[0], "NULL input");
|
|
|
|
/* Memory allocation. */
|
|
s0 = strdup(string);
|
|
p = calloc(1, sizeof(struct rte_swx_ipsec_sa_params));
|
|
CHECK(s0 && p, "Not enough memory");
|
|
|
|
/* Parse the string into tokens. */
|
|
for (s = s0; ; ) {
|
|
char *token;
|
|
|
|
token = strtok_r(s, " \f\n\r\t\v", &s);
|
|
if (!token || token_is_comment(token))
|
|
break;
|
|
|
|
CHECK(n_tokens < RTE_DIM(token_array), "Too many tokens");
|
|
|
|
token_array[n_tokens] = token;
|
|
n_tokens++;
|
|
}
|
|
|
|
t = token_array;
|
|
if (!n_tokens) {
|
|
blank_or_comment = 1;
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* Crypto operation.
|
|
*/
|
|
if (!strcmp(t[0], "encrypt"))
|
|
p->encrypt = 1;
|
|
else if (!strcmp(t[0], "decrypt"))
|
|
p->encrypt = 0;
|
|
else
|
|
CHECK(0, "Missing \"encrypt\"/\"decrypt\" keyword");
|
|
|
|
t++;
|
|
n_tokens--;
|
|
|
|
/*
|
|
* Crypto parameters.
|
|
*/
|
|
CHECK(n_tokens >= 2, "Not enough tokens");
|
|
|
|
if (!strcmp(t[0], "cipher")) {
|
|
struct cipher_alg *cipher_alg;
|
|
struct auth_alg *auth_alg;
|
|
uint32_t key_size;
|
|
|
|
p->crypto.is_aead = 0;
|
|
|
|
/* cipher. */
|
|
cipher_alg = cipher_alg_find(t[1]);
|
|
CHECK(cipher_alg, "Unsupported cipher algorithm");
|
|
|
|
key_size = cipher_alg->key_size;
|
|
p->crypto.cipher_auth.cipher.alg = cipher_alg->alg;
|
|
p->crypto.cipher_auth.cipher.key_size = key_size;
|
|
|
|
t += 2;
|
|
n_tokens -= 2;
|
|
|
|
if (key_size) {
|
|
int status;
|
|
|
|
CHECK(n_tokens >= 2, "Not enough tokens");
|
|
CHECK(!strcmp(t[0], "key"), "Missing cipher \"key\" keyword");
|
|
CHECK(key_size <= RTE_DIM(p->crypto.cipher_auth.cipher.key),
|
|
"Cipher algorithm key too big");
|
|
|
|
status = hex_string_parse(t[1], p->crypto.cipher_auth.cipher.key, key_size);
|
|
CHECK(!status, "Cipher key invalid format");
|
|
|
|
t += 2;
|
|
n_tokens -= 2;
|
|
}
|
|
|
|
/* authentication. */
|
|
CHECK(n_tokens >= 2, "Not enough tokens");
|
|
CHECK(!strcmp(t[0], "auth"), "Missing \"auth\" keyword");
|
|
|
|
auth_alg = auth_alg_find(t[1]);
|
|
CHECK(auth_alg, "Unsupported authentication algorithm");
|
|
|
|
key_size = auth_alg->key_size;
|
|
p->crypto.cipher_auth.auth.alg = auth_alg->alg;
|
|
p->crypto.cipher_auth.auth.key_size = key_size;
|
|
|
|
t += 2;
|
|
n_tokens -= 2;
|
|
|
|
if (key_size) {
|
|
int status;
|
|
|
|
CHECK(n_tokens >= 2, "Not enough tokens");
|
|
CHECK(!strcmp(t[0], "key"), "Missing authentication \"key\" keyword");
|
|
CHECK(key_size <= RTE_DIM(p->crypto.cipher_auth.auth.key),
|
|
"Authentication algorithm key too big");
|
|
|
|
status = hex_string_parse(t[1], p->crypto.cipher_auth.auth.key, key_size);
|
|
CHECK(!status, "Authentication key invalid format");
|
|
|
|
t += 2;
|
|
n_tokens -= 2;
|
|
}
|
|
} else if (!strcmp(t[0], "aead")) {
|
|
struct aead_alg *alg;
|
|
uint32_t key_size;
|
|
int status;
|
|
|
|
p->crypto.is_aead = 1;
|
|
|
|
CHECK(n_tokens >= 4, "Not enough tokens");
|
|
alg = aead_alg_find(t[1]);
|
|
CHECK(alg, "Unsupported AEAD algorithm");
|
|
|
|
key_size = alg->key_size;
|
|
p->crypto.aead.alg = alg->alg;
|
|
p->crypto.aead.key_size = key_size;
|
|
|
|
CHECK(!strcmp(t[2], "key"), "Missing AEAD \"key\" keyword");
|
|
CHECK(key_size <= RTE_DIM(p->crypto.aead.key),
|
|
"AEAD algorithm key too big");
|
|
|
|
status = hex_string_parse(t[3], p->crypto.aead.key, key_size);
|
|
CHECK(!status, "AEAD key invalid format");
|
|
|
|
t += 4;
|
|
n_tokens -= 4;
|
|
} else
|
|
CHECK(0, "Missing \"cipher\"/\"aead\" keyword");
|
|
|
|
/*
|
|
* Packet ecapsulation parameters.
|
|
*/
|
|
CHECK(n_tokens >= 4, "Not enough tokens");
|
|
CHECK(!strcmp(t[0], "esp"), "Missing \"esp\" keyword");
|
|
CHECK(!strcmp(t[1], "spi"), "Missing \"spi\" keyword");
|
|
|
|
p->encap.esp.spi = strtoul(t[2], &t[2], 0);
|
|
CHECK(!t[2][0], "ESP SPI field invalid format");
|
|
|
|
t += 3;
|
|
n_tokens -= 3;
|
|
|
|
if (!strcmp(t[0], "tunnel")) {
|
|
p->encap.tunnel_mode = 1;
|
|
|
|
CHECK(n_tokens >= 6, "Not enough tokens");
|
|
|
|
if (!strcmp(t[1], "ipv4")) {
|
|
uint32_t addr;
|
|
|
|
p->encap.tunnel_ipv4 = 1;
|
|
|
|
CHECK(!strcmp(t[2], "srcaddr"), "Missing \"srcaddr\" keyword");
|
|
|
|
addr = strtoul(t[3], &t[3], 0);
|
|
CHECK(!t[3][0], "Tunnel IPv4 source address invalid format");
|
|
p->encap.tunnel.ipv4.src_addr.s_addr = htonl(addr);
|
|
|
|
CHECK(!strcmp(t[4], "dstaddr"), "Missing \"dstaddr\" keyword");
|
|
|
|
addr = strtoul(t[5], &t[5], 0);
|
|
CHECK(!t[5][0], "Tunnel IPv4 destination address invalid format");
|
|
p->encap.tunnel.ipv4.dst_addr.s_addr = htonl(addr);
|
|
|
|
t += 6;
|
|
n_tokens -= 6;
|
|
} else if (!strcmp(t[1], "ipv6")) {
|
|
int status;
|
|
|
|
p->encap.tunnel_ipv4 = 0;
|
|
|
|
CHECK(!strcmp(t[2], "srcaddr"), "Missing \"srcaddr\" keyword");
|
|
|
|
status = hex_string_parse(t[3],
|
|
p->encap.tunnel.ipv6.src_addr.s6_addr,
|
|
16);
|
|
CHECK(!status, "Tunnel IPv6 source address invalid format");
|
|
|
|
CHECK(!strcmp(t[4], "dstaddr"), "Missing \"dstaddr\" keyword");
|
|
|
|
status = hex_string_parse(t[5],
|
|
p->encap.tunnel.ipv6.dst_addr.s6_addr,
|
|
16);
|
|
CHECK(!status, "Tunnel IPv6 destination address invalid format");
|
|
|
|
t += 6;
|
|
n_tokens -= 6;
|
|
} else
|
|
CHECK(0, "Missing \"ipv4\"/\"ipv6\" keyword");
|
|
} else if (!strcmp(t[0], "transport")) {
|
|
p->encap.tunnel_mode = 0;
|
|
|
|
t++;
|
|
n_tokens--;
|
|
} else
|
|
CHECK(0, "Missing \"tunnel\"/\"transport\" keyword");
|
|
|
|
/*
|
|
* Any other parameters.
|
|
*/
|
|
CHECK(!n_tokens, "Unexpected trailing tokens");
|
|
|
|
free(s0);
|
|
return p;
|
|
|
|
error:
|
|
free(p);
|
|
free(s0);
|
|
if (is_blank_or_comment)
|
|
*is_blank_or_comment = blank_or_comment;
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
tunnel_ipv4_header_set(struct rte_ipv4_hdr *h, struct rte_swx_ipsec_sa_params *p)
|
|
{
|
|
struct rte_ipv4_hdr ipv4_hdr = {
|
|
.version_ihl = 0x45,
|
|
.type_of_service = 0,
|
|
.total_length = 0, /* Cannot be pre-computed. */
|
|
.packet_id = 0,
|
|
.fragment_offset = 0,
|
|
.time_to_live = 64,
|
|
.next_proto_id = IPPROTO_ESP,
|
|
.hdr_checksum = 0, /* Cannot be pre-computed. */
|
|
.src_addr = p->encap.tunnel.ipv4.src_addr.s_addr,
|
|
.dst_addr = p->encap.tunnel.ipv4.dst_addr.s_addr,
|
|
};
|
|
|
|
memcpy(h, &ipv4_hdr, sizeof(ipv4_hdr));
|
|
}
|
|
|
|
static void
|
|
tunnel_ipv6_header_set(struct rte_ipv6_hdr *h, struct rte_swx_ipsec_sa_params *p)
|
|
{
|
|
struct rte_ipv6_hdr ipv6_hdr = {
|
|
.vtc_flow = 0x60000000,
|
|
.payload_len = 0, /* Cannot be pre-computed. */
|
|
.proto = IPPROTO_ESP,
|
|
.hop_limits = 64,
|
|
.src_addr = {0},
|
|
.dst_addr = {0},
|
|
};
|
|
|
|
memcpy(h, &ipv6_hdr, sizeof(ipv6_hdr));
|
|
memcpy(h->src_addr, p->encap.tunnel.ipv6.src_addr.s6_addr, 16);
|
|
memcpy(h->dst_addr, p->encap.tunnel.ipv6.dst_addr.s6_addr, 16);
|
|
}
|
|
|
|
/* IPsec library SA parameters. */
|
|
static struct rte_crypto_sym_xform *
|
|
crypto_xform_get(struct rte_swx_ipsec_sa_params *p,
|
|
struct rte_crypto_sym_xform *xform,
|
|
uint32_t *salt_out)
|
|
{
|
|
if (p->crypto.is_aead) {
|
|
struct aead_alg *alg;
|
|
uint32_t key_size, salt, iv_length;
|
|
|
|
alg = aead_alg_find_by_id(p->crypto.aead.alg, p->crypto.aead.key_size);
|
|
if (!alg)
|
|
return NULL;
|
|
|
|
/* salt and salt-related key size adjustment. */
|
|
key_size = p->crypto.aead.key_size - 4;
|
|
memcpy(&salt, &p->crypto.aead.key[key_size], 4);
|
|
|
|
/* IV length. */
|
|
iv_length = 12;
|
|
if (p->crypto.aead.alg == RTE_CRYPTO_AEAD_AES_CCM)
|
|
iv_length = 11;
|
|
|
|
/* xform. */
|
|
xform[0].type = RTE_CRYPTO_SYM_XFORM_AEAD;
|
|
xform[0].aead.op = p->encrypt ?
|
|
RTE_CRYPTO_AEAD_OP_ENCRYPT :
|
|
RTE_CRYPTO_AEAD_OP_DECRYPT;
|
|
xform[0].aead.algo = p->crypto.aead.alg;
|
|
xform[0].aead.key.data = p->crypto.aead.key;
|
|
xform[0].aead.key.length = key_size;
|
|
xform[0].aead.iv.offset = IV_OFFSET;
|
|
xform[0].aead.iv.length = iv_length;
|
|
xform[0].aead.digest_length = alg->digest_size;
|
|
xform[0].aead.aad_length = alg->aad_size;
|
|
xform[0].next = NULL;
|
|
|
|
*salt_out = salt;
|
|
return &xform[0];
|
|
} else {
|
|
struct cipher_alg *cipher_alg;
|
|
struct auth_alg *auth_alg;
|
|
uint32_t cipher_key_size, auth_key_size, salt, auth_iv_length;
|
|
|
|
cipher_alg = cipher_alg_find_by_id(p->crypto.cipher_auth.cipher.alg,
|
|
p->crypto.cipher_auth.cipher.key_size);
|
|
if (!cipher_alg)
|
|
return NULL;
|
|
|
|
auth_alg = auth_alg_find_by_id(p->crypto.cipher_auth.auth.alg,
|
|
p->crypto.cipher_auth.auth.key_size);
|
|
if (!auth_alg)
|
|
return NULL;
|
|
|
|
/* salt and salt-related key size adjustment. */
|
|
cipher_key_size = p->crypto.cipher_auth.cipher.key_size;
|
|
auth_key_size = p->crypto.cipher_auth.auth.key_size;
|
|
|
|
switch (p->crypto.cipher_auth.cipher.alg) {
|
|
case RTE_CRYPTO_CIPHER_AES_CBC:
|
|
case RTE_CRYPTO_CIPHER_3DES_CBC:
|
|
salt = (uint32_t)rand();
|
|
break;
|
|
|
|
case RTE_CRYPTO_CIPHER_AES_CTR:
|
|
cipher_key_size -= 4;
|
|
memcpy(&salt, &p->crypto.cipher_auth.cipher.key[cipher_key_size], 4);
|
|
break;
|
|
|
|
default:
|
|
salt = 0;
|
|
}
|
|
|
|
if (p->crypto.cipher_auth.auth.alg == RTE_CRYPTO_AUTH_AES_GMAC) {
|
|
auth_key_size -= 4;
|
|
memcpy(&salt, &p->crypto.cipher_auth.auth.key[auth_key_size], 4);
|
|
}
|
|
|
|
/* IV length. */
|
|
auth_iv_length = cipher_alg->iv_size;
|
|
if (p->crypto.cipher_auth.auth.alg == RTE_CRYPTO_AUTH_AES_GMAC)
|
|
auth_iv_length = 12;
|
|
|
|
/* xform. */
|
|
if (p->encrypt) {
|
|
xform[0].type = RTE_CRYPTO_SYM_XFORM_CIPHER;
|
|
xform[0].cipher.op = RTE_CRYPTO_CIPHER_OP_ENCRYPT;
|
|
xform[0].cipher.algo = p->crypto.cipher_auth.cipher.alg;
|
|
xform[0].cipher.key.data = p->crypto.cipher_auth.cipher.key;
|
|
xform[0].cipher.key.length = cipher_key_size;
|
|
xform[0].cipher.iv.offset = IV_OFFSET;
|
|
xform[0].cipher.iv.length = cipher_alg->iv_size;
|
|
xform[0].cipher.dataunit_len = 0;
|
|
xform[0].next = &xform[1];
|
|
|
|
xform[1].type = RTE_CRYPTO_SYM_XFORM_AUTH;
|
|
xform[1].auth.op = RTE_CRYPTO_AUTH_OP_GENERATE;
|
|
xform[1].auth.algo = p->crypto.cipher_auth.auth.alg;
|
|
xform[1].auth.key.data = p->crypto.cipher_auth.auth.key;
|
|
xform[1].auth.key.length = auth_key_size;
|
|
xform[1].auth.iv.offset = IV_OFFSET;
|
|
xform[1].auth.iv.length = auth_iv_length;
|
|
xform[1].auth.digest_length = auth_alg->digest_size;
|
|
xform[1].next = NULL;
|
|
} else {
|
|
xform[0].type = RTE_CRYPTO_SYM_XFORM_AUTH;
|
|
xform[0].auth.op = RTE_CRYPTO_AUTH_OP_VERIFY;
|
|
xform[0].auth.algo = p->crypto.cipher_auth.auth.alg;
|
|
xform[0].auth.key.data = p->crypto.cipher_auth.auth.key;
|
|
xform[0].auth.key.length = auth_key_size;
|
|
xform[0].auth.iv.offset = IV_OFFSET;
|
|
xform[0].auth.iv.length = auth_iv_length;
|
|
xform[0].auth.digest_length = auth_alg->digest_size;
|
|
xform[0].next = &xform[1];
|
|
|
|
xform[1].type = RTE_CRYPTO_SYM_XFORM_CIPHER;
|
|
xform[1].cipher.op = RTE_CRYPTO_CIPHER_OP_DECRYPT;
|
|
xform[1].cipher.algo = p->crypto.cipher_auth.cipher.alg;
|
|
xform[1].cipher.key.data = p->crypto.cipher_auth.cipher.key;
|
|
xform[1].cipher.key.length = cipher_key_size;
|
|
xform[1].cipher.iv.offset = IV_OFFSET;
|
|
xform[1].cipher.iv.length = cipher_alg->iv_size;
|
|
xform[1].cipher.dataunit_len = 0;
|
|
xform[1].next = NULL;
|
|
}
|
|
|
|
*salt_out = salt;
|
|
|
|
if (p->crypto.cipher_auth.auth.alg == RTE_CRYPTO_AUTH_AES_GMAC) {
|
|
if (p->encrypt)
|
|
return &xform[1];
|
|
|
|
xform[0].next = NULL;
|
|
return &xform[0];
|
|
}
|
|
|
|
return &xform[0];
|
|
}
|
|
}
|
|
|
|
static void
|
|
ipsec_xform_get(struct rte_swx_ipsec_sa_params *p,
|
|
struct rte_security_ipsec_xform *ipsec_xform,
|
|
uint32_t salt)
|
|
{
|
|
ipsec_xform->spi = p->encap.esp.spi;
|
|
|
|
ipsec_xform->salt = salt;
|
|
|
|
ipsec_xform->options.esn = 0;
|
|
ipsec_xform->options.udp_encap = 0;
|
|
ipsec_xform->options.copy_dscp = 1;
|
|
ipsec_xform->options.copy_flabel = 0;
|
|
ipsec_xform->options.copy_df = 0;
|
|
ipsec_xform->options.dec_ttl = 0;
|
|
ipsec_xform->options.ecn = 1;
|
|
ipsec_xform->options.stats = 0;
|
|
ipsec_xform->options.iv_gen_disable = 0;
|
|
ipsec_xform->options.tunnel_hdr_verify = 0;
|
|
ipsec_xform->options.udp_ports_verify = 0;
|
|
ipsec_xform->options.ip_csum_enable = 0;
|
|
ipsec_xform->options.l4_csum_enable = 0;
|
|
ipsec_xform->options.ip_reassembly_en = 0;
|
|
ipsec_xform->options.reserved_opts = 0;
|
|
|
|
ipsec_xform->direction = p->encrypt ?
|
|
RTE_SECURITY_IPSEC_SA_DIR_EGRESS :
|
|
RTE_SECURITY_IPSEC_SA_DIR_INGRESS;
|
|
|
|
ipsec_xform->proto = RTE_SECURITY_IPSEC_SA_PROTO_ESP;
|
|
|
|
ipsec_xform->mode = p->encap.tunnel_mode ?
|
|
RTE_SECURITY_IPSEC_SA_MODE_TUNNEL :
|
|
RTE_SECURITY_IPSEC_SA_MODE_TRANSPORT;
|
|
|
|
ipsec_xform->tunnel.type = p->encap.tunnel_ipv4 ?
|
|
RTE_SECURITY_IPSEC_TUNNEL_IPV4 :
|
|
RTE_SECURITY_IPSEC_TUNNEL_IPV6;
|
|
|
|
if (p->encap.tunnel_mode) {
|
|
if (p->encap.tunnel_ipv4) {
|
|
ipsec_xform->tunnel.ipv4.src_ip = p->encap.tunnel.ipv4.src_addr;
|
|
ipsec_xform->tunnel.ipv4.dst_ip = p->encap.tunnel.ipv4.dst_addr;
|
|
ipsec_xform->tunnel.ipv4.dscp = 0;
|
|
ipsec_xform->tunnel.ipv4.df = 0;
|
|
ipsec_xform->tunnel.ipv4.ttl = 64;
|
|
} else {
|
|
ipsec_xform->tunnel.ipv6.src_addr = p->encap.tunnel.ipv6.src_addr;
|
|
ipsec_xform->tunnel.ipv6.dst_addr = p->encap.tunnel.ipv6.dst_addr;
|
|
ipsec_xform->tunnel.ipv6.dscp = 0;
|
|
ipsec_xform->tunnel.ipv6.flabel = 0;
|
|
ipsec_xform->tunnel.ipv6.hlimit = 64;
|
|
}
|
|
}
|
|
|
|
ipsec_xform->life.packets_soft_limit = 0;
|
|
ipsec_xform->life.bytes_soft_limit = 0;
|
|
ipsec_xform->life.packets_hard_limit = 0;
|
|
ipsec_xform->life.bytes_hard_limit = 0;
|
|
|
|
ipsec_xform->replay_win_sz = 0;
|
|
|
|
ipsec_xform->esn.value = 0;
|
|
|
|
ipsec_xform->udp.dport = 0;
|
|
ipsec_xform->udp.sport = 0;
|
|
}
|
|
|
|
static int
|
|
ipsec_sa_prm_get(struct rte_swx_ipsec_sa_params *p,
|
|
struct rte_ipsec_sa_prm *sa_prm,
|
|
struct rte_ipv4_hdr *ipv4_hdr,
|
|
struct rte_ipv6_hdr *ipv6_hdr,
|
|
struct rte_crypto_sym_xform *crypto_xform)
|
|
{
|
|
uint32_t salt;
|
|
|
|
memset(sa_prm, 0, sizeof(*sa_prm)); /* Better to be safe than sorry. */
|
|
|
|
sa_prm->userdata = 0; /* Not used. */
|
|
|
|
sa_prm->flags = 0; /* Flag RTE_IPSEC_SAFLAG_SQN_ATOM not enabled. */
|
|
|
|
/*
|
|
* crypto_xform.
|
|
*/
|
|
sa_prm->crypto_xform = crypto_xform_get(p, crypto_xform, &salt);
|
|
if (!sa_prm->crypto_xform)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* ipsec_xform.
|
|
*/
|
|
ipsec_xform_get(p, &sa_prm->ipsec_xform, salt);
|
|
|
|
/*
|
|
* tunnel / transport.
|
|
*
|
|
* Currently, the input IP packet type is assumed to be IPv4. To support both IPv4 and IPv6,
|
|
* the input packet type should be added to the SA configuration parameters.
|
|
*/
|
|
if (p->encap.tunnel_mode) {
|
|
if (p->encap.tunnel_ipv4) {
|
|
sa_prm->tun.hdr_len = sizeof(struct rte_ipv4_hdr);
|
|
sa_prm->tun.hdr_l3_off = 0;
|
|
sa_prm->tun.next_proto = IPPROTO_IPIP; /* IPv4. */
|
|
sa_prm->tun.hdr = ipv4_hdr;
|
|
} else {
|
|
sa_prm->tun.hdr_len = sizeof(struct rte_ipv6_hdr);
|
|
sa_prm->tun.hdr_l3_off = 0;
|
|
sa_prm->tun.next_proto = IPPROTO_IPIP; /* IPv4. */
|
|
sa_prm->tun.hdr = ipv6_hdr;
|
|
}
|
|
} else {
|
|
sa_prm->trs.proto = IPPROTO_IPIP; /* IPv4. */
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ipsec_session_create(struct rte_swx_ipsec *ipsec,
|
|
struct rte_swx_ipsec_sa_params *p,
|
|
struct rte_ipsec_session *s)
|
|
{
|
|
struct rte_ipv4_hdr ipv4_hdr;
|
|
struct rte_ipv6_hdr ipv6_hdr;
|
|
struct rte_crypto_sym_xform crypto_xform[2];
|
|
struct rte_ipsec_sa_prm sa_prm;
|
|
struct rte_ipsec_sa *sa = NULL;
|
|
struct rte_cryptodev_sym_session *crypto_session = NULL;
|
|
int sa_size;
|
|
int sa_valid = 0, status = 0;
|
|
|
|
tunnel_ipv4_header_set(&ipv4_hdr, p);
|
|
tunnel_ipv6_header_set(&ipv6_hdr, p);
|
|
|
|
/* IPsec library SA setup. */
|
|
status = ipsec_sa_prm_get(p, &sa_prm, &ipv4_hdr, &ipv6_hdr, crypto_xform);
|
|
if (status)
|
|
goto error;
|
|
|
|
sa_size = rte_ipsec_sa_size(&sa_prm);
|
|
if (sa_size < 0) {
|
|
status = sa_size;
|
|
goto error;
|
|
}
|
|
if (!sa_size) {
|
|
status = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
sa = calloc(1, sa_size);
|
|
if (!sa) {
|
|
status = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
sa_size = rte_ipsec_sa_init(sa, &sa_prm, sa_size);
|
|
if (sa_size < 0) {
|
|
status = sa_size;
|
|
goto error;
|
|
}
|
|
if (!sa_size) {
|
|
status = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
sa_valid = 1;
|
|
|
|
/* Cryptodev library session setup. */
|
|
crypto_session = rte_cryptodev_sym_session_create(ipsec->dev_id,
|
|
sa_prm.crypto_xform,
|
|
ipsec->mp_session);
|
|
if (!crypto_session) {
|
|
status = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
/* IPsec library session setup. */
|
|
s->sa = sa;
|
|
s->type = RTE_SECURITY_ACTION_TYPE_NONE;
|
|
s->crypto.ses = crypto_session;
|
|
s->crypto.dev_id = ipsec->dev_id;
|
|
s->pkt_func.prepare.async = NULL;
|
|
s->pkt_func.process = NULL;
|
|
|
|
status = rte_ipsec_session_prepare(s);
|
|
if (status)
|
|
goto error;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
/* sa. */
|
|
if (sa_valid)
|
|
rte_ipsec_sa_fini(sa);
|
|
|
|
free(sa);
|
|
|
|
/* crypto_session. */
|
|
if (crypto_session)
|
|
rte_cryptodev_sym_session_free(ipsec->dev_id, crypto_session);
|
|
|
|
/* s. */
|
|
memset(s, 0, sizeof(*s));
|
|
|
|
return status;
|
|
}
|
|
|
|
static void
|
|
ipsec_session_free(struct rte_swx_ipsec *ipsec,
|
|
struct rte_ipsec_session *s)
|
|
{
|
|
if (!s)
|
|
return;
|
|
|
|
/* IPsec library SA. */
|
|
if (s->sa)
|
|
rte_ipsec_sa_fini(s->sa);
|
|
free(s->sa);
|
|
|
|
/* Cryptodev library session. */
|
|
if (s->crypto.ses)
|
|
rte_cryptodev_sym_session_free(ipsec->dev_id, s->crypto.ses);
|
|
|
|
/* IPsec library session. */
|
|
memset(s, 0, sizeof(*s));
|
|
}
|
|
|
|
int
|
|
rte_swx_ipsec_sa_add(struct rte_swx_ipsec *ipsec,
|
|
struct rte_swx_ipsec_sa_params *sa_params,
|
|
uint32_t *id)
|
|
{
|
|
struct ipsec_sa *sa;
|
|
uint32_t sa_id;
|
|
int status;
|
|
|
|
/* Check the input parameters. */
|
|
if (!ipsec || !sa_params || !id)
|
|
return -EINVAL;
|
|
|
|
/* Allocate a free SADB entry. */
|
|
if (!ipsec->n_sa_free_id)
|
|
return -ENOSPC;
|
|
|
|
sa_id = ipsec->sa_free_id[ipsec->n_sa_free_id - 1];
|
|
ipsec->n_sa_free_id--;
|
|
|
|
/* Acquire the SA resources. */
|
|
sa = ipsec_sa_get(ipsec, sa_id);
|
|
|
|
status = ipsec_session_create(ipsec, sa_params, &sa->s);
|
|
if (status) {
|
|
/* Free the allocated SADB entry. */
|
|
ipsec->sa_free_id[ipsec->n_sa_free_id] = sa_id;
|
|
ipsec->n_sa_free_id++;
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Validate the new SA. */
|
|
sa->valid = 1;
|
|
*id = sa_id;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
rte_swx_ipsec_sa_delete(struct rte_swx_ipsec *ipsec,
|
|
uint32_t sa_id)
|
|
{
|
|
struct ipsec_sa *sa;
|
|
|
|
/* Check the input parameters. */
|
|
if (!ipsec || (sa_id >= ipsec->n_sa_max))
|
|
return;
|
|
|
|
/* Release the SA resources. */
|
|
sa = ipsec_sa_get(ipsec, sa_id);
|
|
|
|
ipsec_session_free(ipsec, &sa->s);
|
|
|
|
/* Free the SADB entry. */
|
|
ipsec->sa_free_id[ipsec->n_sa_free_id] = sa_id;
|
|
ipsec->n_sa_free_id++;
|
|
|
|
/* Invalidate the SA. */
|
|
sa->valid = 0;
|
|
}
|