From: Stephen Hemminger <stephen@networkplumber.org>
To: netdev@vger.kernel.org
Cc: dsahern@kernel.org, Stephen Hemminger <stephen@networkplumber.org>
Subject: [PATCH iproute2] lib: bound the hostname cache in resolve_address
Date: Mon, 9 Nov 2026 10:08:32 -0800 [thread overview]
Message-ID: <20261109180832.300754-1-stephen@networkplumber.org> (raw)
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
next reply other threads:[~2026-11-09 18:08 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 18:08 Stephen Hemminger [this message]
2026-11-11 0:00 ` [PATCH iproute2] lib: bound the hostname cache in resolve_address patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109180832.300754-1-stephen@networkplumber.org \
--to=stephen@networkplumber.org \
--cc=dsahern@kernel.org \
--cc=netdev@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.