]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: add auto-dependencies for ipv4 icmp
authorFlorian Westphal <fw@strlen.de>
Tue, 8 Dec 2020 14:49:42 +0000 (15:49 +0100)
committerFlorian Westphal <fw@strlen.de>
Wed, 9 Dec 2020 17:33:53 +0000 (18:33 +0100)
The ICMP header has field values that are only exist
for certain types.

Mark the icmp proto 'type' field as a nextheader field
and add a new th description to store the icmp type
dependency.  This can later be re-used for other protocol
dependend definitions such as mptcp options -- which are all share the
same tcp option number and have a special 4 bit marker inside the
mptcp option space that tells how the remaining option looks like.

Signed-off-by: Florian Westphal <fw@strlen.de>
include/payload.h
include/proto.h
src/evaluate.c
src/payload.c
src/proto.c

index a914d23930e92b8ae87d42483a35d895b4fb6f95..7bbb19b936a9cbe97b810c653244ee7d3e20ac06 100644 (file)
@@ -15,6 +15,9 @@ struct eval_ctx;
 struct stmt;
 extern int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
                                  struct stmt **res);
+extern int payload_gen_icmp_dependency(struct eval_ctx *ctx,
+                                      const struct expr *expr,
+                                      struct stmt **res);
 extern int exthdr_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
                                 const struct proto_desc *dependency,
                                 enum proto_bases pb, struct stmt **res);
index 667650d67c97dd3c00f808804ffd6b1554abc888..f383291b5a79b319b69bd0a1f90f4dc8e9b4fb53 100644 (file)
@@ -25,6 +25,13 @@ enum proto_bases {
 extern const char *proto_base_names[];
 extern const char *proto_base_tokens[];
 
+enum icmp_hdr_field_type {
+       PROTO_ICMP_ANY = 0,
+       PROTO_ICMP_ECHO,        /* echo and reply */
+       PROTO_ICMP_MTU,         /* destination unreachable */
+       PROTO_ICMP_ADDRESS,     /* redirect */
+};
+
 /**
  * struct proto_hdr_template - protocol header field description
  *
@@ -33,6 +40,7 @@ extern const char *proto_base_tokens[];
  * @offset:    offset of the header field from base
  * @len:       length of header field
  * @meta_key:  special case: meta expression key
+ * @icmp_dep:  special case: icmp header dependency
  */
 struct proto_hdr_template {
        const char                      *token;
@@ -41,6 +49,7 @@ struct proto_hdr_template {
        uint16_t                        len;
        enum byteorder                  byteorder:8;
        enum nft_meta_keys              meta_key:8;
+       enum icmp_hdr_field_type        icmp_dep:8;
 };
 
 #define PROTO_HDR_TEMPLATE(__token, __dtype,  __byteorder, __offset, __len)\
@@ -170,7 +179,12 @@ extern const struct proto_desc *proto_dev_desc(uint16_t type);
  */
 struct proto_ctx {
        unsigned int                    debug_mask;
-       unsigned int                    family;
+       uint8_t                         family;
+       union {
+               struct {
+                       uint8_t                 type;
+               } icmp;
+       } th_dep;
        struct {
                struct location                 location;
                const struct proto_desc         *desc;
index 76b25b408d559558459641afaafbc146204eb775..3eb8e1bfc2c5443b048dd9abe11052b9773051a9 100644 (file)
@@ -706,7 +706,8 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr)
                        return -1;
 
                rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
-               return 0;
+               desc = ctx->pctx.protocol[base].desc;
+               goto check_icmp;
        }
 
        if (payload->payload.base == desc->base &&
@@ -724,7 +725,24 @@ static int __expr_evaluate_payload(struct eval_ctx *ctx, struct expr *expr)
         * if needed.
         */
        if (desc == payload->payload.desc) {
+               const struct proto_hdr_template *tmpl;
+
                payload->payload.offset += ctx->pctx.protocol[base].offset;
+check_icmp:
+               if (desc != &proto_icmp)
+                       return 0;
+
+               tmpl = expr->payload.tmpl;
+
+               if (!tmpl || !tmpl->icmp_dep)
+                       return 0;
+
+               if (payload_gen_icmp_dependency(ctx, expr, &nstmt) < 0)
+                       return -1;
+
+               if (nstmt)
+                       rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+
                return 0;
        }
        /* If we already have context and this payload is on the same
index e51c5797c589ac325a7a1bf4e0bd8da213cd4bfc..54b08f051dc079b9f58f0c3105b62da0ff85d7aa 100644 (file)
@@ -19,6 +19,7 @@
 #include <arpa/inet.h>
 #include <linux/netfilter.h>
 #include <linux/if_ether.h>
+#include <netinet/ip_icmp.h>
 
 #include <rule.h>
 #include <expression.h>
@@ -95,8 +96,16 @@ static void payload_expr_pctx_update(struct proto_ctx *ctx,
        base = ctx->protocol[left->payload.base].desc;
        desc = proto_find_upper(base, proto);
 
-       if (!desc)
+       if (!desc) {
+               if (base == &proto_icmp) {
+                       /* proto 0 is ECHOREPLY, just pretend its ECHO.
+                        * Not doing this would need an additional marker
+                        * bit to tell when icmp.type was set.
+                        */
+                       ctx->th_dep.icmp.type = proto ? proto : ICMP_ECHO;
+               }
                return;
+       }
 
        assert(desc->base <= PROTO_BASE_MAX);
        if (desc->base == base->base) {
@@ -662,6 +671,19 @@ void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr,
        }
 }
 
