Contrary to iptables, we use the asterisk character '*' as wildcard.
# nft --debug=netlink add rule test test iifname eth\*
ip test test
[ meta load iifname => reg 1 ]
[ cmp eq reg 1 0x00687465 ]
Note that this generates an optimized comparison without bitwise.
In case you want to match a device that contains an asterisk, you have
to escape the asterisk, ie.
# nft add rule test test iifname eth\\*
The wildcard string handling occurs from the evaluation step, where we
convert from:
relational
/ \
/ \
meta value
oifname eth*
to:
relational
/ \
/ \
meta prefix
ofiname
As Patrick suggested, this not actually a wildcard but a prefix since it
only applies to the string when placed at the end.
More comments:
* This relaxes the left->size > right->size from netlink_parse_cmp()
for strings since the optimization that this patch applies may now
result in bogus errors.
* This patch can be later on extended to apply a similar optimization to
payload expressions when:
expr->len % BITS_PER_BYTE == 0
For meta and ct, the kernel checks for the exact length of the attributes
(it expects integer 32 bits) so we can't do it unless we relax that.
* Wildcard strings are not supported from sets and maps yet. Error
reporting is not very good at this stage since expr_evaluate_prefix()
doesn't have enough context (ctx->set is NULL, the set object is
currently created later after evaluating the lhs and rhs of the
relational). I'll be following up on this later.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
extern void *xrealloc(void *ptr, size_t size);
extern void *xzalloc(size_t size);
extern char *xstrdup(const char *s);
+extern void xstrunescape(const char *in, char *out);
#endif /* NFTABLES_UTILS_H */
return expr_evaluate(ctx, expr);
}
+static int expr_evaluate_string(struct eval_ctx *ctx, struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE), datalen;
+ struct expr *value, *prefix;
+ char data[len + 1];
+
+ if (ctx->ectx.len > 0) {
+ if (expr->len > ctx->ectx.len)
+ return expr_error(ctx->msgs, expr,
+ "String exceeds maximum length of %u",
+ ctx->ectx.len / BITS_PER_BYTE);
+ expr->len = ctx->ectx.len;
+ }
+
+ mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
+
+ datalen = strlen(data) - 1;
+ if (data[datalen] != '*')
+ return 0;
+
+ if (datalen - 1 >= 0 &&
+ data[datalen - 1] == '\\') {
+ char unescaped_str[len];
+
+ memset(unescaped_str, 0, sizeof(unescaped_str));
+ xstrunescape(data, unescaped_str);
+
+ value = constant_expr_alloc(&expr->location, &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ expr->len, unescaped_str);
+ expr_free(expr);
+ *exprp = value;
+ return 0;
+ }
+ value = constant_expr_alloc(&expr->location, &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ datalen * BITS_PER_BYTE, data);
+
+ prefix = prefix_expr_alloc(&expr->location, value,
+ datalen * BITS_PER_BYTE);
+ prefix->dtype = &string_type;
+ prefix->flags |= EXPR_F_CONSTANT;
+ prefix->byteorder = BYTEORDER_HOST_ENDIAN;
+
+ expr_free(expr);
+ *exprp = prefix;
+ return 0;
+}
+
static int expr_evaluate_value(struct eval_ctx *ctx, struct expr **expr)
{
mpz_t mask;
mpz_clear(mask);
break;
case TYPE_STRING:
- if (ctx->ectx.len > 0) {
- if ((*expr)->len > ctx->ectx.len)
- return expr_error(ctx->msgs, *expr,
- "String exceeds maximum length of %u",
- ctx->ectx.len / BITS_PER_BYTE);
- (*expr)->len = ctx->ectx.len;
- }
+ if (expr_evaluate_string(ctx, expr) < 0)
+ return -1;
break;
default:
BUG("invalid basetype %s\n", expr_basetype(*expr)->name);
}
/*
- * Prefix expression: the argument must be a constant value of integer base
- * type; the prefix length must be less than or equal to the type width.
+ * Prefix expression: the argument must be a constant value of integer or
+ * string base type; the prefix length must be less than or equal to the type
+ * width.
*/
static int expr_evaluate_prefix(struct eval_ctx *ctx, struct expr **expr)
{
"Prefix expression is undefined for "
"non-constant expressions");
- if (expr_basetype(base)->type != TYPE_INTEGER)
+ switch (expr_basetype(base)->type) {
+ case TYPE_INTEGER:
+ case TYPE_STRING:
+ break;
+ default:
return expr_error(ctx->msgs, prefix,
"Prefix expression is undefined for "
"%s types", base->dtype->desc);
+ }
if (prefix->prefix_len > base->len)
return expr_error(ctx->msgs, prefix,
prefix->prefix_len, base->len);
/* Clear the uncovered bits of the base value */
- mask = constant_expr_alloc(&prefix->location, &integer_type,
+ mask = constant_expr_alloc(&prefix->location, expr_basetype(base),
BYTEORDER_HOST_ENDIAN, base->len, NULL);
- mpz_prefixmask(mask->value, base->len, prefix->prefix_len);
+ switch (expr_basetype(base)->type) {
+ case TYPE_INTEGER:
+ mpz_prefixmask(mask->value, base->len, prefix->prefix_len);
+ break;
+ case TYPE_STRING:
+ mpz_init2(mask->value, base->len);
+ mpz_bitmask(mask->value, prefix->prefix_len);
+ break;
+ }
and = binop_expr_alloc(&prefix->location, OP_AND, base, mask);
-
prefix->prefix = and;
if (expr_evaluate(ctx, &prefix->prefix) < 0)
return -1;
return -1;
right = op->right;
- if (expr_basetype(left)->type != TYPE_INTEGER)
+ switch (expr_basetype(left)->type) {
+ case TYPE_INTEGER:
+ case TYPE_STRING:
+ break;
+ default:
return expr_binary_error(ctx->msgs, left, op,
"Binary operation (%s) is undefined "
"for %s types",
sym, left->dtype->desc);
+ }
if (expr_is_constant(left) && !expr_is_singleton(left))
return expr_binary_error(ctx->msgs, left, op,
nld.value = nftnl_expr_get(nle, NFTNL_EXPR_CMP_DATA, &nld.len);
right = netlink_alloc_value(loc, &nld);
- if (left->len != right->len) {
- if (left->len > right->len)
- return netlink_error(ctx, loc,
- "Relational expression size "
- "mismatch");
+ if (left->len > right->len &&
+ left->dtype != &string_type) {
+ return netlink_error(ctx, loc,
+ "Relational expression size mismatch");
+ } else if (left->len < right->len) {
left = netlink_parse_concat_expr(ctx, loc, sreg, right->len);
if (left == NULL)
return;
}
/* Convert a bitmask to a prefix length */
-static unsigned int expr_mask_to_prefix(struct expr *expr)
+static unsigned int expr_mask_to_prefix(const struct expr *expr)
{
unsigned long n;
}
}
+static struct expr *string_wildcard_expr_alloc(struct location *loc,
+ const struct expr *mask,
+ const struct expr *expr)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE);
+ char data[len + 2];
+ int pos;
+
+ mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
+ pos = div_round_up(expr_mask_to_prefix(mask), BITS_PER_BYTE);
+ data[pos] = '*';
+ data[pos + 1] = '\0';
+
+ return constant_expr_alloc(loc, &string_type, BYTEORDER_HOST_ENDIAN,
+ expr->len + BITS_PER_BYTE, data);
+}
+
+static void escaped_string_wildcard_expr_alloc(struct expr **exprp,
+ unsigned int len)
+{
+ struct expr *expr = *exprp, *tmp;
+ char data[len + 3];
+ int pos;
+
+ mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
+ pos = div_round_up(len, BITS_PER_BYTE);
+ data[pos - 1] = '\\';
+ data[pos] = '*';
+
+ tmp = constant_expr_alloc(&expr->location, &string_type,
+ BYTEORDER_HOST_ENDIAN,
+ expr->len + BITS_PER_BYTE, data);
+ expr_free(expr);
+ *exprp = tmp;
+}
+
+/* This calculates the string length and checks if it is nul-terminated, this
+ * function is quite a hack :)
+ */
+static bool __expr_postprocess_string(struct expr **exprp)
+{
+ struct expr *expr = *exprp;
+ unsigned int len = expr->len;
+ bool nulterminated = false;
+ mpz_t tmp;
+
+ mpz_init(tmp);
+ while (len >= BITS_PER_BYTE) {
+ mpz_bitmask(tmp, BITS_PER_BYTE);
+ mpz_lshift_ui(tmp, len - BITS_PER_BYTE);
+ mpz_and(tmp, tmp, expr->value);
+ if (mpz_cmp_ui(tmp, 0))
+ break;
+ else
+ nulterminated = true;
+ len -= BITS_PER_BYTE;
+ }
+
+ mpz_rshift_ui(tmp, len - BITS_PER_BYTE);
+
+ if (nulterminated &&
+ mpz_cmp_ui(tmp, '*') == 0)
+ escaped_string_wildcard_expr_alloc(exprp, len);
+
+ mpz_clear(tmp);
+ expr->len = len;
+
+ return nulterminated;
+}
+
+static struct expr *expr_postprocess_string(struct expr *expr)
+{
+ struct expr *mask;
+
+ assert(expr->dtype->type == TYPE_STRING);
+ if (__expr_postprocess_string(&expr))
+ return expr;
+
+ mask = constant_expr_alloc(&expr->location, &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ expr->len + BITS_PER_BYTE, NULL);
+ mpz_init_bitmask(mask->value, expr->len);
+ return string_wildcard_expr_alloc(&expr->location, mask, expr);
+}
+
static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
{
struct expr *expr = *exprp, *i;
if (expr->byteorder == BYTEORDER_HOST_ENDIAN)
mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE);
- // Quite a hack :)
- if (expr->dtype->type == TYPE_STRING) {
- unsigned int len = expr->len;
- mpz_t tmp;
- mpz_init(tmp);
- while (len >= BITS_PER_BYTE) {
- mpz_bitmask(tmp, BITS_PER_BYTE);
- mpz_lshift_ui(tmp, len - BITS_PER_BYTE);
- mpz_and(tmp, tmp, expr->value);
- if (mpz_cmp_ui(tmp, 0))
- break;
- len -= BITS_PER_BYTE;
- }
- mpz_clear(tmp);
- expr->len = len;
- }
+ if (expr->dtype->type == TYPE_STRING)
+ *exprp = expr_postprocess_string(expr);
if (expr->dtype->basetype != NULL &&
expr->dtype->basetype->type == TYPE_BITMASK)
struct nftnl_expr *nle;
enum nft_registers sreg;
struct expr *right;
+ int len;
assert(dreg == NFT_REG_VERDICT);
return netlink_gen_range(ctx, expr, dreg);
sreg = get_register(ctx, expr->left);
- netlink_gen_expr(ctx, expr->left, sreg);
switch (expr->right->ops->type) {
case EXPR_PREFIX:
- right = netlink_gen_prefix(ctx, expr, sreg);
+ if (expr->left->dtype->type != TYPE_STRING) {
+ len = div_round_up(expr->right->len, BITS_PER_BYTE);
+ netlink_gen_expr(ctx, expr->left, sreg);
+ right = netlink_gen_prefix(ctx, expr, sreg);
+ } else {
+ len = div_round_up(expr->right->prefix_len, BITS_PER_BYTE);
+ right = expr->right->prefix;
+ expr->left->len = expr->right->prefix_len;
+ netlink_gen_expr(ctx, expr->left, sreg);
+ }
break;
default:
+ len = div_round_up(expr->right->len, BITS_PER_BYTE);
right = expr->right;
+ netlink_gen_expr(ctx, expr->left, sreg);
break;
}
netlink_gen_cmp_op(expr->op));
payload_shift_value(expr->left, right);
netlink_gen_data(right, &nld);
- nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld.value, nld.len);
+ nftnl_expr_set(nle, NFTNL_EXPR_CMP_DATA, nld.value, len);
release_register(ctx, expr->left);
nftnl_rule_add_expr(ctx->nlr, nle);
%token <val> NUM "number"
%token <string> STRING "string"
%token <string> QUOTED_STRING
-%destructor { xfree($$); } STRING QUOTED_STRING
+%token <string> ASTERISK_STRING
+%destructor { xfree($$); } STRING QUOTED_STRING ASTERISK_STRING
%token LL_HDR "ll"
%token NETWORK_HDR "nh"
string : STRING
| QUOTED_STRING
+ | ASTERISK_STRING
;
time_spec : STRING
letter [a-zA-Z]
string ({letter})({letter}|{digit}|[/\-_\.])*
quotedstring \"[^"]*\"
+asteriskstring ({string}\*|{string}\\\*)
comment #.*$
slash \/
return QUOTED_STRING;
}
+{asteriskstring} {
+ yylval->string = xstrdup(yytext);
+ return ASTERISK_STRING;
+ }
+
{string} {
yylval->string = xstrdup(yytext);
return STRING;
memory_allocation_error();
return res;
}
+
+void xstrunescape(const char *in, char *out)
+{
+ unsigned int i, k = 0;
+
+ for (i = 0; i < strlen(in); i++) {
+ if (in[i] == '\\')
+ continue;
+
+ out[k++] = in[i];
+ }
+ out[k++] = '\0';
+}
meta iifname != "eth0";ok;iifname != "eth0"
meta iifname {"eth0", "lo"};ok
- meta iifname != {"eth0", "lo"};ok
+meta iifname "eth*";ok;iifname "eth*"
+meta iifname "eth\*";ok;iifname "eth\*"
meta iiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok
- meta iiftype != {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok
meta oifname != "eth0";ok;oifname != "eth0"
meta oifname { "eth0", "lo"};ok
- meta iifname != {"eth0", "lo"};ok
+meta oifname "eth*";ok;oifname "eth*"
+meta oifname "eth\*";ok;oifname "eth\*"
meta oiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok
- meta oiftype != {ether, ppp, ipip, ipip6, loopback, sit, ipgre};ok
[ meta load iifname => reg 1 ]
[ lookup reg 1 set set%d ]
+# meta iifname "eth*"
+ip test-ip4 input
+ [ meta load iifname => reg 1 ]
+ [ cmp eq reg 1 0x00687465 ]
+
+# meta iifname "eth\*"
+ip test-ip4 input
+ [ meta load iifname => reg 1 ]
+ [ cmp eq reg 1 0x2a687465 0x00000000 0x00000000 0x00000000 ]
+
# meta iiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre}
set%d test-ip4 3
set%d test-ip4 0
[ meta load oifname => reg 1 ]
[ lookup reg 1 set set%d ]
+# meta oifname "eth*"
+ip test-ip4 input
+ [ meta load oifname => reg 1 ]
+ [ cmp eq reg 1 0x00687465 ]
+
+# meta oifname "eth\*"
+ip test-ip4 input
+ [ meta load oifname => reg 1 ]
+ [ cmp eq reg 1 0x2a687465 0x00000000 0x00000000 0x00000000 ]
+
# meta oiftype {ether, ppp, ipip, ipip6, loopback, sit, ipgre}
set%d test-ip4 3
set%d test-ip4 0