app/test-sad: add test application for IPsec SAD

Introduce new application to provide user to evaluate and perform
custom functional and performance tests for IPsec SAD implementation.

According to our measurements on SKX for 1M entries average lookup
cost is ~80 cycles, average add cost ~500 cycles.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Akhil Goyal <akhil.goyal@nxp.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
Tested-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
This commit is contained in:
Vladimir Medvedkin 2019-10-21 15:35:46 +01:00 committed by Akhil Goyal
parent 48083b4d67
commit 908be0651a
7 changed files with 698 additions and 2 deletions

View File

@ -1198,6 +1198,7 @@ F: app/test/test_ipsec.c
F: doc/guides/prog_guide/ipsec_lib.rst
M: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
F: app/test/test_ipsec_sad.c
F: app/test-sad/
Flow Classify - EXPERIMENTAL
M: Bernard Iremonger <bernard.iremonger@intel.com>

View File

@ -10,6 +10,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_PDUMP) += pdump
DIRS-$(CONFIG_RTE_LIBRTE_ACL) += test-acl
DIRS-$(CONFIG_RTE_LIBRTE_CMDLINE) += test-cmdline
DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += test-pipeline
DIRS-$(CONFIG_RTE_LIBRTE_IPSEC) += test-sad
ifeq ($(CONFIG_RTE_LIBRTE_BBDEV),y)
DIRS-$(CONFIG_RTE_TEST_BBDEV) += test-bbdev

View File

@ -15,7 +15,8 @@ apps = [
'test-crypto-perf',
'test-eventdev',
'test-pipeline',
'test-pmd']
'test-pmd',
'test-sad']
# for BSD only
lib_execinfo = cc.find_library('execinfo', required: false)

18
app/test-sad/Makefile Normal file
View File

@ -0,0 +1,18 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
include $(RTE_SDK)/mk/rte.vars.mk
ifeq ($(CONFIG_RTE_LIBRTE_IPSEC),y)
APP = testsad
CFLAGS += $(WERROR_FLAGS)
CFLAGS += -DALLOW_EXPERIMENTAL_API
# all source are stored in SRCS-y
SRCS-y := main.c
include $(RTE_SDK)/mk/rte.app.mk
endif

667
app/test-sad/main.c Normal file
View File

