git: fbe0257b5613 - main - libc/resolv: Reimplement the sortlist parser
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Mon, 06 Jul 2026 12:24:28 UTC
The branch main has been updated by des:
URL: https://cgit.FreeBSD.org/src/commit/?id=fbe0257b5613f457af42f82efb2e3bc9395d0557
commit fbe0257b5613f457af42f82efb2e3bc9395d0557
Author: Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-07-06 12:23:37 +0000
Commit: Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-07-06 12:23:37 +0000
libc/resolv: Reimplement the sortlist parser
When we switched from the BIND4 resolver to the BIND9 resolver, the
sortlist parser was inadvertently disabled due to a missing #define, and
nobody seemed to notice. 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 manual
accordingly. The new parser accepts IPv4 and IPv6 addresses with or
without a mask or prefix length, just like the old one, except IPv6
support was a bit wonky in the original code.
Fixes: 5342d17f09a8 ("Update the resolver in libc to BIND9's one.")
Relnotes: yes
Reviewed by: markj
Differential Revision: https://reviews.freebsd.org/D57925
---
lib/libc/resolv/Makefile.inc | 1 +
lib/libc/resolv/res_init.c | 246 ++++++++++++++++++++++++-------------------
share/man/man5/resolver.5 | 36 ++++---
3 files changed, 160 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 bd3fd866c189..120b30fc74f5 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 */
@@ -631,6 +556,122 @@ res_setnsaddr(res_state statp, int i, const char *str, const char *end)
return (ret);
}
+#ifdef RESOLVSORT
+/*%
+ * Parse a sortlist entry and insert it into the specified slot in the
+ * sortlist. Assumes no leading or trailing space. Returns 1 if an entry
+ * was inserted, 0 otherwise.
+ */
+static 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 (strchr(mask, '.') &&
+ inet_pton(AF_INET, mask, &a) == 1) {
+ statp->sort_list[i].mask = a.s_addr;
+ } else if (*mask != '\0') {
+ if ((num = strtol(mask, &numend, 10)) < 0 ||
+ num > 32 || *numend != '\0')
+ return (0);
+ statp->sort_list[i].mask =
+ num == 0 ? 0 :
+ htonl(0xffffffffU << (32 - num));
+ } else if (IN_CLASSA(ntohl(a.s_addr))) {
+ statp->sort_list[i].mask =
+ htonl(IN_CLASSA_NET);
+ } else if (IN_CLASSB(ntohl(a.s_addr))) {
+ statp->sort_list[i].mask =
+ htonl(IN_CLASSB_NET);
+ } else if (IN_CLASSC(ntohl(a.s_addr))) {
+ statp->sort_list[i].mask =
+ htonl(IN_CLASSC_NET);
+ } else {
+ statp->sort_list[i].mask =
+ htonl(IN_NETMASK_DEFAULT);
+ }
+ 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, &statp->sort_list[i].addr,
+ addr, sizeof(addr));
+ a.s_addr = statp->sort_list[i].mask;
+ inet_ntop(AF_INET, &a, mask, sizeof(mask));
+ printf(";; sortlist %s/%s\n", addr, mask);
+#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.
*/
@@ -775,21 +816,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 c9e7526ab865..749ffba21fbd 100644
--- a/share/man/man5/resolver.5
+++ b/share/man/man5/resolver.5
@@ -100,20 +100,30 @@ 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 mask
+or
+.Ar addr Ns Li / Ns Ar plen
+entries, for instance:
+.Bd -literal
+sortlist 198.51.100.0/24 203.0.113.0/255.255.255.0 2001:db8::/32
+.Ed
+.Pp
+Masks are only supported for IPv4.
+If the mask or 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