diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/evaluate.c | 35 | ||||
-rw-r--r-- | src/exthdr.c | 31 | ||||
-rw-r--r-- | src/netlink_delinearize.c | 3 | ||||
-rw-r--r-- | src/netlink_linearize.c | 3 | ||||
-rw-r--r-- | src/parser_bison.y | 46 | ||||
-rw-r--r-- | src/scanner.l | 1 | ||||
-rw-r--r-- | src/tcpopt.c | 269 |
8 files changed, 378 insertions, 11 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index c6586f5f..99eef7bb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -55,6 +55,7 @@ nft_SOURCES = main.c \ services.c \ mergesort.c \ scanner.l \ + tcpopt.c \ parser_bison.y if BUILD_CLI diff --git a/src/evaluate.c b/src/evaluate.c index 0e02548c..4817a55c 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -438,6 +438,26 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) expr->len % BITS_PER_BYTE != 0) expr_evaluate_bits(ctx, exprp); + switch (expr->exthdr.op) { + case NFT_EXTHDR_OP_TCPOPT: { + static const uint8_t tcphdrlen = 20 * BITS_PER_BYTE; + static const unsigned int max_tcpoptlen = 15 * 4 * BITS_PER_BYTE - tcphdrlen; + unsigned int totlen = 0; + + totlen += expr->exthdr.tmpl->offset; + totlen += expr->exthdr.tmpl->len; + totlen += expr->exthdr.offset; + + if (totlen > max_tcpoptlen) + return expr_error(ctx->msgs, expr, + "offset and size %u exceeds max tcp headerlen (%u)", + totlen, max_tcpoptlen); + break; + } + default: + break; + } + return 0; } @@ -448,11 +468,24 @@ static int __expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) */ static int expr_evaluate_exthdr(struct eval_ctx *ctx, struct expr **exprp) { - const struct proto_desc *base, *dependency = &proto_ip6; + const struct proto_desc *base, *dependency = NULL; enum proto_bases pb = PROTO_BASE_NETWORK_HDR; struct expr *expr = *exprp; struct stmt *nstmt; + switch (expr->exthdr.op) { + case NFT_EXTHDR_OP_TCPOPT: + dependency = &proto_tcp; + pb = PROTO_BASE_TRANSPORT_HDR; + break; + case NFT_EXTHDR_OP_IPV6: + default: + dependency = &proto_ip6; + break; + } + + assert(dependency); + base = ctx->pctx.protocol[pb].desc; if (base == dependency) return __expr_evaluate_exthdr(ctx, exprp); diff --git a/src/exthdr.c b/src/exthdr.c index 45b1b690..cfc6bb60 100644 --- a/src/exthdr.c +++ b/src/exthdr.c @@ -24,13 +24,29 @@ static void exthdr_expr_print(const struct expr *expr) { - printf("%s %s", expr->exthdr.desc->name, expr->exthdr.tmpl->token); + if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) { + /* Offset calcualtion is a bit hacky at this point. + * There might be an tcp option one day with another + * multiplicator + */ + unsigned int offset = expr->exthdr.offset / 64; + char buf[3] = {0}; + + if (offset) + snprintf(buf, sizeof buf, " %d", offset); + printf("tcp option %s%s %s", expr->exthdr.desc->name, buf, + expr->exthdr.tmpl->token); + } + else + printf("%s %s", expr->exthdr.desc->name, + expr->exthdr.tmpl->token); } static bool exthdr_expr_cmp(const struct expr *e1, const struct expr *e2) { return e1->exthdr.desc == e2->exthdr.desc && - e1->exthdr.tmpl == e2->exthdr.tmpl; + e1->exthdr.tmpl == e2->exthdr.tmpl && + e1->exthdr.op == e2->exthdr.op; } static void exthdr_expr_clone(struct expr *new, const struct expr *expr) @@ -38,9 +54,10 @@ static void exthdr_expr_clone(struct expr *new, const struct expr *expr) new->exthdr.desc = expr->exthdr.desc; new->exthdr.tmpl = expr->exthdr.tmpl; new->exthdr.offset = expr->exthdr.offset; + new->exthdr.op = expr->exthdr.op; } -static const struct expr_ops exthdr_expr_ops = { +const struct expr_ops exthdr_expr_ops = { .type = EXPR_EXTHDR, .name = "exthdr", .print = exthdr_expr_print, @@ -86,6 +103,8 @@ void exthdr_init_raw(struct expr *expr, uint8_t type, unsigned int i; assert(expr->ops->type == EXPR_EXTHDR); + if (op == NFT_EXTHDR_OP_TCPOPT) + return tcpopt_init_raw(expr, type, offset, len); expr->len = len; expr->exthdr.offset = offset; @@ -117,6 +136,12 @@ bool exthdr_find_template(struct expr *expr, const struct expr *mask, unsigned i if (expr->exthdr.tmpl != &exthdr_unknown_template) return false; + /* In case we are handling tcp options instead of the default ipv6 + * extension headers. + */ + if (expr->exthdr.op == NFT_EXTHDR_OP_TCPOPT) + return tcpopt_find_template(expr, mask, shift); + mask_offset = mpz_scan1(mask->value, 0); mask_len = mask_length(mask); diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c index f21d2d56..1e94af49 100644 --- a/src/netlink_delinearize.c +++ b/src/netlink_delinearize.c @@ -507,7 +507,7 @@ static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx, type = nftnl_expr_get_u8(nle, NFTNL_EXPR_EXTHDR_TYPE); offset = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET) * BITS_PER_BYTE; len = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_LEN) * BITS_PER_BYTE; - op = NFT_EXTHDR_OP_IPV6; + op = nftnl_expr_get_u32(nle, NFTNL_EXPR_EXTHDR_OP); expr = exthdr_expr_alloc(loc, NULL, 0); exthdr_init_raw(expr, type, offset, len, op); @@ -1221,6 +1221,7 @@ static const struct { { .name = "numgen", .parse = netlink_parse_numgen }, { .name = "hash", .parse = netlink_parse_hash }, { .name = "fib", .parse = netlink_parse_fib }, + { .name = "tcpopt", .parse = netlink_parse_exthdr }, }; static int netlink_parse_expr(const struct nftnl_expr *nle, diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c index 056f1131..8849b0e4 100644 --- a/src/netlink_linearize.c +++ b/src/netlink_linearize.c @@ -162,7 +162,7 @@ static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx, const struct expr *expr, enum nft_registers dreg) { - unsigned int offset = expr->exthdr.tmpl->offset; + unsigned int offset = expr->exthdr.tmpl->offset + expr->exthdr.offset; struct nftnl_expr *nle; nle = alloc_nft_expr("exthdr"); @@ -172,6 +172,7 @@ static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx, nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_OFFSET, offset / BITS_PER_BYTE); nftnl_expr_set_u32(nle, NFTNL_EXPR_EXTHDR_LEN, div_round_up(expr->len, BITS_PER_BYTE)); + nftnl_expr_set_u8(nle, NFTNL_EXPR_EXTHDR_OP, expr->exthdr.op); nftnl_rule_add_expr(ctx->nlr, nle); } diff --git a/src/parser_bison.y b/src/parser_bison.y index d543e3ea..b295bfde 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -308,6 +308,7 @@ static void location_update(struct location *loc, struct location *rhs, int n) %token DOFF "doff" %token WINDOW "window" %token URGPTR "urgptr" +%token OPTION "option" %token DCCP "dccp" @@ -428,8 +429,8 @@ static void location_update(struct location *loc, struct location *rhs, int n) %token NOTRACK "notrack" -%type <string> identifier type_identifier string comment_spec -%destructor { xfree($$); } identifier type_identifier string comment_spec +%type <string> identifier type_identifier string comment_spec tcp_option_name tcp_option_field +%destructor { xfree($$); } identifier type_identifier string comment_spec tcp_option_name tcp_option_field %type <val> time_spec quota_used @@ -581,9 +582,9 @@ static void location_update(struct location *loc, struct location *rhs, int n) %type <expr> auth_hdr_expr esp_hdr_expr comp_hdr_expr %destructor { expr_free($$); } auth_hdr_expr esp_hdr_expr comp_hdr_expr %type <val> auth_hdr_field esp_hdr_field comp_hdr_field -%type <expr> udp_hdr_expr udplite_hdr_expr tcp_hdr_expr -%destructor { expr_free($$); } udp_hdr_expr udplite_hdr_expr tcp_hdr_expr -%type <val> udp_hdr_field udplite_hdr_field tcp_hdr_field +%type <expr> udp_hdr_expr udplite_hdr_expr +%destructor { expr_free($$); } udp_hdr_expr udplite_hdr_expr +%type <val> udp_hdr_field udplite_hdr_field %type <expr> dccp_hdr_expr sctp_hdr_expr %destructor { expr_free($$); } dccp_hdr_expr sctp_hdr_expr %type <val> dccp_hdr_field sctp_hdr_field @@ -600,6 +601,9 @@ static void location_update(struct location *loc, struct location *rhs, int n) %destructor { expr_free($$); } mh_hdr_expr %type <val> mh_hdr_field +%type <expr> tcp_hdr_optexpr +%destructor { expr_free($$); } tcp_hdr_optexpr + %type <expr> meta_expr %destructor { expr_free($$); } meta_expr %type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type @@ -626,6 +630,10 @@ static void location_update(struct location *loc, struct location *rhs, int n) %type <quota> quota_config %destructor { xfree($$); } quota_config +%type <expr> tcp_hdr_expr +%destructor { expr_free($$); } tcp_hdr_expr +%type <val> tcp_hdr_field + %% input : /* empty */ @@ -3232,6 +3240,7 @@ exthdr_expr : hbh_hdr_expr | frag_hdr_expr | dst_hdr_expr | mh_hdr_expr + | tcp_hdr_optexpr ; hbh_hdr_expr : HBH hbh_hdr_field @@ -3314,4 +3323,31 @@ mh_hdr_field : NEXTHDR { $$ = MHHDR_NEXTHDR; } | CHECKSUM { $$ = MHHDR_CHECKSUM; } ; +tcp_option_name : STRING { $$ = $1; } + | WINDOW { $$ = xstrdup("window"); } + ; + +tcp_option_field : STRING { $$ = $1; } + | LENGTH { $$ = xstrdup("length"); } + | SIZE { $$ = xstrdup("size"); } + ; + +tcp_hdr_optexpr : TCP OPTION tcp_option_name tcp_option_field + { + $$ = tcpopt_expr_alloc(&@$, $3, 0, $4); + } + | TCP OPTION STRING NUM tcp_option_field + { + if (strcmp($3, "sack")) { + erec_queue(error(&@2, "tcp: number (%d) can only be used with sack option", $4), state->msgs); + YYERROR; + } + + if ($4 > 3) { + erec_queue(error(&@2, "tcp: option block (%d) too large (0-3)", $4), state->msgs); + YYERROR; + } + $$ = tcpopt_expr_alloc(&@$, $3, $4, $5); + } + ; %% diff --git a/src/scanner.l b/src/scanner.l index d0d25ea9..922d8ec8 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -411,6 +411,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "doff" { return DOFF; } "window" { return WINDOW; } "urgptr" { return URGPTR; } +"option" { return OPTION; } "dccp" { return DCCP; } diff --git a/src/tcpopt.c b/src/tcpopt.c new file mode 100644 index 00000000..e6f92bc6 --- /dev/null +++ b/src/tcpopt.c @@ -0,0 +1,269 @@ +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <netinet/tcp.h> + +#include <utils.h> +#include <headers.h> +#include <expression.h> +#include <tcpopt.h> + +/* We do not need to export these enums, because the tcpopts are parsed at + * runtime and not by bison. + */ +enum tcpopt_eol_hdr_fields { + TCPOPT_EOLHDR_KIND, +}; + +enum tcpopt_nop_hdr_fields { + TCPOPT_NOPHDR_KIND, +}; + +enum tcpopt_maxseg_hdr_fields { + TCPOPT_MAXSEGHDR_KIND, + TCPOPT_MAXSEGHDR_LENGTH, + TCPOPT_MAXSEGHDR_SIZE, +}; + +enum tcpopt_window_hdr_fields { + TCPOPT_WINDOWHDR_KIND, + TCPOPT_WINDOWHDR_LENGTH, + TCPOPT_WINDOWHDR_COUNT, +}; + +enum tcpopt_sack_permitted_hdr_fields { + TCPOPT_SACKPERMHDR_KIND, + TCPOPT_SACKPERMHDR_LENGTH, +}; + +enum tcpopt_sack_hdr_fields { + TCPOPT_SACKHDR_KIND, + TCPOPT_SACKHDR_LENGTH, + TCPOPT_SACKHDR_LEFT, + TCPOPT_SACKHDR_RIGHT, +}; + +enum tcpopt_timestamp_hdr_fields { + TCPOPT_TIMESTAMPSHDR_KIND, + TCPOPT_TIMESTAMPSHDR_LENGTH, + TCPOPT_TIMESTAMPSHDR_TSVAL, + TCPOPT_TIMESTAMPSHDR_TSECR, +}; + +static const struct proto_hdr_template tcpopt_unknown_template = + PROTO_HDR_TEMPLATE("unknown", &invalid_type, BYTEORDER_INVALID, 0, 0); + +#define PHT(__token, __offset, __len) \ + PROTO_HDR_TEMPLATE(__token, &integer_type, BYTEORDER_BIG_ENDIAN, \ + __offset, __len) +const struct exthdr_desc tcpopt_eol = { + .name = "eol", + .type = TCPOPT_EOL, + .templates = { + [TCPOPT_EOLHDR_KIND] = PHT("kind", 0, 8), + }, +}; + +const struct exthdr_desc tcpopt_nop = { + .name = "noop", + .type = TCPOPT_NOP, + .templates = { + [TCPOPT_NOPHDR_KIND] = PHT("kind", 0, 8), + }, +}; + +const struct exthdr_desc tcptopt_maxseg = { + .name = "maxseg", + .type = TCPOPT_MAXSEG, + .templates = { + [TCPOPT_MAXSEGHDR_KIND] = PHT("kind", 0, 8), + [TCPOPT_MAXSEGHDR_LENGTH] = PHT("length", 8, 8), + [TCPOPT_MAXSEGHDR_SIZE] = PHT("size", 16, 16), + }, +}; + +const struct exthdr_desc tcpopt_window = { + .name = "window", + .type = TCPOPT_WINDOW, + .templates = { + [TCPOPT_WINDOWHDR_KIND] = PHT("kind", 0, 8), + [TCPOPT_WINDOWHDR_LENGTH] = PHT("length", 8, 8), + [TCPOPT_WINDOWHDR_COUNT] = PHT("count", 16, 8), + }, +}; + +const struct exthdr_desc tcpopt_sack_permitted = { + .name = "sack_permitted", + .type = TCPOPT_SACK_PERMITTED, + .templates = { + [TCPOPT_SACKPERMHDR_KIND] = PHT("kind", 0, 8), + [TCPOPT_SACKPERMHDR_LENGTH] = PHT("length", 8, 8), + }, +}; + +const struct exthdr_desc tcpopt_sack = { + .name = "sack", + .type = TCPOPT_SACK, + .templates = { + [TCPOPT_SACKHDR_KIND] = PHT("kind", 0, 8), + [TCPOPT_SACKHDR_LENGTH] = PHT("length", 8, 8), + [TCPOPT_SACKHDR_LEFT] = PHT("left", 16, 32), + [TCPOPT_SACKHDR_RIGHT] = PHT("right", 48, 32), + }, +}; + +const struct exthdr_desc tcpopt_timestamp = { + .name = "timestamp", + .type = TCPOPT_TIMESTAMP, + .templates = { + [TCPOPT_TIMESTAMPSHDR_KIND] = PHT("kind", 0, 8), + [TCPOPT_TIMESTAMPSHDR_LENGTH] = PHT("length", 8, 8), + [TCPOPT_TIMESTAMPSHDR_TSVAL] = PHT("tsval", 16, 32), + [TCPOPT_TIMESTAMPSHDR_TSECR] = PHT("tsecr", 48, 32), + }, +}; +#undef PHT + +#define TCPOPT_OBSOLETE ((struct exthdr_desc *)NULL) +#define TCPOPT_ECHO 6 +#define TCPOPT_ECHO_REPLY 7 +const struct exthdr_desc *tcpopt_protocols[] = { + [TCPOPT_EOL] = &tcpopt_eol, + [TCPOPT_NOP] = &tcpopt_nop, + [TCPOPT_MAXSEG] = &tcptopt_maxseg, + [TCPOPT_WINDOW] = &tcpopt_window, + [TCPOPT_SACK_PERMITTED] = &tcpopt_sack_permitted, + [TCPOPT_SACK] = &tcpopt_sack, + [TCPOPT_ECHO] = TCPOPT_OBSOLETE, + [TCPOPT_ECHO_REPLY] = TCPOPT_OBSOLETE, + [TCPOPT_TIMESTAMP] = &tcpopt_timestamp, +}; + +static unsigned int calc_offset(const struct exthdr_desc *desc, + const struct proto_hdr_template *tmpl, + unsigned int num) +{ + if (!desc || tmpl == &tcpopt_unknown_template) + return 0; + + switch (desc->type) { + case TCPOPT_SACK: + /* Make sure, offset calculations only apply to left and right + * fields + */ + return (tmpl->offset < 16) ? 0 : num * 64; + default: + return 0; + } +} + + +static unsigned int calc_offset_reverse(const struct exthdr_desc *desc, + const struct proto_hdr_template *tmpl, + unsigned int offset) +{ + if (!desc || tmpl == &tcpopt_unknown_template) + return offset; + + switch (desc->type) { + case TCPOPT_SACK: + /* We can safely ignore the first left/right field */ + return offset < 80 ? offset : (offset % 64); + default: + return offset; + } +} + + +struct expr *tcpopt_expr_alloc(const struct location *loc, + const char *option_str, + const unsigned int option_num, + const char *option_field) +{ + const struct proto_hdr_template *tmp, *tmpl = &tcpopt_unknown_template; + const struct exthdr_desc *desc = NULL; + struct expr *expr; + unsigned int i, j; + + for (i = 0; i < array_size(tcpopt_protocols); ++i) { + if (tcpopt_protocols[i] == TCPOPT_OBSOLETE) + continue; + + if (!tcpopt_protocols[i]->name || + strcmp(option_str, tcpopt_protocols[i]->name)) + continue; + + for (j = 0; j < array_size(tcpopt_protocols[i]->templates); ++j) { + tmp = &tcpopt_protocols[i]->templates[j]; + if (!tmp->token || strcmp(option_field, tmp->token)) + continue; + + desc = tcpopt_protocols[i]; + tmpl = tmp; + goto found; + } + } + +found: + /* tmpl still points to tcpopt_unknown_template if nothing was found and + * desc is null + */ + expr = expr_alloc(loc, &exthdr_expr_ops, tmpl->dtype, + BYTEORDER_BIG_ENDIAN, tmpl->len); + expr->exthdr.desc = desc; + expr->exthdr.tmpl = tmpl; + expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; + expr->exthdr.offset = calc_offset(desc, tmpl, option_num); + + return expr; +} + +void tcpopt_init_raw(struct expr *expr, uint8_t type, unsigned int offset, + unsigned int len) +{ + const struct proto_hdr_template *tmpl; + unsigned int i, off; + + assert(expr->ops->type == EXPR_EXTHDR); + + expr->len = len; + expr->exthdr.offset = offset; + + assert(type < array_size(tcpopt_protocols)); + expr->exthdr.desc = tcpopt_protocols[type]; + assert(expr->exthdr.desc != TCPOPT_OBSOLETE); + + for (i = 0; i < array_size(expr->exthdr.desc->templates); ++i) { + tmpl = &expr->exthdr.desc->templates[i]; + /* We have to reverse calculate the offset for the sack options + * at this point + */ + off = calc_offset_reverse(expr->exthdr.desc, tmpl, offset); + if (tmpl->offset != off || tmpl->len != len) + continue; + + expr->dtype = tmpl->dtype; + expr->exthdr.tmpl = tmpl; + expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; + break; + } +} + +bool tcpopt_find_template(struct expr *expr, const struct expr *mask, + unsigned int *shift) +{ + if (expr->exthdr.tmpl != &tcpopt_unknown_template) + return false; + + tcpopt_init_raw(expr, expr->exthdr.desc->type, expr->exthdr.offset, + expr->len); + + if (expr->exthdr.tmpl == &tcpopt_unknown_template) + return false; + + return true; +} |