+static uint8_t icmp_dep_to_type(enum icmp_hdr_field_type t)
+{
+       switch (t) {
+       case PROTO_ICMP_ANY:
+               BUG("Invalid map for simple dependency");
+       case PROTO_ICMP_ECHO: return ICMP_ECHO;
+       case PROTO_ICMP_MTU: return ICMP_DEST_UNREACH;
+       case PROTO_ICMP_ADDRESS: return ICMP_REDIRECT;
+       }
+
+       BUG("Missing icmp type mapping");
+}
+
 /**
  * payload_expr_complete - fill in type information of a raw payload expr
  *
@@ -913,3 +935,108 @@ struct expr *payload_expr_join(const struct expr *e1, const struct expr *e2)
        expr->len            = e1->len + e2->len;
        return expr;
 }
+
+static struct stmt *
+__payload_gen_icmp_simple_dependency(struct eval_ctx *ctx, const struct expr *expr,
+                                    const struct datatype *icmp_type,
+                                    const struct proto_desc *desc,
+                                    uint8_t type)
+{
+       struct expr *left, *right, *dep;
+
+       left = payload_expr_alloc(&expr->location, desc, desc->protocol_key);
+       right = constant_expr_alloc(&expr->location, icmp_type,
+                                   BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE,
+                                   constant_data_ptr(type, BITS_PER_BYTE));
+
+       dep = relational_expr_alloc(&expr->location, OP_EQ, left, right);
+       return expr_stmt_alloc(&dep->location, dep);
+}
+
+static struct stmt *
+__payload_gen_icmp_echo_dependency(struct eval_ctx *ctx, const struct expr *expr,
+                                  uint8_t echo, uint8_t reply,
+                                  const struct datatype *icmp_type,
+                                  const struct proto_desc *desc)
+{
+       struct expr *left, *right, *dep, *set;
+
+       left = payload_expr_alloc(&expr->location, desc, desc->protocol_key);
+
+       set = set_expr_alloc(&expr->location, NULL);
+
+       right = constant_expr_alloc(&expr->location, icmp_type,
+                                   BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE,
+                                   constant_data_ptr(echo, BITS_PER_BYTE));
+       right = set_elem_expr_alloc(&expr->location, right);
+       compound_expr_add(set, right);
+
+       right = constant_expr_alloc(&expr->location, icmp_type,
+                                   BYTEORDER_BIG_ENDIAN, BITS_PER_BYTE,
+                                   constant_data_ptr(reply, BITS_PER_BYTE));
+       right = set_elem_expr_alloc(&expr->location, right);
+       compound_expr_add(set, right);
+
+       dep = relational_expr_alloc(&expr->location, OP_IMPLICIT, left, set);
+       return expr_stmt_alloc(&dep->location, dep);
+}
+
+int payload_gen_icmp_dependency(struct eval_ctx *ctx, const struct expr *expr,
+                               struct stmt **res)
+{
+       const struct proto_hdr_template *tmpl;
+       const struct proto_desc *desc;
+       struct stmt *stmt = NULL;
+       uint8_t type;
+
+       assert(expr->etype == EXPR_PAYLOAD);
+
+       tmpl = expr->payload.tmpl;
+       desc = expr->payload.desc;
+
+       switch (tmpl->icmp_dep) {
+       case PROTO_ICMP_ANY:
+               BUG("No dependency needed");
+               break;
+       case PROTO_ICMP_ECHO:
+               /* do not test ICMP_ECHOREPLY here: its 0 */
+               if (ctx->pctx.th_dep.icmp.type == ICMP_ECHO)
+                       goto done;
+
+               type = ICMP_ECHO;
+               if (ctx->pctx.th_dep.icmp.type)
+                       goto bad_proto;
+
+               stmt = __payload_gen_icmp_echo_dependency(ctx, expr,
+                                                         ICMP_ECHO, ICMP_ECHOREPLY,
+                                                         &icmp_type_type,
+                                                         desc);
+               break;
+       case PROTO_ICMP_MTU:
+       case PROTO_ICMP_ADDRESS:
+               type = icmp_dep_to_type(tmpl->icmp_dep);
+               if (ctx->pctx.th_dep.icmp.type == type)
+                       goto done;
+               if (ctx->pctx.th_dep.icmp.type)
+                       goto bad_proto;
+               stmt = __payload_gen_icmp_simple_dependency(ctx, expr,
+                                                           &icmp_type_type,
+                                                           desc, type);
+               break;
+       default:
+               BUG("Unhandled icmp dependency code");
+       }
+
+       ctx->pctx.th_dep.icmp.type = type;
+
+       if (stmt_evaluate(ctx, stmt) < 0)
+               return expr_error(ctx->msgs, expr,
+                                 "icmp dependency statement is invalid");
+done:
+       *res = stmt;
+       return 0;
+
+bad_proto:
+       return expr_error(ctx->msgs, expr, "incompatible icmp match: rule has %d, need %u",
+                         ctx->pctx.th_dep.icmp.type, type);
+}
index c42e8f517bae602ff2380ff552980051f2d1849b..d3371ac6597572a1a80a2f75f3d3fc13cc3e2ebd 100644 (file)
@@ -396,25 +396,34 @@ const struct datatype icmp_type_type = {
        .sym_tbl        = &icmp_type_tbl,
 };
 
