]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: add gre support
authorPablo Neira Ayuso <pablo@netfilter.org>
Mon, 2 Jan 2023 14:36:33 +0000 (15:36 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 2 Jan 2023 14:36:33 +0000 (15:36 +0100)
GRE has a number of fields that are conditional based on flags,
which requires custom dependency code similar to icmp and icmpv6.
Matching on optional fields is not supported at this stage.

Since this is a layer 3 tunnel protocol, an implicit dependency on
NFT_META_L4PROTO for IPPROTO_GRE is generated. To achieve this, this
patch adds new infrastructure to remove an outer dependency based on
the inner protocol from delinearize path.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/parser.h
include/payload.h
include/proto.h
src/evaluate.c
src/netlink_delinearize.c
src/parser_bison.y
src/payload.c
src/proto.c
src/scanner.l

index 977fbb94fbd625a1e2bcaff7f27ccbc0a7090f44..1bd490f085d2d994e53a4758e996a0dcd44d18a4 100644 (file)
@@ -37,6 +37,7 @@ enum startcond_type {
        PARSER_SC_CT,
        PARSER_SC_COUNTER,
        PARSER_SC_ETH,
+       PARSER_SC_GRE,
        PARSER_SC_ICMP,
        PARSER_SC_IGMP,
        PARSER_SC_IP,
index aac553ee6b89ff6d64b43a91980b358e78435c6d..08e45f7f79e243ddb52fb0ce6d405183b1ecae05 100644 (file)
@@ -15,6 +15,8 @@ struct eval_ctx;
 struct stmt;
 extern int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
                                  struct stmt **res);
+int payload_gen_inner_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);
index 32e0744854a588a6047818711220b99c1b57a153..4b0c71467638636d3ebc85bf36a069c541703b38 100644 (file)
@@ -97,6 +97,7 @@ enum proto_desc_id {
        PROTO_DESC_VLAN,
        PROTO_DESC_ETHER,
        PROTO_DESC_VXLAN,
+       PROTO_DESC_GRE,
        __PROTO_DESC_MAX
 };
 #define PROTO_DESC_MAX (__PROTO_DESC_MAX - 1)
@@ -396,7 +397,20 @@ enum vxlan_hdr_fields {
        VXLANHDR_FLAGS,
 };
 
+struct grehdr {
+       uint16_t flags;
+       uint16_t protocol;
+};
+
+enum gre_hdr_fields {
+       GREHDR_INVALID,
+       GREHDR_VERSION,
+       GREHDR_FLAGS,
+       GREHDR_PROTOCOL,
+};
+
 extern const struct proto_desc proto_vxlan;
+extern const struct proto_desc proto_gre;
 
 extern const struct proto_desc proto_icmp;
 extern const struct proto_desc proto_igmp;
