From 57c2b152c5f0866be5bf1acda2f341ba26ba9448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Eckl?= Date: Wed, 5 Sep 2018 11:16:44 +0200 Subject: src: add ipsec (xfrm) expression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows matching on ipsec tunnel/beet addresses in xfrm state associated with a packet, ipsec request id and the SPI. Examples: ipsec in ip saddr 192.168.1.0/24 ipsec out ip6 daddr @endpoints ipsec in spi 1-65536 Joint work with Florian Westphal. Cc: Máté Eckl Signed-off-by: Florian Westphal --- src/Makefile.am | 1 + src/evaluate.c | 19 ++++++++ src/json.c | 46 ++++++++++++++++++ src/netlink_delinearize.c | 21 ++++++++ src/netlink_linearize.c | 16 +++++++ src/parser_bison.y | 66 +++++++++++++++++++++++++ src/parser_json.c | 101 +++++++++++++++++++++++++++++++++----- src/scanner.l | 9 ++++ src/xfrm.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 388 insertions(+), 11 deletions(-) create mode 100644 src/xfrm.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 8e69232f..307bab10 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -43,6 +43,7 @@ libnftables_la_SOURCES = \ rt.c \ numgen.c \ ct.c \ + xfrm.c \ netlink.c \ netlink_linearize.c \ netlink_delinearize.c \ diff --git a/src/evaluate.c b/src/evaluate.c index c8010852..ff36f576 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -1751,6 +1751,23 @@ static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp) return expr_evaluate(ctx, exprp); } +static int expr_evaluate_xfrm(struct eval_ctx *ctx, struct expr **exprp) +{ + struct expr *expr = *exprp; + + switch (ctx->pctx.family) { + case NFPROTO_IPV4: + case NFPROTO_IPV6: + case NFPROTO_INET: + break; + default: + return expr_error(ctx->msgs, expr, "ipsec expression is only" + " valid in ip/ip6/inet tables"); + } + + return expr_evaluate_primary(ctx, exprp); +} + static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) { if (ctx->debug_mask & NFT_DEBUG_EVALUATION) { @@ -1816,6 +1833,8 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr) return expr_evaluate_numgen(ctx, expr); case EXPR_HASH: return expr_evaluate_hash(ctx, expr); + case EXPR_XFRM: + return expr_evaluate_xfrm(ctx, expr); default: BUG("unknown expression type %s\n", (*expr)->ops->name); } diff --git a/src/json.c b/src/json.c index 1708f22d..0191a2ea 100644 --- a/src/json.c +++ b/src/json.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -813,6 +814,51 @@ json_t *osf_expr_json(const struct expr *expr, struct output_ctx *octx) return json_pack("{s:{s:s}}", "osf", "key", "name"); } +json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx) +{ + const char *name = xfrm_templates[expr->xfrm.key].token; + const char *family = NULL; + const char *dirstr; + json_t *root; + + switch (expr->xfrm.direction) { + case XFRM_POLICY_IN: + dirstr = "in"; + break; + case XFRM_POLICY_OUT: + dirstr = "out"; + break; + default: + return NULL; + } + + switch (expr->xfrm.key) { + case NFT_XFRM_KEY_UNSPEC: + case NFT_XFRM_KEY_SPI: + case NFT_XFRM_KEY_REQID: + case __NFT_XFRM_KEY_MAX: + break; + case NFT_XFRM_KEY_DADDR_IP4: + case NFT_XFRM_KEY_SADDR_IP4: + family = "ip"; + break; + case NFT_XFRM_KEY_DADDR_IP6: + case NFT_XFRM_KEY_SADDR_IP6: + family = "ip6"; + break; + } + + root = json_pack("{s:s}", "key", name); + + if (family) + json_object_set_new(root, "family", json_string(family)); + + json_object_set_new(root, "dir", json_string(dirstr)); + json_object_set_new(root, "spnum", json_integer(expr->xfrm.spnum)); + + return json_pack("{s:o}", "ipsec", root); +} + json_t *integer_type_json(const struct expr *expr, struct output_ctx *octx) { char buf[1024] = "0x"; diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index 6c5188cd..0a6ebe05 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -189,6 +189,25 @@ static void netlink_parse_immediate(struct netlink_parse_ctx *ctx, netlink_set_register(ctx, dreg, expr); } +static void netlink_parse_xfrm(struct netlink_parse_ctx *ctx, + const struct location *loc, + const struct nftnl_expr *nle) +{ + enum nft_registers dreg; + enum nft_xfrm_keys key; + struct expr *expr; + uint32_t spnum; + uint8_t dir; + + key = nftnl_expr_get_u32(nle, NFTNL_EXPR_XFRM_KEY); + dir = nftnl_expr_get_u8(nle, NFTNL_EXPR_XFRM_DIR); + spnum = nftnl_expr_get_u32(nle, NFTNL_EXPR_XFRM_SPNUM); + expr = xfrm_expr_alloc(loc, dir, spnum, key); + + dreg = netlink_parse_register(nle, NFTNL_EXPR_XFRM_DREG); + netlink_set_register(ctx, dreg, expr); +} + static enum ops netlink_parse_range_op(const struct nftnl_expr *nle) { switch (nftnl_expr_get_u32(nle, NFTNL_EXPR_RANGE_OP)) { @@ -1441,6 +1460,7 @@ static const struct { { .name = "fib", .parse = netlink_parse_fib }, { .name = "tcpopt", .parse = netlink_parse_exthdr }, { .name = "flow_offload", .parse = netlink_parse_flow_offload }, + { .name = "xfrm", .parse = netlink_parse_xfrm }, }; static int netlink_parse_expr(const struct nftnl_expr *nle, @@ -2106,6 +2126,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp) case EXPR_FIB: case EXPR_SOCKET: case EXPR_OSF: + case EXPR_XFRM: break; case EXPR_HASH: if (expr->hash.expr) diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index 0bd946a1..0ac51bd0 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -679,6 +679,20 @@ static void netlink_gen_immediate(struct netlink_linearize_ctx *ctx, nftnl_rule_add_expr(ctx->nlr, nle); } +static void netlink_gen_xfrm(struct netlink_linearize_ctx *ctx, + const struct expr *expr, + enum nft_registers dreg) +{ + struct nftnl_expr *nle; + + nle = alloc_nft_expr("xfrm"); + netlink_put_register(nle, NFTNL_EXPR_XFRM_DREG, dreg); + nftnl_expr_set_u32(nle, NFTNL_EXPR_XFRM_KEY, expr->xfrm.key); + nftnl_expr_set_u8(nle, NFTNL_EXPR_XFRM_DIR, expr->xfrm.direction); + nftnl_expr_set_u32(nle, NFTNL_EXPR_XFRM_SPNUM, expr->xfrm.spnum); + nftnl_rule_add_expr(ctx->nlr, nle); +} + static void netlink_gen_expr(struct netlink_linearize_ctx *ctx, const struct expr *expr, enum nft_registers dreg) @@ -721,6 +735,8 @@ static void netlink_gen_expr(struct netlink_linearize_ctx *ctx, return netlink_gen_socket(ctx, expr, dreg); case EXPR_OSF: return netlink_gen_osf(ctx, expr, dreg); + case EXPR_XFRM: + return netlink_gen_xfrm(ctx, expr, dreg); default: BUG("unknown expression type %s\n", expr->ops->name); } diff --git a/src/parser_bison.y b/src/parser_bison.y index 5fd304a9..1c68b4f4 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -511,6 +512,15 @@ int nft_lex(void *, void *, void *); %token EXTHDR "exthdr" %token IPSEC "ipsec" +%token MODE "mode" +%token REQID "reqid" +%token SPNUM "spnum" +%token TRANSPORT "transport" +%token TUNNEL "tunnel" + +%token IN "in" +%token OUT "out" + %type identifier type_identifier string comment_spec %destructor { xfree($$); } identifier type_identifier string comment_spec @@ -759,6 +769,10 @@ int nft_lex(void *, void *, void *); %type timeout_states timeout_state %destructor { xfree($$); } timeout_states timeout_state +%type xfrm_state_key xfrm_state_proto_key xfrm_dir xfrm_spnum +%type xfrm_expr +%destructor { expr_free($$); } xfrm_expr + %% input : /* empty */ @@ -3043,6 +3057,7 @@ primary_expr : symbol_expr { $$ = $1; } | hash_expr { $$ = $1; } | fib_expr { $$ = $1; } | osf_expr { $$ = $1; } + | xfrm_expr { $$ = $1; } | '(' basic_expr ')' { $$ = $2; } ; @@ -3785,6 +3800,57 @@ numgen_expr : NUMGEN numgen_type MOD NUM offset_opt } ; +xfrm_spnum : SPNUM NUM { $$ = $2; } + | { $$ = 0; } + ; + +xfrm_dir : IN { $$ = XFRM_POLICY_IN; } + | OUT { $$ = XFRM_POLICY_OUT; } + ; + +xfrm_state_key : SPI { $$ = NFT_XFRM_KEY_SPI; } + | REQID { $$ = NFT_XFRM_KEY_REQID; } + ; + +xfrm_state_proto_key : DADDR { $$ = NFT_XFRM_KEY_DADDR_IP4; } + | SADDR { $$ = NFT_XFRM_KEY_SADDR_IP4; } + ; + +xfrm_expr : IPSEC xfrm_dir xfrm_spnum xfrm_state_key + { + if ($3 > 255) { + erec_queue(error(&@3, "value too large"), state->msgs); + YYERROR; + } + $$ = xfrm_expr_alloc(&@$, $2, $3, $4); + } + | IPSEC xfrm_dir xfrm_spnum nf_key_proto xfrm_state_proto_key + { + enum nft_xfrm_keys xfrmk = $5; + + switch ($4) { + case NFPROTO_IPV4: + break; + case NFPROTO_IPV6: + if ($5 == NFT_XFRM_KEY_SADDR_IP4) + xfrmk = NFT_XFRM_KEY_SADDR_IP6; + else if ($5 == NFT_XFRM_KEY_DADDR_IP4) + xfrmk = NFT_XFRM_KEY_DADDR_IP6; + break; + default: + YYERROR; + break; + } + + if ($3 > 255) { + erec_queue(error(&@3, "value too large"), state->msgs); + YYERROR; + } + + $$ = xfrm_expr_alloc(&@$, $2, $3, xfrmk); + } + ; + hash_expr : JHASH expr MOD NUM SEED NUM offset_opt { $$ = hash_expr_alloc(&@$, $4, true, $6, $7, NFT_HASH_JENKINS); diff --git a/src/parser_json.c b/src/parser_json.c index 3f0ab0ac..9aadc33e 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -16,6 +16,8 @@ #include #include #include +#include + #include #include #include @@ -679,12 +681,32 @@ static bool ct_key_is_dir(enum nft_ct_keys key) return false; } +static int json_parse_family(struct json_ctx *ctx, json_t *root) +{ + const char *family; + + if (!json_unpack(root, "{s:s}", "family", &family)) { + int familyval = parse_family(family); + + switch (familyval) { + case NFPROTO_IPV6: + case NFPROTO_IPV4: + return familyval; + default: + json_error(ctx, "Invalid family '%s'.", family); + return -1; + } + } + + return NFPROTO_UNSPEC; +} + static struct expr *json_parse_ct_expr(struct json_ctx *ctx, const char *type, json_t *root) { - const char *key, *dir, *family; + const char *key, *dir; unsigned int i; - int dirval = -1, familyval = NFPROTO_UNSPEC, keyval = -1; + int dirval = -1, familyval, keyval = -1; if (json_unpack_err(ctx, root, "{s:s}", "key", &key)) return NULL; @@ -701,14 +723,9 @@ static struct expr *json_parse_ct_expr(struct json_ctx *ctx, return NULL; } - if (!json_unpack(root, "{s:s}", "family", &family)) { - familyval = parse_family(family); - if (familyval != NFPROTO_IPV4 && - familyval != NFPROTO_IPV6) { - json_error(ctx, "Invalid CT family '%s'.", family); - return NULL; - } - } + familyval = json_parse_family(ctx, root); + if (familyval < 0) + return NULL; if (!json_unpack(root, "{s:s}", "dir", &dir)) { if (!strcmp(dir, "original")) { @@ -716,7 +733,7 @@ static struct expr *json_parse_ct_expr(struct json_ctx *ctx, } else if (!strcmp(dir, "reply")) { dirval = IP_CT_DIR_REPLY; } else { - json_error(ctx, "Invalid ct direction '%s'.", dir); + json_error(ctx, "Invalid direction '%s'.", dir); return NULL; } @@ -1167,6 +1184,67 @@ static struct expr *json_parse_set_elem_expr(struct json_ctx *ctx, return expr; } +static struct expr *json_parse_xfrm_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const char *key, *dir; + unsigned int i, spnum; + int dirval = -1, familyval, keyval = -1; + + if (json_unpack_err(ctx, root, "{s:s}", "key", &key)) + return NULL; + + for (i = 1; i < array_size(xfrm_templates); i++) { + if (strcmp(key, xfrm_templates[i].token)) + continue; + keyval = i; + break; + } + + if (keyval == -1) { + json_error(ctx, "Unknown xfrm key '%s'.", key); + return NULL; + } + + familyval = json_parse_family(ctx, root); + if (familyval < 0) + return NULL; + + if (!json_unpack(root, "{s:s}", "dir", &dir)) { + if (!strcmp(dir, "in")) { + dirval = XFRM_POLICY_IN; + } else if (!strcmp(dir, "out")) { + dirval = XFRM_POLICY_OUT; + } else { + json_error(ctx, "Invalid direction '%s'.", dir); + return NULL; + } + } + + spnum = 0; + if (!json_unpack(root, "{s:i}", "spnum", &spnum)) { + if (spnum > 255) { + json_error(ctx, "Invalid spnum'%d'.", spnum); + return NULL; + } + } + + switch (keyval) { + case NFT_XFRM_KEY_SADDR_IP4: + if (familyval == NFPROTO_IPV6) + keyval = NFT_XFRM_KEY_SADDR_IP6; + break; + case NFT_XFRM_KEY_DADDR_IP4: + if (familyval == NFPROTO_IPV6) + keyval = NFT_XFRM_KEY_DADDR_IP6; + break; + default: + break; + } + + return xfrm_expr_alloc(int_loc, dirval, spnum, keyval); +} + static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root) { const struct { @@ -1185,6 +1263,7 @@ static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root) { "tcp option", json_parse_tcp_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES }, { "meta", json_parse_meta_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, { "osf", json_parse_osf_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_MAP }, + { "ipsec", json_parse_xfrm_expr, CTX_F_PRIMARY | CTX_F_MAP }, { "socket", json_parse_socket_expr, CTX_F_PRIMARY }, { "rt", json_parse_rt_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, { "ct", json_parse_ct_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, diff --git a/src/scanner.l b/src/scanner.l index 26e63b9b..4a143b1e 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -555,6 +555,15 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "exthdr" { return EXTHDR; } "ipsec" { return IPSEC; } +"mode" { return MODE; } +"reqid" { return REQID; } +"spnum" { return SPNUM; } +"transport" { return TRANSPORT; } +"tunnel" { return TUNNEL; } + +"in" { return IN; } +"out" { return OUT; } + {addrstring} { yylval->string = xstrdup(yytext); return STRING; diff --git a/src/xfrm.c b/src/xfrm.c new file mode 100644 index 00000000..0f5818c5 --- /dev/null +++ b/src/xfrm.c @@ -0,0 +1,120 @@ +/* + * XFRM (ipsec) expression + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define XFRM_TEMPLATE_BE(__token, __dtype, __len) { \ + .token = (__token), \ + .dtype = (__dtype), \ + .len = (__len), \ + .byteorder = BYTEORDER_BIG_ENDIAN, \ +} + +#define XFRM_TEMPLATE_HE(__token, __dtype, __len) { \ + .token = (__token), \ + .dtype = (__dtype), \ + .len = (__len), \ + .byteorder = BYTEORDER_HOST_ENDIAN, \ +} + +const struct xfrm_template xfrm_templates[] = { + [NFT_XFRM_KEY_DADDR_IP4] = XFRM_TEMPLATE_BE("daddr", &ipaddr_type, 4 * BITS_PER_BYTE), + [NFT_XFRM_KEY_SADDR_IP4] = XFRM_TEMPLATE_BE("saddr", &ipaddr_type, 4 * BITS_PER_BYTE), + [NFT_XFRM_KEY_DADDR_IP6] = XFRM_TEMPLATE_BE("daddr", &ip6addr_type, 16 * BITS_PER_BYTE), + [NFT_XFRM_KEY_SADDR_IP6] = XFRM_TEMPLATE_BE("saddr", &ip6addr_type, 16 * BITS_PER_BYTE), + [NFT_XFRM_KEY_REQID] = XFRM_TEMPLATE_HE("reqid", &integer_type, 4 * BITS_PER_BYTE), + [NFT_XFRM_KEY_SPI] = XFRM_TEMPLATE_HE("spi", &integer_type, 4 * BITS_PER_BYTE), +}; + +static void xfrm_expr_print(const struct expr *expr, struct output_ctx *octx) +{ + switch (expr->xfrm.direction) { + case XFRM_POLICY_IN: + nft_print(octx, "ipsec in"); + break; + case XFRM_POLICY_OUT: + nft_print(octx, "ipsec out"); + break; + default: + nft_print(octx, "ipsec (unknown dir %d)", expr->xfrm.direction); + break; + } + + if (expr->xfrm.spnum) + nft_print(octx, " spnum %u", expr->xfrm.spnum); + + switch (expr->xfrm.key) { + case NFT_XFRM_KEY_DADDR_IP4: + case NFT_XFRM_KEY_SADDR_IP4: + nft_print(octx, " ip"); + break; + case NFT_XFRM_KEY_DADDR_IP6: + case NFT_XFRM_KEY_SADDR_IP6: + nft_print(octx, " ip6"); + break; + case NFT_XFRM_KEY_REQID: + case NFT_XFRM_KEY_SPI: + break; + default: + nft_print(octx, " (unknown key 0x%x)", expr->xfrm.key); + return; + } + + nft_print(octx, " %s", xfrm_templates[expr->xfrm.key].token); +} + +static bool xfrm_expr_cmp(const struct expr *e1, const struct expr *e2) +{ + return e1->xfrm.key == e2->xfrm.key && + e1->xfrm.direction == e2->xfrm.direction && + e1->xfrm.spnum == e2->xfrm.spnum; +} + +static void xfrm_expr_clone(struct expr *new, const struct expr *expr) +{ + memcpy(&new->xfrm, &expr->xfrm, sizeof(new->xfrm)); +} + +static const struct expr_ops xfrm_expr_ops = { + .type = EXPR_XFRM, + .name = "xfrm", + .print = xfrm_expr_print, + .json = xfrm_expr_json, + .cmp = xfrm_expr_cmp, + .clone = xfrm_expr_clone, +}; + +struct expr *xfrm_expr_alloc(const struct location *loc, + uint8_t direction, + uint8_t spnum, + enum nft_xfrm_keys key) +{ + struct expr *expr; + + expr = expr_alloc(loc, &xfrm_expr_ops, + xfrm_templates[key].dtype, + xfrm_templates[key].byteorder, + xfrm_templates[key].len); + + expr->xfrm.direction = direction; + expr->xfrm.spnum = spnum; + expr->xfrm.key = key; + + return expr; +} -- cgit v1.2.3