Skip to content

Commit

Permalink
netfilter: nf_tables: switch registers to 32 bit addressing
Browse files Browse the repository at this point in the history
Switch the nf_tables registers from 128 bit addressing to 32 bit
addressing to support so called concatenations, where multiple values
can be concatenated over multiple registers for O(1) exact matches of
multiple dimensions using sets.

The old register values are mapped to areas of 128 bits for compatibility.
When dumping register numbers, values are expressed using the old values
if they refer to the beginning of a 128 bit area for compatibility.

To support concatenations, register loads of less than a full 32 bit
value need to be padded. This mainly affects the payload and exthdr
expressions, which both unconditionally zero the last word before
copying the data.

Userspace fully passes the testsuite using both old and new register
addressing.

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
  • Loading branch information
kaber authored and ummakynes committed Apr 13, 2015
1 parent b1c96ed commit 49499c3
Show file tree
Hide file tree
Showing 16 changed files with 110 additions and 48 deletions.
13 changes: 5 additions & 8 deletions include/net/netfilter/nf_tables.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,15 @@ struct nft_data {
*/
struct nft_regs {
union {
struct nft_data data[NFT_REG_MAX + 1];
u32 data[20];
struct nft_verdict verdict;
};
};

static inline void nft_data_copy(struct nft_data *dst,
const struct nft_data *src)
static inline void nft_data_copy(u32 *dst, const struct nft_data *src,
unsigned int len)
{
BUILD_BUG_ON(__alignof__(*dst) != __alignof__(u64));
*(u64 *)&dst->data[0] = *(u64 *)&src->data[0];
*(u64 *)&dst->data[2] = *(u64 *)&src->data[2];
memcpy(dst, src, len);
}

static inline void nft_data_debug(const struct nft_data *data)
Expand Down Expand Up @@ -502,8 +500,7 @@ static inline struct nft_set_ext *nft_set_elem_ext(const struct nft_set *set,

void *nft_set_elem_init(const struct nft_set *set,
const struct nft_set_ext_tmpl *tmpl,
const struct nft_data *key,
const struct nft_data *data,
const u32 *key, const u32 *data,
u64 timeout, gfp_t gfp);
void nft_set_elem_destroy(const struct nft_set *set, void *elem);

Expand Down
31 changes: 30 additions & 1 deletion include/uapi/linux/netfilter/nf_tables.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,45 @@
#define NFT_CHAIN_MAXNAMELEN 32
#define NFT_USERDATA_MAXLEN 256

/**
* enum nft_registers - nf_tables registers
*
* nf_tables used to have five registers: a verdict register and four data
* registers of size 16. The data registers have been changed to 16 registers
* of size 4. For compatibility reasons, the NFT_REG_[1-4] registers still
* map to areas of size 16, the 4 byte registers are addressed using
* NFT_REG32_00 - NFT_REG32_15.
*/
enum nft_registers {
NFT_REG_VERDICT,
NFT_REG_1,
NFT_REG_2,
NFT_REG_3,
NFT_REG_4,
__NFT_REG_MAX
__NFT_REG_MAX,

NFT_REG32_00 = 8,
MFT_REG32_01,
NFT_REG32_02,
NFT_REG32_03,
NFT_REG32_04,
NFT_REG32_05,
NFT_REG32_06,
NFT_REG32_07,
NFT_REG32_08,
NFT_REG32_09,
NFT_REG32_10,
NFT_REG32_11,
NFT_REG32_12,
NFT_REG32_13,
NFT_REG32_14,
NFT_REG32_15,
};
#define NFT_REG_MAX (__NFT_REG_MAX - 1)

#define NFT_REG_SIZE 16
#define NFT_REG32_SIZE 4

/**
* enum nft_verdicts - nf_tables internal verdicts
*
Expand Down
2 changes: 1 addition & 1 deletion net/bridge/netfilter/nft_meta_bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ static void nft_meta_bridge_get_eval(const struct nft_expr *expr,
{
const struct nft_meta *priv = nft_expr_priv(expr);
const struct net_device *in = pkt->in, *out = pkt->out;
u32 *dest = &regs->data[priv->dreg].data[0];
u32 *dest = &regs->data[priv->dreg];
const struct net_bridge_port *p;

switch (priv->key) {
Expand Down
4 changes: 2 additions & 2 deletions net/ipv4/netfilter/nft_redir_ipv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ static void nft_redir_ipv4_eval(const struct nft_expr *expr,
memset(&mr, 0, sizeof(mr));
if (priv->sreg_proto_min) {
mr.range[0].min.all =
*(__be16 *)&regs->data[priv->sreg_proto_min].data[0];
*(__be16 *)&regs->data[priv->sreg_proto_min];
mr.range[0].max.all =
*(__be16 *)&regs->data[priv->sreg_proto_max].data[0];
*(__be16 *)&regs->data[priv->sreg_proto_max];
mr.range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
}

Expand Down
4 changes: 2 additions & 2 deletions net/ipv6/netfilter/nft_redir_ipv6.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ static void nft_redir_ipv6_eval(const struct nft_expr *expr,
memset(&range, 0, sizeof(range));
if (priv->sreg_proto_min) {
range.min_proto.all =
*(__be16 *)&regs->data[priv->sreg_proto_min].data[0];
*(__be16 *)&regs->data[priv->sreg_proto_min],
range.max_proto.all =
*(__be16 *)&regs->data[priv->sreg_proto_max].data[0];
*(__be16 *)&regs->data[priv->sreg_proto_max],
range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
}

Expand Down
54 changes: 42 additions & 12 deletions net/netfilter/nf_tables_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -3201,8 +3201,7 @@ static struct nft_trans *nft_trans_elem_alloc(struct nft_ctx *ctx,

void *nft_set_elem_init(const struct nft_set *set,
const struct nft_set_ext_tmpl *tmpl,
const struct nft_data *key,
const struct nft_data *data,
const u32 *key, const u32 *data,
u64 timeout, gfp_t gfp)
{
struct nft_set_ext *ext;
Expand Down Expand Up @@ -3357,7 +3356,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
}

err = -ENOMEM;
elem.priv = nft_set_elem_init(set, &tmpl, &elem.key, &data,
elem.priv = nft_set_elem_init(set, &tmpl, elem.key.data, data.data,
timeout, GFP_KERNEL);
if (elem.priv == NULL)
goto err3;
Expand Down Expand Up @@ -4122,14 +4121,47 @@ static int nf_tables_check_loops(const struct nft_ctx *ctx,
return 0;
}

/**
* nft_parse_register - parse a register value from a netlink attribute
*
* @attr: netlink attribute
*
* Parse and translate a register value from a netlink attribute.
* Registers used to be 128 bit wide, these register numbers will be
* mapped to the corresponding 32 bit register numbers.
*/
unsigned int nft_parse_register(const struct nlattr *attr)
{
return ntohl(nla_get_be32(attr));
unsigned int reg;

reg = ntohl(nla_get_be32(attr));
switch (reg) {
case NFT_REG_VERDICT...NFT_REG_4:
return reg * NFT_REG_SIZE / NFT_REG32_SIZE;
default:
return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
}
}
EXPORT_SYMBOL_GPL(nft_parse_register);

/**
* nft_dump_register - dump a register value to a netlink attribute
*
* @skb: socket buffer
* @attr: attribute number
* @reg: register number
*
* Construct a netlink attribute containing the register number. For
* compatibility reasons, register numbers being a multiple of 4 are
* translated to the corresponding 128 bit register numbers.
*/
int nft_dump_register(struct sk_buff *skb, unsigned int attr, unsigned int reg)
{
if (reg % (NFT_REG_SIZE / NFT_REG32_SIZE) == 0)
reg = reg / (NFT_REG_SIZE / NFT_REG32_SIZE);
else
reg = reg - NFT_REG_SIZE / NFT_REG32_SIZE + NFT_REG32_00;

return nla_put_be32(skb, attr, htonl(reg));
}
EXPORT_SYMBOL_GPL(nft_dump_register);
Expand All @@ -4145,14 +4177,13 @@ EXPORT_SYMBOL_GPL(nft_dump_register);
*/
int nft_validate_register_load(enum nft_registers reg, unsigned int len)
{
if (reg <= NFT_REG_VERDICT)
if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)
return -EINVAL;
if (reg > NFT_REG_MAX)
return -ERANGE;
if (len == 0)
return -EINVAL;
if (len > FIELD_SIZEOF(struct nft_data, data))
if (reg * NFT_REG32_SIZE + len > FIELD_SIZEOF(struct nft_regs, data))
return -ERANGE;

return 0;
}
EXPORT_SYMBOL_GPL(nft_validate_register_load);
Expand Down Expand Up @@ -4200,13 +4231,12 @@ int nft_validate_register_store(const struct nft_ctx *ctx,

return 0;
default:
if (reg < NFT_REG_1)
if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)
return -EINVAL;
if (reg > NFT_REG_MAX)
return -ERANGE;
if (len == 0)
return -EINVAL;
if (len > FIELD_SIZEOF(struct nft_data, data))
if (reg * NFT_REG32_SIZE + len >
FIELD_SIZEOF(struct nft_regs, data))
return -ERANGE;

if (data != NULL && type != NFT_DATA_VALUE)
Expand Down
5 changes: 3 additions & 2 deletions net/netfilter/nf_tables_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ static void nft_cmp_fast_eval(const struct nft_expr *expr,
const struct nft_cmp_fast_expr *priv = nft_expr_priv(expr);
u32 mask = nft_cmp_fast_mask(priv->len);

if ((regs->data[priv->sreg].data[0] & mask) == priv->data)
if ((regs->data[priv->sreg] & mask) == priv->data)
return;
regs->verdict.code = NFT_BREAK;
}
Expand All @@ -81,7 +81,7 @@ static bool nft_payload_fast_eval(const struct nft_expr *expr,
{
const struct nft_payload *priv = nft_expr_priv(expr);
const struct sk_buff *skb = pkt->skb;
u32 *dest = &regs->data[priv->dreg].data[0];
u32 *dest = &regs->data[priv->dreg];
unsigned char *ptr;

if (priv->base == NFT_PAYLOAD_NETWORK_HEADER)
Expand All @@ -94,6 +94,7 @@ static bool nft_payload_fast_eval(const struct nft_expr *expr,
if (unlikely(ptr + priv->len >= skb_tail_pointer(skb)))
return false;

*dest = 0;
if (priv->len == 2)
*(u16 *)dest = *(u16 *)ptr;
else if (priv->len == 4)
Expand Down
4 changes: 2 additions & 2 deletions net/netfilter/nft_bitwise.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ static void nft_bitwise_eval(const struct nft_expr *expr,
const struct nft_pktinfo *pkt)
{
const struct nft_bitwise *priv = nft_expr_priv(expr);
const u32 *src = &regs->data[priv->sreg].data[0];
u32 *dst = &regs->data[priv->dreg].data[0];
const u32 *src = &regs->data[priv->sreg];
u32 *dst = &regs->data[priv->dreg];
unsigned int i;

for (i = 0; i < DIV_ROUND_UP(priv->len, 4); i++)
Expand Down
4 changes: 2 additions & 2 deletions net/netfilter/nft_byteorder.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ static void nft_byteorder_eval(const struct nft_expr *expr,
const struct nft_pktinfo *pkt)
{
const struct nft_byteorder *priv = nft_expr_priv(expr);
u32 *src = &regs->data[priv->sreg].data[0];
u32 *dst = &regs->data[priv->dreg].data[0];
u32 *src = &regs->data[priv->sreg];
u32 *dst = &regs->data[priv->dreg];
union { u32 u32; u16 u16; } *s, *d;
unsigned int i;

Expand Down
4 changes: 2 additions & 2 deletions net/netfilter/nft_ct.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static void nft_ct_get_eval(const struct nft_expr *expr,
const struct nft_pktinfo *pkt)
{
const struct nft_ct *priv = nft_expr_priv(expr);
u32 *dest = &regs->data[priv->dreg].data[0];
u32 *dest = &regs->data[priv->dreg];
enum ip_conntrack_info ctinfo;
const struct nf_conn *ct;
const struct nf_conn_help *help;
Expand Down Expand Up @@ -156,7 +156,7 @@ static void nft_ct_set_eval(const struct nft_expr *expr,
const struct nft_ct *priv = nft_expr_priv(expr);
struct sk_buff *skb = pkt->skb;
#ifdef CONFIG_NF_CONNTRACK_MARK
u32 value = regs->data[priv->sreg].data[0];
u32 value = regs->data[priv->sreg];
#endif
enum ip_conntrack_info ctinfo;
struct nf_conn *ct;
Expand Down
3 changes: 2 additions & 1 deletion net/netfilter/nft_exthdr.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ static void nft_exthdr_eval(const struct nft_expr *expr,
const struct nft_pktinfo *pkt)
{
struct nft_exthdr *priv = nft_expr_priv(expr);
u32 *dest = &regs->data[priv->dreg].data[0];
u32 *dest = &regs->data[priv->dreg];
unsigned int offset = 0;
int err;

Expand All @@ -39,6 +39,7 @@ static void nft_exthdr_eval(const struct nft_expr *expr,
goto err;
offset += priv->offset;

dest[priv->len / NFT_REG32_SIZE] = 0;
if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
goto err;
return;
Expand Down
2 changes: 1 addition & 1 deletion net/netfilter/nft_immediate.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ static void nft_immediate_eval(const struct nft_expr *expr,
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);

nft_data_copy(&regs->data[priv->dreg], &priv->data);
nft_data_copy(&regs->data[priv->dreg], &priv->data, priv->dlen);
}

static const struct nla_policy nft_immediate_policy[NFTA_IMMEDIATE_MAX + 1] = {
Expand Down
2 changes: 1 addition & 1 deletion net/netfilter/nft_lookup.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ static void nft_lookup_eval(const struct nft_expr *expr,
if (set->ops->lookup(set, &regs->data[priv->sreg], &ext)) {
if (set->flags & NFT_SET_MAP)
nft_data_copy(&regs->data[priv->dreg],
nft_set_ext_data(ext));
nft_set_ext_data(ext), set->dlen);
return;
}
regs->verdict.code = NFT_BREAK;
Expand Down
7 changes: 5 additions & 2 deletions net/netfilter/nft_meta.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@ void nft_meta_get_eval(const struct nft_expr *expr,
const struct nft_meta *priv = nft_expr_priv(expr);
const struct sk_buff *skb = pkt->skb;
const struct net_device *in = pkt->in, *out = pkt->out;
u32 *dest = &regs->data[priv->dreg].data[0];
u32 *dest = &regs->data[priv->dreg];

switch (priv->key) {
case NFT_META_LEN:
*dest = skb->len;
break;
case NFT_META_PROTOCOL:
*dest = 0;
*(__be16 *)dest = skb->protocol;
break;
case NFT_META_NFPROTO:
Expand Down Expand Up @@ -75,11 +76,13 @@ void nft_meta_get_eval(const struct nft_expr *expr,
case NFT_META_IIFTYPE:
if (in == NULL)
goto err;
*dest = 0;
*(u16 *)dest = in->type;
break;
case NFT_META_OIFTYPE:
if (out == NULL)
goto err;
*dest = 0;
*(u16 *)dest = out->type;
break;
case NFT_META_SKUID:
Expand Down Expand Up @@ -185,7 +188,7 @@ void nft_meta_set_eval(const struct nft_expr *expr,
{
const struct nft_meta *meta = nft_expr_priv(expr);
struct sk_buff *skb = pkt->skb;
u32 value = regs->data[meta->sreg].data[0];
u32 value = regs->data[meta->sreg];

switch (meta->key) {
case NFT_META_MARK:
Expand Down
16 changes: 8 additions & 8 deletions net/netfilter/nft_nat.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,26 @@ static void nft_nat_eval(const struct nft_expr *expr,
if (priv->sreg_addr_min) {
if (priv->family == AF_INET) {
range.min_addr.ip = (__force __be32)
regs->data[priv->sreg_addr_min].data[0];
regs->data[priv->sreg_addr_min];
range.max_addr.ip = (__force __be32)
regs->data[priv->sreg_addr_max].data[0];
regs->data[priv->sreg_addr_max];

} else {
memcpy(range.min_addr.ip6,
&regs->data[priv->sreg_addr_min].data,
sizeof(struct nft_data));
&regs->data[priv->sreg_addr_min],
sizeof(range.min_addr.ip6));
memcpy(range.max_addr.ip6,
&regs->data[priv->sreg_addr_max].data,
sizeof(struct nft_data));
&regs->data[priv->sreg_addr_max],
sizeof(range.max_addr.ip6));
}
range.flags |= NF_NAT_RANGE_MAP_IPS;
}

if (priv->sreg_proto_min) {
range.min_proto.all =
*(__be16 *)&regs->data[priv->sreg_proto_min].data[0];
*(__be16 *)&regs->data[priv->sreg_proto_min];
range.max_proto.all =
*(__be16 *)&regs->data[priv->sreg_proto_max].data[0];
*(__be16 *)&regs->data[priv->sreg_proto_max];
range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
}

Expand Down
Loading

0 comments on commit 49499c3

Please sign in to comment.