index e98ea424ef13df0d44fc08f0656919c296eb5fbc..21de18409c8734f072b79d8d919d0160b844f1d9 100644 (file)
@@ -881,24 +881,43 @@ static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **exprp)
 static int expr_evaluate_inner(struct eval_ctx *ctx, struct expr **exprp)
 {
        struct proto_ctx *pctx = eval_proto_ctx(ctx);
-       const struct proto_desc *desc;
+       const struct proto_desc *desc = NULL;
        struct expr *expr = *exprp;
        int ret;
 
-       desc = pctx->protocol[expr->payload.inner_desc->base - 1].desc;
-       if (!desc) {
-               return expr_error(ctx->msgs, expr,
-                                 "no transport protocol specified");
-       }
+       assert(expr->etype == EXPR_PAYLOAD);
 
-       if (proto_find_num(desc, expr->payload.inner_desc) < 0) {
-               return expr_error(ctx->msgs, expr,
-                                 "unexpected transport protocol %s",
-                                 desc->name);
+       pctx = eval_proto_ctx(ctx);
+       desc = pctx->protocol[PROTO_BASE_TRANSPORT_HDR].desc;
+
+       if (desc == NULL &&
+           expr->payload.inner_desc->base < PROTO_BASE_INNER_HDR) {
+               struct stmt *nstmt;
+
+               if (payload_gen_inner_dependency(ctx, expr, &nstmt) < 0)
+                       return -1;
+
+               rule_stmt_insert_at(ctx->rule, nstmt, ctx->stmt);
+
+               proto_ctx_update(pctx, PROTO_BASE_TRANSPORT_HDR, &expr->location, expr->payload.inner_desc);
        }
 
-       proto_ctx_update(pctx, PROTO_BASE_INNER_HDR, &expr->location,
-                        expr->payload.inner_desc);
+       if (expr->payload.inner_desc->base == PROTO_BASE_INNER_HDR) {
+               desc = pctx->protocol[expr->payload.inner_desc->base - 1].desc;
+               if (!desc) {
+                       return expr_error(ctx->msgs, expr,
+                                         "no transport protocol specified");
+               }
+
+               if (proto_find_num(desc, expr->payload.inner_desc) < 0) {
+                       return expr_error(ctx->msgs, expr,
+                                         "unexpected transport protocol %s",
+                                         desc->name);
+               }
+
+               proto_ctx_update(pctx, expr->payload.inner_desc->base, &expr->location,
+                                expr->payload.inner_desc);
+       }
 
        if (expr->payload.base != PROTO_BASE_INNER_HDR)
                ctx->inner_desc = expr->payload.inner_desc;
index 6be181e1f1cb920ff94b39bbfc39e73c7556acde..4f99dabbc310d04ad2540c2de0e6c9496c9d6ef5 100644 (file)
@@ -35,6 +35,11 @@ struct dl_proto_ctx *dl_proto_ctx(struct rule_pp_ctx *ctx)
        return ctx->dl;
 }
 
+static struct dl_proto_ctx *dl_proto_ctx_outer(struct rule_pp_ctx *ctx)
+{
+       return &ctx->_dl[0];
+}
+
 static int netlink_parse_expr(const struct nftnl_expr *nle,
                              struct netlink_parse_ctx *ctx);
 
@@ -1960,6 +1965,36 @@ struct stmt *netlink_parse_set_expr(const struct set *set,
        return pctx->stmt;
 }
 
+static bool meta_outer_may_dependency_kill(struct rule_pp_ctx *ctx,
+                                          const struct expr *expr)
+{
+       struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx);
+       struct stmt *stmt = dl_outer->pdctx.pdeps[expr->payload.inner_desc->base];
+       struct expr *dep;
+       uint8_t l4proto;
+
+       if (!stmt)
+               return false;
+
+       dep = stmt->expr;
+
+       if (dep->left->meta.key != NFT_META_L4PROTO)
+               return false;
+
+       l4proto = mpz_get_uint8(dep->right->value);
+
+       switch (l4proto) {
+       case IPPROTO_GRE:
+               if (expr->payload.inner_desc == &proto_gre)
+                       return true;
+               break;
+       default:
+               break;
+       }
+
+       return false;
+}
+
 static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp);
 
 static void payload_match_expand(struct rule_pp_ctx *ctx,
@@ -2003,6 +2038,12 @@ static void payload_match_expand(struct rule_pp_ctx *ctx,
                                nexpr->left->payload.tmpl = expr->left->payload.tmpl;
                        }
                        nexpr->left->payload.inner_desc = expr->left->payload.inner_desc;
+
+                       if (meta_outer_may_dependency_kill(ctx, expr->left)) {
+                               struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx);
+
+                               payload_dependency_release(&dl_outer->pdctx, expr->left->payload.inner_desc->base);
+                       }
                }
 
                if (payload_is_stacked(dl->pctx.protocol[base].desc, nexpr))
@@ -2747,6 +2788,13 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
                break;
        case EXPR_PAYLOAD:
                payload_expr_complete(expr, &dl->pctx);
+               if (expr->payload.inner_desc) {
+                       if (meta_outer_may_dependency_kill(ctx, expr)) {
+                               struct dl_proto_ctx *dl_outer = dl_proto_ctx_outer(ctx);
+
+                               payload_dependency_release(&dl_outer->pdctx, expr->payload.inner_desc->base);
+                       }
+               }
                payload_dependency_kill(&dl->pdctx, expr, dl->pctx.family);
                break;
        case EXPR_VALUE:
index c48283eee851d5165b092c6695b8f14034d60ed6..b29739951374eb40c49f8f2a6d763fb4e6da282b 100644 (file)
@@ -442,6 +442,8 @@ int nft_lex(void *, void *, void *);
 %token VXLAN                   "vxlan"
 %token VNI                     "vni"
 
+%token GRE                     "gre"
+
 %token SCTP                    "sctp"
 %token CHUNK                   "chunk"
 %token DATA                    "data"
