]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: add ipsec (xfrm) expression
authorMáté Eckl <ecklm94@gmail.com>
Wed, 5 Sep 2018 09:16:44 +0000 (11:16 +0200)
committerFlorian Westphal <fw@strlen.de>
Fri, 21 Sep 2018 10:06:27 +0000 (12:06 +0200)
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 <ecklm94@gmail.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
17 files changed:
doc/primary-expression.txt
include/expression.h
include/json.h
include/linux/netfilter/nf_tables.h
include/xfrm.h [new file with mode: 0644]
src/Makefile.am
src/evaluate.c
src/json.c
src/netlink_delinearize.c
src/netlink_linearize.c
src/parser_bison.y
src/parser_json.c
src/scanner.l
src/xfrm.c [new file with mode: 0644]
tests/py/inet/ipsec.t [new file with mode: 0644]
tests/py/inet/ipsec.t.json [new file with mode: 0644]
tests/py/inet/ipsec.t.payload [new file with mode: 0644]

index 6db7edae3238a6a313035f4b708fdb8aa74f9229..0fda76dda11b048f3cf4ec6d14b8f26a1d137a9c 100644 (file)
@@ -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
+|=================================
index f2c5c1ad7a04a25effe00b3de1b046e00aa98a13..fb52abfea76eecd5c7971e94fb77a5af3dad3364 100644 (file)
@@ -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;
        };
 };
 
index 66f60e76aa83e15ab3913b60765bafbfcd6638ca..99b676446516b6b4a1c552195c838e4e075ca5a1 100644 (file)
@@ -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)
index 1a63bd1e32f455b54233c5cbbf63758743716917..169c2abcfcff6a90fa14f6186fba6e823fd85954 100644 (file)
@@ -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 (file)
index 0000000..ea7d322
--- /dev/null
@@ -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
index 8e69232fd0b70124f56563b78ed4682b14d31ee3..307bab108cca9d230ddebf146ca91099ee885f66 100644 (file)
@@ -43,6 +43,7 @@ libnftables_la_SOURCES =                      \
                rt.c                            \
                numgen.c                        \
                ct.c                            \
+               xfrm.c                          \
                netlink.c                       \
                netlink_linearize.c             \
                netlink_delinearize.c           \
index c8010852eecb2f349f739e0465e31c9e47d9fef5..ff36f576c11d0ec6d4944489c2e1a4618ceadf13 100644 (file)
@@ -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);
        }
index 1708f22dda4085406f0f18834762bac4f32bd409..0191a2ea7df0daf2395ee819df3e0077ce9cc031 100644 (file)
@@ -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";
index 6c5188cd9b5f282cb714464a2f45ff6774301e1d..0a6ebe05ca7ca7a05e111c89f639d5efa2d73493 100644 (file)
@@ -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)
index 0bd946a1cf80b424ca5c5c7c9e4c7779ae27df4e..0ac51bd0d710fdc5dd9ded64f1403081cb39823b 100644 (file)
@@ -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);
        }
index 5fd304a9381f8143f47638d67998be4f37e86aeb..1c68b4f4420e78eac6d1b0076ea6da3f7d197242 100644 (file)
@@ -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);
index 3f0ab0ac1993db21bd9d25046d1c0740bc64b6a6..9aadc33ed93a0543d3ac79ba9a468b31aa24f4db 100644 (file)
@@ -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 },
index 26e63b9bcc0cc9811ec1a0b4cce7a475c91914b1..4a143b1e59555f931fe05c857714d20bbd32cd6e 100644 (file)
@@ -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 (file)
index 0000000..0f5818c
--- /dev/null
@@ -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 (file)
index 0000000..e924e9b
--- /dev/null
@@ -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 (file)
index 0000000..d7d3a03
--- /dev/null
@@ -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 (file)
index 0000000..6049c66
--- /dev/null
@@ -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 ]
+