From nobody Mon Apr 13 13:26:21 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 923F2C433FE for ; Sat, 12 Nov 2022 11:46:47 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233445AbiKLLqq (ORCPT ); Sat, 12 Nov 2022 06:46:46 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43462 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231534AbiKLLqn (ORCPT ); Sat, 12 Nov 2022 06:46:43 -0500 X-Greylist: delayed 3487 seconds by postgrey-1.37 at lindbergh.monkeyblade.net; Sat, 12 Nov 2022 03:46:41 PST Received: from mail-01-2.mymagenta.at (mail-01-2.mymagenta.at [80.109.253.247]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E6E1619026 for ; Sat, 12 Nov 2022 03:46:41 -0800 (PST) Received: from [192.168.232.135] (helo=ren-mail-psmtp-mg01.) by mail-01.mymagenta.at with esmtp (Exim 4.93) (envelope-from ) id 1oto3q-003ArI-Qb for linux-kernel@vger.kernel.org; Sat, 12 Nov 2022 11:48:30 +0100 Received: from mr2 ([80.108.14.110]) by ren-mail-psmtp-mg01. with ESMTP id to3poEhQDOG5Zto3podtXj; Sat, 12 Nov 2022 11:48:30 +0100 X-Env-Mailfrom: thomas.zeitlhofer+lkml@ze-it.at X-Env-Rcptto: linux-kernel@vger.kernel.org X-SourceIP: 80.108.14.110 X-CNFS-Analysis: v=2.4 cv=KJo5sHJo c=1 sm=1 tr=0 ts=636f79fe a=tVdsoMYQxO1CUTS6b4mKlQ==:117 a=tVdsoMYQxO1CUTS6b4mKlQ==:17 a=kj9zAlcOel0A:10 a=VG87k5yWxwQ-MwjZPvcA:9 a=CjuIK1q_8ugA:10 Date: Sat, 12 Nov 2022 11:48:27 +0100 From: Thomas Zeitlhofer To: "David S. Miller" , Alexander Mikhalitsyn , "Denis V. Lunev" Cc: Eric Dumazet , Jakub Kicinski , Paolo Abeni , David Ahern , Daniel Borkmann , Yang Yingliang , Muchun Song , Vasily Averin , Yuwei Wang , netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH] net: neigh: decrement the family specific qlen Message-ID: MIME-Version: 1.0 Content-Disposition: inline X-CMAE-Envelope: MS4xfFVWgsp8IYBGTwQJ6kSook4nMZDwx0h6hGZImeDR02IB3tnm9k1hkM6XugViv1f6IsJjWGoLVbNO6mRDcxd/p6KQNSlRHESZW0yN+nerryB5E/mlXJ6K kf3ktCAFNF8Xr1SLDgLEoI9rdRb+g5MIowjvFXEglk75M/m3DggEvpDJrih9etJbWVVh+6hmPoRkMB79WoL7HDzjFkFvRoue6DW9xzBmmt3Bs0pURIBPzoz+ EdOJwcF+qHu0pdE8WsCPu/vV4W3XyBKQooOFXWdDdcrVbLmB5W9kgHjOghezG5Zlzj3x6xS47uCxmwRh1K/n8AcN5zM+LkzB3ERYOGkGNHv/eUnycF2lfIrx il4Zec/jcxmNM2Cq9AnYsV+/R0dbvk7vnIDy7ac6aRmXFi82A8P2i1AUG9dly94RberQ04ZbQTmWgKEGWjBsz/N8W7PLaXqa4iT8Lv0F7BPBwAiccdVN7Pfs YAS3++IAxvAhX1GSmWUYYhurR2LkH9gUOQVl9FgN8hKe5VAcFFexCfGL5XBHI+CGCRU9GEgWPaf63q7oMm6qZ0zHheKeO7X8FzBhhrA5QBHJrCoSdUOZfsml b2D464HdZj7c886C4WgaQPYrqU/UGj+Eb+ysYHQW/hvTSA== Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- include/net/neighbour.h | 2 +- net/core/neighbour.c | 58 +++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/include/net/neighbour.h b/include/net/neighbour.h index 20745cf7ae1a..cc0b65b7c829 100644 --- a/include/net/neighbour.h +++ b/include/net/neighbour.h @@ -83,7 +83,7 @@ struct neigh_parms { struct rcu_head rcu_head; =20 int reachable_time; - int qlen; + __u32 qlen; int data[NEIGH_VAR_DATA_MAX]; DECLARE_BITMAP(data_state, NEIGH_VAR_DATA_MAX); }; diff --git a/net/core/neighbour.c b/net/core/neighbour.c index a77a85e357e0..952a54763358 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -307,7 +307,31 @@ static int neigh_del_timer(struct neighbour *n) return 0; } =20 -static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net) +static struct neigh_parms *neigh_get_dev_parms_rcu(struct net_device *dev, + int family) +{ + switch (family) { + case AF_INET: + return __in_dev_arp_parms_get_rcu(dev); + case AF_INET6: + return __in6_dev_nd_parms_get_rcu(dev); + } + return NULL; +} + +static void neigh_parms_qlen_dec(struct net_device *dev, int family) +{ + struct neigh_parms *p; + + rcu_read_lock(); + p =3D neigh_get_dev_parms_rcu(dev, family); + if (p) + p->qlen--; + rcu_read_unlock(); +} + +static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net, + int family) { struct sk_buff_head tmp; unsigned long flags; @@ -321,13 +345,7 @@ static void pneigh_queue_purge(struct sk_buff_head *li= st, struct net *net) struct net_device *dev =3D skb->dev; =20 if (net =3D=3D NULL || net_eq(dev_net(dev), net)) { - struct in_device *in_dev; - - rcu_read_lock(); - in_dev =3D __in_dev_get_rcu(dev); - if (in_dev) - in_dev->arp_parms->qlen--; - rcu_read_unlock(); + neigh_parms_qlen_dec(dev, family); __skb_unlink(skb, list); __skb_queue_tail(&tmp, skb); } @@ -409,7 +427,8 @@ static int __neigh_ifdown(struct neigh_table *tbl, stru= ct net_device *dev, write_lock_bh(&tbl->lock); neigh_flush_dev(tbl, dev, skip_perm); pneigh_ifdown_and_unlock(tbl, dev); - pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL); + pneigh_queue_purge(&tbl->proxy_queue, dev ? dev_net(dev) : NULL, + tbl->family); if (skb_queue_empty_lockless(&tbl->proxy_queue)) del_timer_sync(&tbl->proxy_timer); return 0; @@ -1621,13 +1640,8 @@ static void neigh_proxy_process(struct timer_list *t) =20 if (tdif <=3D 0) { struct net_device *dev =3D skb->dev; - struct in_device *in_dev; =20 - rcu_read_lock(); - in_dev =3D __in_dev_get_rcu(dev); - if (in_dev) - in_dev->arp_parms->qlen--; - rcu_read_unlock(); + neigh_parms_qlen_dec(dev, tbl->family); __skb_unlink(skb, &tbl->proxy_queue); =20 if (tbl->proxy_redo && netif_running(dev)) { @@ -1821,7 +1835,7 @@ int neigh_table_clear(int index, struct neigh_table *= tbl) cancel_delayed_work_sync(&tbl->managed_work); cancel_delayed_work_sync(&tbl->gc_work); del_timer_sync(&tbl->proxy_timer); - pneigh_queue_purge(&tbl->proxy_queue, NULL); + pneigh_queue_purge(&tbl->proxy_queue, NULL, tbl->family); neigh_ifdown(tbl, NULL); if (atomic_read(&tbl->entries)) pr_crit("neighbour leakage\n"); @@ -3539,18 +3553,6 @@ static int proc_unres_qlen(struct ctl_table *ctl, in= t write, return ret; } =20 -static struct neigh_parms *neigh_get_dev_parms_rcu(struct net_device *dev, - int family) -{ - switch (family) { - case AF_INET: - return __in_dev_arp_parms_get_rcu(dev); - case AF_INET6: - return __in6_dev_nd_parms_get_rcu(dev); - } - return NULL; -} - static void neigh_copy_dflt_parms(struct net *net, struct neigh_parms *p, int index) { --=20 2.30.2