Netdev List
 help / color / mirror / Atom feed
* [PATCH iproute2] lib: bound the hostname cache in resolve_address
@ 2026-11-09 18:08 Stephen Hemminger
  2026-11-11  0:00 ` patchwork-bot+netdevbpf
  0 siblings, 1 reply; 2+ messages in thread
From: Stephen Hemminger @ 2026-11-09 18:08 UTC (permalink / raw)
  To: netdev; +Cc: dsahern, Stephen Hemminger

The reverse lookup cache in resolve_address() grows without bound,
including negative entries, so a long running "ip -r monitor" can be
made to consume memory by churning addresses.

Replace it with a small fixed size open addressed table (32 slots,
multiplicative hash, oldest entry in the probe window is replaced)
and use getnameinfo() instead of the obsolete gethostbyaddr().

Reported-by: Anthropic OSS scanner (ANT-2026-XBIHX07L)
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/utils.c | 119 ++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 83 insertions(+), 36 deletions(-)

diff --git a/lib/utils.c b/lib/utils.c
index c4cd3c3d..f4bd53ff 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -1092,57 +1092,104 @@ const char *family_name(int family)
 }
 
 #ifdef RESOLVE_HOSTNAMES
+/*
+ * Small fixed size cache of reverse lookups (including failures).
+ * Open addressing, the oldest entry in the probe window is replaced,
+ * so memory stays bounded for long running ip monitor.
+ */
+#define NHASH_BITS	5
+#define NHASH		(1u << NHASH_BITS)
+#define NPROBE		4
+
 struct namerec {
-	struct namerec *next;
-	const char *name;
-	inet_prefix addr;
+	unsigned int	stamp;
+	__u8		family;		/* 0 = unused */
+	__u8		len;
+	__u8		addr[16];
+	char		name[256];	/* "" = lookup failed */
 };
 
-#define NHASH 257
-static struct namerec *nht[NHASH];
+static struct namerec nht[NHASH];
+static unsigned int nstamp;
 
-static const char *resolve_address(const void *addr, int len, int af)
+static unsigned int name_hash(const void *addr, int len)
 {
-	struct namerec *n;
-	struct hostent *h_ent;
-	unsigned int hash;
-	static int notfirst;
+	__u32 h = 0, w;
+	int i;
 
+	for (i = 0; i < len; i += 4) {
+		memcpy(&w, addr + i, 4);
+		h ^= w;
+	}
+	return (h * 0x9e3779b1u) >> (32 - NHASH_BITS);
+}
+
+static void name_lookup(struct namerec *n)
+{
+	union {
+		struct sockaddr sa;
+		struct sockaddr_in sin;
+		struct sockaddr_in6 sin6;
+	} u = { };
+	socklen_t salen;
+
+	if (n->family == AF_INET) {
+		u.sin.sin_family = AF_INET;
+		memcpy(&u.sin.sin_addr, n->addr, 4);
+		salen = sizeof(u.sin);
+	} else {
+		u.sin6.sin6_family = AF_INET6;
+		memcpy(&u.sin6.sin6_addr, n->addr, 16);
+		salen = sizeof(u.sin6);
+	}
 
-	if (af == AF_INET6 && ((__u32 *)addr)[0] == 0 &&
-	    ((__u32 *)addr)[1] == 0 && ((__u32 *)addr)[2] == htonl(0xffff)) {
+	fflush(stdout);
+	if (getnameinfo(&u.sa, salen, n->name, sizeof(n->name),
+			NULL, 0, NI_NAMEREQD))
+		n->name[0] = '\0';
+}
+
+static const char *resolve_address(int af, int len, const void *addr,
+				   char *buf, int buflen)
+{
+	struct namerec *n, *victim = NULL;
+	unsigned int hash, i;
+
+	if (af == AF_INET6 && len == 16 &&
+	    IN6_IS_ADDR_V4MAPPED((const struct in6_addr *)addr)) {
 		af = AF_INET;
 		addr += 12;
 		len = 4;
 	}
 
-	hash = *(__u32 *)(addr + len - 4) % NHASH;
+	if (!((af == AF_INET && len == 4) || (af == AF_INET6 && len == 16)))
+		return NULL;
+
+	hash = name_hash(addr, len);
+	for (i = 0; i < NPROBE; i++) {
+		n = &nht[(hash + i) % NHASH];
+
+		if (n->family == af && n->len == len &&
+		    memcmp(n->addr, addr, len) == 0)
+			goto found;
 
-	for (n = nht[hash]; n; n = n->next) {
-		if (n->addr.family == af &&
-		    n->addr.bytelen == len &&
-		    memcmp(n->addr.data, addr, len) == 0)
-			return n->name;
+		if (!victim || (victim->family &&
+				(!n->family || n->stamp < victim->stamp)))
+			victim = n;
 	}
-	n = malloc(sizeof(*n));
-	if (n == NULL)
-		return NULL;
-	n->addr.family = af;
-	n->addr.bytelen = len;
-	n->name = NULL;
-	memcpy(n->addr.data, addr, len);
-	n->next = nht[hash];
-	nht[hash] = n;
-	if (++notfirst == 1)
-		sethostent(1);
-	fflush(stdout);
 
-	h_ent = gethostbyaddr(addr, len, af);
-	if (h_ent != NULL)
-		n->name = strdup(h_ent->h_name);
+	n = victim;
+	n->family = af;
+	n->len = len;
+	memcpy(n->addr, addr, len);
+	name_lookup(n);
+found:
+	n->stamp = ++nstamp;
+	if (n->name[0] == '\0' ||
+	    strlcpy(buf, n->name, buflen) >= (size_t)buflen)
+		return NULL;
 
-	/* Even if we fail, "negative" entry is remembered. */
-	return n->name;
+	return buf;
 }
 #endif
 
@@ -1156,7 +1203,7 @@ const char *format_host_r(int af, int len, const void *addr,
 		len = len <= 0 ? af_byte_len(af) : len;
 
 		if (len > 0 &&
-		    (n = resolve_address(addr, len, af)) != NULL)
+		    (n = resolve_address(af, len, addr, buf, buflen)) != NULL)
 			return n;
 	}
 #endif
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH iproute2] lib: bound the hostname cache in resolve_address
  2026-11-09 18:08 [PATCH iproute2] lib: bound the hostname cache in resolve_address Stephen Hemminger
@ 2026-11-11  0:00 ` patchwork-bot+netdevbpf
  0 siblings, 0 replies; 2+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-11-11  0:00 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: netdev, dsahern

Hello:

This patch was applied to iproute2/iproute2.git (main)
by Stephen Hemminger <stephen@networkplumber.org>:

On Mon,  9 Nov 2026 10:08:32 -0800 you wrote:
> The reverse lookup cache in resolve_address() grows without bound,
> including negative entries, so a long running "ip -r monitor" can be
> made to consume memory by churning addresses.
> 
> Replace it with a small fixed size open addressed table (32 slots,
> multiplicative hash, oldest entry in the probe window is replaced)
> and use getnameinfo() instead of the obsolete gethostbyaddr().
> 
> [...]

Here is the summary with links:
  - [iproute2] lib: bound the hostname cache in resolve_address
    https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/commit/?id=f7ebdb7e560e

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-11  0:00 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-09 18:08 [PATCH iproute2] lib: bound the hostname cache in resolve_address Stephen Hemminger
2026-11-11  0:00 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox