aboutsummaryrefslogtreecommitdiff
path: root/resolv
diff options
context:
space:
mode:
Diffstat (limited to 'resolv')
-rw-r--r--resolv/Makefile8
-rw-r--r--resolv/inet_ntop.c358
-rw-r--r--resolv/inet_pton.c187
-rw-r--r--resolv/inet_pton_length.c223
-rw-r--r--resolv/tst-resolv-getaddrinfo-fqdn.c147
5 files changed, 569 insertions, 354 deletions
diff --git a/resolv/Makefile b/resolv/Makefile
index 48b16dc..8fa3398 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -38,6 +38,7 @@ routines := \
inet_addr \
inet_ntop \
inet_pton \
+ inet_pton_length \
ns_makecanon \
ns_name_compress \
ns_name_length_uncompressed \
@@ -73,6 +74,11 @@ routines := \
resolv_context \
# routines
+# Exclude fortified routines from being built with _FORTIFY_SOURCE
+routines_no_fortify += \
+ inet_pton \
+ # routines_no_fortify
+
tests = tst-aton tst-leaks tst-inet_ntop
tests-container = tst-leaks2
@@ -134,6 +140,7 @@ tests += \
tst-resolv-ai_idn-latin1 \
tst-resolv-ai_idn-nolibidn2 \
tst-resolv-canonname \
+ tst-resolv-getaddrinfo-fqdn \
tst-resolv-trustad \
# Needs resolv_context.
@@ -310,6 +317,7 @@ $(objpfx)tst-resolv-threads: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-txnid-collision: $(objpfx)libresolv.a \
$(static-thread-library)
$(objpfx)tst-resolv-canonname: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-getaddrinfo-fqdn: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-trustad: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-ns_name: $(objpfx)libresolv.so
diff --git a/resolv/inet_ntop.c b/resolv/inet_ntop.c
index acf5f3cb..0fdef32 100644
--- a/resolv/inet_ntop.c
+++ b/resolv/inet_ntop.c
@@ -1,186 +1,210 @@
-/*
- * Copyright (c) 1996-1999 by Internet Software Consortium.
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
- * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
- * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
- * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
- * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
- * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
- * SOFTWARE.
- */
-
-#include <sys/param.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <netinet/in.h>
+/* Convert IPv4/IPv6 addresses from binary to text form.
+ Copyright (C) 1996-2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
#include <arpa/inet.h>
#include <arpa/nameser.h>
-
#include <errno.h>
-#include <stdio.h>
#include <string.h>
+#include <_itoa.h>
-#ifdef SPRINTF_CHAR
-# define SPRINTF(x) strlen(sprintf/**/x)
-#else
-# define SPRINTF(x) ((size_t)sprintf x)
-#endif
-
-/*
- * WARNING: Don't even consider trying to compile this on a system where
- * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
- */
-
-static const char *inet_ntop4 (const u_char *src, char *dst, socklen_t size);
-static const char *inet_ntop6 (const u_char *src, char *dst, socklen_t size);
-
-/* char *
- * __inet_ntop(af, src, dst, size)
- * convert a network format address to presentation format.
- * return:
- * pointer to presentation format address (`dst'), or NULL (see errno).
- * author:
- * Paul Vixie, 1996.
- */
-const char *
-__inet_ntop (int af, const void *src, char *dst, socklen_t size)
+static inline char *
+put_uint8 (uint8_t word, char *tp)
{
- switch (af) {
- case AF_INET:
- return (inet_ntop4(src, dst, size));
- case AF_INET6:
- return (inet_ntop6(src, dst, size));
- default:
- __set_errno (EAFNOSUPPORT);
- return (NULL);
+ int s = 1;
+ if (word >= 10)
+ {
+ if (word >= 100)
+ {
+ tp[2] = '0' + word % 10;
+ word /= 10;
+ s += 1;
}
- /* NOTREACHED */
+
+ tp[1] = '0' + word % 10;
+ word /= 10;
+ s += 1;
+ }
+ *tp = '0' + word;
+ return tp + s;
}
-libc_hidden_def (__inet_ntop)
-weak_alias (__inet_ntop, inet_ntop)
-/* const char *
- * inet_ntop4(src, dst, size)
- * format an IPv4 address
- * return:
- * `dst' (as a const)
- * notes:
- * (1) uses no statics
- * (2) takes a u_char* not an in_addr as input
- * author:
- * Paul Vixie, 1996.
- */
-static const char *
-inet_ntop4 (const u_char *src, char *dst, socklen_t size)
+static inline char *
+put_uint16 (uint16_t word, char *tp)
{
- static const char fmt[] = "%u.%u.%u.%u";
- char tmp[sizeof "255.255.255.255"];
+ if (word >= 0x1000)
+ *tp++ = _itoa_lower_digits[(word >> 12) & 0xf];
+ if (word >= 0x100)
+ *tp++ = _itoa_lower_digits[(word >> 8) & 0xf];
+ if (word >= 0x10)
+ *tp++ = _itoa_lower_digits[(word >> 4) & 0xf];
+ *tp++ = _itoa_lower_digits[word & 0xf];
+ return tp;
+}
- if (SPRINTF((tmp, fmt, src[0], src[1], src[2], src[3])) >= size) {
- __set_errno (ENOSPC);
- return (NULL);
- }
- return strcpy(dst, tmp);
+static __always_inline char *
+inet_ntop4_format (const uint8_t *src, char *dst)
+{
+ dst = put_uint8 (src[0], dst);
+ *(dst++) = '.';
+ dst = put_uint8 (src[1], dst);
+ *(dst++) = '.';
+ dst = put_uint8 (src[2], dst);
+ *(dst++) = '.';
+ dst = put_uint8 (src[3], dst);
+ *dst++ = '\0';
+ return dst;
}
-/* const char *
- * inet_ntop6(src, dst, size)
- * convert IPv6 binary address into presentation (printable) format
- * author:
- * Paul Vixie, 1996.
- */
-static const char *
-inet_ntop6 (const u_char *src, char *dst, socklen_t size)
+static __always_inline const char *
+inet_ntop4 (const uint8_t *src, char *dst, socklen_t size)
+{
+ if (size >= INET_ADDRSTRLEN)
+ {
+ inet_ntop4_format (src, dst);
+ return dst;
+ }
+
+ char tmp[INET_ADDRSTRLEN];
+ char *tp = inet_ntop4_format (src, tmp);
+ socklen_t tmp_s = tp - tmp;
+ if (tmp_s > size)
+ {
+ __set_errno (ENOSPC);
+ return NULL;
+ }
+ return memcpy (dst, tmp, tmp_s);
+}
+
+struct best_t
+{
+ int base;
+ int len;
+};
+
+static inline uint16_t
+in6_addr_addr16 (const struct in6_addr *src, int idx)
{
- /*
- * Note that int32_t and int16_t need only be "at least" large enough
- * to contain a value of the specified size. On some systems, like
- * Crays, there is no such thing as an integer variable with 16 bits.
- * Keep this in mind if you think this function should have been coded
- * to use pointer overlays. All the world's not a VAX.
- */
- char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
- struct { int base, len; } best, cur;
- u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
- int i;
-
- /*
- * Preprocess:
- * Copy the input (bytewise) array into a wordwise array.
- * Find the longest run of 0x00's in src[] for :: shorthanding.
- */
- memset(words, '\0', sizeof words);
- for (i = 0; i < NS_IN6ADDRSZ; i += 2)
- words[i / 2] = (src[i] << 8) | src[i + 1];
- best.base = -1;
- cur.base = -1;
- best.len = 0;
- cur.len = 0;
- for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
- if (words[i] == 0) {
- if (cur.base == -1)
- cur.base = i, cur.len = 1;
- else
- cur.len++;
- } else {
- if (cur.base != -1) {
- if (best.base == -1 || cur.len > best.len)
- best = cur;
- cur.base = -1;
- }
- }
+ const struct { uint16_t x; } __attribute__((__packed__)) *pptr =
+ (typeof(pptr))(&src->s6_addr16[idx]);
+ return ntohs (pptr->x);
+}
+
+static __always_inline char *
+inet_ntop6_format (const struct in6_addr *src, struct best_t best, char *dst)
+{
+ char *tp = dst;
+ for (int i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++)
+ {
+ /* Are we inside the best run of 0x00's? */
+ if (best.base != -1 && i >= best.base && i < (best.base + best.len))
+ {
+ if (i == best.base)
+ *tp++ = ':';
+ continue;
}
- if (cur.base != -1) {
- if (best.base == -1 || cur.len > best.len)
- best = cur;
+ /* Are we following an initial run of 0x00s or any real hex? */
+ if (i != 0)
+ *tp++ = ':';
+ /* Is this address an encapsulated IPv4? */
+ if (i == 6 && best.base == 0
+ && (best.len == 6 || (best.len == 5
+ && in6_addr_addr16 (src, 5) == 0xffff)))
+ {
+ if (!inet_ntop4 (src->s6_addr + 12, tp,
+ INET6_ADDRSTRLEN - (tp - dst)))
+ return NULL;
+ tp += strlen (tp);
+ break;
}
- if (best.base != -1 && best.len < 2)
- best.base = -1;
-
- /*
- * Format the result.
- */
- tp = tmp;
- for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
- /* Are we inside the best run of 0x00's? */
- if (best.base != -1 && i >= best.base &&
- i < (best.base + best.len)) {
- if (i == best.base)
- *tp++ = ':';
- continue;
- }
- /* Are we following an initial run of 0x00s or any real hex? */
- if (i != 0)
- *tp++ = ':';
- /* Is this address an encapsulated IPv4? */
- if (i == 6 && best.base == 0 &&
- (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) {
- if (!inet_ntop4(src+12, tp, sizeof tmp - (tp - tmp)))
- return (NULL);
- tp += strlen(tp);
- break;
- }
- tp += SPRINTF((tp, "%x", words[i]));
+ tp = put_uint16 (in6_addr_addr16 (src, i), tp);
+ }
+ /* Was it a trailing run of 0x00's? */
+ if (best.base != -1 && (best.base + best.len) == (NS_IN6ADDRSZ / NS_INT16SZ))
+ *tp++ = ':';
+ *tp++ = '\0';
+
+ return tp;
+}
+
+static inline const char *
+inet_ntop6 (const struct in6_addr *src, char *dst, socklen_t size)
+{
+ struct best_t best = { -1, 0 }, cur = { -1, 0 };
+
+ /* ind the longest run of 0x00's in src[] for :: shorthanding. */
+ for (int i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++)
+ {
+ if (in6_addr_addr16 (src, i) == 0)
+ {
+ if (cur.base == -1)
+ cur.base = i, cur.len = 1;
+ else
+ cur.len++;
}
- /* Was it a trailing run of 0x00's? */
- if (best.base != -1 && (best.base + best.len) ==
- (NS_IN6ADDRSZ / NS_INT16SZ))
- *tp++ = ':';
- *tp++ = '\0';
-
- /*
- * Check for overflow, copy, and we're done.
- */
- if ((socklen_t)(tp - tmp) > size) {
- __set_errno (ENOSPC);
- return (NULL);
+ else
+ {
+ if (cur.base != -1)
+ {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ cur.base = -1;
+ }
}
- return strcpy(dst, tmp);
+ }
+ if (cur.base != -1)
+ {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ }
+ if (best.base != -1 && best.len < 2)
+ best.base = -1;
+
+ if (size >= INET6_ADDRSTRLEN)
+ {
+ inet_ntop6_format (src, best, dst);
+ return dst;
+ }
+
+ char tmp[INET6_ADDRSTRLEN];
+ char *tp = inet_ntop6_format (src, best, tmp);
+
+ socklen_t tmp_s = tp - tmp;
+ if (tmp_s > size)
+ {
+ __set_errno (ENOSPC);
+ return (NULL);
+ }
+ return memcpy (dst, tmp, tmp_s);
}
+
+const char *
+__inet_ntop (int af, const void *src, char *dst, socklen_t size)
+{
+ switch (af)
+ {
+ case AF_INET:
+ return (inet_ntop4 (src, dst, size));
+ case AF_INET6:
+ return (inet_ntop6 (src, dst, size));
+ default:
+ __set_errno (EAFNOSUPPORT);
+ return (NULL);
+ }
+}
+libc_hidden_def (__inet_ntop)
+weak_alias (__inet_ntop, inet_ntop)
diff --git a/resolv/inet_pton.c b/resolv/inet_pton.c
index 88d63c6..5307a5b 100644
--- a/resolv/inet_pton.c
+++ b/resolv/inet_pton.c
@@ -33,33 +33,7 @@
*/
#include <arpa/inet.h>
-#include <arpa/nameser.h>
-#include <ctype.h>
-#include <errno.h>
-#include <netinet/in.h>
#include <resolv/resolv-internal.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-static int inet_pton4 (const char *src, const char *src_end, u_char *dst);
-static int inet_pton6 (const char *src, const char *src_end, u_char *dst);
-
-int
-__inet_pton_length (int af, const char *src, size_t srclen, void *dst)
-{
- switch (af)
- {
- case AF_INET:
- return inet_pton4 (src, src + srclen, dst);
- case AF_INET6:
- return inet_pton6 (src, src + srclen, dst);
- default:
- __set_errno (EAFNOSUPPORT);
- return -1;
- }
-}
-libc_hidden_def (__inet_pton_length)
/* Like __inet_pton_length, but use strlen (SRC) as the length of
SRC. */
@@ -71,164 +45,3 @@ __inet_pton (int af, const char *src, void *dst)
libc_hidden_def (__inet_pton)
weak_alias (__inet_pton, inet_pton)
libc_hidden_weak (inet_pton)
-
-/* Like inet_aton but without all the hexadecimal, octal and shorthand
- (and trailing garbage is not ignored). Return 1 if SRC is a valid
- dotted quad, else 0. This function does not touch DST unless it's
- returning 1.
- Author: Paul Vixie, 1996. */
-static int
-inet_pton4 (const char *src, const char *end, unsigned char *dst)
-{
- int saw_digit, octets, ch;
- unsigned char tmp[NS_INADDRSZ], *tp;
-
- saw_digit = 0;
- octets = 0;
- *(tp = tmp) = 0;
- while (src < end)
- {
- ch = *src++;
- if (ch >= '0' && ch <= '9')
- {
- unsigned int new = *tp * 10 + (ch - '0');
-
- if (saw_digit && *tp == 0)
- return 0;
- if (new > 255)
- return 0;
- *tp = new;
- if (! saw_digit)
- {
- if (++octets > 4)
- return 0;
- saw_digit = 1;
- }
- }
- else if (ch == '.' && saw_digit)
- {
- if (octets == 4)
- return 0;
- *++tp = 0;
- saw_digit = 0;
- }
- else
- return 0;
- }
- if (octets < 4)
- return 0;
- memcpy (dst, tmp, NS_INADDRSZ);
- return 1;
-}
-
-/* Return the value of CH as a hexadecimal digit, or -1 if it is a
- different type of character. */
-static int
-hex_digit_value (char ch)
-{
- if ('0' <= ch && ch <= '9')
- return ch - '0';
- if ('a' <= ch && ch <= 'f')
- return ch - 'a' + 10;
- if ('A' <= ch && ch <= 'F')
- return ch - 'A' + 10;
- return -1;
-}
-
-/* Convert presentation-level IPv6 address to network order binary
- form. Return 1 if SRC is a valid [RFC1884 2.2] address, else 0.
- This function does not touch DST unless it's returning 1.
- Author: Paul Vixie, 1996. Inspired by Mark Andrews. */
-static int
-inet_pton6 (const char *src, const char *src_endp, unsigned char *dst)
-{
- unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
- const char *curtok;
- int ch;
- size_t xdigits_seen; /* Number of hex digits since colon. */
- unsigned int val;
-
- tp = memset (tmp, '\0', NS_IN6ADDRSZ);
- endp = tp + NS_IN6ADDRSZ;
- colonp = NULL;
-
- /* Leading :: requires some special handling. */
- if (src == src_endp)
- return 0;
- if (*src == ':')
- {
- ++src;
- if (src == src_endp || *src != ':')
- return 0;
- }
-
- curtok = src;
- xdigits_seen = 0;
- val = 0;
- while (src < src_endp)
- {
- ch = *src++;
- int digit = hex_digit_value (ch);
- if (digit >= 0)
- {
- if (xdigits_seen == 4)
- return 0;
- val <<= 4;
- val |= digit;
- if (val > 0xffff)
- return 0;
- ++xdigits_seen;
- continue;
- }
- if (ch == ':')
- {
- curtok = src;
- if (xdigits_seen == 0)
- {
- if (colonp)
- return 0;
- colonp = tp;
- continue;
- }
- else if (src == src_endp)
- return 0;
- if (tp + NS_INT16SZ > endp)
- return 0;
- *tp++ = (unsigned char) (val >> 8) & 0xff;
- *tp++ = (unsigned char) val & 0xff;
- xdigits_seen = 0;
- val = 0;
- continue;
- }
- if (ch == '.' && ((tp + NS_INADDRSZ) <= endp)
- && inet_pton4 (curtok, src_endp, tp) > 0)
- {
- tp += NS_INADDRSZ;
- xdigits_seen = 0;
- break; /* '\0' was seen by inet_pton4. */
- }
- return 0;
- }
- if (xdigits_seen > 0)
- {
- if (tp + NS_INT16SZ > endp)
- return 0;
- *tp++ = (unsigned char) (val >> 8) & 0xff;
- *tp++ = (unsigned char) val & 0xff;
- }
- if (colonp != NULL)
- {
- /* Replace :: with zeros. */
- if (tp == endp)
- /* :: would expand to a zero-width field. */
- return 0;
- size_t n = tp - colonp;
- memmove (endp - n, colonp, n);
- memset (colonp, 0, endp - n - colonp);
- tp = endp;
- }
- if (tp != endp)
- return 0;
- memcpy (dst, tmp, NS_IN6ADDRSZ);
- return 1;
-}
diff --git a/resolv/inet_pton_length.c b/resolv/inet_pton_length.c
new file mode 100644
index 0000000..c361407
--- /dev/null
+++ b/resolv/inet_pton_length.c
@@ -0,0 +1,223 @@
+/* Copyright (C) 1996-2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/*
+ * Copyright (c) 1996,1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <resolv/resolv-internal.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+static int inet_pton4 (const char *src, const char *src_end, u_char *dst);
+static int inet_pton6 (const char *src, const char *src_end, u_char *dst);
+
+int
+__inet_pton_length (int af, const char *src, size_t srclen, void *dst)
+{
+ switch (af)
+ {
+ case AF_INET:
+ return inet_pton4 (src, src + srclen, dst);
+ case AF_INET6:
+ return inet_pton6 (src, src + srclen, dst);
+ default:
+ __set_errno (EAFNOSUPPORT);
+ return -1;
+ }
+}
+libc_hidden_def (__inet_pton_length)
+
+/* Like inet_aton but without all the hexadecimal, octal and shorthand
+ (and trailing garbage is not ignored). Return 1 if SRC is a valid
+ dotted quad, else 0. This function does not touch DST unless it's
+ returning 1.
+ Author: Paul Vixie, 1996. */
+static int
+inet_pton4 (const char *src, const char *end, unsigned char *dst)
+{
+ int saw_digit, octets, ch;
+ unsigned char tmp[NS_INADDRSZ], *tp;
+
+ saw_digit = 0;
+ octets = 0;
+ *(tp = tmp) = 0;
+ while (src < end)
+ {
+ ch = *src++;
+ if (ch >= '0' && ch <= '9')
+ {
+ unsigned int new = *tp * 10 + (ch - '0');
+
+ if (saw_digit && *tp == 0)
+ return 0;
+ if (new > 255)
+ return 0;
+ *tp = new;
+ if (! saw_digit)
+ {
+ if (++octets > 4)
+ return 0;
+ saw_digit = 1;
+ }
+ }
+ else if (ch == '.' && saw_digit)
+ {
+ if (octets == 4)
+ return 0;
+ *++tp = 0;
+ saw_digit = 0;
+ }
+ else
+ return 0;
+ }
+ if (octets < 4)
+ return 0;
+ memcpy (dst, tmp, NS_INADDRSZ);
+ return 1;
+}
+
+/* Return the value of CH as a hexadecimal digit, or -1 if it is a
+ different type of character. */
+static int
+hex_digit_value (char ch)
+{
+ if ('0' <= ch && ch <= '9')
+ return ch - '0';
+ if ('a' <= ch && ch <= 'f')
+ return ch - 'a' + 10;
+ if ('A' <= ch && ch <= 'F')
+ return ch - 'A' + 10;
+ return -1;
+}
+
+/* Convert presentation-level IPv6 address to network order binary
+ form. Return 1 if SRC is a valid [RFC1884 2.2] address, else 0.
+ This function does not touch DST unless it's returning 1.
+ Author: Paul Vixie, 1996. Inspired by Mark Andrews. */
+static int
+inet_pton6 (const char *src, const char *src_endp, unsigned char *dst)
+{
+ unsigned char tmp[NS_IN6ADDRSZ], *tp, *endp, *colonp;
+ const char *curtok;
+ int ch;
+ size_t xdigits_seen; /* Number of hex digits since colon. */
+ unsigned int val;
+
+ tp = memset (tmp, '\0', NS_IN6ADDRSZ);
+ endp = tp + NS_IN6ADDRSZ;
+ colonp = NULL;
+
+ /* Leading :: requires some special handling. */
+ if (src == src_endp)
+ return 0;
+ if (*src == ':')
+ {
+ ++src;
+ if (src == src_endp || *src != ':')
+ return 0;
+ }
+
+ curtok = src;
+ xdigits_seen = 0;
+ val = 0;
+ while (src < src_endp)
+ {
+ ch = *src++;
+ int digit = hex_digit_value (ch);
+ if (digit >= 0)
+ {
+ if (xdigits_seen == 4)
+ return 0;
+ val <<= 4;
+ val |= digit;
+ if (val > 0xffff)
+ return 0;
+ ++xdigits_seen;
+ continue;
+ }
+ if (ch == ':')
+ {
+ curtok = src;
+ if (xdigits_seen == 0)
+ {
+ if (colonp)
+ return 0;
+ colonp = tp;
+ continue;
+ }
+ else if (src == src_endp)
+ return 0;
+ if (tp + NS_INT16SZ > endp)
+ return 0;
+ *tp++ = (unsigned char) (val >> 8) & 0xff;
+ *tp++ = (unsigned char) val & 0xff;
+ xdigits_seen = 0;
+ val = 0;
+ continue;
+ }
+ if (ch == '.' && ((tp + NS_INADDRSZ) <= endp)
+ && inet_pton4 (curtok, src_endp, tp) > 0)
+ {
+ tp += NS_INADDRSZ;
+ xdigits_seen = 0;
+ break; /* '\0' was seen by inet_pton4. */
+ }
+ return 0;
+ }
+ if (xdigits_seen > 0)
+ {
+ if (tp + NS_INT16SZ > endp)
+ return 0;
+ *tp++ = (unsigned char) (val >> 8) & 0xff;
+ *tp++ = (unsigned char) val & 0xff;
+ }
+ if (colonp != NULL)
+ {
+ /* Replace :: with zeros. */
+ if (tp == endp)
+ /* :: would expand to a zero-width field. */
+ return 0;
+ size_t n = tp - colonp;
+ memmove (endp - n, colonp, n);
+ memset (colonp, 0, endp - n - colonp);
+ tp = endp;
+ }
+ if (tp != endp)
+ return 0;
+ memcpy (dst, tmp, NS_IN6ADDRSZ);
+ return 1;
+}
diff --git a/resolv/tst-resolv-getaddrinfo-fqdn.c b/resolv/tst-resolv-getaddrinfo-fqdn.c
new file mode 100644
index 0000000..75bfe7e
--- /dev/null
+++ b/resolv/tst-resolv-getaddrinfo-fqdn.c
@@ -0,0 +1,147 @@
+/* Test for BZ #15218. Verify that getaddrinfo returns FQDN in
+ ai_canonname, when AI_CANONNAME is requested and search domain apply.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+static void
+response (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ if (strcmp (qname, "foo.site.example") == 0
+ || strcmp (qname, "bar.foo.site.example") == 0
+ || strcmp (qname, "site.example") == 0)
+ {
+ struct resolv_response_flags flags = { };
+ resolv_response_init (b, flags);
+ resolv_response_add_question (b, qname, qclass, qtype);
+ resolv_response_section (b, ns_s_an);
+ if (qtype == T_A)
+ {
+ char addr_ipv4[4] = { 127, 126, 125, 124 };
+ resolv_response_open_record (b, qname, qclass, T_A, 0x12345678);
+ resolv_response_add_data (b, addr_ipv4, sizeof (addr_ipv4));
+ resolv_response_close_record (b);
+ }
+ else if (qtype == T_AAAA)
+ {
+ char addr_ipv6[16] =
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
+ resolv_response_open_record (b, qname, qclass, T_AAAA, 0x12345678);
+ resolv_response_add_data (b, addr_ipv6, sizeof (addr_ipv6));
+ resolv_response_close_record (b);
+ }
+ else
+ FAIL_EXIT1 ("qtype must be one of A, AAAA");
+ }
+ else
+ {
+ struct resolv_response_flags flags = {.rcode = ns_r_nxdomain, };
+ resolv_response_init (b, flags);
+ resolv_response_add_question (b, qname, qclass, qtype);
+ }
+}
+
+void
+query_host (const char *host_name, const char *expected_name)
+{
+ int family[] = { AF_INET, AF_INET6, AF_UNSPEC };
+ const char *family_names[] = { "AF_INET", "AF_INET6", "AF_UNSPEC" };
+
+ for (int i = 0; i < 3; i++)
+ {
+ struct addrinfo hints = {
+ .ai_socktype = 0,
+ .ai_protocol = 0,
+ .ai_family = family[i],
+ .ai_flags = AI_CANONNAME,
+ };
+ struct addrinfo *result, *current;
+ int res = getaddrinfo (host_name, NULL, &hints, &result);
+ if (res != 0)
+ FAIL_EXIT1 ("getaddrinfo (%s, %s): %s\n", host_name, family_names[i],
+ gai_strerror (res));
+ else
+ {
+ int count = 0;
+ for (current = result;
+ current != NULL && current->ai_canonname != NULL;
+ current = current->ai_next)
+ {
+ TEST_COMPARE_STRING (current->ai_canonname, expected_name);
+ count++;
+ }
+ freeaddrinfo (result);
+ if (count > 1)
+ FAIL_EXIT1 ("Expected exactly one canonname, but got %d\n", count);
+ }
+ }
+}
+
+/* test with site.example domain. */
+void
+test_search_with_site_example_domain (void)
+{
+ struct resolv_test *aux = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response,
+ .search = { "site.example" },
+ });
+
+ query_host ("foo", "foo.site.example");
+ query_host ("bar.foo", "bar.foo.site.example");
+
+ resolv_test_end (aux);
+}
+
+/* test with example domain. */
+void
+test_search_with_example_domain (void)
+{
+ struct resolv_test *aux = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response,
+ .search = { "example" },
+ });
+
+ query_host ("foo.site", "foo.site.example");
+ query_host ("bar.foo.site", "bar.foo.site.example");
+ query_host ("site", "site.example");
+
+ resolv_test_end (aux);
+}
+
+static int
+do_test (void)
+{
+ test_search_with_site_example_domain ();
+ test_search_with_example_domain ();
+
+ return 0;
+}
+
+#include <support/test-driver.c>