Merge tag 'nf-next-25-05-23' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next

Pablo Neira Ayuso says:

====================
Netfilter updates for net-next

The following batch contains Netfilter updates for net-next,
specifically 26 patches: 5 patches adding/updating selftests,
4 fixes, 3 PREEMPT_RT fixes, and 14 patches to enhance nf_tables):

1) Improve selftest coverage for pipapo 4 bit group format, from
   Florian Westphal.

2) Fix incorrect dependencies when compiling a kernel without
   legacy ip{6}tables support, also from Florian.

3) Two patches to fix nft_fib vrf issues, including selftest updates
   to improve coverage, also from Florian Westphal.

4) Fix incorrect nesting in nft_tunnel's GENEVE support, from
   Fernando F. Mancera.

5) Three patches to fix PREEMPT_RT issues with nf_dup infrastructure
   and nft_inner to match in inner headers, from Sebastian Andrzej Siewior.

6) Integrate conntrack information into nft trace infrastructure,
   from Florian Westphal.

7) A series of 13 patches to allow to specify wildcard netdevice in
   netdev basechain and flowtables, eg.

   table netdev filter {
       chain ingress {
           type filter hook ingress devices = { eth0, eth1, vlan* } priority 0; policy accept;
       }
   }

   This also allows for runtime hook registration on NETDEV_{UN}REGISTER
   event, from Phil Sutter.

netfilter pull request 25-05-23

* tag 'nf-next-25-05-23' of git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next: (26 commits)
  selftests: netfilter: Torture nftables netdev hooks
  netfilter: nf_tables: Add notifications for hook changes
  netfilter: nf_tables: Support wildcard netdev hook specs
  netfilter: nf_tables: Sort labels in nft_netdev_hook_alloc()
  netfilter: nf_tables: Handle NETDEV_CHANGENAME events
  netfilter: nf_tables: Wrap netdev notifiers
  netfilter: nf_tables: Respect NETDEV_REGISTER events
  netfilter: nf_tables: Prepare for handling NETDEV_REGISTER events
  netfilter: nf_tables: Have a list of nf_hook_ops in nft_hook
  netfilter: nf_tables: Pass nf_hook_ops to nft_unregister_flowtable_hook()
  netfilter: nf_tables: Introduce nft_register_flowtable_ops()
  netfilter: nf_tables: Introduce nft_hook_find_ops{,_rcu}()
  netfilter: nf_tables: Introduce functions freeing nft_hook objects
  netfilter: nf_tables: add packets conntrack state to debug trace info
  netfilter: conntrack: make nf_conntrack_id callable without a module dependency
  netfilter: nf_dup_netdev: Move the recursion counter struct netdev_xmit
  netfilter: nft_inner: Use nested-BH locking for nft_pcpu_tun_ctx
  netfilter: nf_dup{4, 6}: Move duplication check to task_struct
  netfilter: nft_tunnel: fix geneve_opt dump
  selftests: netfilter: nft_fib.sh: add type and oif tests with and without VRFs
  ...
====================

Link: https://patch.msgid.link/20250523132712.458507-1-pablo@netfilter.org
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni
2025-05-26 18:53:40 +02:00
31 changed files with 1505 additions and 231 deletions

View File