@@ -902,9 +904,9 @@ int nft_lex(void *, void *, void *);
 %type <val>                    tcpopt_field_maxseg     tcpopt_field_mptcp      tcpopt_field_sack        tcpopt_field_tsopt     tcpopt_field_window
 %type <tcp_kind_field>         tcp_hdr_option_kind_and_field
 
-%type <expr>                   inner_eth_expr inner_inet_expr inner_expr vxlan_hdr_expr
-%destructor { expr_free($$); } inner_eth_expr inner_inet_expr inner_expr vxlan_hdr_expr
-%type <val>                    vxlan_hdr_field
+%type <expr>                   inner_eth_expr inner_inet_expr inner_expr vxlan_hdr_expr gre_hdr_expr
+%destructor { expr_free($$); } inner_eth_expr inner_inet_expr inner_expr vxlan_hdr_expr gre_hdr_expr
+%type <val>                    vxlan_hdr_field gre_hdr_field
 
 %type <stmt>                   optstrip_stmt
 %destructor { stmt_free($$); } optstrip_stmt
@@ -967,6 +969,7 @@ close_scope_export  : { scanner_pop_start_cond(nft->scanner, PARSER_SC_CMD_EXPORT
 close_scope_fib                : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_FIB); };
 close_scope_frag       : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_FRAG); };
 close_scope_fwd                : { scanner_pop_start_cond(nft->scanner, PARSER_SC_STMT_FWD); };
+close_scope_gre                : { scanner_pop_start_cond(nft->scanner, PARSER_SC_GRE); };
 close_scope_hash       : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HASH); };
 close_scope_hbh                : { scanner_pop_start_cond(nft->scanner, PARSER_SC_EXPR_HBH); };
 close_scope_ip         : { scanner_pop_start_cond(nft->scanner, PARSER_SC_IP); };
@@ -4881,6 +4884,13 @@ primary_rhs_expr :       symbol_expr             { $$ = $1; }
                                                         BYTEORDER_HOST_ENDIAN,
                                                         sizeof(data) * BITS_PER_BYTE, &data);
                        }
