summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorFlorian Westphal <fw@strlen.de>2015-07-05 20:27:28 +0200
committerFlorian Westphal <fw@strlen.de>2015-09-18 00:05:02 +0200
commit7ead4932f9ab00fdb8a2cc339b41ee2f5aee441e (patch)
tree93a488d6905ed9ec9c21646a139dcf373c7756a1 /src
parentc02107ef4e0fa49e5e57bfc8fb4e2b83654e8c68 (diff)
nft: allow stacking vlan header on top of ethernet
currently 'vlan id 42' or even 'vlan type ip' doesn't work since we expect ethernet header but get vlan. So if we want to add another protocol header to the same base, we attempt to figure out if the new header can fit on top of the existing one (i.e. proto_find_num gives a protocol number when asking to find link between the two). We also annotate protocol description for eth and vlan with the full header size and track the offset from the current base. Otherwise, 'vlan type ip' fetches the protocol field from mac header offset 0, which is some mac address. Instead, we must consider full size of ethernet header. Signed-off-by: Florian Westphal <fw@strlen.de>
Diffstat (limited to 'src')
-rw-r--r--src/evaluate.c78
-rw-r--r--src/netlink_delinearize.c47
-rw-r--r--src/proto.c2
3 files changed, 117 insertions, 10 deletions
diff --git a/src/evaluate.c b/src/evaluate.c
index 22dd6d56..95469180 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -249,6 +249,82 @@ static int expr_evaluate_primary(struct eval_ctx *ctx, struct expr **expr)
return 0;
}
+static int
+conflict_resolution_gen_dependency(struct eval_ctx *ctx, int protocol,
+ const struct expr *expr,
+ struct stmt **res)
+{
+ enum proto_bases base = expr->payload.base;
+ const struct proto_hdr_template *tmpl;
+ const struct proto_desc *desc = NULL;
+ struct expr *dep, *left, *right;
+ struct stmt *stmt;
+
+ assert(expr->payload.base == PROTO_BASE_LL_HDR);
+
+ desc = ctx->pctx.protocol[base].desc;
+ tmpl = &desc->templates[desc->protocol_key];
+ left = payload_expr_alloc(&expr->location, desc, desc->protocol_key);
+
+ right = constant_expr_alloc(&expr->location, tmpl->dtype,
+ tmpl->dtype->byteorder, tmpl->len,
+ constant_data_ptr(protocol, tmpl->len));
+
+ dep = relational_expr_alloc(&expr->location, OP_EQ, left, right);
+ stmt = expr_stmt_alloc(&dep->location, dep);
+ if (stmt_evaluate(ctx, stmt) < 0)
+ return expr_error(ctx->msgs, expr,
+ "dependency statement is invalid");
+
+ ctx->pctx.protocol[base].desc = expr->payload.desc;
+ assert(ctx->pctx.protocol[base].offset == 0);
+
+ assert(desc->length);
+ ctx->pctx.protocol[base].offset += desc->length;
+
+ *res = stmt;
+ return 0;
+}
+
+static bool resolve_protocol_conflict(struct eval_ctx *ctx,
+ struct expr *payload)
+{
+ const struct hook_proto_desc *h = &hook_proto_desc[ctx->pctx.family];
+ enum proto_bases base = payload->payload.base;
+ const struct proto_desc *desc;
+ struct stmt *nstmt = NULL;
+ int link;
+
+ desc = ctx->pctx.protocol[base].desc;
+
+ if (desc == payload->payload.desc) {
+ payload->payload.offset += ctx->pctx.protocol[base].offset;
+ return true;
+ }
+
+ if (payload->payload.base != h->base)
+ return false;
+
+ assert(desc->length);
+ if (base < PROTO_BASE_MAX) {
+ const struct proto_desc *next = ctx->pctx.protocol[base + 1].desc;
+
+ if (payload->payload.desc == next) {
+ payload->payload.offset += desc->length;
+ return true;
+ }
+ }
+
+ link = proto_find_num(desc, payload->payload.desc);
+ if (link < 0 || conflict_resolution_gen_dependency(ctx, link, payload, &nstmt) < 0)
+ return false;
+
+ payload->payload.offset += ctx->pctx.protocol[base].offset;
+ list_add_tail(&nstmt->list, &ctx->stmt->list);
+
+ return true;
+}
+
/*
* Payload expression: check whether dependencies are fulfilled, otherwise
* generate the necessary relational expression and prepend it to the current
@@ -264,7 +340,7 @@ static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **expr)
if (payload_gen_dependency(ctx, payload, &nstmt) < 0)
return -1;
list_add_tail(&nstmt->list, &ctx->stmt->list);
- } else if (ctx->pctx.protocol[base].desc != payload->payload.desc)
+ } else if (!resolve_protocol_conflict(ctx, payload))
return expr_error(ctx->msgs, payload,
"conflicting protocols specified: %s vs. %s",
ctx->pctx.protocol[base].desc->name,
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index 787eec76..af972032 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -923,7 +923,9 @@ static void payload_match_expand(struct rule_pp_ctx *ctx, struct expr *expr)
struct expr *left = expr->left, *right = expr->right, *tmp;
struct list_head list = LIST_HEAD_INIT(list);
struct stmt *nstmt;
- struct expr *nexpr;
+ 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);
list_for_each_entry(left, &list, list) {
@@ -940,16 +942,37 @@ static void payload_match_expand(struct rule_pp_ctx *ctx, struct expr *expr)
nstmt = expr_stmt_alloc(&ctx->stmt->location, nexpr);
list_add_tail(&nstmt->list, &ctx->stmt->list);
+ assert(left->ops == payload_ops);
+ assert(left->payload.base);
+ assert(base == left->payload.base);
+
/* Remember the first payload protocol expression to
* kill it later on if made redundant by a higher layer
* payload expression.
*/
if (ctx->pbase == PROTO_BASE_INVALID &&
- left->flags & EXPR_F_PROTOCOL)
- payload_dependency_store(ctx, nstmt,
- left->payload.base);
- else
+ left->flags & EXPR_F_PROTOCOL) {
+ unsigned int proto = mpz_get_be16(tmp->value);
+ const struct proto_desc *desc, *next;
+ bool stacked_header = false;
+
+ desc = ctx->pctx.protocol[base].desc;
+ assert(desc);
+ if (desc) {
+ next = proto_find_upper(desc, proto);
+ stacked_header = next && next->base == base;
+ }
+
+ if (stacked_header) {
+ ctx->pctx.protocol[base].desc = next;
+ ctx->pctx.protocol[base].offset += desc->length;
+ payload_dependency_store(ctx, nstmt, base - 1);
+ } else {
+ payload_dependency_store(ctx, nstmt, base);
+ }
+ } else {
payload_dependency_kill(ctx, nexpr->left);
+ }
}
list_del(&ctx->stmt->list);
stmt_free(ctx->stmt);
@@ -959,6 +982,12 @@ 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)
{
+ enum proto_bases base = expr->left->payload.base;
+ struct expr *payload = expr->left;
+
+ assert(payload->payload.offset >= ctx->pctx.protocol[base].offset);
+ payload->payload.offset -= ctx->pctx.protocol[base].offset;
+
switch (expr->op) {
case OP_EQ:
case OP_NEQ:
@@ -968,10 +997,10 @@ static void payload_match_postprocess(struct rule_pp_ctx *ctx,
}
/* Fall through */
default:
- payload_expr_complete(expr->left, &ctx->pctx);
- expr_set_type(expr->right, expr->left->dtype,
- expr->left->byteorder);
- payload_dependency_kill(ctx, expr->left);
+ payload_expr_complete(payload, &ctx->pctx);
+ expr_set_type(expr->right, payload->dtype,
+ payload->byteorder);
+ payload_dependency_kill(ctx, payload);
break;
}
}
diff --git a/src/proto.c b/src/proto.c
index d40caebc..b2aeac48 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -714,6 +714,7 @@ const struct proto_desc proto_vlan = {
.name = "vlan",
.base = PROTO_BASE_LL_HDR,
.protocol_key = VLANHDR_TYPE,
+ .length = sizeof(struct vlan_hdr) * BITS_PER_BYTE,
.protocols = {
PROTO_LINK(__constant_htons(ETH_P_IP), &proto_ip),
PROTO_LINK(__constant_htons(ETH_P_ARP), &proto_arp),
@@ -783,6 +784,7 @@ const struct proto_desc proto_eth = {
.name = "ether",
.base = PROTO_BASE_LL_HDR,
.protocol_key = ETHHDR_TYPE,
+ .length = sizeof(struct ether_header) * BITS_PER_BYTE,
.protocols = {
PROTO_LINK(__constant_htons(ETH_P_IP), &proto_ip),
PROTO_LINK(__constant_htons(ETH_P_ARP), &proto_arp),