diff --git a/src/lib/ares_sysconfig_win.c b/src/lib/ares_sysconfig_win.c
new file mode 100644
index 00000000..f58c90d4
--- /dev/null
+++ b/src/lib/ares_sysconfig_win.c
@@ -0,0 +1,622 @@
+/* MIT License
+ *
+ * Copyright (c) 1998 Massachusetts Institute of Technology
+ * Copyright (c) 2007 Daniel Stenberg
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "ares_setup.h"
+
+#ifdef HAVE_SYS_PARAM_H
+#  include <sys/param.h>
+#endif
+
+#ifdef HAVE_NETINET_IN_H
+#  include <netinet/in.h>
+#endif
+
+#ifdef HAVE_NETDB_H
+#  include <netdb.h>
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+#  include <arpa/inet.h>
+#endif
+
+#include "ares_nameser.h"
+
+#if defined(USE_WINSOCK)
+#  if defined(HAVE_IPHLPAPI_H)
+#    include <iphlpapi.h>
+#  endif
+#  if defined(HAVE_NETIOAPI_H)
+#    include <netioapi.h>
+#  endif
+#endif
+
+#include "ares.h"
+#include "ares_inet_net_pton.h"
+#include "ares_platform.h"
+#include "ares_private.h"
+
+#if defined(USE_WINSOCK)
+/*
+ * get_REG_SZ()
+ *
+ * Given a 'hKey' handle to an open registry key and a 'leafKeyName' pointer
+ * to the name of the registry leaf key to be queried, fetch it's string
+ * value and return a pointer in *outptr to a newly allocated memory area
+ * holding it as a null-terminated string.
+ *
+ * Returns 0 and nullifies *outptr upon inability to return a string value.
+ *
+ * Returns 1 and sets *outptr when returning a dynamically allocated string.
+ *
+ * Supported on Windows NT 3.5 and newer.
+ */
+static ares_bool_t get_REG_SZ(HKEY hKey, const char *leafKeyName, char **outptr)
+{
+  DWORD size = 0;
+  int   res;
+
+  *outptr = NULL;
+
+  /* Find out size of string stored in registry */
+  res = RegQueryValueExA(hKey, leafKeyName, 0, NULL, NULL, &size);
+  if ((res != ERROR_SUCCESS && res != ERROR_MORE_DATA) || !size) {
+    return ARES_FALSE;
+  }
+
+  /* Allocate buffer of indicated size plus one given that string
+     might have been stored without null termination */
+  *outptr = ares_malloc(size + 1);
+  if (!*outptr) {
+    return ARES_FALSE;
+  }
+
+  /* Get the value for real */
+  res = RegQueryValueExA(hKey, leafKeyName, 0, NULL, (unsigned char *)*outptr,
+                         &size);
+  if ((res != ERROR_SUCCESS) || (size == 1)) {
+    ares_free(*outptr);
+    *outptr = NULL;
+    return ARES_FALSE;
+  }
+
+  /* Null terminate buffer always */
+  *(*outptr + size) = '\0';
+
+  return ARES_TRUE;
+}
+
+static void commanjoin(char **dst, const char * const src, const size_t len)
+{
+  char  *newbuf;
+  size_t newsize;
+
+  /* 1 for terminating 0 and 2 for , and terminating 0 */
+  newsize = len + (*dst ? (ares_strlen(*dst) + 2) : 1);
+  newbuf  = ares_realloc(*dst, newsize);
+  if (!newbuf) {
+    return;
+  }
+  if (*dst == NULL) {
+    *newbuf = '\0';
+  }
+  *dst = newbuf;
+  if (ares_strlen(*dst) != 0) {
+    strcat(*dst, ",");
+  }
+  strncat(*dst, src, len);
+}
+
+/*
+ * commajoin()
+ *
+ * RTF code.
+ */
+static void commajoin(char **dst, const char *src)
+{
+  commanjoin(dst, src, ares_strlen(src));
+}
+
+/* A structure to hold the string form of IPv4 and IPv6 addresses so we can
+ * sort them by a metric.
+ */
+typedef struct {
+  /* The metric we sort them by. */
+  ULONG  metric;
+
+  /* Original index of the item, used as a secondary sort parameter to make
+   * qsort() stable if the metrics are equal */
+  size_t orig_idx;
+
+  /* Room enough for the string form of any IPv4 or IPv6 address that
+   * ares_inet_ntop() will create.  Based on the existing c-ares practice.
+   */
+  char   text[INET6_ADDRSTRLEN + 8 + 64]; /* [%s]:NNNNN%iface */
+} Address;
+
+/* Sort Address values \a left and \a right by metric, returning the usual
+ * indicators for qsort().
+ */
+static int compareAddresses(const void *arg1, const void *arg2)
+{
+  const Address * const left  = arg1;
+  const Address * const right = arg2;
+  /* Lower metric the more preferred */
+  if (left->metric < right->metric) {
+    return -1;
+  }
+  if (left->metric > right->metric) {
+    return 1;
+  }
+  /* If metrics are equal, lower original index more preferred */
+  if (left->orig_idx < right->orig_idx) {
+    return -1;
+  }
+  if (left->orig_idx > right->orig_idx) {
+    return 1;
+  }
+  return 0;
+}
+
+/* There can be multiple routes to "the Internet".  And there can be different
+ * DNS servers associated with each of the interfaces that offer those routes.
+ * We have to assume that any DNS server can serve any request.  But, some DNS
+ * servers may only respond if requested over their associated interface.  But
+ * we also want to use "the preferred route to the Internet" whenever possible
+ * (and not use DNS servers on a non-preferred route even by forcing request
+ * to go out on the associated non-preferred interface).  i.e. We want to use
+ * the DNS servers associated with the same interface that we would use to
+ * make a general request to anything else.
+ *
+ * But, Windows won't sort the DNS servers by the metrics associated with the
+ * routes and interfaces _even_ though it obviously sends IP packets based on
+ * those same routes and metrics.  So, we must do it ourselves.
+ *
+ * So, we sort the DNS servers by the same metric values used to determine how
+ * an outgoing IP packet will go, thus effectively using the DNS servers
+ * associated with the interface that the DNS requests themselves will
+ * travel.  This gives us optimal routing and avoids issues where DNS servers
+ * won't respond to requests that don't arrive via some specific subnetwork
+ * (and thus some specific interface).
+ *
+ * This function computes the metric we use to sort.  On the interface
+ * identified by \a luid, it determines the best route to \a dest and combines
+ * that route's metric with \a interfaceMetric to compute a metric for the
+ * destination address on that interface.  This metric can be used as a weight
+ * to sort the DNS server addresses associated with each interface (lower is
+ * better).
+ *
+ * Note that by restricting the route search to the specific interface with
+ * which the DNS servers are associated, this function asks the question "What
+ * is the metric for sending IP packets to this DNS server?" which allows us
+ * to sort the DNS servers correctly.
+ */
+static ULONG getBestRouteMetric(IF_LUID * const luid, /* Can't be const :( */
+                                const SOCKADDR_INET * const dest,
+                                const ULONG                 interfaceMetric)
+{
+  /* On this interface, get the best route to that destination. */
+#  if defined(__WATCOMC__)
+  /* OpenWatcom's builtin Windows SDK does not have a definition for
+   * MIB_IPFORWARD_ROW2, and also does not allow the usage of SOCKADDR_INET
+   * as a variable. Let's work around this by returning the worst possible
+   * metric, but only when using the OpenWatcom compiler.
+   * It may be worth investigating using a different version of the Windows
+   * SDK with OpenWatcom in the future, though this may be fixed in OpenWatcom
+   * 2.0.
+   */
+  return (ULONG)-1;
+#  else
+  MIB_IPFORWARD_ROW2 row;
+  SOCKADDR_INET      ignored;
+  if (GetBestRoute2(/* The interface to use.  The index is ignored since we are
+                     * passing a LUID.
+                     */
+                    luid, 0,
+                    /* No specific source address. */
+                    NULL,
+                    /* Our destination address. */
+                    dest,
+                    /* No options. */
+                    0,
+                    /* The route row. */
+                    &row,
+                    /* The best source address, which we don't need. */
+                    &ignored) != NO_ERROR
+      /* If the metric is "unused" (-1) or too large for us to add the two
+       * metrics, use the worst possible, thus sorting this last.
+       */
+      || row.Metric == (ULONG)-1 ||
+      row.Metric > ((ULONG)-1) - interfaceMetric) {
+    /* Return the worst possible metric. */
+    return (ULONG)-1;
+  }
+
+  /* Return the metric value from that row, plus the interface metric.
+   *
+   * See
+   * http://msdn.microsoft.com/en-us/library/windows/desktop/aa814494(v=vs.85).aspx
+   * which describes the combination as a "sum".
+   */
+  return row.Metric + interfaceMetric;
+#  endif /* __WATCOMC__ */
+}
+
+/*
+ * get_DNS_Windows()
+ *
+ * Locates DNS info using GetAdaptersAddresses() function from the Internet
+ * Protocol Helper (IP Helper) API. When located, this returns a pointer
+ * in *outptr to a newly allocated memory area holding a null-terminated
+ * string with a space or comma separated list of DNS IP addresses.
+ *
+ * Returns 0 and nullifies *outptr upon inability to return DNSes string.
+ *
+ * Returns 1 and sets *outptr when returning a dynamically allocated string.
+ *
+ * Implementation supports Windows XP and newer.
+ */
+#  define IPAA_INITIAL_BUF_SZ 15 * 1024
+#  define IPAA_MAX_TRIES      3
+
+static ares_bool_t get_DNS_Windows(char **outptr)
+{
+  IP_ADAPTER_DNS_SERVER_ADDRESS *ipaDNSAddr;
+  IP_ADAPTER_ADDRESSES          *ipaa;
+  IP_ADAPTER_ADDRESSES          *newipaa;
+  IP_ADAPTER_ADDRESSES          *ipaaEntry;
+  ULONG                          ReqBufsz  = IPAA_INITIAL_BUF_SZ;
+  ULONG                          Bufsz     = IPAA_INITIAL_BUF_SZ;
+  ULONG                          AddrFlags = 0;
+  int                            trying    = IPAA_MAX_TRIES;
+  ULONG                          res;
+
+  /* The capacity of addresses, in elements. */
+  size_t                         addressesSize;
+  /* The number of elements in addresses. */
+  size_t                         addressesIndex = 0;
+  /* The addresses we will sort. */
+  Address                       *addresses;
+
+  union {
+    struct sockaddr     *sa;
+    struct sockaddr_in  *sa4;
+    struct sockaddr_in6 *sa6;
+  } namesrvr;
+
+  *outptr = NULL;
+
+  ipaa = ares_malloc(Bufsz);
+  if (!ipaa) {
+    return ARES_FALSE;
+  }
+
+  /* Start with enough room for a few DNS server addresses and we'll grow it
+   * as we encounter more.
+   */
+  addressesSize = 4;
+  addresses     = (Address *)ares_malloc(sizeof(Address) * addressesSize);
+  if (addresses == NULL) {
+    /* We need room for at least some addresses to function. */
+    ares_free(ipaa);
+    return ARES_FALSE;
+  }
+
+  /* Usually this call succeeds with initial buffer size */
+  res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz);
+  if ((res != ERROR_BUFFER_OVERFLOW) && (res != ERROR_SUCCESS)) {
+    goto done;
+  }
+
+  while ((res == ERROR_BUFFER_OVERFLOW) && (--trying)) {
+    if (Bufsz < ReqBufsz) {
+      newipaa = ares_realloc(ipaa, ReqBufsz);
+      if (!newipaa) {
+        goto done;
+      }
+      Bufsz = ReqBufsz;
+      ipaa  = newipaa;
+    }
+    res = GetAdaptersAddresses(AF_UNSPEC, AddrFlags, NULL, ipaa, &ReqBufsz);
+    if (res == ERROR_SUCCESS) {
+      break;
+    }
+  }
+  if (res != ERROR_SUCCESS) {
+    goto done;
+  }
+
+  for (ipaaEntry = ipaa; ipaaEntry; ipaaEntry = ipaaEntry->Next) {
+    if (ipaaEntry->OperStatus != IfOperStatusUp) {
+      continue;
+    }
+
+    /* For each interface, find any associated DNS servers as IPv4 or IPv6
+     * addresses.  For each found address, find the best route to that DNS
+     * server address _on_ _that_ _interface_ (at this moment in time) and
+     * compute the resulting total metric, just as Windows routing will do.
+     * Then, sort all the addresses found by the metric.
+     */
+    for (ipaDNSAddr = ipaaEntry->FirstDnsServerAddress; ipaDNSAddr;
+         ipaDNSAddr = ipaDNSAddr->Next) {
+      char ipaddr[INET6_ADDRSTRLEN] = "";
+      namesrvr.sa                   = ipaDNSAddr->Address.lpSockaddr;
+
+      if (namesrvr.sa->sa_family == AF_INET) {
+        if ((namesrvr.sa4->sin_addr.S_un.S_addr == INADDR_ANY) ||
+            (namesrvr.sa4->sin_addr.S_un.S_addr == INADDR_NONE)) {
+          continue;
+        }
+
+        /* Allocate room for another address, if necessary, else skip. */
+        if (addressesIndex == addressesSize) {
+          const size_t    newSize = addressesSize + 4;
+          Address * const newMem =
+            (Address *)ares_realloc(addresses, sizeof(Address) * newSize);
+          if (newMem == NULL) {
+            continue;
+          }
+          addresses     = newMem;
+          addressesSize = newSize;
+        }
+
+        addresses[addressesIndex].metric = getBestRouteMetric(
+          &ipaaEntry->Luid, (SOCKADDR_INET *)((void *)(namesrvr.sa)),
+          ipaaEntry->Ipv4Metric);
+
+        /* Record insertion index to make qsort stable */
+        addresses[addressesIndex].orig_idx = addressesIndex;
+
+        if (!ares_inet_ntop(AF_INET, &namesrvr.sa4->sin_addr, ipaddr,
+                            sizeof(ipaddr))) {
+          continue;
+        }
+        snprintf(addresses[addressesIndex].text,
+                 sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr,
+                 ntohs(namesrvr.sa4->sin_port));
+        ++addressesIndex;
+      } else if (namesrvr.sa->sa_family == AF_INET6) {
+        unsigned int     ll_scope = 0;
+        struct ares_addr addr;
+
+        if (memcmp(&namesrvr.sa6->sin6_addr, &ares_in6addr_any,
+                   sizeof(namesrvr.sa6->sin6_addr)) == 0) {
+          continue;
+        }
+
+        /* Allocate room for another address, if necessary, else skip. */
+        if (addressesIndex == addressesSize) {
+          const size_t    newSize = addressesSize + 4;
+          Address * const newMem =
+            (Address *)ares_realloc(addresses, sizeof(Address) * newSize);
+          if (newMem == NULL) {
+            continue;
+          }
+          addresses     = newMem;
+          addressesSize = newSize;
+        }
+
+        /* See if its link-local */
+        memset(&addr, 0, sizeof(addr));
+        addr.family = AF_INET6;
+        memcpy(&addr.addr.addr6, &namesrvr.sa6->sin6_addr, 16);
+        if (ares__addr_is_linklocal(&addr)) {
+          ll_scope = ipaaEntry->Ipv6IfIndex;
+        }
+
+        addresses[addressesIndex].metric = getBestRouteMetric(
+          &ipaaEntry->Luid, (SOCKADDR_INET *)((void *)(namesrvr.sa)),
+          ipaaEntry->Ipv6Metric);
+
+        /* Record insertion index to make qsort stable */
+        addresses[addressesIndex].orig_idx = addressesIndex;
+
+        if (!ares_inet_ntop(AF_INET6, &namesrvr.sa6->sin6_addr, ipaddr,
+                            sizeof(ipaddr))) {
+          continue;
+        }
+
+        if (ll_scope) {
+          snprintf(addresses[addressesIndex].text,
+                   sizeof(addresses[addressesIndex].text), "[%s]:%u%%%u",
+                   ipaddr, ntohs(namesrvr.sa6->sin6_port), ll_scope);
+        } else {
+          snprintf(addresses[addressesIndex].text,
+                   sizeof(addresses[addressesIndex].text), "[%s]:%u", ipaddr,
+                   ntohs(namesrvr.sa6->sin6_port));
+        }
+        ++addressesIndex;
+      } else {
+        /* Skip non-IPv4/IPv6 addresses completely. */
+        continue;
+      }
+    }
+  }
+
+  /* Sort all of the textual addresses by their metric (and original index if
+   * metrics are equal). */
+  qsort(addresses, addressesIndex, sizeof(*addresses), compareAddresses);
+
+  /* Join them all into a single string, removing duplicates. */
+  {
+    size_t i;
+    for (i = 0; i < addressesIndex; ++i) {
+      size_t j;
+      /* Look for this address text appearing previously in the results. */
+      for (j = 0; j < i; ++j) {
+        if (strcmp(addresses[j].text, addresses[i].text) == 0) {
+          break;
+        }
+      }
+      /* Iff we didn't emit this address already, emit it now. */
+      if (j == i) {
+        /* Add that to outptr (if we can). */
+        commajoin(outptr, addresses[i].text);
+      }
+    }
+  }
+
+done:
+  ares_free(addresses);
+
+  if (ipaa) {
+    ares_free(ipaa);
+  }
+
+  if (!*outptr) {
+    return ARES_FALSE;
+  }
+
+  return ARES_TRUE;
+}
+
+/*
+ * get_SuffixList_Windows()
+ *
+ * Reads the "DNS Suffix Search List" from registry and writes the list items
+ * whitespace separated to outptr. If the Search List is empty, the
+ * "Primary Dns Suffix" is written to outptr.
+ *
+ * Returns 0 and nullifies *outptr upon inability to return the suffix list.
+ *
+ * Returns 1 and sets *outptr when returning a dynamically allocated string.
+ *
+ * Implementation supports Windows Server 2003 and newer
+ */
+static ares_bool_t get_SuffixList_Windows(char **outptr)
+{
+  HKEY  hKey;
+  HKEY  hKeyEnum;
+  char  keyName[256];
+  DWORD keyNameBuffSize;
+  DWORD keyIdx = 0;
+  char *p      = NULL;
+
+  *outptr = NULL;
+
+  if (ares__getplatform() != WIN_NT) {
+    return ARES_FALSE;
+  }
+
+  /* 1. Global DNS Suffix Search List */
+  if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ, &hKey) ==
+      ERROR_SUCCESS) {
+    get_REG_SZ(hKey, SEARCHLIST_KEY, outptr);
+    if (get_REG_SZ(hKey, DOMAIN_KEY, &p)) {
+      commajoin(outptr, p);
+      ares_free(p);
+      p = NULL;
+    }
+    RegCloseKey(hKey);
+  }
+
+  if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NT_DNSCLIENT, 0, KEY_READ, &hKey) ==
+      ERROR_SUCCESS) {
+    if (get_REG_SZ(hKey, SEARCHLIST_KEY, &p)) {
+      commajoin(outptr, p);
+      ares_free(p);
+      p = NULL;
+    }
+    RegCloseKey(hKey);
+  }
+
+  /* 2. Connection Specific Search List composed of:
+   *  a. Primary DNS Suffix */
+  if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_DNSCLIENT, 0, KEY_READ, &hKey) ==
+      ERROR_SUCCESS) {
+    if (get_REG_SZ(hKey, PRIMARYDNSSUFFIX_KEY, &p)) {
+      commajoin(outptr, p);
+      ares_free(p);
+      p = NULL;
+    }
+    RegCloseKey(hKey);
+  }
+
+  /*  b. Interface SearchList, Domain, DhcpDomain */
+  if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY "\\" INTERFACES_KEY, 0,
+                    KEY_READ, &hKey) == ERROR_SUCCESS) {
+    for (;;) {
+      keyNameBuffSize = sizeof(keyName);
+      if (RegEnumKeyExA(hKey, keyIdx++, keyName, &keyNameBuffSize, 0, NULL,
+                        NULL, NULL) != ERROR_SUCCESS) {
+        break;
+      }
+      if (RegOpenKeyExA(hKey, keyName, 0, KEY_QUERY_VALUE, &hKeyEnum) !=
+          ERROR_SUCCESS) {
+        continue;
+      }
+      /* p can be comma separated (SearchList) */
+      if (get_REG_SZ(hKeyEnum, SEARCHLIST_KEY, &p)) {
+        commajoin(outptr, p);
+        ares_free(p);
+        p = NULL;
+      }
+      if (get_REG_SZ(hKeyEnum, DOMAIN_KEY, &p)) {
+        commajoin(outptr, p);
+        ares_free(p);
+        p = NULL;
+      }
+      if (get_REG_SZ(hKeyEnum, DHCPDOMAIN_KEY, &p)) {
+        commajoin(outptr, p);
+        ares_free(p);
+        p = NULL;
+      }
+      RegCloseKey(hKeyEnum);
+    }
+    RegCloseKey(hKey);
+  }
+
+  return *outptr != NULL ? ARES_TRUE : ARES_FALSE;
+}
+
+ares_status_t ares__init_sysconfig_windows(ares_sysconfig_t *sysconfig)
+{
+  char         *line   = NULL;
+  ares_status_t status = ARES_SUCCESS;
+
+  if (get_DNS_Windows(&line)) {
+    status = ares__sconfig_append_fromstr(&sysconfig->sconfig, line, ARES_TRUE);
+    ares_free(line);
+    if (status != ARES_SUCCESS) {
+      goto done;
+    }
+  }
+
+  if (get_SuffixList_Windows(&line)) {
+    sysconfig->domains = ares__strsplit(line, ", ", &sysconfig->ndomains);
+    ares_free(line);
+    if (sysconfig->domains == NULL) {
+      status = ARES_EFILE;
+    }
+    if (status != ARES_SUCCESS) {
+      goto done;
+    }
+  }
+
+done:
+  return status;
+}
+#endif