@ -0,0 +1,667 @@
/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2019 Intel Corporation
*/
#include <rte_string_fns.h>
#include <rte_ipsec_sad.h>
#include <getopt.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rte_cycles.h>
#include <rte_errno.h>
#include <rte_ip.h>
#include <rte_random.h>
#include <rte_malloc.h>
#define PRINT_USAGE_START "%s [EAL options] --\n"
#define GET_CB_FIELD(in, fd, base, lim, dlm) do { \
unsigned long val; \
char *end_fld; \
errno = 0; \
val = strtoul((in), &end_fld, (base)); \
if (errno != 0 || end_fld[0] != (dlm) || val > (lim)) \
return -EINVAL; \
(fd) = (typeof(fd))val; \
(in) = end_fld + 1; \
} while (0)
#define DEF_RULE_NUM 0x10000
#define DEF_TUPLES_NUM 0x100000
#define BURST_SZ_MAX 64
static struct {
const char *prgname;
const char *rules_file;
const char *tuples_file;
uint32_t nb_rules;
uint32_t nb_tuples;
uint32_t nb_rules_32;
uint32_t nb_rules_64;
uint32_t nb_rules_96;
uint32_t nb_tuples_rnd;
uint32_t burst_sz;
uint8_t fract_32;
uint8_t fract_64;
uint8_t fract_96;
uint8_t fract_rnd_tuples;
int ipv6;
int verbose;
int parallel_lookup;
int concurrent_rw;
} config = {
.rules_file = NULL,
.tuples_file = NULL,
.nb_rules = DEF_RULE_NUM,
.nb_tuples = DEF_TUPLES_NUM,
.nb_rules_32 = 0,
.nb_rules_64 = 0,
.nb_rules_96 = 0,
.nb_tuples_rnd = 0,
.burst_sz = BURST_SZ_MAX,
.fract_32 = 90,
.fract_64 = 9,
.fract_96 = 1,
.fract_rnd_tuples = 0,
.ipv6 = 0,
.verbose = 0,
.parallel_lookup = 0,
.concurrent_rw = 0
};
enum {
CB_RULE_SPI,
CB_RULE_DIP,
CB_RULE_SIP,
CB_RULE_LEN,
CB_RULE_NUM,
};
static char line[LINE_MAX];
struct rule {
union rte_ipsec_sad_key tuple;
int rule_type;
};
static struct rule *rules_tbl;
static struct rule *tuples_tbl;
static int
parse_distrib(const char *in)
{
int a, b, c;
GET_CB_FIELD(in, a, 0, UINT8_MAX, '/');
GET_CB_FIELD(in, b, 0, UINT8_MAX, '/');
GET_CB_FIELD(in, c, 0, UINT8_MAX, 0);
if ((a + b + c) != 100)
return -EINVAL;
config.fract_32 = a;
config.fract_64 = b;
config.fract_96 = c;
return 0;
}
static void
print_config(void)
{
fprintf(stdout,
"Rules total: %u\n"
"Configured rules distribution SPI/SPI_DIP/SIP_DIP_SIP:"
"%u/%u/%u\n"
"SPI only rules: %u\n"
"SPI_DIP rules: %u\n"
"SPI_DIP_SIP rules: %u\n"
"Lookup tuples: %u\n"
"Lookup burst size %u\n"
"Configured fraction of random tuples: %u\n"
"Random lookup tuples: %u\n",
config.nb_rules, config.fract_32, config.fract_64,
config.fract_96, config.nb_rules_32, config.nb_rules_64,
config.nb_rules_96, config.nb_tuples, config.burst_sz,
config.fract_rnd_tuples, config.nb_tuples_rnd);
}
static void
print_usage(void)
{
fprintf(stdout,
PRINT_USAGE_START
"[-f <rules file>]\n"
"[-t <tuples file for lookup>]\n"
"[-n <rules number (if -f is not specified)>]\n"
"[-l <lookup tuples number (if -t is not specified)>]\n"
"[-6 <ipv6 tests>]\n"
"[-d <\"/\" separated rules length distribution"
"(if -f is not specified)>]\n"
"[-r <random tuples fraction to lookup"
"(if -t is not specified)>]\n"
"[-b <lookup burst size: 1-64 >]\n"
"[-v <verbose, print results on lookup>]\n"
"[-p <parallel lookup on all available cores>]\n"
"[-c <init sad supporting read/write concurrency>]\n",
config.prgname);
}
static int
get_str_num(FILE *f, int num)
{
int n_lines = 0;
if (f != NULL) {
while (fgets(line, sizeof(line), f) != NULL)
n_lines++;
rewind(f);
} else {
n_lines = num;
}
return n_lines;
}
static int
parse_file(FILE *f, struct rule *tbl, int rule_tbl)
{
int ret, i, j = 0;
char *s, *sp, *in[CB_RULE_NUM];
static const char *dlm = " \t\n";
int string_tok_nb = RTE_DIM(in);
string_tok_nb -= (rule_tbl == 0) ? 1 : 0;
while (fgets(line, sizeof(line), f) != NULL) {
s = line;
for (i = 0; i != string_tok_nb; i++) {
in[i] = strtok_r(s, dlm, &sp);
if (in[i] == NULL)
return -EINVAL;
s = NULL;
}
GET_CB_FIELD(in[CB_RULE_SPI], tbl[j].tuple.v4.spi, 0,
UINT32_MAX, 0);
if (config.ipv6)
ret = inet_pton(AF_INET6, in[CB_RULE_DIP],
&tbl[j].tuple.v6.dip);
else
ret = inet_pton(AF_INET, in[CB_RULE_DIP],
&tbl[j].tuple.v4.dip);
if (ret != 1)
return -EINVAL;
if (config.ipv6)
ret = inet_pton(AF_INET6, in[CB_RULE_SIP],
&tbl[j].tuple.v6.sip);
else
ret = inet_pton(AF_INET, in[CB_RULE_SIP],
&tbl[j].tuple.v4.sip);
if (ret != 1)
return -EINVAL;
if ((rule_tbl) && (in[CB_RULE_LEN] != NULL)) {
if (strcmp(in[CB_RULE_LEN], "SPI_DIP_SIP") == 0) {
tbl[j].rule_type = RTE_IPSEC_SAD_SPI_DIP_SIP;
config.nb_rules_96++;
} else if (strcmp(in[CB_RULE_LEN], "SPI_DIP") == 0) {
tbl[j].rule_type = RTE_IPSEC_SAD_SPI_DIP;
config.nb_rules_64++;
} else if (strcmp(in[CB_RULE_LEN], "SPI") == 0) {
tbl[j].rule_type = RTE_IPSEC_SAD_SPI_ONLY;
config.nb_rules_32++;
} else {
return -EINVAL;
}
}
j++;
}
return 0;
}
static uint64_t
get_rnd_rng(uint64_t l, uint64_t u)
{
if (l == u)
return l;
else
return (rte_rand() % (u - l) + l);
}
static void
get_random_rules(struct rule *tbl, uint32_t nb_rules, int rule_tbl)
{
unsigned int i, j, rnd;
int rule_type;
double edge = 0;
double step;
step = (double)UINT32_MAX / nb_rules;
for (i = 0; i < nb_rules; i++, edge += step) {
rnd = rte_rand() % 100;
if (rule_tbl) {
tbl[i].tuple.v4.spi = get_rnd_rng((uint64_t)edge,
(uint64_t)(edge + step));
if (config.ipv6) {
for (j = 0; j < 16; j++) {
tbl[i].tuple.v6.dip[j] = rte_rand();
tbl[i].tuple.v6.sip[j] = rte_rand();
}
} else {
tbl[i].tuple.v4.dip = rte_rand();
tbl[i].tuple.v4.sip = rte_rand();
}
if (rnd >= (100UL - config.fract_32)) {
rule_type = RTE_IPSEC_SAD_SPI_ONLY;
config.nb_rules_32++;
} else if (rnd >= (100UL - (config.fract_32 +
config.fract_64))) {
rule_type = RTE_IPSEC_SAD_SPI_DIP;
config.nb_rules_64++;
} else {
rule_type = RTE_IPSEC_SAD_SPI_DIP_SIP;
config.nb_rules_96++;
}
tbl[i].rule_type = rule_type;
} else {
if (rnd >= 100UL - config.fract_rnd_tuples) {
tbl[i].tuple.v4.spi =
get_rnd_rng((uint64_t)edge,
(uint64_t)(edge + step));
if (config.ipv6) {
for (j = 0; j < 16; j++) {
tbl[i].tuple.v6.dip[j] =
rte_rand();
tbl[i].tuple.v6.sip[j] =
rte_rand();
}
} else {
tbl[i].tuple.v4.dip = rte_rand();
tbl[i].tuple.v4.sip = rte_rand();
}
config.nb_tuples_rnd++;
} else {
tbl[i].tuple.v4.spi = rules_tbl[i %
config.nb_rules].tuple.v4.spi;
if (config.ipv6) {
int r_idx = i % config.nb_rules;
memcpy(tbl[i].tuple.v6.dip,
rules_tbl[r_idx].tuple.v6.dip,
sizeof(tbl[i].tuple.v6.dip));
memcpy(tbl[i].tuple.v6.sip,
rules_tbl[r_idx].tuple.v6.sip,
sizeof(tbl[i].tuple.v6.sip));
} else {
tbl[i].tuple.v4.dip = rules_tbl[i %
config.nb_rules].tuple.v4.dip;
tbl[i].tuple.v4.sip = rules_tbl[i %
config.nb_rules].tuple.v4.sip;
}
}
}
}
}
static void
tbl_init(struct rule **tbl, uint32_t *n_entries,
const char *file_name, int rule_tbl)
{
FILE *f = NULL;
int ret;
const char *rules = "rules";
const char *tuples = "tuples";
if (file_name != NULL) {
f = fopen(file_name, "r");
if (f == NULL)
rte_exit(-EINVAL, "failed to open file: %s\n",
file_name);
}
printf("init %s table...", (rule_tbl) ? rules : tuples);
*n_entries = get_str_num(f, *n_entries);
printf("%d entries\n", *n_entries);
*tbl = rte_zmalloc(NULL, sizeof(struct rule) * *n_entries,
RTE_CACHE_LINE_SIZE);
if (*tbl == NULL)
rte_exit(-ENOMEM, "failed to allocate tbl\n");
if (f != NULL) {
printf("parse file %s\n", file_name);
ret = parse_file(f, *tbl, rule_tbl);
if (ret != 0)
rte_exit(-EINVAL, "failed to parse file %s\n"
"rules file must be: "
"<uint32_t: spi> <space> "
"<ip_addr: dip> <space> "
"<ip_addr: sip> <space> "
"<string: SPI|SPI_DIP|SIP_DIP_SIP>\n"
"tuples file must be: "
"<uint32_t: spi> <space> "
"<ip_addr: dip> <space> "
"<ip_addr: sip>\n",
file_name);
} else {
printf("generate random values in %s table\n",
(rule_tbl) ? rules : tuples);
get_random_rules(*tbl, *n_entries, rule_tbl);
}
if (f != NULL)
fclose(f);
}
static void
parse_opts(int argc, char **argv)
{
int opt, ret;
char *endptr;
while ((opt = getopt(argc, argv, "f:t:n:d:l:r:6b:vpc")) != -1) {
switch (opt) {
case 'f':
config.rules_file = optarg;
break;
case 't':
config.tuples_file = optarg;
break;
case 'n':
errno = 0;
config.nb_rules = strtoul(optarg, &endptr, 10);
if ((errno != 0) || (config.nb_rules == 0) ||
(endptr[0] != 0)) {
print_usage();
rte_exit(-EINVAL, "Invalid option -n\n");
}
break;
case 'd':
ret = parse_distrib(optarg);
if (ret != 0) {
print_usage();
rte_exit(-EINVAL, "Invalid option -d\n");
}
break;
case 'b':
errno = 0;
config.burst_sz = strtoul(optarg, &endptr, 10);
if ((errno != 0) || (config.burst_sz == 0) ||
(config.burst_sz > BURST_SZ_MAX) ||
(endptr[0] != 0)) {
print_usage();
rte_exit(-EINVAL, "Invalid option -b\n");
}
break;
case 'l':
errno = 0;
config.nb_tuples = strtoul(optarg, &endptr, 10);
if ((errno != 0) || (config.nb_tuples == 0) ||
(endptr[0] != 0)) {
print_usage();
rte_exit(-EINVAL, "Invalid option -l\n");
}
break;
case 'r':
errno = 0;
config.fract_rnd_tuples = strtoul(optarg, &endptr, 10);
if ((errno != 0) || (config.fract_rnd_tuples == 0) ||
(config.fract_rnd_tuples >= 100) ||
(endptr[0] != 0)) {
print_usage();
rte_exit(-EINVAL, "Invalid option -r\n");
}
break;
case '6':
config.ipv6 = 1;
break;
case 'v':
config.verbose = 1;
break;
case 'p':
config.parallel_lookup = 1;
break;
case 'c':
config.concurrent_rw = 1;
break;
default:
print_usage();
rte_exit(-EINVAL, "Invalid options\n");
}
}
}
static void
print_addr(int af, const void *addr)
{
char str[INET6_ADDRSTRLEN];
const char *ret;
ret = inet_ntop(af, addr, str, sizeof(str));
if (ret != NULL)
printf("%s", str);
}
static void
print_tuple(int af, uint32_t spi, const void *dip, const void *sip)
{
printf("<SPI: %u DIP: ", spi);
print_addr(af, dip);
printf(" SIP: ");
print_addr(af, sip);
printf(">");
}
static void
print_result(const union rte_ipsec_sad_key *key, void *res)
{
struct rule *rule = res;
const struct rte_ipsec_sadv4_key *v4;
const struct rte_ipsec_sadv6_key *v6;
const char *spi_only = "SPI_ONLY";
const char *spi_dip = "SPI_DIP";
const char *spi_dip_sip = "SPI_DIP_SIP";
const char *rule_type;
const void *dip, *sip;
uint32_t spi;
int af;
af = (config.ipv6) ? AF_INET6 : AF_INET;
v4 = &key->v4;
v6 = &key->v6;
spi = (config.ipv6 == 0) ? v4->spi : v6->spi;
dip = (config.ipv6 == 0) ? &v4->dip : (const void *)v6->dip;
sip = (config.ipv6 == 0) ? &v4->sip : (const void *)v6->sip;
if (res == NULL) {
printf("TUPLE: ");
print_tuple(af, spi, dip, sip);
printf(" not found\n");
return;
}
switch (rule->rule_type) {
case RTE_IPSEC_SAD_SPI_ONLY:
rule_type = spi_only;
break;
case RTE_IPSEC_SAD_SPI_DIP:
rule_type = spi_dip;
break;
case RTE_IPSEC_SAD_SPI_DIP_SIP:
rule_type = spi_dip_sip;
break;
default:
return;
}
print_tuple(af, spi, dip, sip);
v4 = &rule->tuple.v4;
v6 = &rule->tuple.v6;
spi = (config.ipv6 == 0) ? v4->spi : v6->spi;
dip = (config.ipv6 == 0) ? &v4->dip : (const void *)v6->dip;
sip = (config.ipv6 == 0) ? &v4->sip : (const void *)v6->sip;
printf("\n\tpoints to RULE ID %zu ",
RTE_PTR_DIFF(res, rules_tbl)/sizeof(struct rule));
print_tuple(af, spi, dip, sip);
printf(" %s\n", rule_type);
}
static int
lookup(void *arg)
{
int ret;
unsigned int i, j;
const union rte_ipsec_sad_key *keys[BURST_SZ_MAX];
void *vals[BURST_SZ_MAX];
uint64_t start, acc = 0;
uint32_t burst_sz;
struct rte_ipsec_sad *sad = arg;
burst_sz = RTE_MIN(config.burst_sz, config.nb_tuples);
for (i = 0; i < config.nb_tuples; i += burst_sz) {
for (j = 0; j < burst_sz; j++)
keys[j] = (union rte_ipsec_sad_key *)
(&tuples_tbl[i + j].tuple);
start = rte_rdtsc_precise();
ret = rte_ipsec_sad_lookup(sad, keys, vals, burst_sz);
acc += rte_rdtsc_precise() - start;
if (ret < 0)
rte_exit(-EINVAL, "Lookup failed\n");
if (config.verbose) {
for (j = 0; j < burst_sz; j++)
print_result(keys[j], vals[j]);
}
}
printf("Average lookup cycles %.2Lf, lookups/sec: %.2Lf\n",
(long double)acc / config.nb_tuples,
(long double)config.nb_tuples * rte_get_tsc_hz() / acc);
return 0;
}
static void
add_rules(struct rte_ipsec_sad *sad, uint32_t fract)
{
int32_t ret;
uint32_t i, j, f, fn, n;
uint64_t start, tm[fract + 1];
uint32_t nm[fract + 1];
f = (config.nb_rules > fract) ? config.nb_rules / fract : 1;
for (n = 0, j = 0; n != config.nb_rules; n = fn, j++) {
fn = n + f;
fn = fn > config.nb_rules ? config.nb_rules : fn;
start = rte_rdtsc_precise();
for (i = n; i != fn; i++) {
ret = rte_ipsec_sad_add(sad,
&rules_tbl[i].tuple,
rules_tbl[i].rule_type, &rules_tbl[i]);
if (ret != 0)
rte_exit(ret, "%s failed @ %u-th rule\n",
__func__, i);
}
tm[j] = rte_rdtsc_precise() - start;
nm[j] = fn - n;
}
for (i = 0; i != j; i++)
printf("ADD %u rules, %.2Lf cycles/rule, %.2Lf ADD/sec\n",
nm[i], (long double)tm[i] / nm[i],
(long double)nm[i] * rte_get_tsc_hz() / tm[i]);
}
static void
del_rules(struct rte_ipsec_sad *sad, uint32_t fract)
{
int32_t ret;
uint32_t i, j, f, fn, n;
uint64_t start, tm[fract + 1];
uint32_t nm[fract + 1];
f = (config.nb_rules > fract) ? config.nb_rules / fract : 1;
for (n = 0, j = 0; n != config.nb_rules; n = fn, j++) {
fn = n + f;
fn = fn > config.nb_rules ? config.nb_rules : fn;
start = rte_rdtsc_precise();
for (i = n; i != fn; i++) {
ret = rte_ipsec_sad_del(sad,
&rules_tbl[i].tuple,
rules_tbl[i].rule_type);
if (ret != 0 && ret != -ENOENT)
rte_exit(ret, "%s failed @ %u-th rule\n",
__func__, i);
}
tm[j] = rte_rdtsc_precise() - start;
nm[j] = fn - n;
}
for (i = 0; i != j; i++)
printf("DEL %u rules, %.2Lf cycles/rule, %.2Lf DEL/sec\n",
nm[i], (long double)tm[i] / nm[i],
(long double)nm[i] * rte_get_tsc_hz() / tm[i]);
}
int
main(int argc, char **argv)
{
int ret;
struct rte_ipsec_sad *sad;
struct rte_ipsec_sad_conf conf;
unsigned int lcore_id;
ret = rte_eal_init(argc, argv);
if (ret < 0)
rte_panic("Cannot init EAL\n");
argc -= ret;
argv += ret;
config.prgname = argv[0];
parse_opts(argc, argv);
tbl_init(&rules_tbl, &config.nb_rules, config.rules_file, 1);
tbl_init(&tuples_tbl, &config.nb_tuples, config.tuples_file, 0);
if (config.rules_file != NULL) {
config.fract_32 = (100 * config.nb_rules_32) / config.nb_rules;
config.fract_64 = (100 * config.nb_rules_64) / config.nb_rules;
config.fract_96 = (100 * config.nb_rules_96) / config.nb_rules;
}
if (config.tuples_file != NULL) {
config.fract_rnd_tuples = 0;
config.nb_tuples_rnd = 0;
}
conf.socket_id = -1;
conf.max_sa[RTE_IPSEC_SAD_SPI_ONLY] = config.nb_rules_32 * 5 / 4;
conf.max_sa[RTE_IPSEC_SAD_SPI_DIP] = config.nb_rules_64 * 5 / 4;
conf.max_sa[RTE_IPSEC_SAD_SPI_DIP_SIP] = config.nb_rules_96 * 5 / 4;
if (config.ipv6)
conf.flags |= RTE_IPSEC_SAD_FLAG_IPV6;
if (config.concurrent_rw)
conf.flags |= RTE_IPSEC_SAD_FLAG_RW_CONCURRENCY;
sad = rte_ipsec_sad_create("test", &conf);
if (sad == NULL)
rte_exit(-rte_errno, "can not allocate SAD table\n");
print_config();
add_rules(sad, 10);
if (config.parallel_lookup)
rte_eal_mp_remote_launch(lookup, sad, SKIP_MASTER);
lookup(sad);
if (config.parallel_lookup)
RTE_LCORE_FOREACH_SLAVE(lcore_id)
if (rte_eal_wait_lcore(lcore_id) < 0)
return -1;
del_rules(sad, 10);
return 0;
}

6
app/test-sad/meson.build Normal file
View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2019 Intel Corporation
allow_experimental_apis = true
sources = files('main.c')
deps += ['ipsec', 'net']

View File

@ -138,7 +138,9 @@ New Features
* **Updated the IPSec library.**
Added SA Database API to ``librte_ipsec``.
Added SA Database API to ``librte_ipsec``. A new test-sad application is also
introduced to evaluate and perform custom functional and performance tests
for IPsec SAD implementation.
* **Introduced FIFO for NTB PMD.**