summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Westphal <fw@strlen.de>2020-12-08 15:49:42 +0100
committerFlorian Westphal <fw@strlen.de>2020-12-09 18:33:53 +0100
commit98b871512c4677848a12e8204fe35eb870660304 (patch)
tree797ec078c8875b6e33fda15a94c9dfa86f73df22
parente63f067f597d1129b3fff91d2404701de90226d1 (diff)
src: add auto-dependencies for ipv4 icmp
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>
-rw-r--r--include/payload.h3
-rw-r--r--include/proto.h16
-rw-r--r--src/evaluate.c20
-rw-r--r--src/payload.c129
-rw-r--r--src/proto.c25
5 files changed, 182 insertions, 11 deletions
diff --git a/include/payload.h b/include/payload.h
index a914d239..7bbb19b9 100644
--- a/include/payload.h
+++ b/include/payload.h
@@ -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);
diff --git a/include/proto.h b/include/proto.h
index 667650d6..f383291b 100644
--- a/include/proto.h
+++ b/include/proto.h
@@ -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;
diff --git a/src/evaluate.c b/src/evaluate.c
index 76b25b40..3eb8e1bf 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -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
diff --git a/src/payload.c b/src/payload.c
index e51c5797..54b08f05 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -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);
+}
diff --git a/src/proto.c b/src/proto.c
index c42e8f51..d3371ac6 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -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),
},
};