summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/primary-expression.txt34
-rw-r--r--include/expression.h8
-rw-r--r--include/json.h2
-rw-r--r--include/linux/netfilter/nf_tables.h29
-rw-r--r--include/xfrm.h16
-rw-r--r--src/Makefile.am1
-rw-r--r--src/evaluate.c19
-rw-r--r--src/json.c46
-rw-r--r--src/netlink_delinearize.c21
-rw-r--r--src/netlink_linearize.c16
-rw-r--r--src/parser_bison.y66
-rw-r--r--src/parser_json.c101
-rw-r--r--src/scanner.l9
-rw-r--r--src/xfrm.c120
-rw-r--r--tests/py/inet/ipsec.t21
-rw-r--r--tests/py/inet/ipsec.t.json136
-rw-r--r--tests/py/inet/ipsec.t.payload40
17 files changed, 674 insertions, 11 deletions
diff --git a/doc/primary-expression.txt b/doc/primary-expression.txt
index 6db7edae..0fda76dd 100644
--- a/doc/primary-expression.txt
+++ b/doc/primary-expression.txt
@@ -285,3 +285,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 f2c5c1ad..fb52abfe 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 66f60e76..99b67644 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 1a63bd1e..169c2abc 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 00000000..ea7d322c
--- /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 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 <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>
@@ -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 <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 3f0ab0ac..9aadc33e 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 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 <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 00000000..e924e9bc
--- /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 00000000..d7d3a03c
--- /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 00000000..6049c664
--- /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 ]
+