+                       |       GRE close_scope_gre
+                       {
+                               uint8_t data = IPPROTO_GRE;
+                               $$ = constant_expr_alloc(&@$, &inet_protocol_type,
+                                                        BYTEORDER_HOST_ENDIAN,
+                                                        sizeof(data) * BITS_PER_BYTE, &data);
+                       }
                        |       COMP    close_scope_comp
                        {
                                uint8_t data = IPPROTO_COMP;
@@ -5337,6 +5347,7 @@ payload_expr              :       payload_raw_expr
                        |       sctp_hdr_expr
                        |       th_hdr_expr
                        |       vxlan_hdr_expr
+                       |       gre_hdr_expr
                        ;
 
 payload_raw_expr       :       AT      payload_base_spec       COMMA   NUM     COMMA   NUM     close_scope_at
@@ -5633,6 +5644,22 @@ vxlan_hdr_field          :       VNI                     { $$ = VXLANHDR_VNI; }
                        |       FLAGS                   { $$ = VXLANHDR_FLAGS; }
                        ;
 
+gre_hdr_expr           :       GRE     gre_hdr_field   close_scope_gre
+                       {
+                               $$ = payload_expr_alloc(&@$, &proto_gre, $2);
+                       }
+                       |       GRE     close_scope_gre inner_inet_expr
+                       {
+                               $$ = $3;
+                               $$->payload.inner_desc = &proto_gre;
+                       }
+                       ;
+
+gre_hdr_field          :       HDRVERSION              { $$ = GREHDR_VERSION;  }
+                       |       FLAGS                   { $$ = GREHDR_FLAGS; }
+                       |       PROTOCOL                { $$ = GREHDR_PROTOCOL; }
+                       ;
+
 optstrip_stmt          :       RESET   TCP     OPTION  tcp_hdr_option_type     close_scope_tcp
                        {
                                $$ = optstrip_stmt_alloc(&@$, tcpopt_expr_alloc(&@$,
index 71b29a093622b3c631251707f48ba2e6e4e352fa..ed76623c9393898cc991347ca267d01508517bb9 100644 (file)
@@ -487,6 +487,14 @@ payload_gen_special_dependency(struct eval_ctx *ctx, const struct expr *expr)
                                        break;
                        }
 
+                       /* this tunnel protocol does not encapsulate an inner
+                        * link layer, use proto_netdev which relies on
+                        * NFT_META_PROTOCOL for dependencies.
+                        */
+                       if (expr->payload.inner_desc &&
+                           !(expr->payload.inner_desc->inner.flags & NFT_INNER_LL))
+                               desc = &proto_netdev;
+
                        desc_upper = &proto_ip6;
                        if (expr->payload.desc == &proto_icmp ||
                            expr->payload.desc == &proto_igmp)
@@ -1357,3 +1365,42 @@ bad_proto:
        return expr_error(ctx->msgs, expr, "incompatible icmp match: rule has %d, need %u",
                          pctx->th_dep.icmp.type, type);
 }
+
+int payload_gen_inner_dependency(struct eval_ctx *ctx, const struct expr *expr,
+                                struct stmt **res)
+{
+       struct proto_ctx *pctx = eval_proto_ctx(ctx);
+       const struct proto_hdr_template *tmpl;
+       const struct proto_desc *desc, *inner_desc;
+       struct expr *left, *right, *dep;
+       struct stmt *stmt = NULL;
+       int protocol;
+
+       assert(expr->etype == EXPR_PAYLOAD);
+
+       inner_desc = expr->payload.inner_desc;
+       desc = pctx->protocol[inner_desc->base - 1].desc;
+       if (desc == NULL)
+               desc = &proto_ip;
+
+       tmpl = &inner_desc->templates[0];
+       assert(tmpl);
+
+       protocol = proto_find_num(desc, inner_desc);
+       if (protocol < 0)
+                return expr_error(ctx->msgs, expr,
+                                  "conflicting protocols specified: %s vs. %s",
+                                  desc->name, inner_desc->name);
+
+       left = meta_expr_alloc(&expr->location, tmpl->meta_key);
+
+       right = constant_expr_alloc(&expr->location, tmpl->dtype,
+                                   tmpl->dtype->byteorder, tmpl->len,
+                                   constant_data_ptr(protocol, tmpl->len));
+
+       dep = relational_expr_alloc(&expr->location, OP_EQ, left, right);
+       stmt = expr_stmt_alloc(&dep->location, dep);
+
+       *res = stmt;
+       return 0;
+}
index e6c1100bb4486bd140340e1d75a04074b6eef459..3bb4ae74a439940bddc187eb64b81e61a9bafe49 100644 (file)
@@ -90,6 +90,7 @@ int proto_find_num(const struct proto_desc *base,
 
 static const struct proto_desc *inner_protocols[] = {
        &proto_vxlan,
+       &proto_gre,
 };
 
 const struct proto_desc *proto_find_inner(uint32_t type, uint32_t hdrsize,
@@ -771,6 +772,28 @@ const struct datatype ecn_type = {
        .sym_tbl        = &ecn_type_tbl,
 };
 
+#define GREHDR_TEMPLATE(__name, __dtype, __member) \
+       HDR_TEMPLATE(__name, __dtype, struct grehdr, __member)
+#define GREHDR_TYPE(__name, __member) \
+       GREHDR_TEMPLATE(__name, &ethertype_type, __member)
+
+const struct proto_desc proto_gre = {
+       .name           = "gre",
+       .id             = PROTO_DESC_GRE,
+       .base           = PROTO_BASE_TRANSPORT_HDR,
+       .templates      = {
+               [0] = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8),
+               [GREHDR_FLAGS]          = HDR_BITFIELD("flags", &integer_type, 0, 5),
+               [GREHDR_VERSION]        = HDR_BITFIELD("version", &integer_type, 13, 3),
+               [GREHDR_PROTOCOL]       = HDR_BITFIELD("protocol", &ethertype_type, 16, 16),
+       },
+       .inner          = {
+               .hdrsize        = sizeof(struct grehdr),
+               .flags          = NFT_INNER_NH | NFT_INNER_TH,
+               .type           = NFT_INNER_GENEVE + 1,
+       },
+};
+
 #define IPHDR_FIELD(__name, __member) \
        HDR_FIELD(__name, struct iphdr, __member)
 #define IPHDR_ADDR(__name, __member) \
@@ -794,6 +817,7 @@ const struct proto_desc proto_ip = {
                PROTO_LINK(IPPROTO_TCP,         &proto_tcp),
                PROTO_LINK(IPPROTO_DCCP,        &proto_dccp),
                PROTO_LINK(IPPROTO_SCTP,        &proto_sctp),
+               PROTO_LINK(IPPROTO_GRE,         &proto_gre),
        },
        .templates      = {
                [0]     = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8),
@@ -920,6 +944,7 @@ const struct proto_desc proto_ip6 = {
                PROTO_LINK(IPPROTO_ICMP,        &proto_icmp),
                PROTO_LINK(IPPROTO_IGMP,        &proto_igmp),
                PROTO_LINK(IPPROTO_ICMPV6,      &proto_icmp6),
+               PROTO_LINK(IPPROTO_GRE,         &proto_gre),
        },
        .templates      = {
                [0]     = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8),
@@ -985,6 +1010,7 @@ const struct proto_desc proto_inet_service = {
                PROTO_LINK(IPPROTO_ICMP,        &proto_icmp),
                PROTO_LINK(IPPROTO_IGMP,        &proto_igmp),
                PROTO_LINK(IPPROTO_ICMPV6,      &proto_icmp6),
+               PROTO_LINK(IPPROTO_GRE,         &proto_gre),
        },
        .templates      = {
                [0]     = PROTO_META_TEMPLATE("l4proto", &inet_protocol_type, NFT_META_L4PROTO, 8),
@@ -1226,6 +1252,7 @@ static const struct proto_desc *proto_definitions[PROTO_DESC_MAX + 1] = {
        [PROTO_DESC_VLAN]       = &proto_vlan,
        [PROTO_DESC_ETHER]      = &proto_eth,
        [PROTO_DESC_VXLAN]      = &proto_vxlan,
+       [PROTO_DESC_GRE]        = &proto_gre,
 };
 
 const struct proto_desc *proto_find_desc(enum proto_desc_id desc_id)
index 61a49286376d18fcdd59fedb33d0e0603a756587..3d9888ab674700ef64c64d006eade2b3dce25070 100644 (file)
@@ -201,6 +201,7 @@ addrstring  ({macaddr}|{ip4addr}|{ip6addr})
 %s SCANSTATE_CT
 %s SCANSTATE_COUNTER
 %s SCANSTATE_ETH
+%s SCANSTATE_GRE
 %s SCANSTATE_ICMP
 %s SCANSTATE_IGMP
 %s SCANSTATE_IP
@@ -492,7 +493,7 @@ addrstring  ({macaddr}|{ip4addr}|{ip6addr})
 }
 
 "ip"                   { scanner_push_start_cond(yyscanner, SCANSTATE_IP); return IP; }
-<SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_EXPR_OSF>{
+<SCANSTATE_IP,SCANSTATE_IP6,SCANSTATE_EXPR_OSF,SCANSTATE_GRE>{
        "version"               { return HDRVERSION; }
 }
 <SCANSTATE_EXPR_AH,SCANSTATE_EXPR_DST,SCANSTATE_EXPR_HBH,SCANSTATE_EXPR_MH,SCANSTATE_EXPR_RT,SCANSTATE_IP>{
@@ -509,7 +510,7 @@ addrstring  ({macaddr}|{ip4addr}|{ip6addr})
 <SCANSTATE_EXPR_OSF,SCANSTATE_IP>{
        "ttl"                   { return TTL; }
 }
-<SCANSTATE_CT,SCANSTATE_IP,SCANSTATE_META,SCANSTATE_TYPE>"protocol"            { return PROTOCOL; }
+<SCANSTATE_CT,SCANSTATE_IP,SCANSTATE_META,SCANSTATE_TYPE,SCANSTATE_GRE>"protocol"              { return PROTOCOL; }
 <SCANSTATE_EXPR_MH,SCANSTATE_EXPR_UDP,SCANSTATE_EXPR_UDPLITE,SCANSTATE_ICMP,SCANSTATE_IGMP,SCANSTATE_IP,SCANSTATE_SCTP,SCANSTATE_TCP>{
        "checksum"              { return CHECKSUM; }
 }
@@ -624,6 +625,8 @@ addrstring  ({macaddr}|{ip4addr}|{ip6addr})
 "vxlan"                        { return VXLAN; }
 "vni"                  { return VNI; }
 
+"gre"                  { scanner_push_start_cond(yyscanner, SCANSTATE_GRE); return GRE; }
+
 "tcp"                  { scanner_push_start_cond(yyscanner, SCANSTATE_TCP); return TCP; }
 
 "dccp"                 { scanner_push_start_cond(yyscanner, SCANSTATE_EXPR_DCCP); return DCCP; }