This patch allows you to specify an interval of IP address in maps.
table ip x {
chain y {
type nat hook postrouting priority srcnat; policy accept;
snat ip interval to ip saddr map { 10.141.11.4 : 192.168.2.2-192.168.2.4 }
}
}
The example above performs SNAT to packets that comes from 10.141.11.4
to an interval of IP addresses from 192.168.2.2 to 192.168.2.4 (both
included).
You can also combine this with dynamic maps:
table ip x {
map y {
type ipv4_addr : interval ipv4_addr
flags interval
elements = { 10.141.10.0/24 : 192.168.2.2-192.168.2.4 }
}
chain y {
type nat hook postrouting priority srcnat; policy accept;
snat ip interval to ip saddr map @y
}
}
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
* @EXPR_F_PROTOCOL: expressions describes upper layer protocol
* @EXPR_F_INTERVAL_END: set member ends an open interval
* @EXPR_F_BOOLEAN: expression is boolean (set by relational expr on LHS)
+ * @EXPR_F_INTERVAL: expression describes a interval
*/
enum expr_flags {
EXPR_F_CONSTANT = 0x1,
EXPR_F_PROTOCOL = 0x4,
EXPR_F_INTERVAL_END = 0x8,
EXPR_F_BOOLEAN = 0x10,
+ EXPR_F_INTERVAL = 0x20,
};
#include <payload.h>
extern const char *nat_etype2str(enum nft_nat_etypes type);
+enum {
+ STMT_NAT_F_INTERVAL = (1 << 0),
+};
+
struct nat_stmt {
enum nft_nat_etypes type;
struct expr *addr;
uint32_t flags;
uint8_t family;
bool ipportmap;
+ uint32_t type_flags;
};
extern struct stmt *nat_stmt_alloc(const struct location *loc,
if (binop_transfer(ctx, expr) < 0)
return -1;
+ if (ctx->set->data->flags & EXPR_F_INTERVAL)
+ ctx->set->data->len *= 2;
+
ctx->set->key->len = ctx->ectx.len;
ctx->set = NULL;
map = *expr;
{
struct expr *mapping = *expr;
struct set *set = ctx->set;
+ uint32_t datalen;
if (set == NULL)
return expr_error(ctx->msgs, mapping,
mapping->flags |= mapping->left->flags & EXPR_F_SINGLETON;
if (set->data) {
- expr_set_context(&ctx->ectx, set->data->dtype, set->data->len);
+ if (!set_is_anonymous(set->flags) &&
+ set->data->flags & EXPR_F_INTERVAL)
+ datalen = set->data->len / 2;
+ else
+ datalen = set->data->len;
+
+ expr_set_context(&ctx->ectx, set->data->dtype, datalen);
} else {
assert((set->flags & NFT_SET_MAP) == 0);
}
if (!expr_is_constant(mapping->right))
return expr_error(ctx->msgs, mapping->right,
"Value must be a constant");
- if (!expr_is_singleton(mapping->right))
+
+ if (set_is_anonymous(set->flags) &&
+ (mapping->right->etype == EXPR_RANGE ||
+ mapping->right->etype == EXPR_PREFIX))
+ set->data->flags |= EXPR_F_INTERVAL;
+
+ if (!(set->data->flags & EXPR_F_INTERVAL) &&
+ !expr_is_singleton(mapping->right))
return expr_error(ctx->msgs, mapping->right,
"Value must be a singleton");
if (err < 0)
return err;
}
+
+ if (stmt->nat.type_flags & STMT_NAT_F_INTERVAL) {
+ switch (stmt->nat.addr->etype) {
+ case EXPR_MAP:
+ if (!(stmt->nat.addr->mappings->set->data->flags & EXPR_F_INTERVAL))
+ return expr_error(ctx->msgs, stmt->nat.addr,
+ "map is not defined as interval");
+ break;
+ case EXPR_RANGE:
+ case EXPR_PREFIX:
+ break;
+ default:
+ return expr_error(ctx->msgs, stmt->nat.addr,
+ "neither prefix, range nor map expression");
+ }
+
+ stmt->flags |= STMT_F_TERMINAL;
+
+ return 0;
+ }
+
if (stmt->nat.proto != NULL) {
err = nat_evaluate_transport(ctx, stmt, &stmt->nat.proto);
if (err < 0)
return set_error(ctx, set, "map definition does not "
"specify mapping data type");
+ if (set->data->flags & EXPR_F_INTERVAL)
+ set->data->len *= 2;
+
if (set->data->etype == EXPR_CONCAT &&
expr_evaluate_concat(ctx, &set->data, false) < 0)
return -1;
memory_allocation_error();
set_key_expression(ctx, set->key, set->flags, udbuf, NFTNL_UDATA_SET_KEY_TYPEOF);
- if (set->data)
+ if (set->data) {
set_key_expression(ctx, set->data, set->flags, udbuf, NFTNL_UDATA_SET_DATA_TYPEOF);
+ nftnl_udata_put_u32(udbuf, NFTNL_UDATA_SET_DATA_INTERVAL,
+ !!(set->data->flags & EXPR_F_INTERVAL));
+ }
if (set->desc.field_len[0]) {
nftnl_set_set_data(nls, NFTNL_SET_DESC_CONCAT,
assert(nld.len > 0);
/* fallthrough */
case EXPR_VALUE:
+ case EXPR_RANGE:
+ case EXPR_PREFIX:
nftnl_set_elem_set(nlse, NFTNL_SET_ELEM_DATA,
nld.value, nld.len);
break;
}
}
+static void netlink_gen_range(const struct expr *expr,
+ struct nft_data_linearize *nld)
+{
+ unsigned int len = div_round_up(expr->left->len, BITS_PER_BYTE) * 2;
+ unsigned char data[len];
+ unsigned int offset = 0;
+
+ memset(data, 0, len);
+ offset = netlink_export_pad(data, expr->left->value, expr->left);
+ netlink_export_pad(data + offset, expr->right->value, expr->right);
+ memcpy(nld->value, data, len);
+ nld->len = len;
+}
+
+static void netlink_gen_prefix(const struct expr *expr,
+ struct nft_data_linearize *nld)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE) * 2;
+ unsigned char data[len];
+ int offset;
+ mpz_t v;
+
+ offset = netlink_export_pad(data, expr->prefix->value, expr);
+ mpz_init_bitmask(v, expr->len - expr->prefix_len);
+ mpz_add(v, expr->prefix->value, v);
+ netlink_export_pad(data + offset, v, expr->prefix);
+ mpz_clear(v);
+
+ memcpy(nld->value, data, len);
+ nld->len = len;
+}
+
void netlink_gen_data(const struct expr *expr, struct nft_data_linearize *data)
{
switch (expr->etype) {
return netlink_gen_concat_data(expr, data);
case EXPR_VERDICT:
return netlink_gen_verdict(expr, data);
+ case EXPR_RANGE:
+ return netlink_gen_range(expr, data);
+ case EXPR_PREFIX:
+ return netlink_gen_prefix(expr, data);
default:
BUG("invalid data expression type %s\n", expr_name(expr));
}
case NFTNL_UDATA_SET_KEYBYTEORDER:
case NFTNL_UDATA_SET_DATABYTEORDER:
case NFTNL_UDATA_SET_MERGE_ELEMENTS:
+ case NFTNL_UDATA_SET_DATA_INTERVAL:
if (len != sizeof(uint32_t))
return -1;
break;
struct expr *typeof_expr_key, *typeof_expr_data;
uint32_t flags, key, objtype = 0;
const struct datatype *dtype;
+ uint32_t data_interval = 0;
bool automerge = false;
const char *udata;
struct set *set;
GET_U32_UDATA(keybyteorder, NFTNL_UDATA_SET_KEYBYTEORDER);
GET_U32_UDATA(databyteorder, NFTNL_UDATA_SET_DATABYTEORDER);
GET_U32_UDATA(automerge, NFTNL_UDATA_SET_MERGE_ELEMENTS);
+ GET_U32_UDATA(data_interval, NFTNL_UDATA_SET_DATA_INTERVAL);
#undef GET_U32_UDATA
typeof_expr_key = set_make_key(ud[NFTNL_UDATA_SET_KEY_TYPEOF]);
typeof_expr_key = NULL;
}
+ if (data_interval)
+ set->data->flags |= EXPR_F_INTERVAL;
+
if (dtype != datatype)
datatype_free(datatype);
}
}
}
+static bool mpz_bitmask_is_prefix(mpz_t bitmask, uint32_t len)
+{
+ unsigned long n1, n2;
+
+ n1 = mpz_scan0(bitmask, 0);
+ if (n1 == ULONG_MAX)
+ return false;
+
+ n2 = mpz_scan1(bitmask, n1 + 1);
+ if (n2 < len)
+ return false;
+
+ return true;
+}
+
+static uint32_t mpz_bitmask_to_prefix(mpz_t bitmask, uint32_t len)
+{
+ return len - mpz_scan0(bitmask, 0);
+}
+
+static struct expr *expr_range_to_prefix(struct expr *range)
+{
+ struct expr *left = range->left, *right = range->right, *prefix;
+ uint32_t len = left->len, prefix_len;
+ mpz_t bitmask;
+
+ mpz_init2(bitmask, len);
+ mpz_xor(bitmask, left->value, right->value);
+
+ if (mpz_bitmask_is_prefix(bitmask, len)) {
+ prefix_len = mpz_bitmask_to_prefix(bitmask, len);
+ prefix = prefix_expr_alloc(&range->location, expr_get(left),
+ prefix_len);
+ mpz_clear(bitmask);
+ expr_free(range);
+
+ return prefix;
+ }
+ mpz_clear(bitmask);
+
+ return range;
+}
+
+static struct expr *netlink_parse_interval_elem(const struct datatype *dtype,
+ struct expr *expr)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE);
+ struct expr *range, *left, *right;
+ char data[len];
+
+ mpz_export_data(data, expr->value, dtype->byteorder, len);
+ left = constant_expr_alloc(&internal_location, dtype,
+ dtype->byteorder,
+ (len / 2) * BITS_PER_BYTE, &data[0]);
+ right = constant_expr_alloc(&internal_location, dtype,
+ dtype->byteorder,
+ (len / 2) * BITS_PER_BYTE, &data[len / 2]);
+ range = range_expr_alloc(&expr->location, left, right);
+ expr_free(expr);
+
+ return expr_range_to_prefix(range);
+}
+
static struct expr *netlink_parse_concat_elem(const struct datatype *dtype,
struct expr *data)
{
datatype_set(data, set->data->dtype);
data->byteorder = set->data->byteorder;
- if (set->data->dtype->subtypes)
+ if (set->data->flags & EXPR_F_INTERVAL)
+ data = netlink_parse_interval_elem(set->data->dtype, data);
+ else if (set->data->dtype->subtypes)
data = netlink_parse_concat_elem(set->data->dtype, data);
if (data->byteorder == BYTEORDER_HOST_ENDIAN)
ctx->stmt = stmt;
}
+static bool is_nat_addr_map(const struct expr *addr, uint8_t family)
+{
+ const struct expr *mappings, *data;
+ const struct set *set;
+
+ if (!addr ||
+ expr_ops(addr)->type != EXPR_MAP)
+ return false;
+
+ mappings = addr->right;
+ if (expr_ops(mappings)->type != EXPR_SET_REF)
+ return false;
+
+ set = mappings->set;
+ data = set->data;
+
+ if (!(data->flags & EXPR_F_INTERVAL))
+ return false;
+
+ /* if we're dealing with an address:address map,
+ * the length will be bit_sizeof(addr) + 32 (one register).
+ */
+ switch (family) {
+ case NFPROTO_IPV4:
+ return data->len == 32 + 32;
+ case NFPROTO_IPV6:
+ return data->len == 128 + 128;
+ }
+
+ return false;
+}
+
static bool is_nat_proto_map(const struct expr *addr, uint8_t family)
{
const struct expr *mappings, *data;
stmt->nat.addr = addr;
}
+ if (is_nat_addr_map(addr, family)) {
+ stmt->nat.family = family;
+ stmt->nat.type_flags |= STMT_NAT_F_INTERVAL;
+ ctx->stmt = stmt;
+ return;
+ }
+
reg2 = netlink_parse_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MAX);
if (reg2 && reg2 != reg1) {
addr = netlink_get_register(ctx, loc, reg2);
netlink_gen_expr(ctx, stmt->nat.addr, amin_reg);
netlink_put_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MIN,
amin_reg);
+ if (stmt->nat.addr->etype == EXPR_MAP &&
+ stmt->nat.addr->mappings->set->data->flags & EXPR_F_INTERVAL) {
+ amax_reg = get_register(ctx, NULL);
+ registers++;
+ amin_reg += netlink_register_space(nat_addrlen(family));
+ netlink_put_register(nle, NFTNL_EXPR_NAT_REG_ADDR_MAX,
+ amin_reg);
+ }
}
if (stmt->nat.ipportmap) {
$1->flags |= NFT_SET_MAP;
$$ = $1;
}
+ | map_block TYPE
+ data_type_expr COLON INTERVAL data_type_expr
+ stmt_separator
+ {
+ $1->key = $3;
+ $1->data = $6;
+ $1->data->flags |= EXPR_F_INTERVAL;
+
+ $1->flags |= NFT_SET_MAP;
+ $$ = $1;
+ }
| map_block TYPEOF
typeof_expr COLON typeof_expr
stmt_separator
$1->flags |= NFT_SET_MAP;
$$ = $1;
}
+ | map_block TYPEOF
+ typeof_expr COLON INTERVAL typeof_expr
+ stmt_separator
+ {
+ $1->key = $3;
+ datatype_set($1->key, $3->dtype);
+ $1->data = $6;
+ $1->data->flags |= EXPR_F_INTERVAL;
+
+ $1->flags |= NFT_SET_MAP;
+ $$ = $1;
+ }
| map_block TYPE
data_type_expr COLON COUNTER
stmt_separator
$<stmt>0->nat.addr = $6;
$<stmt>0->nat.ipportmap = true;
}
+ | nf_key_proto INTERVAL TO stmt_expr
+ {
+ $<stmt>0->nat.family = $1;
+ $<stmt>0->nat.addr = $4;
+ $<stmt>0->nat.type_flags = STMT_NAT_F_INTERVAL;
+ }
+ | INTERVAL TO stmt_expr
+ {
+ $<stmt>0->nat.addr = $3;
+ $<stmt>0->nat.type_flags = STMT_NAT_F_INTERVAL;
+ }
;
masq_stmt : masq_stmt_alloc masq_stmt_args
if (set_is_datamap(set->flags)) {
nft_print(octx, " : ");
+ if (set->data->flags & EXPR_F_INTERVAL)
+ nft_print(octx, "interval ");
+
if (use_typeof)
expr_print(set->data, octx);
else
if (stmt->nat.ipportmap)
nft_print(octx, " addr . port");
+ else if (stmt->nat.type_flags & STMT_NAT_F_INTERVAL)
+ nft_print(octx, " interval");
nft_print(octx, " to");
}