Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: neigh: decrement the family specific qlen

Commit 0ff4eb3d5ebb ("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: 0ff4eb3d5ebb ("neighbour: make proxy_queue.qlen limit per-device")
Signed-off-by: Thomas Zeitlhofer <thomas.zeitlhofer+lkml@ze-it.at>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Thomas Zeitlhofer and committed by
David S. Miller
8207f253 733d4bbf

+31 -29
+1 -1
include/net/neighbour.h
··· 83 83 struct rcu_head rcu_head; 84 84 85 85 int reachable_time; 86 - int qlen; 86 + u32 qlen; 87 87 int data[NEIGH_VAR_DATA_MAX]; 88 88 DECLARE_BITMAP(data_state, NEIGH_VAR_DATA_MAX); 89 89 };
+30 -28
net/core/neighbour.c
··· 307 307 return 0; 308 308 } 309 309 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) 311 335 { 312 336 struct sk_buff_head tmp; 313 337 unsigned long flags; ··· 345 321 struct net_device *dev = skb->dev; 346 322 347 323 if (net == NULL || net_eq(dev_net(dev), net)) { 348 - struct in_device *in_dev; 349 - 350 - rcu_read_lock(); 351 - in_dev = __in_dev_get_rcu(dev); 352 - if (in_dev) 353 - in_dev->arp_parms->qlen--; 354 - rcu_read_unlock(); 324 + neigh_parms_qlen_dec(dev, family); 355 325 __skb_unlink(skb, list); 356 326 __skb_queue_tail(&tmp, skb); 357 327 } ··· 427 409 write_lock_bh(&tbl->lock); 428 410 neigh_flush_dev(tbl, dev, skip_perm); 429 411 pneigh_ifdown_and_unlock(tbl, dev); 430 - pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL); 412 + pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL, 413 + tbl->family); 431 414 if (skb_queue_empty_lockless(&tbl->proxy_queue)) 432 415 del_timer_sync(&tbl->proxy_timer); 433 416 return 0; ··· 1640 1621 1641 1622 if (tdif <= 0) { 1642 1623 struct net_device *dev = skb->dev; 1643 - struct in_device *in_dev; 1644 1624 1645 - rcu_read_lock(); 1646 - in_dev = __in_dev_get_rcu(dev); 1647 - if (in_dev) 1648 - in_dev->arp_parms->qlen--; 1649 - rcu_read_unlock(); 1625 + neigh_parms_qlen_dec(dev, tbl->family); 1650 1626 __skb_unlink(skb, &tbl->proxy_queue); 1651 1627 1652 1628 if (tbl->proxy_redo && netif_running(dev)) { ··· 1835 1821 cancel_delayed_work_sync(&tbl->managed_work); 1836 1822 cancel_delayed_work_sync(&tbl->gc_work); 1837 1823 del_timer_sync(&tbl->proxy_timer); 1838 - pneigh_queue_purge(&tbl->proxy_queue, NULL); 1824 + pneigh_queue_purge(&tbl->proxy_queue, NULL, tbl->family); 1839 1825 neigh_ifdown(tbl, NULL); 1840 1826 if (atomic_read(&tbl->entries)) 1841 1827 pr_crit("neighbour leakage\n"); ··· 3551 3537 if (write && !ret) 3552 3538 *(int *)ctl->data = size * SKB_TRUESIZE(ETH_FRAME_LEN); 3553 3539 return ret; 3554 - } 3555 - 3556 - static struct neigh_parms *neigh_get_dev_parms_rcu(struct net_device *dev, 3557 - int family) 3558 - { 3559 - switch (family) { 3560 - case AF_INET: 3561 - return __in_dev_arp_parms_get_rcu(dev); 3562 - case AF_INET6: 3563 - return __in6_dev_nd_parms_get_rcu(dev); 3564 - } 3565 - return NULL; 3566 3540 } 3567 3541 3568 3542 static void neigh_copy_dflt_parms(struct net *net, struct neigh_parms *p,