git: 507ab02d751b - main - libc/resolv: Reimplement the sortlist parser

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Mon, 29 Jun 2026 15:52:08 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=507ab02d751b4aaa7f35f30882299e9908448e53

commit 507ab02d751b4aaa7f35f30882299e9908448e53
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-06-27 18:31:36 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-06-29 13:49:18 +0000

    libc/resolv: Reimplement the sortlist parser
    
    When we switched from the BIND4 resolver to the BIND9 resolver, the
    sortlist parser was inadvertently disabled, and nobody noticed.  The
    sorting code remained enabled in the resolver, but there was no way to
    set a sort order.
    
    Reimplement the sortlist parser, but correctly, and update the
    documentation accordingly.  The new parser accepts IPv4 and IPv6
    addresses with or without a prefix length.
    
    Fixes:          5342d17f09a8 ("Update the resolver in libc to BIND9's one.")
    Relnotes:       yes
---
 lib/libc/resolv/Makefile.inc |   1 +
 lib/libc/resolv/res_init.c   | 232 +++++++++++++++++++++++--------------------
 share/man/man5/resolver.5    |  33 +++---
 3 files changed, 143 insertions(+), 123 deletions(-)

diff --git a/lib/libc/resolv/Makefile.inc b/lib/libc/resolv/Makefile.inc
index be900e942435..ecb0c2462b08 100644
--- a/lib/libc/resolv/Makefile.inc
+++ b/lib/libc/resolv/Makefile.inc
@@ -7,4 +7,5 @@ SRCS+=	herror.c h_errno.c mtctxres.c res_comp.c res_data.c res_debug.c \
 
 SYM_MAPS+= ${LIBC_SRCTOP}/resolv/Symbol.map
 
+CFLAGS+=-I${LIBC_SRCTOP}/net
 CFLAGS+=-I${SRCTOP}/lib/libmd
diff --git a/lib/libc/resolv/res_init.c b/lib/libc/resolv/res_init.c
index c61b247b5199..a0cd381d0f0f 100644
--- a/lib/libc/resolv/res_init.c
+++ b/lib/libc/resolv/res_init.c
@@ -95,6 +95,7 @@
 /* ensure that sockaddr_in6 and IN6ADDR_ANY_INIT are declared / defined */
 #include <resolv.h>
 
+#include "res_config.h"
 #include "res_private.h"
 
 static void res_readconf(res_state, const char *, int);
@@ -104,10 +105,8 @@ static int res_setnsaddr(res_state, int, const char *, const char *);
 static void res_setoptions(res_state, const char *, const char *);
 
 #ifdef RESOLVSORT
-static const char sort_mask[] = "/&";
-#define ISSORTMASK(ch) (strchr(sort_mask, ch) != NULL)
-static u_int32_t net_mask(struct in_addr);
-#endif
+static int res_setsortlist(res_state, int, const char *, const char *);
+#endif /* RESOLVSORT */
 
 #define res_space(ch)							\
 	isspace((unsigned char)(ch))
@@ -285,8 +284,7 @@ res_readconf(res_state statp, const char *resconf, int maxns)
 	size_t sz = 0;
 	int nserv = 0;
 #ifdef RESOLVSORT
-	int n, nsort = 0;
-	char *net;
+	int nsort = 0;
 #endif
 
 	if ((fp = fopen(resconf, "re")) == NULL)
@@ -357,101 +355,28 @@ res_readconf(res_state statp, const char *resconf, int maxns)
 			continue;
 		}
 #ifdef RESOLVSORT
+		/* read result sort order */
 		if (res_match(line, eow, "sortlist")) {
-			struct in_addr a;
-			struct in6_addr a6;
-			int m, i;
-			u_char *u;
-			struct __res_state_ext *ext = statp->_u._ext.ext;
-
-			cp = line + sizeof("sortlist") - 1;
 			while (nsort < MAXRESOLVSORT) {
-				while (*cp == ' ' || *cp == '\t')
+				cp = eow;
+				while (cp < eol && res_space(*cp))
 					cp++;
-				if (*cp == '\0' || *cp == '\n' || *cp == ';')
+				if (cp == eol)
 					break;
-				net = cp;
-				while (*cp && !ISSORTMASK(*cp) && *cp != ';' &&
-				    isascii(*cp) && !isspace((unsigned char)*cp))
-					cp++;
-				n = *cp;
-				*cp = 0;
-				if (inet_aton(net, &a)) {
-					statp->sort_list[nsort].addr = a;
-					if (ISSORTMASK(n)) {
-						*cp++ = n;
-						net = cp;
-						while (*cp && *cp != ';' &&
-						    isascii(*cp) &&
-						    !isspace((unsigned char)*cp))
-							cp++;
-						n = *cp;
-						*cp = 0;
-						if (inet_aton(net, &a)) {
-							statp->sort_list[nsort].mask = a.s_addr;
-						} else {
-							statp->sort_list[nsort].mask =
-							    net_mask(statp->sort_list[nsort].addr);
-						}
-					} else {
-						statp->sort_list[nsort].mask =
-						    net_mask(statp->sort_list[nsort].addr);
-					}
-					ext->sort_list[nsort].af = AF_INET;
-					ext->sort_list[nsort].addr.ina =
-					    statp->sort_list[nsort].addr;
-					ext->sort_list[nsort].mask.ina.s_addr =
-					    statp->sort_list[nsort].mask;
-					nsort++;
-				}
-				else if (inet_pton(AF_INET6, net, &a6) == 1) {
-
-					ext->sort_list[nsort].af = AF_INET6;
-					ext->sort_list[nsort].addr.in6a = a6;
-					u = (u_char *)&ext->sort_list[nsort].mask.in6a;
-					*cp++ = n;
-					net = cp;
-					while (*cp && *cp != ';' &&
-					    isascii(*cp) && !isspace(*cp))
-						cp++;
-					m = n;
-					n = *cp;
-					*cp = 0;
-					switch (m) {
-					case '/':
-						m = atoi(net);
-						break;
-					case '&':
-						if (inet_pton(AF_INET6, net, u) == 1) {
-							m = -1;
-							break;
-						}
-						/*FALLTHROUGH*/
-					default:
-						m = sizeof(struct in6_addr) * CHAR_BIT;
-						break;
-					}
-					if (m >= 0) {
-						for (i = 0; i < sizeof(struct in6_addr); i++) {
-							if (m <= 0) {
-								*u = 0;
-							} else {
-								m -= CHAR_BIT;
-								*u = (u_char)~0;
-								if (m < 0)
-									*u <<= -m;
-							}
-							u++;
-						}
-					}
-					statp->sort_list[nsort].addr.s_addr =
-					    (u_int32_t)0xffffffff;
-					statp->sort_list[nsort].mask =
-					    (u_int32_t)0xffffffff;
+				eow = cp;
+				while (eow < eol && !res_space(*eow))
+					eow++;
+				if (res_setsortlist(statp, nsort, cp, eow))
 					nsort++;
-				}
-				*cp = n;
 			}
+#ifdef DEBUG
+			while (cp < eol && res_space(*cp))
+				cp++;
+			if (cp < eol) {
+				printf(";; sortlist overflow: %.*s\n",
+				    (int)(eol - cp), cp);
+			}
+#endif
 			continue;
 		}
 #endif /* RESOLVSORT */
@@ -651,6 +576,108 @@ res_setnsaddr(res_state statp, int i, const char *str, const char *end)
 	return (ret);
 }
 
