Skip to content

Commit 8207f25

Browse files
Thomas Zeitlhoferdavem330
authored andcommitted
net: neigh: decrement the family specific qlen
Commit 0ff4eb3 ("neighbour: make proxy_queue.qlen limit per-device") introduced the length counter qlen in struct neigh_parms. There are separate neigh_parms instances for IPv4/ARP and IPv6/ND, and while the family specific qlen is incremented in pneigh_enqueue(), the mentioned commit decrements always the IPv4/ARP specific qlen, regardless of the currently processed family, in pneigh_queue_purge() and neigh_proxy_process(). As a result, with IPv6/ND, the family specific qlen is only incremented (and never decremented) until it exceeds PROXY_QLEN, and then, according to the check in pneigh_enqueue(), neighbor solicitations are not answered anymore. As an example, this is noted when using the subnet-router anycast address to access a Linux router. After a certain amount of time (in the observed case, qlen exceeded PROXY_QLEN after two days), the Linux router stops answering neighbor solicitations for its subnet-router anycast address and effectively becomes unreachable. Another result with IPv6/ND is that the IPv4/ARP specific qlen is decremented more often than incremented. This leads to negative qlen values, as a signed integer has been used for the length counter qlen, and potentially to an integer overflow. Fix this by introducing the helper function neigh_parms_qlen_dec(), which decrements the family specific qlen. Thereby, make use of the existing helper function neigh_get_dev_parms_rcu(), whose definition therefore needs to be placed earlier in neighbour.c. Take the family member from struct neigh_table to determine the currently processed family and appropriately call neigh_parms_qlen_dec() from pneigh_queue_purge() and neigh_proxy_process(). Additionally, use an unsigned integer for the length counter qlen. Fixes: 0ff4eb3 ("neighbour: make proxy_queue.qlen limit per-device") Signed-off-by: Thomas Zeitlhofer <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 733d4bb commit 8207f25

File tree

2 files changed

+31
-29
lines changed

2 files changed

+31
-29
lines changed

include/net/neighbour.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ struct neigh_parms {
8383
struct rcu_head rcu_head;
8484

8585
int reachable_time;
86-
int qlen;
86+
u32 qlen;
8787
int data[NEIGH_VAR_DATA_MAX];
8888
DECLARE_BITMAP(data_state, NEIGH_VAR_DATA_MAX);
8989
};

net/core/neighbour.c

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,31 @@ static int neigh_del_timer(struct neighbour *n)
307307
return 0;
308308
}
309309

310-
static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net)
310+
static struct neigh_parms *neigh_get_dev_parms_rcu(struct net_device *dev,
311+
int family)
312+
{
313+
switch (family) {
314+
case AF_INET:
315+
return __in_dev_arp_parms_get_rcu(dev);
316+
case AF_INET6:
317+
return __in6_dev_nd_parms_get_rcu(dev);
318+
}
319+
return NULL;
320+
}
321+
322+
static void neigh_parms_qlen_dec(struct net_device *dev, int family)
323+
{
324+
struct neigh_parms *p;
325+
326+
rcu_read_lock();
327+
p = neigh_get_dev_parms_rcu(dev, family);
328+
if (p)
329+
p->qlen--;
330+
rcu_read_unlock();
331+
}
332+
333+
static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net,
334+
int family)
311335
{
312336
struct sk_buff_head tmp;
313337
unsigned long flags;
@@ -321,13 +345,7 @@ static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net)
321345
struct net_device *dev = skb->dev;
322346

323347
if (net == NULL || net_eq(dev_net(dev), net)) {
324-
struct in_device *in_dev;
325-
326-
rcu_read_lock();
327-
in_dev = __in_dev_get_rcu(dev);
328-
if (in_dev)
329-
in_dev->arp_parms->qlen--;
330-
rcu_read_unlock();
348+
neigh_parms_qlen_dec(dev, family);
331349
__skb_unlink(skb, list);
332350
__skb_queue_tail(&tmp, skb);
333351
}
@@ -409,7 +427,8 @@ static int __neigh_ifdown(struct neigh_table *tbl, struct net_device *dev,
409427
write_lock_bh(&tbl->lock);
410428
neigh_flush_dev(tbl, dev, skip_perm);
411429
pneigh_ifdown_and_unlock(tbl, dev);
412-
pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL);
430+
pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL,
431+
tbl->family);
413432
if (skb_queue_empty_lockless(&tbl->proxy_queue))
414433
del_timer_sync(&tbl->proxy_timer);
415434
return 0;
@@ -1621,13 +1640,8 @@ static void neigh_proxy_process(struct timer_list *t)
16211640

16221641
if (tdif <= 0) {
16231642
struct net_device *dev = skb->dev;
1624-
struct in_device *in_dev;
16251643

1626-
rcu_read_lock();
1627-
in_dev = __in_dev_get_rcu(dev);
1628-
if (in_dev)
1629-
in_dev->arp_parms->qlen--;
1630-
rcu_read_unlock();
1644+
neigh_parms_qlen_dec(dev, tbl->family);
16311645
__skb_unlink(skb, &tbl->proxy_queue);
16321646

16331647
if (tbl->proxy_redo && netif_running(dev)) {
@@ -1821,7 +1835,7 @@ int neigh_table_clear(int index, struct neigh_table *tbl)
18211835
cancel_delayed_work_sync(&tbl->managed_work);
18221836
cancel_delayed_work_sync(&tbl->gc_work);
18231837
del_timer_sync(&tbl->proxy_timer);
1824-
pneigh_queue_purge(&tbl->proxy_queue, NULL);
1838+
pneigh_queue_purge(&tbl->proxy_queue, NULL, tbl->family);
18251839
neigh_ifdown(tbl, NULL);
18261840
if (atomic_read(&tbl->entries))
18271841
pr_crit("neighbour leakage\n");
@@ -3539,18 +3553,6 @@ static int proc_unres_qlen(struct ctl_table *ctl, int write,
35393553
return ret;
35403554
}
35413555

3542-
static struct neigh_parms *neigh_get_dev_parms_rcu(struct net_device *dev,
3543-
int family)
3544-
{
3545-
switch (family) {
3546-
case AF_INET:
3547-
return __in_dev_arp_parms_get_rcu(dev);
3548-
case AF_INET6:
3549-
return __in6_dev_nd_parms_get_rcu(dev);
3550-
}
3551-
return NULL;
3552-
}
3553-
35543556
static void neigh_copy_dflt_parms(struct net *net, struct neigh_parms *p,
35553557
int index)
35563558
{

0 commit comments

Comments
 (0)