diff options
Diffstat (limited to 'src/tcpopt.c')
-rw-r--r-- | src/tcpopt.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/src/tcpopt.c b/src/tcpopt.c new file mode 100644 index 0000000..3fcb273 --- /dev/null +++ b/src/tcpopt.c @@ -0,0 +1,266 @@ +#include <nft.h> + +#include <stddef.h> +#include <stdio.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> + +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) +static const struct exthdr_desc tcpopt_eol = { + .name = "eol", + .type = TCPOPT_KIND_EOL, + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + }, +}; + +static const struct exthdr_desc tcpopt_nop = { + .name = "nop", + .type = TCPOPT_KIND_NOP, + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + }, +}; + +static const struct exthdr_desc tcptopt_maxseg = { + .name = "maxseg", + .type = TCPOPT_KIND_MAXSEG, + .templates = { + [TCPOPT_MAXSEG_KIND] = PHT("kind", 0, 8), + [TCPOPT_MAXSEG_LENGTH] = PHT("length", 8, 8), + [TCPOPT_MAXSEG_SIZE] = PHT("size", 16, 16), + }, +}; + +static const struct exthdr_desc tcpopt_window = { + .name = "window", + .type = TCPOPT_KIND_WINDOW, + .templates = { + [TCPOPT_WINDOW_KIND] = PHT("kind", 0, 8), + [TCPOPT_WINDOW_LENGTH] = PHT("length", 8, 8), + [TCPOPT_WINDOW_COUNT] = PHT("count", 16, 8), + }, +}; + +static const struct exthdr_desc tcpopt_sack_permitted = { + .name = "sack-perm", + .type = TCPOPT_KIND_SACK_PERMITTED, + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), + }, +}; + +static const struct exthdr_desc tcpopt_sack = { + .name = "sack", + .type = TCPOPT_KIND_SACK, + .templates = { + [TCPOPT_SACK_KIND] = PHT("kind", 0, 8), + [TCPOPT_SACK_LENGTH] = PHT("length", 8, 8), + [TCPOPT_SACK_LEFT] = PHT("left", 16, 32), + [TCPOPT_SACK_RIGHT] = PHT("right", 48, 32), + [TCPOPT_SACK_LEFT1] = PHT("left", 80, 32), + [TCPOPT_SACK_RIGHT1] = PHT("right", 112, 32), + [TCPOPT_SACK_LEFT2] = PHT("left", 144, 32), + [TCPOPT_SACK_RIGHT2] = PHT("right", 176, 32), + [TCPOPT_SACK_LEFT3] = PHT("left", 208, 32), + [TCPOPT_SACK_RIGHT3] = PHT("right", 240, 32), + }, +}; + +static const struct exthdr_desc tcpopt_timestamp = { + .name = "timestamp", + .type = TCPOPT_KIND_TIMESTAMP, + .templates = { + [TCPOPT_TS_KIND] = PHT("kind", 0, 8), + [TCPOPT_TS_LENGTH] = PHT("length", 8, 8), + [TCPOPT_TS_TSVAL] = PHT("tsval", 16, 32), + [TCPOPT_TS_TSECR] = PHT("tsecr", 48, 32), + }, +}; + +static const struct exthdr_desc tcpopt_fastopen = { + .name = "fastopen", + .type = TCPOPT_KIND_FASTOPEN, + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), + }, +}; + +static const struct exthdr_desc tcpopt_md5sig = { + .name = "md5sig", + .type = TCPOPT_KIND_MD5SIG, + .templates = { + [TCPOPT_COMMON_KIND] = PHT("kind", 0, 8), + [TCPOPT_COMMON_LENGTH] = PHT("length", 8, 8), + }, +}; + + +static const struct exthdr_desc tcpopt_mptcp = { + .name = "mptcp", + .type = TCPOPT_KIND_MPTCP, + .templates = { + [TCPOPT_MPTCP_KIND] = PHT("kind", 0, 8), + [TCPOPT_MPTCP_LENGTH] = PHT("length", 8, 8), + [TCPOPT_MPTCP_SUBTYPE] = PHT("subtype", 16, 4), + }, +}; +#undef PHT + +const struct exthdr_desc *tcpopt_protocols[] = { + [TCPOPT_KIND_EOL] = &tcpopt_eol, + [TCPOPT_KIND_NOP] = &tcpopt_nop, + [TCPOPT_KIND_MAXSEG] = &tcptopt_maxseg, + [TCPOPT_KIND_WINDOW] = &tcpopt_window, + [TCPOPT_KIND_SACK_PERMITTED] = &tcpopt_sack_permitted, + [TCPOPT_KIND_SACK] = &tcpopt_sack, + [TCPOPT_KIND_TIMESTAMP] = &tcpopt_timestamp, + [TCPOPT_KIND_MD5SIG] = &tcpopt_md5sig, + [TCPOPT_KIND_MPTCP] = &tcpopt_mptcp, + [TCPOPT_KIND_FASTOPEN] = &tcpopt_fastopen, +}; + +/** + * tcpopt_expr_alloc - allocate tcp option extension expression + * + * @loc: location from parser + * @kind: raw tcp option value to find in packet + * @field: highlevel field to find in the option if @kind is present in packet + * + * Allocate a new tcp option expression. + * @kind is the raw option value to find in the packet. + * Exception: SACK may use extra OOB data that is mangled here. + * + * @field is the optional field to extract from the @type option. + */ +struct expr *tcpopt_expr_alloc(const struct location *loc, + unsigned int kind, + unsigned int field) +{ + const struct proto_hdr_template *tmpl; + const struct exthdr_desc *desc = NULL; + struct expr *expr; + + switch (kind) { + case TCPOPT_KIND_SACK1: + kind = TCPOPT_KIND_SACK; + if (field == TCPOPT_SACK_LEFT) + field = TCPOPT_SACK_LEFT1; + else if (field == TCPOPT_SACK_RIGHT) + field = TCPOPT_SACK_RIGHT1; + break; + case TCPOPT_KIND_SACK2: + kind = TCPOPT_KIND_SACK; + if (field == TCPOPT_SACK_LEFT) + field = TCPOPT_SACK_LEFT2; + else if (field == TCPOPT_SACK_RIGHT) + field = TCPOPT_SACK_RIGHT2; + break; + case TCPOPT_KIND_SACK3: + kind = TCPOPT_KIND_SACK; + if (field == TCPOPT_SACK_LEFT) + field = TCPOPT_SACK_LEFT3; + else if (field == TCPOPT_SACK_RIGHT) + field = TCPOPT_SACK_RIGHT3; + break; + } + + if (kind < array_size(tcpopt_protocols)) + desc = tcpopt_protocols[kind]; + + if (!desc) { + if (field != TCPOPT_COMMON_KIND || kind > 255) + return NULL; + + expr = expr_alloc(loc, EXPR_EXTHDR, &integer_type, + BYTEORDER_BIG_ENDIAN, 8); + + desc = tcpopt_protocols[TCPOPT_NOP]; + tmpl = &desc->templates[field]; + expr->exthdr.desc = desc; + expr->exthdr.tmpl = tmpl; + expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; + expr->exthdr.raw_type = kind; + return expr; + } + + tmpl = &desc->templates[field]; + if (!tmpl) + return NULL; + + expr = expr_alloc(loc, EXPR_EXTHDR, tmpl->dtype, + BYTEORDER_BIG_ENDIAN, tmpl->len); + expr->exthdr.desc = desc; + expr->exthdr.tmpl = tmpl; + expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; + expr->exthdr.raw_type = desc->type; + expr->exthdr.offset = tmpl->offset; + + return expr; +} + +void tcpopt_init_raw(struct expr *expr, uint8_t type, unsigned int off, + unsigned int len, uint32_t flags) +{ + const struct proto_hdr_template *tmpl; + unsigned int i; + + assert(expr->etype == EXPR_EXTHDR); + + expr->len = len; + expr->exthdr.flags = flags; + expr->exthdr.offset = off; + expr->exthdr.op = NFT_EXTHDR_OP_TCPOPT; + expr->exthdr.tmpl = &tcpopt_unknown_template; + + if (flags & NFT_EXTHDR_F_PRESENT) + datatype_set(expr, &boolean_type); + else + datatype_set(expr, &integer_type); + + if (type >= array_size(tcpopt_protocols) || + !tcpopt_protocols[type]) + return; + + expr->exthdr.desc = tcpopt_protocols[type]; + expr->exthdr.flags = flags; + assert(expr->exthdr.desc != NULL); + + for (i = 0; i < array_size(expr->exthdr.desc->templates); ++i) { + tmpl = &expr->exthdr.desc->templates[i]; + if (tmpl->offset != off || tmpl->len != len) + continue; + + if ((flags & NFT_EXTHDR_F_PRESENT) == 0) + datatype_set(expr, tmpl->dtype); + + expr->exthdr.tmpl = tmpl; + break; + } +} + +bool tcpopt_find_template(struct expr *expr, unsigned int offset, unsigned int len) +{ + if (expr->exthdr.tmpl != &tcpopt_unknown_template) + return false; + + tcpopt_init_raw(expr, expr->exthdr.desc->type, offset, len, 0); + + if (expr->exthdr.tmpl == &tcpopt_unknown_template) + return false; + + return true; +} |