Historically, users have requested support for increasing and decreasing TTL value in nftables in order to migrate from iptables. Following the nftables spirit of flexible and multipurpose expressions, this patch introduces "nft_math" expression. This expression allows to increase and decrease values stored in the register. The math expression intends to be flexible enough in case it needs to be extended in the future, e.g implement non-contiguous bitfields operations. But for now, non-contiguous payloads are not supported. When loading a u8 or u16 payload into a register we don't know if the value is stored at least significant byte or most significant byte. In order to handle such cases, introduce a bitmask indicating what is the target bit and also use it to handle limits to prevent overflow or underflow. Keep on mind that math expression expects that the sreg and dreg are in host byteorder, so the user must use nft_byteorder expressions as needed. This implementation comes with a libnftnl patch that allows the user to generate the following example bytecodes: - Bytecode to increase the TTL of a packet table filter inet flags 0 use 1 handle 1 inet filter input use 1 type filter hook input prio 0 policy accept packets 0 bytes 0 flags 1 inet filter input 4 [ payload load 2b @ network header + 8 => reg 1 ] [ math mask 0x000000ff reg 1 + 1 => 1] [ payload write reg 1 => 2b @ network header + 8 csum_type 1 csum_off 10 csum_flags 0x0 ] - Bytecode to decrease the TTL of a packet table filter inet flags 0 use 1 handle 1 inet filter input use 1 type filter hook input prio 0 policy accept packets 0 bytes 0 flags 1 inet filter input 4 [ payload load 2b @ network header + 8 => reg 1 ] [ math mask 0x000000ff reg 1 - 1 => 1] [ payload write reg 1 => 2b @ network header + 8 csum_type 1 csum_off 10 csum_flags 0x0 ] - Bytecode to increase the meta mark of a packet table mangle inet flags 0 use 1 handle 6 inet mangle output use 1 type filter hook output prio 0 policy accept packets 0 bytes 0 flags 1 inet mangle output 2 [ meta load mark => reg 1 ] [ math mask 0xffffffff reg 1 + 1 => 1] [ meta set mark with reg 1 ] Signed-off-by: Fernando Fernandez Mancera --- v2: dropped the byteorder netlink attribute, added bitmask to handle LSB/MSB when dealing with u8 and u16, simplified eval logic. I've kept nft_math as module, IMHO it would be too much to make it built-in. v3: fixed checkpatch warnings, improved Kconfig description and fixed a wrong EINVAL return. v4: removed NFTA_MATH_LEN as discussed with Phil, added a non-contiguous bitmask check --- include/uapi/linux/netfilter/nf_tables.h | 25 ++++ net/netfilter/Kconfig | 9 ++ net/netfilter/Makefile | 1 + net/netfilter/nft_math.c | 154 +++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 net/netfilter/nft_math.c diff --git a/include/uapi/linux/netfilter/nf_tables.h b/include/uapi/linux/netfilter/nf_tables.h index 0b708153469c..36a5b42c3ddf 100644 --- a/include/uapi/linux/netfilter/nf_tables.h +++ b/include/uapi/linux/netfilter/nf_tables.h @@ -2019,4 +2019,29 @@ enum nft_tunnel_attributes { }; #define NFTA_TUNNEL_MAX (__NFTA_TUNNEL_MAX - 1) +/** + * enum nft_math_attributes - nftables math expression netlink attributes + * + * @NFTA_MATH_SREG: source register (NLA_U32: nft_registers) + * @NFTA_MATH_DREG: destination register (NLA_U32: nft_registers) + * @NFTA_MATH_OP: operation to be performed (NLA_U32) + * @NFTA_MATH_BITMASK: bitmask to be applied on the operation (NLA_U32) + */ +enum nft_math_attributes { + NFTA_MATH_UNSPEC, + NFTA_MATH_SREG, + NFTA_MATH_DREG, + NFTA_MATH_OP, + NFTA_MATH_BITMASK, + __NFTA_MATH_MAX, +}; +#define NFTA_MATH_MAX (__NFTA_MATH_MAX - 1) + +enum nft_math_op { + NFT_MATH_OP_INC, + NFT_MATH_OP_DEC, + __NFT_MATH_OP_MAX, +}; +#define NFT_MATH_OP_MAX (__NFT_MATH_OP_MAX - 1) + #endif /* _LINUX_NF_TABLES_H */ diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index f3ea0cb26f36..49e723f0dcc8 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -667,6 +667,15 @@ config NFT_SYNPROXY server. This allows to avoid conntrack and server resource usage during SYN-flood attacks. +config NFT_MATH + tristate "Netfilter nf_tables math expression support" + depends on NETFILTER_ADVANCED + help + This option enables support for the nftables math expression. + It allows arithmetic operations to be performed on nft registers + content, such as incrementing or decrementing values. Math + expressions can be used in nftables rules to modify packet fields. + if NF_TABLES_NETDEV config NF_DUP_NETDEV diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 6bfc250e474f..fe25b1d1ce0a 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -131,6 +131,7 @@ obj-$(CONFIG_NFT_OSF) += nft_osf.o obj-$(CONFIG_NFT_TPROXY) += nft_tproxy.o obj-$(CONFIG_NFT_XFRM) += nft_xfrm.o obj-$(CONFIG_NFT_SYNPROXY) += nft_synproxy.o +obj-$(CONFIG_NFT_MATH) += nft_math.o obj-$(CONFIG_NFT_NAT) += nft_chain_nat.o diff --git a/net/netfilter/nft_math.c b/net/netfilter/nft_math.c new file mode 100644 index 000000000000..d9538e227bbf --- /dev/null +++ b/net/netfilter/nft_math.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +struct nft_math { + u8 sreg; + u8 dreg; + u32 bitmask; + enum nft_math_op op; +}; + +static const struct nla_policy nft_math_policy[NFTA_MATH_MAX + 1] = { + [NFTA_MATH_SREG] = { .type = NLA_U32 }, + [NFTA_MATH_DREG] = { .type = NLA_U32 }, + [NFTA_MATH_OP] = { .type = NLA_U32 }, + [NFTA_MATH_BITMASK] = { .type = NLA_U32 }, +}; + +static void nft_math_eval_bitmask(u32 *src, u32 *dst, + const struct nft_math *priv) +{ + u32 target, keep, bit_unit; + + target = *src & priv->bitmask; + keep = *src & ~priv->bitmask; + bit_unit = priv->bitmask & -priv->bitmask; + + switch (priv->op) { + case NFT_MATH_OP_INC: + if (target == priv->bitmask) { + *dst = *src; + break; + } + + target = target + bit_unit; + *dst = target | keep; + break; + case NFT_MATH_OP_DEC: + if (!target) { + *dst = *src; + break; + } + + target = target - bit_unit; + *dst = target | keep; + break; + default: + break; + } +} + +static void nft_math_eval(const struct nft_expr *expr, + struct nft_regs *regs, + const struct nft_pktinfo *pkt) +{ + const struct nft_math *priv = nft_expr_priv(expr); + u32 *src = ®s->data[priv->sreg]; + u32 *dst = ®s->data[priv->dreg]; + + nft_math_eval_bitmask(src, dst, priv); +} + +static int nft_math_init(const struct nft_ctx *ctx, + const struct nft_expr *expr, + const struct nlattr * const tb[]) +{ + struct nft_math *priv = nft_expr_priv(expr); + u32 bitmask_check; + int err; + + if (!tb[NFTA_MATH_SREG] || + !tb[NFTA_MATH_DREG] || + !tb[NFTA_MATH_BITMASK] || + !tb[NFTA_MATH_OP]) + return -EINVAL; + + priv->op = ntohl(nla_get_u32(tb[NFTA_MATH_OP])); + priv->bitmask = ntohl(nla_get_u32(tb[NFTA_MATH_BITMASK])); + + if (priv->op > NFT_MATH_OP_MAX) + return -EOPNOTSUPP; + + if (!priv->bitmask) + return -EINVAL; + + /* check if the bitmask is contiguous, otherwise reject it */ + bitmask_check = priv->bitmask + (priv->bitmask & -priv->bitmask); + if (bitmask_check & (bitmask_check - 1)) + return -EINVAL; + + err = nft_parse_register_load(ctx, tb[NFTA_MATH_SREG], &priv->sreg, + sizeof(u32)); + if (err < 0) + return err; + + return nft_parse_register_store(ctx, tb[NFTA_MATH_DREG], + &priv->dreg, NULL, NFT_DATA_VALUE, + sizeof(u32)); +} + +static int nft_math_dump(struct sk_buff *skb, + const struct nft_expr *expr, bool reset) +{ + const struct nft_math *priv = nft_expr_priv(expr); + + if (nft_dump_register(skb, NFTA_MATH_SREG, priv->sreg)) + goto nla_put_failure; + if (nft_dump_register(skb, NFTA_MATH_DREG, priv->dreg)) + goto nla_put_failure; + if (nla_put_u32(skb, NFTA_MATH_BITMASK, htonl(priv->bitmask))) + goto nla_put_failure; + if (nla_put_u32(skb, NFTA_MATH_OP, htonl(priv->op))) + goto nla_put_failure; + return 0; + +nla_put_failure: + return -1; +} + +static struct nft_expr_type nft_math_type; +static const struct nft_expr_ops nft_math_op = { + .eval = nft_math_eval, + .size = NFT_EXPR_SIZE(sizeof(struct nft_math)), + .init = nft_math_init, + .dump = nft_math_dump, + .type = &nft_math_type, +}; + +static struct nft_expr_type nft_math_type __read_mostly = { + .ops = &nft_math_op, + .name = "math", + .owner = THIS_MODULE, + .policy = nft_math_policy, + .maxattr = NFTA_MATH_MAX, +}; + +static int __init nft_math_module_init(void) +{ + return nft_register_expr(&nft_math_type); +} + +static void __exit nft_math_module_exit(void) +{ + nft_unregister_expr(&nft_math_type); +} + +module_init(nft_math_module_init); +module_exit(nft_math_module_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Fernando Fernandez Mancera "); +MODULE_ALIAS_NFT_EXPR("math"); +MODULE_DESCRIPTION("nftables math support to operate with values"); -- 2.53.0