From: Máté Eckl <ecklm94@xxxxxxxxx> 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 Máté Eckl. Cc: Máté Eckl <ecklm94@xxxxxxxxx> Signed-off-by: Florian Westphal <fw@xxxxxxxxx> --- doc/primary-expression.txt | 34 +++++++++ include/expression.h | 8 +++ include/json.h | 2 + include/linux/netfilter/nf_tables.h | 29 ++++++++ include/xfrm.h | 16 +++++ 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 +++++++++++++++++++++++++++++++ tests/py/inet/ipsec.t | 21 ++++++ tests/py/inet/ipsec.t.json | 136 ++++++++++++++++++++++++++++++++++++ tests/py/inet/ipsec.t.payload | 40 +++++++++++ 17 files changed, 674 insertions(+), 11 deletions(-) create mode 100644 include/xfrm.h create mode 100644 src/xfrm.c create mode 100644 tests/py/inet/ipsec.t create mode 100644 tests/py/inet/ipsec.t.json create mode 100644 tests/py/inet/ipsec.t.payload diff --git a/doc/primary-expression.txt b/doc/primary-expression.txt index e404e5423a01..eb0438b2f440 100644 --- a/doc/primary-expression.txt +++ b/doc/primary-expression.txt @@ -284,3 +284,37 @@ ip6 filter output rt nexthop fd00::1 inet filter output rt ip nexthop 192.168.0.1 inet filter output rt ip6 nexthop fd00::1 -------------------------- + +IPSEC EXPRESSIONS +~~~~~~~~~~~~~~~~~ + +[verse] +*ipsec* {in | out} [ spnum 'NUM' ] {reqid | spi } +*ipsec* {in | out} [ spnum 'NUM' ] {ip | ip6 } { saddr | daddr } + +A ipsec expression refers to ipsec data associated with a packet. + +The 'in' or 'out' keyword needs to be used to specify if the expression should +examine inbound or outbound policies. The 'in' keyword can be used in the +prerouting, input and forward hooks. The 'out' keyword applies to forward, +output and postrouting hooks. +The optional keyword spnum can be used to match a specific state in a chain, +it defaults to 0. + +.Ipsec expression types +[options="header"] +|======================= +|Keyword| Description| Type +|reqid| +Request ID| +integer (32 bit) +|spi| +Security Parameter Index| +integer (32 bit) +|saddr| +Source address of the tunnel| +ipv4_addr/ipv6_addr +|daddr| +Destination address of the tunnel| +ipv4_addr/ipv6_addr +|================================= diff --git a/include/expression.h b/include/expression.h index f2c5c1ad7a04..fb52abfea76e 100644 --- a/include/expression.h +++ b/include/expression.h @@ -69,6 +69,7 @@ enum expr_types { EXPR_HASH, EXPR_RT, EXPR_FIB, + EXPR_XFRM, }; enum ops { @@ -194,6 +195,7 @@ enum expr_flags { #include <ct.h> #include <socket.h> #include <osf.h> +#include <xfrm.h> /** * struct expr @@ -337,6 +339,12 @@ struct expr { uint32_t flags; uint32_t result; } fib; + struct { + /* EXPR_XFRM */ + enum nft_xfrm_keys key; + uint8_t direction; + uint8_t spnum; + } xfrm; }; }; diff --git a/include/json.h b/include/json.h index 66f60e76aa83..99b676446516 100644 --- a/include/json.h +++ b/include/json.h @@ -43,6 +43,7 @@ json_t *fib_expr_json(const struct expr *expr, struct output_ctx *octx); json_t *constant_expr_json(const struct expr *expr, struct output_ctx *octx); json_t *socket_expr_json(const struct expr *expr, struct output_ctx *octx); json_t *osf_expr_json(const struct expr *expr, struct output_ctx *octx); +json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx); json_t *integer_type_json(const struct expr *expr, struct output_ctx *octx); json_t *string_type_json(const struct expr *expr, struct output_ctx *octx); @@ -123,6 +124,7 @@ EXPR_PRINT_STUB(fib_expr) EXPR_PRINT_STUB(constant_expr) EXPR_PRINT_STUB(socket_expr) EXPR_PRINT_STUB(osf_expr) +EXPR_PRINT_STUB(xfrm_expr) EXPR_PRINT_STUB(integer_type) EXPR_PRINT_STUB(string_type) diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h index 1a63bd1e32f4..169c2abcfcff 100644 --- a/include/linux/netfilter/nf_tables.h +++ b/include/linux/netfilter/nf_tables.h @@ -1501,6 +1501,35 @@ enum nft_devices_attributes { }; #define NFTA_DEVICE_MAX (__NFTA_DEVICE_MAX - 1) +/* + * enum nft_xfrm_attributes - nf_tables xfrm expr netlink attributes + * + * @NFTA_XFRM_DREG: destination register (NLA_U32) + * @NFTA_XFRM_KEY: enum nft_xfrm_keys (NLA_U32) + * @NFTA_XFRM_DIR: direction (NLA_U8) + * @NFTA_XFRM_SPNUM: index in secpath array (NLA_U32) + */ +enum nft_xfrm_attributes { + NFTA_XFRM_UNSPEC, + NFTA_XFRM_DREG, + NFTA_XFRM_KEY, + NFTA_XFRM_DIR, + NFTA_XFRM_SPNUM, + __NFTA_XFRM_MAX +}; +#define NFTA_XFRM_MAX (__NFTA_XFRM_MAX - 1) + +enum nft_xfrm_keys { + NFT_XFRM_KEY_UNSPEC, + NFT_XFRM_KEY_DADDR_IP4, + NFT_XFRM_KEY_DADDR_IP6, + NFT_XFRM_KEY_SADDR_IP4, + NFT_XFRM_KEY_SADDR_IP6, + NFT_XFRM_KEY_REQID, + NFT_XFRM_KEY_SPI, + __NFT_XFRM_KEY_MAX, +}; +#define NFT_XFRM_KEY_MAX (__NFT_XFRM_KEY_MAX - 1) /** * enum nft_trace_attributes - nf_tables trace netlink attributes diff --git a/include/xfrm.h b/include/xfrm.h new file mode 100644 index 000000000000..ea7d322c6df9 --- /dev/null +++ b/include/xfrm.h @@ -0,0 +1,16 @@ +#ifndef NFTABLES_XFRM_H +#define NFTABLES_XFRM_H + +struct xfrm_template { + const char *token; + const struct datatype *dtype; + unsigned int len; + enum byteorder byteorder; +}; + +extern const struct xfrm_template xfrm_templates[__NFT_XFRM_KEY_MAX]; + +extern struct expr *xfrm_expr_alloc(const struct location *loc, + uint8_t direction, uint8_t spnum, + enum nft_xfrm_keys key); +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 8e69232fd0b7..307bab108cca 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 195508236e1e..fb3b4e56fdcb 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 84bdaa39c0d3..7cd4355a218a 100644 --- a/src/json.c +++ b/src/json.c @@ -16,6 +16,7 @@ #include <linux/netfilter/nf_log.h> #include <linux/netfilter/nf_nat.h> #include <linux/netfilter/nf_tables.h> +#include <linux/xfrm.h> #include <pwd.h> #include <grp.h> #include <jansson.h> @@ -806,6 +807,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 6c5188cd9b5f..0a6ebe05ca7c 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 0bd946a1cf80..0ac51bd0d710 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 5fd304a9381f..1c68b4f4420e 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -21,6 +21,7 @@ #include <linux/netfilter/nf_conntrack_tuple_common.h> #include <linux/netfilter/nf_nat.h> #include <linux/netfilter/nf_log.h> +#include <linux/xfrm.h> #include <netinet/ip_icmp.h> #include <netinet/icmp6.h> #include <libnftnl/common.h> @@ -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 <string> 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 <list> timeout_states timeout_state %destructor { xfree($$); } timeout_states timeout_state +%type <val> xfrm_state_key xfrm_state_proto_key xfrm_dir xfrm_spnum +%type <expr> 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 3f0ab0ac1993..9aadc33ed93a 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -16,6 +16,8 @@ #include <netinet/icmp6.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> +#include <linux/xfrm.h> + #include <linux/netfilter.h> #include <linux/netfilter/nf_conntrack_tuple_common.h> #include <linux/netfilter/nf_log.h> @@ -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 26e63b9bcc0c..4a143b1e5955 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 000000000000..0f5818c568fa --- /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 <nftables.h> +#include <erec.h> +#include <expression.h> +#include <xfrm.h> +#include <datatype.h> +#include <gmputil.h> +#include <utils.h> +#include <string.h> + +#include <netinet/ip.h> +#include <linux/netfilter.h> +#include <linux/xfrm.h> + +#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; +} diff --git a/tests/py/inet/ipsec.t b/tests/py/inet/ipsec.t new file mode 100644 index 000000000000..e924e9bcbdbc --- /dev/null +++ b/tests/py/inet/ipsec.t @@ -0,0 +1,21 @@ +:ipsec-forw;type filter hook forward priority 0 + +*ip;ipsec-ip4;ipsec-forw +*ip6;ipsec-ip6;ipsec-forw +*inet;ipsec-inet;ipsec-forw + +ipsec in reqid 1;ok +ipsec in spnum 0 reqid 1;ok;ipsec in reqid 1 + +ipsec out reqid 0xffffffff;ok;ipsec out reqid 4294967295 +ipsec out spnum 0x100000000;fail + +ipsec i reqid 1;fail + +ipsec out spi 1-561;ok + +ipsec in spnum 2 ip saddr { 1.2.3.4, 10.6.0.0/16 };ok +ipsec in ip6 daddr dead::beef;ok +ipsec out ip6 saddr dead::feed;ok + +ipsec in spnum 256 reqid 1;fail diff --git a/tests/py/inet/ipsec.t.json b/tests/py/inet/ipsec.t.json new file mode 100644 index 000000000000..d7d3a03c2113 --- /dev/null +++ b/tests/py/inet/ipsec.t.json @@ -0,0 +1,136 @@ +# ipsec in reqid 1 +[ + { + "match": { + "left": { + "ipsec": { + "dir": "in", + "key": "reqid", + "spnum": 0 + } + }, + "op": "==", + "right": 1 + } + } +] + +# ipsec in spnum 0 reqid 1 +[ + { + "match": { + "left": { + "ipsec": { + "dir": "in", + "key": "reqid", + "spnum": 0 + } + }, + "op": "==", + "right": 1 + } + } +] + +# ipsec out reqid 0xffffffff +[ + { + "match": { + "left": { + "ipsec": { + "dir": "out", + "key": "reqid", + "spnum": 0 + } + }, + "op": "==", + "right": 4294967295 + } + } +] + +# ipsec out spi 1-561 +[ + { + "match": { + "left": { + "ipsec": { + "dir": "out", + "key": "spi", + "spnum": 0 + } + }, + "op": "==", + "right": { + "range": [ + 1, + 561 + ] + } + } + } +] + +# ipsec in spnum 2 ip saddr { 1.2.3.4, 10.6.0.0/16 } +[ + { + "match": { + "left": { + "ipsec": { + "dir": "in", + "family": "ip", + "key": "saddr", + "spnum": 2 + } + }, + "op": "==", + "right": { + "set": [ + "1.2.3.4", + { + "prefix": { + "addr": "10.6.0.0", + "len": 16 + } + } + ] + } + } + } +] + +# ipsec in ip6 daddr dead::beef +[ + { + "match": { + "left": { + "ipsec": { + "dir": "in", + "family": "ip6", + "key": "daddr", + "spnum": 0 + } + }, + "op": "==", + "right": "dead::beef" + } + } +] + +# ipsec out ip6 saddr dead::feed +[ + { + "match": { + "left": { + "ipsec": { + "dir": "out", + "family": "ip6", + "key": "saddr", + "spnum": 0 + } + }, + "op": "==", + "right": "dead::feed" + } + } +] diff --git a/tests/py/inet/ipsec.t.payload b/tests/py/inet/ipsec.t.payload new file mode 100644 index 000000000000..dfe0edc387bc --- /dev/null +++ b/tests/py/inet/ipsec.t.payload @@ -0,0 +1,40 @@ +# ipsec in reqid 1 +ip ipsec-ip4 ipsec-input + [ xfrm load in 0 reqid => reg 1 ] + [ cmp eq reg 1 0x00000001 ] + +# ipsec in spnum 0 reqid 1 +ip ipsec-ip4 ipsec-input + [ xfrm load in 0 reqid => reg 1 ] + [ cmp eq reg 1 0x00000001 ] + +# ipsec out reqid 0xffffffff +ip ipsec-ip4 ipsec-input + [ xfrm load out 0 reqid => reg 1 ] + [ cmp eq reg 1 0xffffffff ] + +# ipsec out spi 1-561 +inet ipsec-inet ipsec-post + [ xfrm load out 0 spi => reg 1 ] + [ byteorder reg 1 = hton(reg 1, 4, 4) ] + [ cmp gte reg 1 0x01000000 ] + [ cmp lte reg 1 0x31020000 ] + +# ipsec in spnum 2 ip saddr { 1.2.3.4, 10.6.0.0/16 } +__set%d ipsec-ip4 7 size 5 +__set%d ipsec-ip4 0 + element 00000000 : 1 [end] element 04030201 : 0 [end] element 05030201 : 1 [end] element 0000060a : 0 [end] element 0000070a : 1 [end] +ip ipsec-ip4 ipsec-input + [ xfrm load in 2 saddr4 => reg 1 ] + [ lookup reg 1 set __set%d ] + +# ipsec in ip6 daddr dead::beef +ip ipsec-ip4 ipsec-forw + [ xfrm load in 0 daddr6 => reg 1 ] + [ cmp eq reg 1 0x0000adde 0x00000000 0x00000000 0xefbe0000 ] + +# ipsec out ip6 saddr dead::feed +ip ipsec-ip4 ipsec-forw + [ xfrm load out 0 saddr6 => reg 1 ] + [ cmp eq reg 1 0x0000adde 0x00000000 0x00000000 0xedfe0000 ] + -- 2.16.4