summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Westphal <fw@strlen.de>2015-08-07 12:09:08 +0200
committerFlorian Westphal <fw@strlen.de>2015-09-18 00:06:49 +0200
commit39f15c243912a20f3014c0efba6b8dbc80caf0e9 (patch)
treef0724240418d2c93175669bb575913edc8630b3c
parente540acb8c1e16c9a9afc4c800ce524e47be803d4 (diff)
nft: support listing expressions that use non-byte header fields
This allows to list rules that check fields that are not aligned on byte boundary. Signed-off-by: Florian Westphal <fw@strlen.de>
-rw-r--r--include/payload.h3
-rw-r--r--src/evaluate.c6
-rw-r--r--src/netlink_delinearize.c80
-rw-r--r--src/payload.c83
4 files changed, 159 insertions, 13 deletions
diff --git a/include/payload.h b/include/payload.h
index 95364af1..ca9b0138 100644
--- a/include/payload.h
+++ b/include/payload.h
@@ -19,7 +19,10 @@ extern bool payload_is_adjacent(const struct expr *e1, const struct expr *e2);
extern struct expr *payload_expr_join(const struct expr *e1,
const struct expr *e2);
+bool payload_expr_trim(struct expr *expr, struct expr *mask,
+ const struct proto_ctx *ctx);
extern void payload_expr_expand(struct list_head *list, struct expr *expr,
+ struct expr *mask,
const struct proto_ctx *ctx);
extern void payload_expr_complete(struct expr *expr,
const struct proto_ctx *ctx);
diff --git a/src/evaluate.c b/src/evaluate.c
index 95469180..581f3641 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -310,8 +310,13 @@ static bool resolve_protocol_conflict(struct eval_ctx *ctx,
const struct proto_desc *next = ctx->pctx.protocol[base + 1].desc;
if (payload->payload.desc == next) {
+ ctx->pctx.protocol[base + 1].desc = NULL;
+ ctx->pctx.protocol[base].desc = next;
+ ctx->pctx.protocol[base].offset += desc->length;
payload->payload.offset += desc->length;
return true;
+ } else if (next) {
+ return false;
}
}
@@ -321,6 +326,7 @@ static bool resolve_protocol_conflict(struct eval_ctx *ctx,
payload->payload.offset += ctx->pctx.protocol[base].offset;
list_add_tail(&nstmt->list, &ctx->stmt->list);
+ ctx->pctx.protocol[base + 1].desc = NULL;
return true;
}
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index af972032..16e3aaaf 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -918,16 +918,20 @@ static void integer_type_postprocess(struct expr *expr)
}
}
-static void payload_match_expand(struct rule_pp_ctx *ctx, struct expr *expr)
+static void payload_match_expand(struct rule_pp_ctx *ctx,
+ struct expr *expr,
+ struct expr *payload,
+ struct expr *mask)
{
- struct expr *left = expr->left, *right = expr->right, *tmp;
+ struct expr *left = payload, *right = expr->right, *tmp;
struct list_head list = LIST_HEAD_INIT(list);
struct stmt *nstmt;
struct expr *nexpr = NULL;
enum proto_bases base = left->payload.base;
const struct expr_ops *payload_ops = left->ops;
- payload_expr_expand(&list, left, &ctx->pctx);
+ payload_expr_expand(&list, left, mask, &ctx->pctx);
+
list_for_each_entry(left, &list, list) {
tmp = constant_expr_splice(right, left->len);
expr_set_type(tmp, left->dtype, left->byteorder);
@@ -980,10 +984,11 @@ static void payload_match_expand(struct rule_pp_ctx *ctx, struct expr *expr)
}
static void payload_match_postprocess(struct rule_pp_ctx *ctx,
- struct expr *expr)
+ struct expr *expr,
+ struct expr *payload,
+ struct expr *mask)
{
- enum proto_bases base = expr->left->payload.base;
- struct expr *payload = expr->left;
+ enum proto_bases base = payload->payload.base;
assert(payload->payload.offset >= ctx->pctx.protocol[base].offset);
payload->payload.offset -= ctx->pctx.protocol[base].offset;
@@ -992,7 +997,7 @@ static void payload_match_postprocess(struct rule_pp_ctx *ctx,
case OP_EQ:
case OP_NEQ:
if (expr->right->ops->type == EXPR_VALUE) {
- payload_match_expand(ctx, expr);
+ payload_match_expand(ctx, expr, payload, mask);
break;
}
/* Fall through */
@@ -1073,7 +1078,7 @@ static struct expr *binop_tree_to_list(struct expr *list, struct expr *expr)
return list;
}
-static void relational_binop_postprocess(struct expr *expr)
+static void relational_binop_postprocess(struct rule_pp_ctx *ctx, struct expr *expr)
{
struct expr *binop = expr->left, *value = expr->right;
@@ -1101,6 +1106,61 @@ static void relational_binop_postprocess(struct expr *expr)
expr_mask_to_prefix(binop->right));
expr_free(value);
expr_free(binop);
+ } else if (binop->op == OP_AND &&
+ binop->left->ops->type == EXPR_PAYLOAD &&
+ binop->right->ops->type == EXPR_VALUE) {
+ struct expr *payload = expr->left->left;
+ struct expr *mask = expr->left->right;
+
+ /*
+ * This *might* be a payload match testing header fields that
+ * have non byte divisible offsets and/or bit lengths.
+ *
+ * Thus we need to deal with two different cases.
+ *
+ * 1 the simple version:
+ * relation
+ * payload value|setlookup
+ *
+ * expr: relation, left: payload, right: value, e.g. tcp dport == 22.
+ *
+ * 2. The '&' version (this is what we're looking at now).
+ * relation
+ * binop value1|setlookup
+ * payload value2
+ *
+ * expr: relation, left: binop, right: value, e.g.
+ * ip saddr 10.0.0.0/8
+ *
+ * payload_expr_trim will figure out if the mask is needed to match
+ * templates.
+ */
+ if (payload_expr_trim(payload, mask, &ctx->pctx)) {
+ /* mask is implicit, binop needs to be removed.
+ *
+ * Fix all values of the expression according to the mask
+ * and then process the payload instruction using the real
+ * sizes and offsets we're interested in.
+ *
+ * Finally, convert the expression to 1) by replacing
+ * the binop with the binop payload expr.
+ */
+ if (value->ops->type == EXPR_VALUE) {
+ assert(value->len >= expr->left->right->len);
+ value->len = mask->len;
+ }
+
+ payload->len = mask->len;
+ payload->payload.offset += mpz_scan1(mask->value, 0);
+
+ payload_match_postprocess(ctx, expr, payload, mask);
+
+ assert(expr->left->ops->type == EXPR_BINOP);
+
+ assert(binop->left == payload);
+ expr->left = payload;
+ expr_free(binop);
+ }
}
}
@@ -1159,7 +1219,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
case EXPR_RELATIONAL:
switch (expr->left->ops->type) {
case EXPR_PAYLOAD:
- payload_match_postprocess(ctx, expr);
+ payload_match_postprocess(ctx, expr, expr->left, NULL);
return;
default:
expr_postprocess(ctx, &expr->left);
@@ -1174,7 +1234,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
meta_match_postprocess(ctx, expr);
break;
case EXPR_BINOP:
- relational_binop_postprocess(expr);
+ relational_binop_postprocess(ctx, expr);
break;
default:
break;
diff --git a/src/payload.c b/src/payload.c
index 880b5d0d..23afa2f2 100644
--- a/src/payload.c
+++ b/src/payload.c
@@ -297,12 +297,87 @@ void payload_expr_complete(struct expr *expr, const struct proto_ctx *ctx)
}
}
+static unsigned int mask_to_offset(const struct expr *mask)
+{
+ return mask ? mpz_scan1(mask->value, 0) : 0;
+}
+
+static unsigned int mask_length(const struct expr *mask)
+{
+ unsigned long off;
+
+ off = mask_to_offset(mask);
+
+ return mpz_scan0(mask->value, off + 1);
+}
+
+/**
+ * payload_expr_trim - trim payload expression according to mask
+ *
+ * @expr: the payload expression
+ * @mask: mask to use when searching templates
+ * @ctx: protocol context
+ *
+ * Walk the template list and determine if a match can be found without
+ * using the provided mask.
+ *
+ * If the mask has to be used, trim the mask length accordingly
+ * and return true to let the caller know that the mask is a dependency.
+ */
+bool payload_expr_trim(struct expr *expr, struct expr *mask,
+ const struct proto_ctx *ctx)
+{
+ unsigned int payload_offset = expr->payload.offset + mask_to_offset(mask);
+ unsigned int mask_len = mask_length(mask);
+ const struct proto_hdr_template *tmpl;
+ unsigned int payload_len = expr->len;
+ const struct proto_desc *desc;
+ unsigned int i, matched_len = mask_to_offset(mask);
+
+ assert(expr->ops->type == EXPR_PAYLOAD);
+
+ desc = ctx->protocol[expr->payload.base].desc;
+ if (desc == NULL)
+ return false;
+
+ assert(desc->base == expr->payload.base);
+
+ if (ctx->protocol[expr->payload.base].offset) {
+ assert(payload_offset >= ctx->protocol[expr->payload.base].offset);
+ payload_offset -= ctx->protocol[expr->payload.base].offset;
+ }
+
+ for (i = 1; i < array_size(desc->templates); i++) {
+ tmpl = &desc->templates[i];
+ if (tmpl->offset != payload_offset)
+ continue;
+
+ if (tmpl->len > payload_len)
+ return false;
+
+ payload_len -= tmpl->len;
+ matched_len += tmpl->len;
+ payload_offset += tmpl->len;
+ if (payload_len == 0)
+ return false;
+
+ if (matched_len == mask_len) {
+ assert(mask->len >= mask_len);
+ mask->len = mask_len;
+ return true;
+ }
+ }
+
+ return false;
+}
+
/**
* payload_expr_expand - expand raw merged adjacent payload expressions into its
* original components
*
* @list: list to append expanded payload expressions to
* @expr: the payload expression to expand
+ * @mask: optional/implicit mask to use when searching templates
* @ctx: protocol context
*
* Expand a merged adjacent payload expression into its original components
@@ -312,14 +387,16 @@ void payload_expr_complete(struct expr *expr, const struct proto_ctx *ctx)
* offset order.
*/
void payload_expr_expand(struct list_head *list, struct expr *expr,
- const struct proto_ctx *ctx)
+ struct expr *mask, const struct proto_ctx *ctx)
{
- const struct proto_desc *desc;
+ unsigned int off = mask_to_offset(mask);
const struct proto_hdr_template *tmpl;
+ const struct proto_desc *desc;
struct expr *new;
unsigned int i;
assert(expr->ops->type == EXPR_PAYLOAD);
+ assert(!mask || mask->len != 0);
desc = ctx->protocol[expr->payload.base].desc;
if (desc == NULL)
@@ -336,7 +413,7 @@ void payload_expr_expand(struct list_head *list, struct expr *expr,
list_add_tail(&new->list, list);
expr->len -= tmpl->len;
expr->payload.offset += tmpl->len;
- if (expr->len == 0)
+ if (expr->len == off)
return;
} else
break;