Skip to content

Commit

Permalink
netfilter: nf_nat_masquerade: defer conntrack walk to work queue
Browse files Browse the repository at this point in the history
The ipv4 and device notifiers are called with RTNL mutex held.
The table walk can take some time, better not block other RTNL users.

'ip a' has been reported to block for up to 20 seconds when conntrack table
has many entries and device down events are frequent (e.g., PPP).

Reported-and-tested-by: Martin Zaharinov <[email protected]>
Signed-off-by: Florian Westphal <[email protected]>
Signed-off-by: Pablo Neira Ayuso <[email protected]>
  • Loading branch information
Florian Westphal authored and ummakynes committed Sep 21, 2021
1 parent 30db406 commit 7970a19
Showing 1 changed file with 24 additions and 26 deletions.
50 changes: 24 additions & 26 deletions net/netfilter/nf_nat_masquerade.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,14 @@ static void nf_nat_masq_schedule(struct net *net, union nf_inet_addr *addr,
put_net(net);
}

static int device_cmp(struct nf_conn *i, void *ifindex)
static int device_cmp(struct nf_conn *i, void *arg)
{
const struct nf_conn_nat *nat = nfct_nat(i);
const struct masq_dev_work *w = arg;

if (!nat)
return 0;
return nat->masq_index == (int)(long)ifindex;
return nat->masq_index == w->ifindex;
}

static int masq_device_event(struct notifier_block *this,
Expand All @@ -153,44 +154,54 @@ static int masq_device_event(struct notifier_block *this,
* and forget them.
*/

nf_ct_iterate_cleanup_net(net, device_cmp,
(void *)(long)dev->ifindex, 0, 0);
nf_nat_masq_schedule(net, NULL, dev->ifindex,
device_cmp, GFP_KERNEL);
}

return NOTIFY_DONE;
}

static int inet_cmp(struct nf_conn *ct, void *ptr)
{
struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
struct net_device *dev = ifa->ifa_dev->dev;
struct nf_conntrack_tuple *tuple;
struct masq_dev_work *w = ptr;

if (!device_cmp(ct, (void *)(long)dev->ifindex))
if (!device_cmp(ct, ptr))
return 0;

tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;

return ifa->ifa_address == tuple->dst.u3.ip;
return nf_inet_addr_cmp(&w->addr, &tuple->dst.u3);
}

static int masq_inet_event(struct notifier_block *this,
unsigned long event,
void *ptr)
{
struct in_device *idev = ((struct in_ifaddr *)ptr)->ifa_dev;
struct net *net = dev_net(idev->dev);
const struct in_ifaddr *ifa = ptr;
const struct in_device *idev;
const struct net_device *dev;
union nf_inet_addr addr;

if (event != NETDEV_DOWN)
return NOTIFY_DONE;

/* The masq_dev_notifier will catch the case of the device going
* down. So if the inetdev is dead and being destroyed we have
* no work to do. Otherwise this is an individual address removal
* and we have to perform the flush.
*/
idev = ifa->ifa_dev;
if (idev->dead)
return NOTIFY_DONE;

if (event == NETDEV_DOWN)
nf_ct_iterate_cleanup_net(net, inet_cmp, ptr, 0, 0);
memset(&addr, 0, sizeof(addr));

addr.ip = ifa->ifa_address;

dev = idev->dev;
nf_nat_masq_schedule(dev_net(idev->dev), &addr, dev->ifindex,
inet_cmp, GFP_KERNEL);

return NOTIFY_DONE;
}
Expand Down Expand Up @@ -253,19 +264,6 @@ nf_nat_masquerade_ipv6(struct sk_buff *skb, const struct nf_nat_range2 *range,
}
EXPORT_SYMBOL_GPL(nf_nat_masquerade_ipv6);

static int inet6_cmp(struct nf_conn *ct, void *work)
{
struct masq_dev_work *w = (struct masq_dev_work *)work;
struct nf_conntrack_tuple *tuple;

if (!device_cmp(ct, (void *)(long)w->ifindex))
return 0;

tuple = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;

return nf_inet_addr_cmp(&w->addr, &tuple->dst.u3);
}

/* atomic notifier; can't call nf_ct_iterate_cleanup_net (it can sleep).
*
* Defer it to the system workqueue.
Expand All @@ -289,7 +287,7 @@ static int masq_inet6_event(struct notifier_block *this,

addr.in6 = ifa->addr;

nf_nat_masq_schedule(dev_net(dev), &addr, dev->ifindex, inet6_cmp,
nf_nat_masq_schedule(dev_net(dev), &addr, dev->ifindex, inet_cmp,
GFP_ATOMIC);
return NOTIFY_DONE;
}
Expand Down

0 comments on commit 7970a19

Please sign in to comment.