numam-dpdk/examples/ipsec-secgw/sp4.c
Vladimir Medvedkin b0806375ae examples/ipsec-secgw: get rid of maximum SP limitation
Get rid of maximum SP limitation.
Keep parsed SP's into the sorted by SPI value array.
Use binary search in the sorted SP array to find appropriate SP
for a given SPI.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
Acked-by: Akhil Goyal <akhil.goyal@nxp.com>
Acked-by: Anoob Joseph <anoobj@marvell.com>
2020-02-05 15:20:51 +01:00

649 lines
15 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause
* Copyright(c) 2016 Intel Corporation
*/
/*
* Security Policies
*/
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <rte_acl.h>
#include <rte_ip.h>
#include "ipsec.h"
#include "parser.h"
#define INIT_ACL_RULE_NUM 128
#define IPV4_DST_FROM_SP(acr) \
(rte_cpu_to_be_32((acr).field[DST_FIELD_IPV4].value.u32))
#define IPV4_SRC_FROM_SP(acr) \
(rte_cpu_to_be_32((acr).field[SRC_FIELD_IPV4].value.u32))
#define IPV4_DST_MASK_FROM_SP(acr) \
((acr).field[DST_FIELD_IPV4].mask_range.u32)
#define IPV4_SRC_MASK_FROM_SP(acr) \
((acr).field[SRC_FIELD_IPV4].mask_range.u32)
/*
* Rule and trace formats definitions.
*/
enum {
PROTO_FIELD_IPV4,
SRC_FIELD_IPV4,
DST_FIELD_IPV4,
SRCP_FIELD_IPV4,
DSTP_FIELD_IPV4,
NUM_FIELDS_IPV4
};
/*
* That effectively defines order of IPV4 classifications:
* - PROTO
* - SRC IP ADDRESS
* - DST IP ADDRESS
* - PORTS (SRC and DST)
*/
enum {
RTE_ACL_IPV4_PROTO,
RTE_ACL_IPV4_SRC,
RTE_ACL_IPV4_DST,
RTE_ACL_IPV4_PORTS,
RTE_ACL_IPV4_NUM
};
static struct rte_acl_field_def ip4_defs[NUM_FIELDS_IPV4] = {
{
.type = RTE_ACL_FIELD_TYPE_BITMASK,
.size = sizeof(uint8_t),
.field_index = PROTO_FIELD_IPV4,
.input_index = RTE_ACL_IPV4_PROTO,
.offset = 0,
},
{
.type = RTE_ACL_FIELD_TYPE_MASK,
.size = sizeof(uint32_t),
.field_index = SRC_FIELD_IPV4,
.input_index = RTE_ACL_IPV4_SRC,
.offset = offsetof(struct ip, ip_src) - offsetof(struct ip, ip_p)
},
{
.type = RTE_ACL_FIELD_TYPE_MASK,
.size = sizeof(uint32_t),
.field_index = DST_FIELD_IPV4,
.input_index = RTE_ACL_IPV4_DST,
.offset = offsetof(struct ip, ip_dst) - offsetof(struct ip, ip_p)
},
{
.type = RTE_ACL_FIELD_TYPE_RANGE,
.size = sizeof(uint16_t),
.field_index = SRCP_FIELD_IPV4,
.input_index = RTE_ACL_IPV4_PORTS,
.offset = sizeof(struct ip) - offsetof(struct ip, ip_p)
},
{
.type = RTE_ACL_FIELD_TYPE_RANGE,
.size = sizeof(uint16_t),
.field_index = DSTP_FIELD_IPV4,
.input_index = RTE_ACL_IPV4_PORTS,
.offset = sizeof(struct ip) - offsetof(struct ip, ip_p) +
sizeof(uint16_t)
},
};
RTE_ACL_RULE_DEF(acl4_rules, RTE_DIM(ip4_defs));
static struct acl4_rules *acl4_rules_out;
static uint32_t nb_acl4_rules_out;
static uint32_t sp_out_sz;
static struct acl4_rules *acl4_rules_in;
static uint32_t nb_acl4_rules_in;
static uint32_t sp_in_sz;
static int
extend_sp_arr(struct acl4_rules **sp_tbl, uint32_t cur_cnt, uint32_t *cur_sz)
{
if (*sp_tbl == NULL) {
*sp_tbl = calloc(INIT_ACL_RULE_NUM, sizeof(struct acl4_rules));
if (*sp_tbl == NULL)
return -1;
*cur_sz = INIT_ACL_RULE_NUM;
return 0;
}
if (cur_cnt >= *cur_sz) {
*sp_tbl = realloc(*sp_tbl,
*cur_sz * sizeof(struct acl4_rules) * 2);
if (*sp_tbl == NULL)
return -1;
/* clean reallocated extra space */
memset(&(*sp_tbl)[*cur_sz], 0,
*cur_sz * sizeof(struct acl4_rules));
*cur_sz *= 2;
}
return 0;
}
void
parse_sp4_tokens(char **tokens, uint32_t n_tokens,
struct parse_status *status)
{
struct acl4_rules *rule_ipv4 = NULL;
uint32_t *ri = NULL; /* rule index */
uint32_t ti = 0; /* token index */
uint32_t tv;
uint32_t esp_p = 0;
uint32_t protect_p = 0;
uint32_t bypass_p = 0;
uint32_t discard_p = 0;
uint32_t pri_p = 0;
uint32_t src_p = 0;
uint32_t dst_p = 0;
uint32_t proto_p = 0;
uint32_t sport_p = 0;
uint32_t dport_p = 0;
if (strcmp(tokens[1], "in") == 0) {
ri = &nb_acl4_rules_in;
if (extend_sp_arr(&acl4_rules_in, nb_acl4_rules_in,
&sp_in_sz) < 0)
return;
rule_ipv4 = &acl4_rules_in[*ri];
} else if (strcmp(tokens[1], "out") == 0) {
ri = &nb_acl4_rules_out;
if (extend_sp_arr(&acl4_rules_out, nb_acl4_rules_out,
&sp_out_sz) < 0)
return;
rule_ipv4 = &acl4_rules_out[*ri];
} else {
APP_CHECK(0, status, "unrecognized input \"%s\", expect"
" \"in\" or \"out\"\n", tokens[ti]);
return;
}
rule_ipv4->data.category_mask = 1;
for (ti = 2; ti < n_tokens; ti++) {
if (strcmp(tokens[ti], "esp") == 0) {
/* currently do nothing */
APP_CHECK_PRESENCE(esp_p, tokens[ti], status);
if (status->status < 0)
return;
esp_p = 1;
continue;
}
if (strcmp(tokens[ti], "protect") == 0) {
APP_CHECK_PRESENCE(protect_p, tokens[ti], status);
if (status->status < 0)
return;
APP_CHECK(bypass_p == 0, status, "conflict item "
"between \"%s\" and \"%s\"", tokens[ti],
"bypass");
if (status->status < 0)
return;
APP_CHECK(discard_p == 0, status, "conflict item "
"between \"%s\" and \"%s\"", tokens[ti],
"discard");
if (status->status < 0)
return;
INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
if (status->status < 0)
return;
APP_CHECK_TOKEN_IS_NUM(tokens, ti, status);
if (status->status < 0)
return;
tv = atoi(tokens[ti]);
APP_CHECK(tv != DISCARD && tv != BYPASS, status,
"invalid SPI: %s", tokens[ti]);
if (status->status < 0)
return;
rule_ipv4->data.userdata = tv;
protect_p = 1;
continue;
}
if (strcmp(tokens[ti], "bypass") == 0) {
APP_CHECK_PRESENCE(bypass_p, tokens[ti], status);
if (status->status < 0)
return;
APP_CHECK(protect_p == 0, status, "conflict item "
"between \"%s\" and \"%s\"", tokens[ti],
"protect");
if (status->status < 0)
return;
APP_CHECK(discard_p == 0, status, "conflict item "
"between \"%s\" and \"%s\"", tokens[ti],
"discard");
if (status->status < 0)
return;
rule_ipv4->data.userdata = BYPASS;
bypass_p = 1;
continue;
}
if (strcmp(tokens[ti], "discard") == 0) {
APP_CHECK_PRESENCE(discard_p, tokens[ti], status);
if (status->status < 0)
return;
APP_CHECK(protect_p == 0, status, "conflict item "
"between \"%s\" and \"%s\"", tokens[ti],
"protect");
if (status->status < 0)
return;
APP_CHECK(bypass_p == 0, status, "conflict item "
"between \"%s\" and \"%s\"", tokens[ti],
"discard");
if (status->status < 0)
return;
rule_ipv4->data.userdata = DISCARD;
discard_p = 1;
continue;
}
if (strcmp(tokens[ti], "pri") == 0) {
APP_CHECK_PRESENCE(pri_p, tokens[ti], status);
if (status->status < 0)
return;
INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
if (status->status < 0)
return;
APP_CHECK_TOKEN_IS_NUM(tokens, ti, status);
if (status->status < 0)
return;
rule_ipv4->data.priority = atoi(tokens[ti]);
pri_p = 1;
continue;
}
if (strcmp(tokens[ti], "src") == 0) {
struct in_addr ip;
uint32_t depth;
APP_CHECK_PRESENCE(src_p, tokens[ti], status);
if (status->status < 0)
return;
INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
if (status->status < 0)
return;
APP_CHECK(parse_ipv4_addr(tokens[ti], &ip,
&depth) == 0, status, "unrecognized "
"input \"%s\", expect valid ipv4 addr",
tokens[ti]);
if (status->status < 0)
return;
rule_ipv4->field[1].value.u32 =
rte_bswap32(ip.s_addr);
rule_ipv4->field[1].mask_range.u32 =
depth;
src_p = 1;
continue;
}
if (strcmp(tokens[ti], "dst") == 0) {
struct in_addr ip;
uint32_t depth;
APP_CHECK_PRESENCE(dst_p, tokens[ti], status);
if (status->status < 0)
return;
INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
if (status->status < 0)
return;
APP_CHECK(parse_ipv4_addr(tokens[ti], &ip,
&depth) == 0, status, "unrecognized "
"input \"%s\", expect valid ipv4 addr",
tokens[ti]);
if (status->status < 0)
return;
rule_ipv4->field[2].value.u32 =
rte_bswap32(ip.s_addr);
rule_ipv4->field[2].mask_range.u32 =
depth;
dst_p = 1;
continue;
}
if (strcmp(tokens[ti], "proto") == 0) {
uint16_t low, high;
APP_CHECK_PRESENCE(proto_p, tokens[ti], status);
if (status->status < 0)
return;
INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
if (status->status < 0)
return;
APP_CHECK(parse_range(tokens[ti], &low, &high)
== 0, status, "unrecognized input \"%s\""
", expect \"from:to\"", tokens[ti]);
if (status->status < 0)
return;
APP_CHECK(low <= 0xff, status, "proto low "
"over-limit");
if (status->status < 0)
return;
APP_CHECK(high <= 0xff, status, "proto high "
"over-limit");
if (status->status < 0)
return;
rule_ipv4->field[0].value.u8 = (uint8_t)low;
rule_ipv4->field[0].mask_range.u8 = (uint8_t)high;
proto_p = 1;
continue;
}
if (strcmp(tokens[ti], "sport") == 0) {
uint16_t port_low, port_high;
APP_CHECK_PRESENCE(sport_p, tokens[ti], status);
if (status->status < 0)
return;
INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
if (status->status < 0)
return;
APP_CHECK(parse_range(tokens[ti], &port_low,
&port_high) == 0, status, "unrecognized "
"input \"%s\", expect \"port_from:"
"port_to\"", tokens[ti]);
if (status->status < 0)
return;
rule_ipv4->field[3].value.u16 = port_low;
rule_ipv4->field[3].mask_range.u16 = port_high;
sport_p = 1;
continue;
}
if (strcmp(tokens[ti], "dport") == 0) {
uint16_t port_low, port_high;
APP_CHECK_PRESENCE(dport_p, tokens[ti], status);
if (status->status < 0)
return;
INCREMENT_TOKEN_INDEX(ti, n_tokens, status);
if (status->status < 0)
return;
APP_CHECK(parse_range(tokens[ti], &port_low,
&port_high) == 0, status, "unrecognized "
"input \"%s\", expect \"port_from:"
"port_to\"", tokens[ti]);
if (status->status < 0)
return;
rule_ipv4->field[4].value.u16 = port_low;
rule_ipv4->field[4].mask_range.u16 = port_high;
dport_p = 1;
continue;
}
/* unrecognizeable input */
APP_CHECK(0, status, "unrecognized input \"%s\"",
tokens[ti]);
return;
}
/* check if argument(s) are missing */
APP_CHECK(esp_p == 1, status, "missing argument \"esp\"");
if (status->status < 0)
return;
APP_CHECK(protect_p | bypass_p | discard_p, status, "missing "
"argument \"protect\", \"bypass\", or \"discard\"");
if (status->status < 0)
return;
*ri = *ri + 1;
}
static void
print_one_ip4_rule(const struct acl4_rules *rule, int32_t extra)
{
uint8_t a, b, c, d;
uint32_t_to_char(rule->field[SRC_FIELD_IPV4].value.u32,
&a, &b, &c, &d);
printf("%hhu.%hhu.%hhu.%hhu/%u ", a, b, c, d,
rule->field[SRC_FIELD_IPV4].mask_range.u32);
uint32_t_to_char(rule->field[DST_FIELD_IPV4].value.u32,
&a, &b, &c, &d);
printf("%hhu.%hhu.%hhu.%hhu/%u ", a, b, c, d,
rule->field[DST_FIELD_IPV4].mask_range.u32);
printf("%hu : %hu %hu : %hu 0x%hhx/0x%hhx ",
rule->field[SRCP_FIELD_IPV4].value.u16,
rule->field[SRCP_FIELD_IPV4].mask_range.u16,
rule->field[DSTP_FIELD_IPV4].value.u16,
rule->field[DSTP_FIELD_IPV4].mask_range.u16,
rule->field[PROTO_FIELD_IPV4].value.u8,
rule->field[PROTO_FIELD_IPV4].mask_range.u8);
if (extra)
printf("0x%x-0x%x-0x%x ",
rule->data.category_mask,
rule->data.priority,
rule->data.userdata);
}
static inline void
dump_ip4_rules(const struct acl4_rules *rule, int32_t num, int32_t extra)
{
int32_t i;
for (i = 0; i < num; i++, rule++) {
printf("\t%d:", i + 1);
print_one_ip4_rule(rule, extra);
printf("\n");
}
}
static struct rte_acl_ctx *
acl4_init(const char *name, int32_t socketid, const struct acl4_rules *rules,
uint32_t rules_nb)
{
char s[PATH_MAX];
struct rte_acl_param acl_param;
struct rte_acl_config acl_build_param;
struct rte_acl_ctx *ctx;
printf("Creating SP context with %u rules\n", rules_nb);
memset(&acl_param, 0, sizeof(acl_param));
/* Create ACL contexts */
snprintf(s, sizeof(s), "%s_%d", name, socketid);
printf("IPv4 %s entries [%u]:\n", s, rules_nb);
dump_ip4_rules(rules, rules_nb, 1);
acl_param.name = s;
acl_param.socket_id = socketid;
acl_param.rule_size = RTE_ACL_RULE_SZ(RTE_DIM(ip4_defs));
acl_param.max_rule_num = rules_nb;
ctx = rte_acl_create(&acl_param);
if (ctx == NULL)
rte_exit(EXIT_FAILURE, "Failed to create ACL context\n");
if (rte_acl_add_rules(ctx, (const struct rte_acl_rule *)rules,
rules_nb) < 0)
rte_exit(EXIT_FAILURE, "add rules failed\n");
/* Perform builds */
memset(&acl_build_param, 0, sizeof(acl_build_param));
acl_build_param.num_categories = DEFAULT_MAX_CATEGORIES;
acl_build_param.num_fields = RTE_DIM(ip4_defs);
memcpy(&acl_build_param.defs, ip4_defs, sizeof(ip4_defs));
if (rte_acl_build(ctx, &acl_build_param) != 0)
rte_exit(EXIT_FAILURE, "Failed to build ACL trie\n");
rte_acl_dump(ctx);
return ctx;
}
/*
* check that for each rule it's SPI has a correspondent entry in SAD
*/
static int
check_spi_value(struct sa_ctx *sa_ctx, int inbound)
{
uint32_t i, num, spi;
int32_t spi_idx;
struct acl4_rules *acr;
if (inbound != 0) {
acr = acl4_rules_in;
num = nb_acl4_rules_in;
} else {
acr = acl4_rules_out;
num = nb_acl4_rules_out;
}
for (i = 0; i != num; i++) {
spi = acr[i].data.userdata;
if (spi != DISCARD && spi != BYPASS) {
spi_idx = sa_spi_present(sa_ctx, spi, inbound);
if (spi_idx < 0) {
RTE_LOG(ERR, IPSEC,
"SPI %u is not present in SAD\n",
spi);
return -ENOENT;
}
/* Update userdata with spi index */
acr[i].data.userdata = spi_idx + 1;
}
}
return 0;
}
void
sp4_init(struct socket_ctx *ctx, int32_t socket_id)
{
const char *name;
if (ctx == NULL)
rte_exit(EXIT_FAILURE, "NULL context.\n");
if (ctx->sp_ip4_in != NULL)
rte_exit(EXIT_FAILURE, "Inbound SP DB for socket %u already "
"initialized\n", socket_id);
if (ctx->sp_ip4_out != NULL)
rte_exit(EXIT_FAILURE, "Outbound SP DB for socket %u already "
"initialized\n", socket_id);
if (check_spi_value(ctx->sa_in, 1) < 0)
rte_exit(EXIT_FAILURE,
"Inbound IPv4 SP DB has unmatched in SAD SPIs\n");
if (check_spi_value(ctx->sa_out, 0) < 0)
rte_exit(EXIT_FAILURE,
"Outbound IPv4 SP DB has unmatched in SAD SPIs\n");
if (nb_acl4_rules_in > 0) {
name = "sp_ip4_in";
ctx->sp_ip4_in = (struct sp_ctx *)acl4_init(name,
socket_id, acl4_rules_in, nb_acl4_rules_in);
} else
RTE_LOG(WARNING, IPSEC, "No IPv4 SP Inbound rule "
"specified\n");
if (nb_acl4_rules_out > 0) {
name = "sp_ip4_out";
ctx->sp_ip4_out = (struct sp_ctx *)acl4_init(name,
socket_id, acl4_rules_out, nb_acl4_rules_out);
} else
RTE_LOG(WARNING, IPSEC, "No IPv4 SP Outbound rule "
"specified\n");
}
static int
sp_cmp(const void *p, const void *q)
{
uint32_t spi1 = ((const struct acl4_rules *)p)->data.userdata;
uint32_t spi2 = ((const struct acl4_rules *)q)->data.userdata;
return (int)(spi1 - spi2);
}
/*
* Search though SP rules for given SPI.
*/
int
sp4_spi_present(uint32_t spi, int inbound, struct ip_addr ip_addr[2],
uint32_t mask[2])
{
uint32_t num;
struct acl4_rules *rule;
const struct acl4_rules *acr;
struct acl4_rules tmpl;
if (inbound != 0) {
acr = acl4_rules_in;
num = nb_acl4_rules_in;
} else {
acr = acl4_rules_out;
num = nb_acl4_rules_out;
}
tmpl.data.userdata = spi;
rule = bsearch(&tmpl, acr, num, sizeof(struct acl4_rules), sp_cmp);
if (rule != NULL) {
if (NULL != ip_addr && NULL != mask) {
ip_addr[0].ip.ip4 = IPV4_SRC_FROM_SP(*rule);
ip_addr[1].ip.ip4 = IPV4_DST_FROM_SP(*rule);
mask[0] = IPV4_SRC_MASK_FROM_SP(*rule);
mask[1] = IPV4_DST_MASK_FROM_SP(*rule);
}
return RTE_PTR_DIFF(rule, acr) / sizeof(struct acl4_rules);
}
return -ENOENT;
}
void
sp4_sort_arr(void)
{
qsort(acl4_rules_in, nb_acl4_rules_in, sizeof(struct acl4_rules),
sp_cmp);
qsort(acl4_rules_out, nb_acl4_rules_out, sizeof(struct acl4_rules),
sp_cmp);
}