From 226a0e072d5c1edeb53cb61b959b011168c5c29a Mon Sep 17 00:00:00 2001 From: Stephen Suryaputra Date: Wed, 3 Jul 2019 20:30:52 -0400 Subject: exthdr: add support for matching IPv4 options Add capability to have rules matching IPv4 options. This is developed mainly to support dropping of IP packets with loose and/or strict source route route options. Signed-off-by: Stephen Suryaputra Signed-off-by: Pablo Neira Ayuso --- src/Makefile.am | 1 + src/evaluate.c | 17 +++++++++++++++ src/exthdr.c | 22 ++++++++++++++++++-- src/json.c | 8 +++++++ src/parser_bison.y | 31 +++++++++++++++++++++++++++ src/parser_json.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/payload.c | 4 ++++ src/scanner.l | 8 +++++++ 8 files changed, 150 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 9ad7e1f2..e2b53139 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -40,6 +40,7 @@ libnftables_la_SOURCES = \ exthdr.c \ fib.c \ hash.c \ + ipopt.c \ meta.c \ rt.c \ numgen.c \ diff --git a/src/evaluate.c b/src/evaluate.c index 19c2d4c6..8086f750 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -513,6 +513,20 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) totlen, max_tcpoptlen); break; } + case NFT_EXTHDR_OP_IPV4: { + static const unsigned int max_ipoptlen = 40 * BITS_PER_BYTE; + unsigned int totlen = 0; + + totlen += expr->exthdr.tmpl->offset; + totlen += expr->exthdr.tmpl->len; + totlen += expr->exthdr.offset; + + if (totlen > max_ipoptlen) + return expr_error(ctx->msgs, expr, + "offset and size %u exceeds max ip option len (%u)", + totlen, max_ipoptlen); + break; + } default: break; } @@ -537,6 +551,9 @@ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) dependency = &proto_tcp; pb = PROTO_BASE_TRANSPORT_HDR; break; + case NFT_EXTHDR_OP_IPV4: + dependency = &proto_ip; + break; case NFT_EXTHDR_OP_IPV6: default: dependency = &proto_ip6; diff --git a/src/exthdr.c b/src/exthdr.c index c9c2bf50..e1ec6f3d 100644 --- a/src/exthdr.c +++ b/src/exthdr.c @@ -38,6 +38,11 @@ static void exthdr_expr_print(const struct expr *expr, struct output_ctx *octx) if (offset) nft_print(octx, "%d", offset); nft_print(octx, " %s", expr->exthdr.tmpl->token); + } else if (expr->exthdr.op == NFT_EXTHDR_OP_IPV4) { + nft_print(octx, "ip option %s", expr->exthdr.desc->name); + if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) + return; + nft_print(octx, " %s", expr->exthdr.tmpl->token); } else { if (expr->exthdr.flags & NFT_EXTHDR_F_PRESENT) nft_print(octx, "exthdr %s", expr->exthdr.desc->name); @@ -172,6 +177,8 @@ void exthdr_init_raw(struct expr *expr, uint8_t type, assert(expr->etype == EXPR_EXTHDR); if (op == NFT_EXTHDR_OP_TCPOPT) return tcpopt_init_raw(expr, type, offset, len, flags); + if (op == NFT_EXTHDR_OP_IPV4) + return ipopt_init_raw(expr, type, offset, len, flags, true); expr->len = len; expr->exthdr.flags = flags; @@ -222,7 +229,8 @@ bool exthdr_find_template(struct expr *expr, const struct expr *mask, unsigned i { unsigned int off, mask_offset, mask_len; - if (expr->exthdr.tmpl != &exthdr_unknown_template) + if (expr->exthdr.op != NFT_EXTHDR_OP_IPV4 && + expr->exthdr.tmpl != &exthdr_unknown_template) return false; /* In case we are handling tcp options instead of the default ipv6 @@ -237,8 +245,18 @@ bool exthdr_find_template(struct expr *expr, const struct expr *mask, unsigned i off = expr->exthdr.offset; off += round_up(mask->len, BITS_PER_BYTE) - mask_len; + /* Handle ip options after the offset and mask have been calculated. */ + if (expr->exthdr.op == NFT_EXTHDR_OP_IPV4) { + if (ipopt_find_template(expr, off, mask_len - mask_offset)) { + *shift = mask_offset; + return true; + } else { + return false; + } + } + exthdr_init_raw(expr, expr->exthdr.desc->type, - off, mask_len - mask_offset, NFT_EXTHDR_OP_IPV6, 0); + off, mask_len - mask_offset, expr->exthdr.op, 0); /* still failed to find a template... Bug. */ if (expr->exthdr.tmpl == &exthdr_unknown_template) diff --git a/src/json.c b/src/json.c index 1484c21b..1006d7bb 100644 --- a/src/json.c +++ b/src/json.c @@ -634,6 +634,14 @@ json_t *exthdr_expr_json(const struct expr *expr, struct output_ctx *octx) return json_pack("{s:o}", "tcp option", root); } + if (expr->exthdr.op == NFT_EXTHDR_OP_IPV4) { + root = json_pack("{s:s}", "name", desc); + + if (!is_exists) + json_object_set_new(root, "field", json_string(field)); + + return json_pack("{s:o}", "ip option", root); + } root = json_pack("{s:s}", "name", desc); diff --git a/src/parser_bison.y b/src/parser_bison.y index 153ef326..a4905f2a 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -309,6 +309,14 @@ int nft_lex(void *, void *, void *); %token PROTOCOL "protocol" %token CHECKSUM "checksum" +%token PTR "ptr" +%token VALUE "value" + +%token LSRR "lsrr" +%token RR "rr" +%token SSRR "ssrr" +%token RA "ra" + %token ICMP "icmp" %token CODE "code" %token SEQUENCE "seq" @@ -698,6 +706,7 @@ int nft_lex(void *, void *, void *); %type ip_hdr_expr icmp_hdr_expr igmp_hdr_expr numgen_expr hash_expr %destructor { expr_free($$); } ip_hdr_expr icmp_hdr_expr igmp_hdr_expr numgen_expr hash_expr %type ip_hdr_field icmp_hdr_field igmp_hdr_field +%type ip_option_type ip_option_field %type ip6_hdr_expr icmp6_hdr_expr %destructor { expr_free($$); } ip6_hdr_expr icmp6_hdr_expr %type ip6_hdr_field icmp6_hdr_field @@ -4249,6 +4258,15 @@ ip_hdr_expr : IP ip_hdr_field { $$ = payload_expr_alloc(&@$, &proto_ip, $2); } + | IP OPTION ip_option_type ip_option_field + { + $$ = ipopt_expr_alloc(&@$, $3, $4, 0); + } + | IP OPTION ip_option_type + { + $$ = ipopt_expr_alloc(&@$, $3, IPOPT_FIELD_TYPE, 0); + $$->exthdr.flags = NFT_EXTHDR_F_PRESENT; + } ; ip_hdr_field : HDRVERSION { $$ = IPHDR_VERSION; } @@ -4265,6 +4283,19 @@ ip_hdr_field : HDRVERSION { $$ = IPHDR_VERSION; } | DADDR { $$ = IPHDR_DADDR; } ; +ip_option_type : LSRR { $$ = IPOPT_LSRR; } + | RR { $$ = IPOPT_RR; } + | SSRR { $$ = IPOPT_SSRR; } + | RA { $$ = IPOPT_RA; } + ; + +ip_option_field : TYPE { $$ = IPOPT_FIELD_TYPE; } + | LENGTH { $$ = IPOPT_FIELD_LENGTH; } + | VALUE { $$ = IPOPT_FIELD_VALUE; } + | PTR { $$ = IPOPT_FIELD_PTR; } + | ADDR { $$ = IPOPT_FIELD_ADDR_0; } + ; + icmp_hdr_expr : ICMP icmp_hdr_field { $$ = payload_expr_alloc(&@$, &proto_icmp, $2); diff --git a/src/parser_json.c b/src/parser_json.c index 30b17173..f701ebdf 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -587,6 +587,66 @@ static struct expr *json_parse_tcp_option_expr(struct json_ctx *ctx, return tcpopt_expr_alloc(int_loc, descval, fieldval); } +static int json_parse_ip_option_type(const char *name, int *val) +{ + unsigned int i; + + for (i = 0; i < array_size(ipopt_protocols); i++) { + if (ipopt_protocols[i] && + !strcmp(ipopt_protocols[i]->name, name)) { + if (val) + *val = i; + return 0; + } + } + return 1; +} + +static int json_parse_ip_option_field(int type, const char *name, int *val) +{ + unsigned int i; + const struct exthdr_desc *desc = ipopt_protocols[type]; + + for (i = 0; i < array_size(desc->templates); i++) { + if (desc->templates[i].token && + !strcmp(desc->templates[i].token, name)) { + if (val) + *val = i; + return 0; + } + } + return 1; +} + +static struct expr *json_parse_ip_option_expr(struct json_ctx *ctx, + const char *type, json_t *root) +{ + const char *desc, *field; + int descval, fieldval; + struct expr *expr; + + if (json_unpack_err(ctx, root, "{s:s}", "name", &desc)) + return NULL; + + if (json_parse_ip_option_type(desc, &descval)) { + json_error(ctx, "Unknown ip option name '%s'.", desc); + return NULL; + } + + if (json_unpack(root, "{s:s}", "field", &field)) { + expr = ipopt_expr_alloc(int_loc, descval, + IPOPT_FIELD_TYPE, 0); + expr->exthdr.flags = NFT_EXTHDR_F_PRESENT; + + return expr; + } + if (json_parse_ip_option_field(descval, field, &fieldval)) { + json_error(ctx, "Unknown ip option field '%s'.", field); + return NULL; + } + return ipopt_expr_alloc(int_loc, descval, fieldval, 0); +} + static const struct exthdr_desc *exthdr_lookup_byname(const char *name) { const struct exthdr_desc *exthdr_tbl[] = { @@ -1291,6 +1351,7 @@ static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root) { "payload", json_parse_payload_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP }, { "exthdr", json_parse_exthdr_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP }, { "tcp option", json_parse_tcp_option_expr, CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES }, + { "ip option", json_parse_ip_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 }, diff --git a/src/payload.c b/src/payload.c index 7e4f935b..3bf1ecc7 100644 --- a/src/payload.c +++ b/src/payload.c @@ -542,6 +542,10 @@ void exthdr_dependency_kill(struct payload_dep_ctx *ctx, struct expr *expr, if (payload_dependency_exists(ctx, PROTO_BASE_NETWORK_HDR)) payload_dependency_release(ctx); break; + case NFT_EXTHDR_OP_IPV4: + if (payload_dependency_exists(ctx, PROTO_BASE_NETWORK_HDR)) + payload_dependency_release(ctx); + break; default: break; } diff --git a/src/scanner.l b/src/scanner.l index b46b25e7..7f6c0431 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -402,6 +402,14 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "protocol" { return PROTOCOL; } "checksum" { return CHECKSUM; } +"lsrr" { return LSRR; } +"rr" { return RR; } +"ssrr" { return SSRR; } +"ra" { return RA; } + +"value" { return VALUE; } +"ptr" { return PTR; } + "echo" { return ECHO; } "eol" { return EOL; } "maxseg" { return MAXSEG; } -- cgit v1.2.3