+#ifdef RESOLVSORT
+/*%
+ * Set the sort order.  Assumes no leading or trailing space.
+ */
+int
+res_setsortlist(res_state statp, int i, const char *str, const char *end)
+{
+	struct __res_state_ext *ext = statp->_u._ext.ext;
+	char addr[48], mask[48], *dst, *numend;
+	_Static_assert(sizeof(addr) > INET6_ADDRSTRLEN,
+	    "address buffer too small for AF_INET6");
+	_Static_assert(sizeof(addr) > INET_ADDRSTRLEN,
+	    "address buffer too small for AF_INET");
+	_Static_assert(sizeof(mask) > INET6_ADDRSTRLEN,
+	    "mask buffer too small for AF_INET6");
+	_Static_assert(sizeof(mask) > INET_ADDRSTRLEN,
+	    "mask buffer too small for AF_INET");
+	struct in_addr a;
+	struct in6_addr a6;
+	uint8_t *u;
+	long num;
+
+	/* copy the address */
+	dst = addr;
+	while (str < end && !res_space(*str) && *str != '/') {
+		*dst++ = *str++;
+		if (dst == addr + sizeof(addr))
+			return (0);
+	}
+	*dst = '\0';
+
+	/* copy the mask, if there is one */
+	dst = mask;
+	if (str < end) {
+		if (*str++ != '/')
+			return (0);
+		if (end - str >= sizeof(mask))
+			return (0);
+		memcpy(mask, str, end - str);
+		dst += end - str;
+	}
+	*dst = '\0';
+
+	/* IPv4 case */
+	if (inet_pton(AF_INET, addr, &a) == 1) {
+		statp->sort_list[i].addr = a;
+		if (mask[0] != '\0') {
+			if ((num = strtol(mask, &numend, 10)) < 0 ||
+			    num > 32 || *numend != '\0')
+				return (0);
+		} else {
+			if (IN_CLASSA(ntohl(a.s_addr)))
+				num = 8;
+			else if (IN_CLASSB(ntohl(a.s_addr)))
+				num = 16;
+			else
+				num = 24;
+		}
+		statp->sort_list[i].mask =
+		    htonl(0xffffffffU << (32 - num));
+		ext->sort_list[i].af = AF_INET;
+		ext->sort_list[i].addr.ina =
+		    statp->sort_list[i].addr;
+		ext->sort_list[i].mask.ina.s_addr =
+		    statp->sort_list[i].mask;
+#ifdef DEBUG
+		inet_ntop(AF_INET, &a, addr, sizeof(addr));
+		printf(";; sortlist %s/%ld\n", addr, num);
+#endif
+		return (1);
+	}
+
+	/* IPv6 case */
+	if (inet_pton(AF_INET6, addr, &a6) == 1) {
+		statp->sort_list[i].addr.s_addr = INADDR_NONE;
+		statp->sort_list[i].mask = INADDR_NONE;
+		ext->sort_list[i].af = AF_INET6;
+		ext->sort_list[i].addr.in6a = a6;
+		if (mask[0]) {
+			/* prefix length */
+			if ((num = strtol(mask, &numend, 10)) < 0 ||
+			    num > 128 || *numend != '\0')
+				return (0);
+		} else {
+			num = 64;
+		}
+#ifdef DEBUG
+		inet_ntop(AF_INET6, &a6, addr, sizeof(addr));
+		printf(";; sortlist %s/%ld\n", addr, num);
+#endif
+		a6 = (struct in6_addr)IN6ADDR_ANY_INIT;
+		for (u = a6.s6_addr8; num > 0; num -= 8)
+			*u++ = num > 8 ? 0xffU : 0xffU << (8 - num);
+		ext->sort_list[i].mask.in6a = a6;
+		return (1);
+	}
+
+	/* invalid */
+	return (0);
+}
+#endif /* RESOLVSORT */
+
 /*%
  * Split the string up into words and interpret them as resolver options.
  */
@@ -790,21 +817,6 @@ res_setoptions(res_state statp, const char *options, const char *source)
 	}
 }
 
-#ifdef RESOLVSORT
-/* XXX - should really support CIDR which means explicit masks always. */
-static u_int32_t
-net_mask(struct in_addr in)		/*!< XXX - should really use system's version of this  */
-{
-	u_int32_t i = ntohl(in.s_addr);
-
-	if (IN_CLASSA(i))
-		return (htonl(IN_CLASSA_NET));
-	else if (IN_CLASSB(i))
-		return (htonl(IN_CLASSB_NET));
-	return (htonl(IN_CLASSC_NET));
-}
-#endif
-
 void
 freebsd15_res_rndinit(res_state statp)
 {
diff --git a/share/man/man5/resolver.5 b/share/man/man5/resolver.5
index 8af69fd2dac7..135e56287b5f 100644
--- a/share/man/man5/resolver.5
+++ b/share/man/man5/resolver.5
@@ -100,20 +100,27 @@ for one of the domains.
 .Pp
 The search list is currently limited to six domains
 with a total of 256 characters.
-.It Sy sortlist
-Sortlist allows addresses returned by the resolver to be sorted.
-A sortlist is specified by IP address netmask pairs.
-If the netmask is not specified,
-it defaults to the historical Class A/B/C netmask of the net;
-this usage is deprecated.
-The IP address
-and network pairs are separated by slashes.
-Up to 10 pairs may
-be specified.
-E.g.,
+.It Cm sortlist Ar addr1 Ns Oo Li / Ns Ar plen1 Oc Op addr2 Ns Oo Li / Ns Ar plen2 Oc ...
+Sort order for query results in the form of a list of
+.Ar addr Ns Li / Ns Ar plen
+entries, for instance:
+.Bd -literal
+sortlist 198.51.100.0/24 203.0.113.0/24 2001:db8::/32
+.Ed
+.Pp
+If the prefix length is omitted, it defaults to historical class-based
+addressing for IPv4 and to 64 for IPv6.
+.Pp
+Query results that match one of the sort list entries are sorted
+accordingly and ahead of query results that don't.
+If multiple results match a single sort list entry, the order is
+unspecified.
 .Pp
-.Dl "sortlist 10.9.1.0/255.255.240.0 10.9.0.0/255.255.0.0"
-.It Cm options Ar option1 Op Ar option2 ...
+If multiple
+.Cm sortlist
+lines are encountered, entries are appended to the list in the order
+in which they were encountered, up to a maximum of ten.
+.It Cm options
 Options allows certain internal resolver variables to be modified.
 The following options are supported:
 .Bl -tag -width indent