-#define ICMPHDR_FIELD(__name, __member) \
-       HDR_FIELD(__name, struct icmphdr, __member)
+#define ICMPHDR_FIELD(__token, __member, __dep)                                        \
+       {                                                                       \
+               .token          = (__token),                                    \
+               .dtype          = &integer_type,                                \
+               .byteorder      = BYTEORDER_BIG_ENDIAN,                         \
+               .offset         = offsetof(struct icmphdr, __member) * 8,       \
+               .len            = field_sizeof(struct icmphdr, __member) * 8,   \
+               .icmp_dep       = (__dep),                                      \
+       }
+
 #define ICMPHDR_TYPE(__name, __type, __member) \
-       HDR_TYPE(__name, __type, struct icmphdr, __member)
+       HDR_TYPE(__name,  __type, struct icmphdr, __member)
 
 const struct proto_desc proto_icmp = {
        .name           = "icmp",
        .id             = PROTO_DESC_ICMP,
        .base           = PROTO_BASE_TRANSPORT_HDR,
+       .protocol_key   = ICMPHDR_TYPE,
        .checksum_key   = ICMPHDR_CHECKSUM,
        .checksum_type  = NFT_PAYLOAD_CSUM_INET,
        .templates      = {
                [ICMPHDR_TYPE]          = ICMPHDR_TYPE("type", &icmp_type_type, type),
                [ICMPHDR_CODE]          = ICMPHDR_TYPE("code", &icmp_code_type, code),
-               [ICMPHDR_CHECKSUM]      = ICMPHDR_FIELD("checksum", checksum),
-               [ICMPHDR_ID]            = ICMPHDR_FIELD("id", un.echo.id),
-               [ICMPHDR_SEQ]           = ICMPHDR_FIELD("sequence", un.echo.sequence),
-               [ICMPHDR_GATEWAY]       = ICMPHDR_FIELD("gateway", un.gateway),
-               [ICMPHDR_MTU]           = ICMPHDR_FIELD("mtu", un.frag.mtu),
+               [ICMPHDR_CHECKSUM]      = ICMPHDR_FIELD("checksum", checksum, PROTO_ICMP_ANY),
+               [ICMPHDR_ID]            = ICMPHDR_FIELD("id", un.echo.id, PROTO_ICMP_ECHO),
+               [ICMPHDR_SEQ]           = ICMPHDR_FIELD("sequence", un.echo.sequence, PROTO_ICMP_ECHO),
+               [ICMPHDR_GATEWAY]       = ICMPHDR_FIELD("gateway", un.gateway, PROTO_ICMP_ADDRESS),
+               [ICMPHDR_MTU]           = ICMPHDR_FIELD("mtu", un.frag.mtu, PROTO_ICMP_MTU),
        },
 };