@@ -31,9 +31,6 @@
const struct nf_ipv6_ops __rcu *nf_ipv6_ops __read_mostly;
EXPORT_SYMBOL_GPL(nf_ipv6_ops);
DEFINE_PER_CPU(bool, nf_skb_duplicated);
EXPORT_SYMBOL_GPL(nf_skb_duplicated);
#ifdef CONFIG_JUMP_LABEL
struct static_key nf_hooks_needed[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
EXPORT_SYMBOL(nf_hooks_needed);

View File

@@ -505,6 +505,11 @@ u32 nf_ct_get_id(const struct nf_conn *ct)
}
EXPORT_SYMBOL_GPL(nf_ct_get_id);
static u32 nf_conntrack_get_id(const struct nf_conntrack *nfct)
{
return nf_ct_get_id(nf_ct_to_nf_conn(nfct));
}
static void
clean_from_lists(struct nf_conn *ct)
{
@@ -2710,6 +2715,7 @@ static const struct nf_ct_hook nf_conntrack_hook = {
.attach = nf_conntrack_attach,
.set_closing = nf_conntrack_set_closing,
.confirm = __nf_conntrack_confirm,
.get_id = nf_conntrack_get_id,
};
void nf_conntrack_init_end(void)

View File

@@ -15,12 +15,26 @@
#define NF_RECURSION_LIMIT 2
static DEFINE_PER_CPU(u8, nf_dup_skb_recursion);
#ifndef CONFIG_PREEMPT_RT
static u8 *nf_get_nf_dup_skb_recursion(void)
{
return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion);
}
#else
static u8 *nf_get_nf_dup_skb_recursion(void)
{
return &current->net_xmit.nf_dup_skb_recursion;
}
#endif
static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev,
enum nf_dev_hooks hook)
{
if (__this_cpu_read(nf_dup_skb_recursion) > NF_RECURSION_LIMIT)
u8 *nf_dup_skb_recursion = nf_get_nf_dup_skb_recursion();
if (*nf_dup_skb_recursion > NF_RECURSION_LIMIT)
goto err;
if (hook == NF_NETDEV_INGRESS && skb_mac_header_was_set(skb)) {
@@ -32,9 +46,9 @@ static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev,
skb->dev = dev;
skb_clear_tstamp(skb);
__this_cpu_inc(nf_dup_skb_recursion);
(*nf_dup_skb_recursion)++;
dev_queue_xmit(skb);
__this_cpu_dec(nf_dup_skb_recursion);
(*nf_dup_skb_recursion)--;
return;
err:
kfree_skb(skb);

View File

@@ -300,40 +300,75 @@ void nf_tables_unbind_chain(const struct nft_ctx *ctx, struct nft_chain *chain)
static int nft_netdev_register_hooks(struct net *net,
struct list_head *hook_list)
{
struct nf_hook_ops *ops;
struct nft_hook *hook;
int err, j;
j = 0;
list_for_each_entry(hook, hook_list, list) {
err = nf_register_net_hook(net, &hook->ops);
if (err < 0)
goto err_register;
list_for_each_entry(ops, &hook->ops_list, list) {
err = nf_register_net_hook(net, ops);
if (err < 0)
goto err_register;
j++;
j++;
}
}
return 0;
err_register:
list_for_each_entry(hook, hook_list, list) {
if (j-- <= 0)
break;
list_for_each_entry(ops, &hook->ops_list, list) {
if (j-- <= 0)
break;
nf_unregister_net_hook(net, &hook->ops);
nf_unregister_net_hook(net, ops);
}
}
return err;
}
static void nft_netdev_hook_free_ops(struct nft_hook *hook)
{
struct nf_hook_ops *ops, *next;
list_for_each_entry_safe(ops, next, &hook->ops_list, list) {
list_del(&ops->list);
kfree(ops);
}
}
static void nft_netdev_hook_free(struct nft_hook *hook)
{
nft_netdev_hook_free_ops(hook);
kfree(hook);
}
static void __nft_netdev_hook_free_rcu(struct rcu_head *rcu)
{
struct nft_hook *hook = container_of(rcu, struct nft_hook, rcu);
nft_netdev_hook_free(hook);
}
static void nft_netdev_hook_free_rcu(struct nft_hook *hook)
{
call_rcu(&hook->rcu, __nft_netdev_hook_free_rcu);
}
static void nft_netdev_unregister_hooks(struct net *net,
struct list_head *hook_list,
bool release_netdev)
{
struct nft_hook *hook, *next;
struct nf_hook_ops *ops;
list_for_each_entry_safe(hook, next, hook_list, list) {
nf_unregister_net_hook(net, &hook->ops);
list_for_each_entry(ops, &hook->ops_list, list)
nf_unregister_net_hook(net, ops);
if (release_netdev) {
list_del(&hook->list);
kfree_rcu(hook, rcu);
nft_netdev_hook_free_rcu(hook);
}
}
}
@@ -2253,7 +2288,7 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
list_for_each_entry_safe(hook, next,
&basechain->hook_list, list) {
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
nft_netdev_hook_free_rcu(hook);
}
}
module_put(basechain->type->owner);
@@ -2274,19 +2309,20 @@ void nf_tables_chain_destroy(struct nft_chain *chain)
static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
const struct nlattr *attr)
{
struct nf_hook_ops *ops;
struct net_device *dev;
struct nft_hook *hook;
int err;
hook = kzalloc(sizeof(struct nft_hook), GFP_KERNEL_ACCOUNT);
if (!hook) {
err = -ENOMEM;
goto err_hook_alloc;
}
if (!hook)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&hook->ops_list);
err = nla_strscpy(hook->ifname, attr, IFNAMSIZ);
if (err < 0)
goto err_hook_dev;
goto err_hook_free;
hook->ifnamelen = nla_len(attr);
@@ -2294,18 +2330,22 @@ static struct nft_hook *nft_netdev_hook_alloc(struct net *net,
* indirectly serializing all the other holders of the commit_mutex with
* the rtnl_mutex.
*/
dev = __dev_get_by_name(net, hook->ifname);
if (!dev) {
err = -ENOENT;
goto err_hook_dev;
}
hook->ops.dev = dev;
for_each_netdev(net, dev) {
if (strncmp(dev->name, hook->ifname, hook->ifnamelen))
continue;
ops = kzalloc(sizeof(struct nf_hook_ops), GFP_KERNEL_ACCOUNT);
if (!ops) {
err = -ENOMEM;
goto err_hook_free;
}
ops->dev = dev;
list_add_tail(&ops->list, &hook->ops_list);
}
return hook;
err_hook_dev:
kfree(hook);
err_hook_alloc:
err_hook_free:
nft_netdev_hook_free(hook);
return ERR_PTR(err);
}
@@ -2315,7 +2355,8 @@ static struct nft_hook *nft_hook_list_find(struct list_head *hook_list,
struct nft_hook *hook;
list_for_each_entry(hook, hook_list, list) {
if (!strcmp(hook->ifname, this->ifname))
if (!strncmp(hook->ifname, this->ifname,
min(hook->ifnamelen, this->ifnamelen)))
return hook;
}
@@ -2345,7 +2386,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net,
}
if (nft_hook_list_find(hook_list, hook)) {
NL_SET_BAD_ATTR(extack, tmp);
kfree(hook);
nft_netdev_hook_free(hook);
err = -EEXIST;
goto err_hook;
}
@@ -2363,7 +2404,7 @@ static int nf_tables_parse_netdev_hooks(struct net *net,
err_hook:
list_for_each_entry_safe(hook, next, hook_list, list) {
list_del(&hook->list);
kfree(hook);
nft_netdev_hook_free(hook);
}
return err;
}
@@ -2506,7 +2547,7 @@ static void nft_chain_release_hook(struct nft_chain_hook *hook)
list_for_each_entry_safe(h, next, &hook->list, list) {
list_del(&h->list);
kfree(h);
nft_netdev_hook_free(h);
}
module_put(hook->type->owner);
}
@@ -2559,6 +2600,7 @@ static int nft_basechain_init(struct nft_base_chain *basechain, u8 family,
struct nft_chain_hook *hook, u32 flags)
{
struct nft_chain *chain;
struct nf_hook_ops *ops;
struct nft_hook *h;
basechain->type = hook->type;
@@ -2567,8 +2609,10 @@ static int nft_basechain_init(struct nft_base_chain *basechain, u8 family,
if (nft_base_chain_netdev(family, hook->num)) {
list_splice_init(&hook->list, &basechain->hook_list);
list_for_each_entry(h, &basechain->hook_list, list)
nft_basechain_hook_init(&h->ops, family, hook, chain);
list_for_each_entry(h, &basechain->hook_list, list) {
list_for_each_entry(ops, &h->ops_list, list)
nft_basechain_hook_init(ops, family, hook, chain);
}
}
nft_basechain_hook_init(&basechain->ops, family, hook, chain);
@@ -2787,15 +2831,17 @@ static int nf_tables_updchain(struct nft_ctx *ctx, u8 genmask, u8 policy,
if (nft_base_chain_netdev(ctx->family, basechain->ops.hooknum)) {
list_for_each_entry_safe(h, next, &hook.list, list) {
h->ops.pf = basechain->ops.pf;
h->ops.hooknum = basechain->ops.hooknum;
h->ops.priority = basechain->ops.priority;
h->ops.priv = basechain->ops.priv;
h->ops.hook = basechain->ops.hook;
list_for_each_entry(ops, &h->ops_list, list) {
ops->pf = basechain->ops.pf;
ops->hooknum = basechain->ops.hooknum;
ops->priority = basechain->ops.priority;
ops->priv = basechain->ops.priv;
ops->hook = basechain->ops.hook;
}
if (nft_hook_list_find(&basechain->hook_list, h)) {
list_del(&h->list);
kfree(h);
nft_netdev_hook_free(h);
}
}
} else {
@@ -2913,10 +2959,12 @@ err_trans:
err_hooks:
if (nla[NFTA_CHAIN_HOOK]) {
list_for_each_entry_safe(h, next, &hook.list, list) {
if (unregister)
nf_unregister_net_hook(ctx->net, &h->ops);
if (unregister) {
list_for_each_entry(ops, &h->ops_list, list)
nf_unregister_net_hook(ctx->net, ops);
}
list_del(&h->list);
kfree_rcu(h, rcu);
nft_netdev_hook_free_rcu(h);
}
module_put(hook.type->owner);
}
@@ -8785,6 +8833,7 @@ static int nft_flowtable_parse_hook(const struct nft_ctx *ctx,
struct netlink_ext_ack *extack, bool add)
{
struct nlattr *tb[NFTA_FLOWTABLE_HOOK_MAX + 1];
struct nf_hook_ops *ops;
struct nft_hook *hook;
int hooknum, priority;
int err;
@@ -8839,11 +8888,13 @@ static int nft_flowtable_parse_hook(const struct nft_ctx *ctx,
}
list_for_each_entry(hook, &flowtable_hook->list, list) {
hook->ops.pf = NFPROTO_NETDEV;
hook->ops.hooknum = flowtable_hook->num;
hook->ops.priority = flowtable_hook->priority;
hook->ops.priv = &flowtable->data;
hook->ops.hook = flowtable->data.type->hook;
list_for_each_entry(ops, &hook->ops_list, list) {
ops->pf = NFPROTO_NETDEV;
ops->hooknum = flowtable_hook->num;
ops->priority = flowtable_hook->priority;
ops->priv = &flowtable->data;
ops->hook = flowtable->data.type->hook;
}
}
return err;
@@ -8885,12 +8936,12 @@ nft_flowtable_type_get(struct net *net, u8 family)
}
/* Only called from error and netdev event paths. */
static void nft_unregister_flowtable_hook(struct net *net,
struct nft_flowtable *flowtable,
struct nft_hook *hook)
static void nft_unregister_flowtable_ops(struct net *net,
struct nft_flowtable *flowtable,
struct nf_hook_ops *ops)
{
nf_unregister_net_hook(net, &hook->ops);
flowtable->data.type->setup(&flowtable->data, hook->ops.dev,
nf_unregister_net_hook(net, ops);
flowtable->data.type->setup(&flowtable->data, ops->dev,
FLOW_BLOCK_UNBIND);
}
@@ -8900,14 +8951,14 @@ static void __nft_unregister_flowtable_net_hooks(struct net *net,
bool release_netdev)
{
struct nft_hook *hook, *next;
struct nf_hook_ops *ops;
list_for_each_entry_safe(hook, next, hook_list, list) {
nf_unregister_net_hook(net, &hook->ops);
flowtable->data.type->setup(&flowtable->data, hook->ops.dev,
FLOW_BLOCK_UNBIND);
list_for_each_entry(ops, &hook->ops_list, list)
nft_unregister_flowtable_ops(net, flowtable, ops);
if (release_netdev) {
list_del(&hook->list);
kfree_rcu(hook, rcu);
nft_netdev_hook_free_rcu(hook);
}
}
}
@@ -8919,6 +8970,26 @@ static void nft_unregister_flowtable_net_hooks(struct net *net,
__nft_unregister_flowtable_net_hooks(net, flowtable, hook_list, false);
}
static int nft_register_flowtable_ops(struct net *net,
struct nft_flowtable *flowtable,
struct nf_hook_ops *ops)
{
int err;
err = flowtable->data.type->setup(&flowtable->data,
ops->dev, FLOW_BLOCK_BIND);
if (err < 0)
return err;
err = nf_register_net_hook(net, ops);
if (!err)
return 0;
flowtable->data.type->setup(&flowtable->data,
ops->dev, FLOW_BLOCK_UNBIND);
return err;
}
static int nft_register_flowtable_net_hooks(struct net *net,
struct nft_table *table,
struct list_head *hook_list,
@@ -8926,6 +8997,7 @@ static int nft_register_flowtable_net_hooks(struct net *net,
{
struct nft_hook *hook, *next;
struct nft_flowtable *ft;
struct nf_hook_ops *ops;
int err, i = 0;
list_for_each_entry(hook, hook_list, list) {
@@ -8939,33 +9011,27 @@ static int nft_register_flowtable_net_hooks(struct net *net,
}
}
err = flowtable->data.type->setup(&flowtable->data,
hook->ops.dev,
FLOW_BLOCK_BIND);
if (err < 0)
goto err_unregister_net_hooks;
list_for_each_entry(ops, &hook->ops_list, list) {
err = nft_register_flowtable_ops(net, flowtable, ops);
if (err < 0)
goto err_unregister_net_hooks;
err = nf_register_net_hook(net, &hook->ops);
if (err < 0) {
flowtable->data.type->setup(&flowtable->data,
hook->ops.dev,
FLOW_BLOCK_UNBIND);
goto err_unregister_net_hooks;
i++;
}
i++;
}
return 0;
err_unregister_net_hooks:
list_for_each_entry_safe(hook, next, hook_list, list) {
if (i-- <= 0)
break;
list_for_each_entry(ops, &hook->ops_list, list) {
if (i-- <= 0)
break;
nft_unregister_flowtable_hook(net, flowtable, hook);
nft_unregister_flowtable_ops(net, flowtable, ops);
}
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
nft_netdev_hook_free_rcu(hook);
}
return err;
@@ -8977,7 +9043,7 @@ static void nft_hooks_destroy(struct list_head *hook_list)
list_for_each_entry_safe(hook, next, hook_list, list) {
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
nft_netdev_hook_free_rcu(hook);
}
}
@@ -8988,6 +9054,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
const struct nlattr * const *nla = ctx->nla;
struct nft_flowtable_hook flowtable_hook;
struct nft_hook *hook, *next;
struct nf_hook_ops *ops;
struct nft_trans *trans;
bool unregister = false;
u32 flags;
@@ -9001,7 +9068,7 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) {
if (nft_hook_list_find(&flowtable->hook_list, hook)) {
list_del(&hook->list);
kfree(hook);
nft_netdev_hook_free(hook);
}
}
@@ -9045,10 +9112,13 @@ static int nft_flowtable_update(struct nft_ctx *ctx, const struct nlmsghdr *nlh,
err_flowtable_update_hook:
list_for_each_entry_safe(hook, next, &flowtable_hook.list, list) {
if (unregister)
nft_unregister_flowtable_hook(ctx->net, flowtable, hook);
if (unregister) {
list_for_each_entry(ops, &hook->ops_list, list)
nft_unregister_flowtable_ops(ctx->net,
flowtable, ops);
}
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
nft_netdev_hook_free_rcu(hook);
}
return err;
@@ -9194,7 +9264,7 @@ static void nft_flowtable_hook_release(struct nft_flowtable_hook *flowtable_hook
list_for_each_entry_safe(this, next, &flowtable_hook->list, list) {
list_del(&this->list);
kfree(this);
nft_netdev_hook_free(this);
}
}
@@ -9557,7 +9627,7 @@ static void nf_tables_flowtable_destroy(struct nft_flowtable *flowtable)
flowtable->data.type->free(&flowtable->data);
list_for_each_entry_safe(hook, next, &flowtable->hook_list, list) {
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
nft_netdev_hook_free_rcu(hook);
}
kfree(flowtable->name);
module_put(flowtable->data.type->owner);
@@ -9590,46 +9660,190 @@ nla_put_failure:
return -EMSGSIZE;
}
static void nft_flowtable_event(unsigned long event, struct net_device *dev,
struct nft_flowtable *flowtable)
struct nf_hook_ops *nft_hook_find_ops(const struct nft_hook *hook,
const struct net_device *dev)
{
struct nf_hook_ops *ops;
list_for_each_entry(ops, &hook->ops_list, list) {
if (ops->dev == dev)
return ops;
}
return NULL;
}
EXPORT_SYMBOL_GPL(nft_hook_find_ops);
struct nf_hook_ops *nft_hook_find_ops_rcu(const struct nft_hook *hook,
const struct net_device *dev)
{
struct nf_hook_ops *ops;
list_for_each_entry_rcu(ops, &hook->ops_list, list) {
if (ops->dev == dev)
return ops;
}
return NULL;
}
EXPORT_SYMBOL_GPL(nft_hook_find_ops_rcu);
static void
nf_tables_device_notify(const struct nft_table *table, int attr,
const char *name, const struct nft_hook *hook,
const struct net_device *dev, int event)
{
struct net *net = dev_net(dev);
struct nlmsghdr *nlh;
struct sk_buff *skb;
u16 flags = 0;
if (!nfnetlink_has_listeners(net, NFNLGRP_NFT_DEV))
return;
skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
if (!skb)
goto err;
event = event == NETDEV_REGISTER ? NFT_MSG_NEWDEV : NFT_MSG_DELDEV;
event = nfnl_msg_type(NFNL_SUBSYS_NFTABLES, event);
nlh = nfnl_msg_put(skb, 0, 0, event, flags, table->family,
NFNETLINK_V0, nft_base_seq(net));
if (!nlh)
goto err;
if (nla_put_string(skb, NFTA_DEVICE_TABLE, table->name) ||
nla_put_string(skb, attr, name) ||
nla_put(skb, NFTA_DEVICE_SPEC, hook->ifnamelen, hook->ifname) ||
nla_put_string(skb, NFTA_DEVICE_NAME, dev->name))
goto err;
nlmsg_end(skb, nlh);
nfnetlink_send(skb, net, 0, NFNLGRP_NFT_DEV,
nlmsg_report(nlh), GFP_KERNEL);
return;
err:
if (skb)
kfree_skb(skb);
nfnetlink_set_err(net, 0, NFNLGRP_NFT_DEV, -ENOBUFS);
}
void
nf_tables_chain_device_notify(const struct nft_chain *chain,
const struct nft_hook *hook,
const struct net_device *dev, int event)
{
nf_tables_device_notify(chain->table, NFTA_DEVICE_CHAIN,
chain->name, hook, dev, event);
}
static void
nf_tables_flowtable_device_notify(const struct nft_flowtable *ft,
const struct nft_hook *hook,
const struct net_device *dev, int event)
{
nf_tables_device_notify(ft->table, NFTA_DEVICE_FLOWTABLE,
ft->name, hook, dev, event);
}
static int nft_flowtable_event(unsigned long event, struct net_device *dev,
struct nft_flowtable *flowtable, bool changename)
{
struct nf_hook_ops *ops;
struct nft_hook *hook;
bool match;
list_for_each_entry(hook, &flowtable->hook_list, list) {
if (hook->ops.dev != dev)
continue;
ops = nft_hook_find_ops(hook, dev);
match = !strncmp(hook->ifname, dev->name, hook->ifnamelen);
/* flow_offload_netdev_event() cleans up entries for us. */
nft_unregister_flowtable_hook(dev_net(dev), flowtable, hook);
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
switch (event) {
case NETDEV_UNREGISTER:
/* NOP if not found or new name still matching */
if (!ops || (changename && match))
continue;
/* flow_offload_netdev_event() cleans up entries for us. */
nft_unregister_flowtable_ops(dev_net(dev),
flowtable, ops);
list_del_rcu(&ops->list);
kfree_rcu(ops, rcu);
break;
case NETDEV_REGISTER:
/* NOP if not matching or already registered */
if (!match || (changename && ops))
continue;
ops = kzalloc(sizeof(struct nf_hook_ops),
GFP_KERNEL_ACCOUNT);
if (!ops)
return 1;
ops->pf = NFPROTO_NETDEV;
ops->hooknum = flowtable->hooknum;
ops->priority = flowtable->data.priority;
ops->priv = &flowtable->data;
ops->hook = flowtable->data.type->hook;
ops->dev = dev;
if (nft_register_flowtable_ops(dev_net(dev),
flowtable, ops)) {
kfree(ops);
return 1;
}
list_add_tail_rcu(&ops->list, &hook->ops_list);
break;
}
nf_tables_flowtable_device_notify(flowtable, hook, dev, event);
break;
}
return 0;
}
static int __nf_tables_flowtable_event(unsigned long event,
struct net_device *dev,
bool changename)
{
struct nftables_pernet *nft_net = nft_pernet(dev_net(dev));
struct nft_flowtable *flowtable;
struct nft_table *table;
list_for_each_entry(table, &nft_net->tables, list) {
list_for_each_entry(flowtable, &table->flowtables, list) {
if (nft_flowtable_event(event, dev,
flowtable, changename))
return 1;
}
}
return 0;
}
static int nf_tables_flowtable_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct nft_flowtable *flowtable;
struct nftables_pernet *nft_net;
struct nft_table *table;
int ret = NOTIFY_DONE;
struct net *net;
if (event != NETDEV_UNREGISTER)
return 0;
if (event != NETDEV_REGISTER &&
event != NETDEV_UNREGISTER &&
event != NETDEV_CHANGENAME)
return NOTIFY_DONE;
net = dev_net(dev);
nft_net = nft_pernet(net);
mutex_lock(&nft_net->commit_mutex);
list_for_each_entry(table, &nft_net->tables, list) {
list_for_each_entry(flowtable, &table->flowtables, list) {
nft_flowtable_event(event, dev, flowtable);
}
}
mutex_unlock(&nft_net->commit_mutex);
return NOTIFY_DONE;
if (event == NETDEV_CHANGENAME) {
if (__nf_tables_flowtable_event(NETDEV_REGISTER, dev, true)) {
ret = NOTIFY_BAD;
goto out_unlock;
}
__nf_tables_flowtable_event(NETDEV_UNREGISTER, dev, true);
} else if (__nf_tables_flowtable_event(event, dev, false)) {
ret = NOTIFY_BAD;
}
out_unlock:
mutex_unlock(&nft_net->commit_mutex);
return ret;
}
static struct notifier_block nf_tables_flowtable_notifier = {

View File

@@ -220,6 +220,7 @@ static int nft_chain_offload_priority(const struct nft_base_chain *basechain)
bool nft_chain_offload_support(const struct nft_base_chain *basechain)
{
struct nf_hook_ops *ops;
struct net_device *dev;
struct nft_hook *hook;
@@ -227,13 +228,16 @@ bool nft_chain_offload_support(const struct nft_base_chain *basechain)
return false;
list_for_each_entry(hook, &basechain->hook_list, list) {
if (hook->ops.pf != NFPROTO_NETDEV ||
hook->ops.hooknum != NF_NETDEV_INGRESS)
return false;
list_for_each_entry(ops, &hook->ops_list, list) {
if (ops->pf != NFPROTO_NETDEV ||
ops->hooknum != NF_NETDEV_INGRESS)
return false;
dev = hook->ops.dev;
if (!dev->netdev_ops->ndo_setup_tc && !flow_indr_dev_exists())
return false;
dev = ops->dev;
if (!dev->netdev_ops->ndo_setup_tc &&
!flow_indr_dev_exists())
return false;
}
}
return true;
@@ -455,34 +459,37 @@ static int nft_flow_block_chain(struct nft_base_chain *basechain,
const struct net_device *this_dev,
enum flow_block_command cmd)
{
struct net_device *dev;
struct nf_hook_ops *ops;
struct nft_hook *hook;
int err, i = 0;
list_for_each_entry(hook, &basechain->hook_list, list) {
dev = hook->ops.dev;
if (this_dev && this_dev != dev)
continue;
list_for_each_entry(ops, &hook->ops_list, list) {
if (this_dev && this_dev != ops->dev)
continue;
err = nft_chain_offload_cmd(basechain, dev, cmd);
if (err < 0 && cmd == FLOW_BLOCK_BIND) {
if (!this_dev)
goto err_flow_block;
err = nft_chain_offload_cmd(basechain, ops->dev, cmd);
if (err < 0 && cmd == FLOW_BLOCK_BIND) {
if (!this_dev)
goto err_flow_block;
return err;
return err;
}
i++;
}
i++;
}
return 0;
err_flow_block:
list_for_each_entry(hook, &basechain->hook_list, list) {
if (i-- <= 0)
break;
list_for_each_entry(ops, &hook->ops_list, list) {
if (i-- <= 0)
break;
dev = hook->ops.dev;
nft_chain_offload_cmd(basechain, dev, FLOW_BLOCK_UNBIND);
nft_chain_offload_cmd(basechain, ops->dev,
FLOW_BLOCK_UNBIND);
}
}
return err;
}
@@ -638,7 +645,7 @@ static struct nft_chain *__nft_offload_get_chain(const struct nftables_pernet *n
found = NULL;
basechain = nft_base_chain(chain);
list_for_each_entry(hook, &basechain->hook_list, list) {
if (hook->ops.dev != dev)
if (!nft_hook_find_ops(hook, dev))
continue;
found = hook;

View File

@@ -15,6 +15,7 @@
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
@@ -90,6 +91,49 @@ static int nf_trace_fill_dev_info(struct sk_buff *nlskb,
return 0;
}
static int nf_trace_fill_ct_info(struct sk_buff *nlskb,
const struct sk_buff *skb)
{
const struct nf_ct_hook *ct_hook;
enum ip_conntrack_info ctinfo;
const struct nf_conn *ct;
u32 state;
ct_hook = rcu_dereference(nf_ct_hook);
if (!ct_hook)
return 0;
ct = nf_ct_get(skb, &ctinfo);
if (!ct) {
if (ctinfo != IP_CT_UNTRACKED) /* not seen by conntrack or invalid */
return 0;
state = NF_CT_STATE_UNTRACKED_BIT;
} else {
state = NF_CT_STATE_BIT(ctinfo);
}
if (nla_put_be32(nlskb, NFTA_TRACE_CT_STATE, htonl(state)))
return -1;
if (ct) {
u32 id = ct_hook->get_id(&ct->ct_general);
u32 status = READ_ONCE(ct->status);
u8 dir = CTINFO2DIR(ctinfo);
if (nla_put_u8(nlskb, NFTA_TRACE_CT_DIRECTION, dir))
return -1;
if (nla_put_be32(nlskb, NFTA_TRACE_CT_ID, (__force __be32)id))
return -1;
if (status && nla_put_be32(nlskb, NFTA_TRACE_CT_STATUS, htonl(status)))
return -1;
}
return 0;
}
static int nf_trace_fill_pkt_info(struct sk_buff *nlskb,
const struct nft_pktinfo *pkt)
{
@@ -210,7 +254,11 @@ void nft_trace_notify(const struct nft_pktinfo *pkt,
nla_total_size(sizeof(__be32)) + /* trace type */
nla_total_size(0) + /* VERDICT, nested */
nla_total_size(sizeof(u32)) + /* verdict code */
nla_total_size(sizeof(u32)) + /* id */
nla_total_size(sizeof(u32)) + /* ct id */
nla_total_size(sizeof(u8)) + /* ct direction */
nla_total_size(sizeof(u32)) + /* ct state */
nla_total_size(sizeof(u32)) + /* ct status */
nla_total_size(sizeof(u32)) + /* trace id */
nla_total_size(NFT_TRACETYPE_LL_HSIZE) +
nla_total_size(NFT_TRACETYPE_NETWORK_HSIZE) +
nla_total_size(NFT_TRACETYPE_TRANSPORT_HSIZE) +
@@ -291,6 +339,10 @@ void nft_trace_notify(const struct nft_pktinfo *pkt,
if (nf_trace_fill_pkt_info(skb, pkt))
goto nla_put_failure;
if (nf_trace_fill_ct_info(skb, pkt->skb))
goto nla_put_failure;
info->packet_dumped = true;
}

View File

@@ -86,6 +86,7 @@ static const int nfnl_group2type[NFNLGRP_MAX+1] = {
[NFNLGRP_NFTABLES] = NFNL_SUBSYS_NFTABLES,
[NFNLGRP_ACCT_QUOTA] = NFNL_SUBSYS_ACCT,
[NFNLGRP_NFTRACE] = NFNL_SUBSYS_NFTABLES,
[NFNLGRP_NFT_DEV] = NFNL_SUBSYS_NFTABLES,
};
static struct nfnl_net *nfnl_pernet(struct net *net)

View File

@@ -318,38 +318,68 @@ static const struct nft_chain_type nft_chain_filter_netdev = {
},
};
static void nft_netdev_event(unsigned long event, struct net_device *dev,
struct nft_base_chain *basechain)
static int nft_netdev_event(unsigned long event, struct net_device *dev,
struct nft_base_chain *basechain, bool changename)
{
struct nft_table *table = basechain->chain.table;
struct nf_hook_ops *ops;
struct nft_hook *hook;
bool match;
list_for_each_entry(hook, &basechain->hook_list, list) {
if (hook->ops.dev != dev)
continue;
ops = nft_hook_find_ops(hook, dev);
match = !strncmp(hook->ifname, dev->name, hook->ifnamelen);
if (!(basechain->chain.table->flags & NFT_TABLE_F_DORMANT))
nf_unregister_net_hook(dev_net(dev), &hook->ops);
switch (event) {
case NETDEV_UNREGISTER:
/* NOP if not found or new name still matching */
if (!ops || (changename && match))
continue;
list_del_rcu(&hook->list);
kfree_rcu(hook, rcu);
if (!(table->flags & NFT_TABLE_F_DORMANT))
nf_unregister_net_hook(dev_net(dev), ops);
list_del_rcu(&ops->list);
kfree_rcu(ops, rcu);
break;
case NETDEV_REGISTER:
/* NOP if not matching or already registered */
if (!match || (changename && ops))
continue;
ops = kmemdup(&basechain->ops,
sizeof(struct nf_hook_ops),
GFP_KERNEL_ACCOUNT);
if (!ops)
return 1;
ops->dev = dev;
if (!(table->flags & NFT_TABLE_F_DORMANT) &&
nf_register_net_hook(dev_net(dev), ops)) {
kfree(ops);
return 1;
}
list_add_tail_rcu(&ops->list, &hook->ops_list);
break;
}
nf_tables_chain_device_notify(&basechain->chain,
hook, dev, event);
break;
}
return 0;
}
static int nf_tables_netdev_event(struct notifier_block *this,
unsigned long event, void *ptr)
static int __nf_tables_netdev_event(unsigned long event,
struct net_device *dev,
bool changename)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct nft_base_chain *basechain;
struct nftables_pernet *nft_net;
struct nft_chain *chain;
struct nft_table *table;
if (event != NETDEV_UNREGISTER)
return NOTIFY_DONE;
nft_net = nft_pernet(dev_net(dev));
mutex_lock(&nft_net->commit_mutex);
list_for_each_entry(table, &nft_net->tables, list) {
if (table->family != NFPROTO_NETDEV &&
table->family != NFPROTO_INET)
@@ -364,12 +394,40 @@ static int nf_tables_netdev_event(struct notifier_block *this,
basechain->ops.hooknum != NF_INET_INGRESS)
continue;
nft_netdev_event(event, dev, basechain);
if (nft_netdev_event(event, dev, basechain, changename))
return 1;
}
}
mutex_unlock(&nft_net->commit_mutex);
return 0;
}
return NOTIFY_DONE;
static int nf_tables_netdev_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct nftables_pernet *nft_net;
int ret = NOTIFY_DONE;
if (event != NETDEV_REGISTER &&
event != NETDEV_UNREGISTER &&
event != NETDEV_CHANGENAME)
return NOTIFY_DONE;
nft_net = nft_pernet(dev_net(dev));
mutex_lock(&nft_net->commit_mutex);
if (event == NETDEV_CHANGENAME) {
if (__nf_tables_netdev_event(NETDEV_REGISTER, dev, true)) {
ret = NOTIFY_BAD;
goto out_unlock;
}
__nf_tables_netdev_event(NETDEV_UNREGISTER, dev, true);
} else if (__nf_tables_netdev_event(event, dev, false)) {
ret = NOTIFY_BAD;
}
out_unlock:
mutex_unlock(&nft_net->commit_mutex);
return ret;
}
static struct notifier_block nf_tables_netdev_notifier = {

View File

@@ -175,7 +175,7 @@ static bool nft_flowtable_find_dev(const struct net_device *dev,
bool found = false;
list_for_each_entry_rcu(hook, &ft->hook_list, list) {
if (hook->ops.dev != dev)
if (!nft_hook_find_ops_rcu(hook, dev))
continue;
found = true;

View File

@@ -23,7 +23,14 @@
#include <linux/ip.h>
#include <linux/ipv6.h>
static DEFINE_PER_CPU(struct nft_inner_tun_ctx, nft_pcpu_tun_ctx);
struct nft_inner_tun_ctx_locked {
struct nft_inner_tun_ctx ctx;
local_lock_t bh_lock;
};
static DEFINE_PER_CPU(struct nft_inner_tun_ctx_locked, nft_pcpu_tun_ctx) = {
.bh_lock = INIT_LOCAL_LOCK(bh_lock),
};
/* Same layout as nft_expr but it embeds the private expression data area. */
struct __nft_expr {
@@ -237,12 +244,15 @@ static bool nft_inner_restore_tun_ctx(const struct nft_pktinfo *pkt,
struct nft_inner_tun_ctx *this_cpu_tun_ctx;
local_bh_disable();
this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx);
local_lock_nested_bh(&nft_pcpu_tun_ctx.bh_lock);
this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx.ctx);
if (this_cpu_tun_ctx->cookie != (unsigned long)pkt->skb) {
local_bh_enable();
local_unlock_nested_bh(&nft_pcpu_tun_ctx.bh_lock);
return false;
}
*tun_ctx = *this_cpu_tun_ctx;
local_unlock_nested_bh(&nft_pcpu_tun_ctx.bh_lock);
local_bh_enable();
return true;
@@ -254,9 +264,11 @@ static void nft_inner_save_tun_ctx(const struct nft_pktinfo *pkt,
struct nft_inner_tun_ctx *this_cpu_tun_ctx;
local_bh_disable();
this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx);
local_lock_nested_bh(&nft_pcpu_tun_ctx.bh_lock);
this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx.ctx);
if (this_cpu_tun_ctx->cookie != tun_ctx->cookie)
*this_cpu_tun_ctx = *tun_ctx;
local_unlock_nested_bh(&nft_pcpu_tun_ctx.bh_lock);
local_bh_enable();
}

View File

@@ -621,10 +621,10 @@ static int nft_tunnel_opts_dump(struct sk_buff *skb,
struct geneve_opt *opt;
int offset = 0;
inner = nla_nest_start_noflag(skb, NFTA_TUNNEL_KEY_OPTS_GENEVE);
if (!inner)
goto failure;
while (opts->len > offset) {
inner = nla_nest_start_noflag(skb, NFTA_TUNNEL_KEY_OPTS_GENEVE);
if (!inner)
goto failure;
opt = (struct geneve_opt *)(opts->u.data + offset);
if (nla_put_be16(skb, NFTA_TUNNEL_KEY_GENEVE_CLASS,
opt->opt_class) ||
@@ -634,8 +634,8 @@ static int nft_tunnel_opts_dump(struct sk_buff *skb,
opt->length * 4, opt->opt_data))
goto inner_failure;
offset += sizeof(*opt) + opt->length * 4;
nla_nest_end(skb, inner);
}
nla_nest_end(skb, inner);
}
nla_nest_end(skb, nest);
return 0;

View File

@@ -91,7 +91,7 @@ tcpoptstrip_tg4(struct sk_buff *skb, const struct xt_action_param *par)
return tcpoptstrip_mangle_packet(skb, par, ip_hdrlen(skb));
}
#if IS_ENABLED(CONFIG_IP6_NF_MANGLE)
#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
static unsigned int
tcpoptstrip_tg6(struct sk_buff *skb, const struct xt_action_param *par)
{
@@ -119,7 +119,7 @@ static struct xt_target tcpoptstrip_tg_reg[] __read_mostly = {
.targetsize = sizeof(struct xt_tcpoptstrip_target_info),
.me = THIS_MODULE,
},
#if IS_ENABLED(CONFIG_IP6_NF_MANGLE)
#if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
{
.name = "TCPOPTSTRIP",
.family = NFPROTO_IPV6,

View File

@@ -48,7 +48,7 @@ static struct xt_target mark_tg_reg[] __read_mostly = {
.targetsize = sizeof(struct xt_mark_tginfo2),
.me = THIS_MODULE,
},
#if IS_ENABLED(CONFIG_IP_NF_ARPTABLES)
#if IS_ENABLED(CONFIG_IP_NF_ARPTABLES) || IS_ENABLED(CONFIG_NFT_COMPAT_ARP)
{
.name = "MARK",
.revision = 2,