[Openvpn-devel,v2] Factor out address resolve code to a new file resolve.c

Message ID 20260819074905.25119-1-gert@greenie.muc.de
State New
Headers
Series [Openvpn-devel,v2] Factor out address resolve code to a new file resolve.c |

Commit Message

Gert Doering Aug. 19, 2026, 7:48 a.m. UTC
  From: Frank Lichtenheld <frank@lichtenheld.com>

The code was weirdly split between socket.c, socket_util.c,
and options.c. The new structure makes more sense.

While here move some related string helper code to buffer.h.

Change-Id: I12bef330e2c8a5e5b21c2fa807584319b2b7101f
Signed-off-by: Frank Lichtenheld <frank@lichtenheld.com>
Acked-by: Razvan Cojocaru <razvanc@mailbox.org>
Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1846
---

This change was reviewed on Gerrit and approved by at least one
developer. I request to merge it to master.

Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1846
This mail reflects revision 2 of this Change.

Acked-by according to Gerrit (reflected above):
Razvan Cojocaru <razvanc@mailbox.org>
  

Patch

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2b0adff..ec09ada 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -567,6 +567,8 @@ 
     src/openvpn/reflect_filter.h
     src/openvpn/reliable.c
     src/openvpn/reliable.h
+    src/openvpn/resolve.c
+    src/openvpn/resolve.h
     src/openvpn/route.c
     src/openvpn/route.h
     src/openvpn/run_command.c
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 182d732..ad46ed0 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -125,6 +125,7 @@ 
 	pushlist.h \
 	reflect_filter.c reflect_filter.h \
 	reliable.c reliable.h \
+	resolve.c resolve.h \
 	route.c route.h \
 	run_command.c run_command.h \
 	schedule.c schedule.h \
diff --git a/src/openvpn/buffer.c b/src/openvpn/buffer.c
index 8c19902..32bdb2d 100644
--- a/src/openvpn/buffer.c
+++ b/src/openvpn/buffer.c
@@ -1098,6 +1098,23 @@ 
     }
 }
 
+bool
+streqnull(const char *a, const char *b)
+{
+    if (a == NULL && b == NULL)
+    {
+        return true;
+    }
+    else if (a == NULL || b == NULL)
+    {
+        return false;
+    }
+    else
+    {
+        return streq(a, b);
+    }
+}
+
 char *
 string_substitute(const char *src, char from, char to, struct gc_arena *gc)
 {
diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h
index 651aaed..ea6d8e4 100644
--- a/src/openvpn/buffer.h
+++ b/src/openvpn/buffer.h
@@ -1766,6 +1766,18 @@ 
     return 0 == strncmp(str, prefix, strlen(prefix));
 }
 
+/** helper to work around the semantics of the strcmp() return value
+ * @return true if the strings are equal according to strcmp()
+ */
+#define streq(x, y) (!strcmp((x), (y)))
+
+/** compare two strings which might be NULL
+ * @return true if both strings are NULL, or if they are equal according to strcmp()
+ */
+bool streqnull(const char *a, const char *b);
+/** compare two strings which might be NULL, but NULL is never equal
+ * @return true if neither string is NULL and they are equal according to strcmp()
+ */
 bool string_defined_equal(const char *s1, const char *s2);
 char *string_substitute(const char *src, char from, char to, struct gc_arena *gc);
 
diff --git a/src/openvpn/clinat.c b/src/openvpn/clinat.c
index 32c1325..3f42254 100644
--- a/src/openvpn/clinat.c
+++ b/src/openvpn/clinat.c
@@ -30,6 +30,7 @@ 
 #include "proto.h"
 #include "socket_util.h"
 #include "memdbg.h"
+#include "resolve.h"
 
 static bool
 add_entry(struct client_nat_option_list *dest, const struct client_nat_entry *e)
diff --git a/src/openvpn/dns.c b/src/openvpn/dns.c
index 1465cdf..4535c99 100644
--- a/src/openvpn/dns.c
+++ b/src/openvpn/dns.c
@@ -27,7 +27,7 @@ 
 #include "syshead.h"
 
 #include "dns.h"
-#include "socket_util.h"
+#include "resolve.h"
 #include "options.h"
 #include "run_command.h"
 #include "domain_helper.h"
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index fa62cdd..89a2c38 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -55,6 +55,7 @@ 
 #include "tun_afunix.h"
 #include "schedule.h"
 #include "options_string.h"
+#include "resolve.h"
 
 #include "memdbg.h"
 
diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c
index 2a3023c..2cea9df 100644
--- a/src/openvpn/manage.c
+++ b/src/openvpn/manage.c
@@ -44,6 +44,7 @@ 
 #include "dco.h"
 #include "push.h"
 #include "multi.h"
+#include "resolve.h"
 
 #include "memdbg.h"
 
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 418329e..c7075be 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -64,6 +64,7 @@ 
 #include "domain_helper.h"
 #include "mbuf.h"
 #include "check_file_access.h"
+#include "resolve.h"
 
 #include <ctype.h>
 
@@ -1038,26 +1039,6 @@ 
 
 #endif /* ifndef _WIN32 */
 
-static in_addr_t
-get_ip_addr(const char *ip_string, msglvl_t msglevel, bool *error)
-{
-    unsigned int flags = GETADDR_HOST_ORDER;
-    bool succeeded = false;
-    in_addr_t ret;
-
-    if (msglevel & M_FATAL)
-    {
-        flags |= GETADDR_FATAL;
-    }
-
-    ret = getaddr(flags, ip_string, 0, &succeeded, NULL);
-    if (!succeeded && error)
-    {
-        *error = true;
-    }
-    return ret;
-}
-
 /**
  * Returns newly allocated string containing address part without "/nn".
  *
@@ -1080,13 +1061,6 @@ 
     }
     return ret;
 }
-
-static bool
-ipv6_addr_safe_hexplusbits(const char *ipv6_prefix_spec)
-{
-    return get_ipv6_addr(ipv6_prefix_spec, NULL, NULL, M_WARN);
-}
-
 /**
  * Parses a hexstring and checks if the string has the correct length. Return
  * a verify_hash_list containing the parsed hash string.
@@ -3321,7 +3295,7 @@ 
     options_postprocess_verify(options);
 #ifndef ENABLE_SMALL
     options_postprocess_filechecks(options);
-#endif /* !ENABLE_SMALL */
+#endif
 }
 
 /*
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index c23d0b3..2b68a0e 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -723,8 +723,6 @@ 
     unsigned int imported_protocol_flags;
 };
 
-#define streq(x, y) (!strcmp((x), (y)))
-
 /*
  * Option classes.
  */
diff --git a/src/openvpn/pool.c b/src/openvpn/pool.c
index ff74e7c..7e74649 100644
--- a/src/openvpn/pool.c
+++ b/src/openvpn/pool.c
@@ -31,6 +31,7 @@ 
 #include "error.h"
 #include "socket_util.h"
 #include "otime.h"
+#include "resolve.h"
 
 #include "memdbg.h"
 
diff --git a/src/openvpn/ps.c b/src/openvpn/ps.c
index c3f54ed..2f596da 100644
--- a/src/openvpn/ps.c
+++ b/src/openvpn/ps.c
@@ -32,6 +32,7 @@ 
 #include "socket.h"
 #include "fdmisc.h"
 #include "crypto.h"
+#include "resolve.h"
 #include "ps.h"
 
 #include "memdbg.h"
diff --git a/src/openvpn/push.c b/src/openvpn/push.c
index 5b15f71..467ebc9 100644
--- a/src/openvpn/push.c
+++ b/src/openvpn/push.c
@@ -37,6 +37,7 @@ 
 #include "memdbg.h"
 #include "ssl_util.h"
 #include "options_util.h"
+#include "resolve.h"
 
 /*
  * Auth username/password
diff --git a/src/openvpn/resolve.c b/src/openvpn/resolve.c
new file mode 100644
index 0000000..b8d9899
--- /dev/null
+++ b/src/openvpn/resolve.c
@@ -0,0 +1,665 @@ 
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "syshead.h"
+
+#include "buffer.h"
+#include "crypto.h"
+#include "manage.h"
+#include "openvpn.h"
+#include "socket.h"
+#include "socket_util.h"
+
+#include "resolve.h"
+
+/*
+ * Convert sockflags/getaddr_flags into getaddr_flags
+ */
+unsigned int
+sf2gaf(const unsigned int getaddr_flags, const unsigned int sockflags)
+{
+    if (sockflags & SF_HOST_RANDOMIZE)
+    {
+        return getaddr_flags | GETADDR_RANDOMIZE;
+    }
+    else
+    {
+        return getaddr_flags;
+    }
+}
+
+/*
+ * Functions related to the translation of DNS names to IP addresses.
+ */
+
+/**
+ * Small helper function for openvpn_getaddrinfo to print the address
+ * family when resolving fails
+ */
+static const char *
+getaddrinfo_addr_family_name(int af)
+{
+    switch (af)
+    {
+        case AF_INET:
+            return "[AF_INET]";
+
+        case AF_INET6:
+            return "[AF_INET6]";
+    }
+    return "";
+}
+
+/*
+ * Prepend a random string to hostname to prevent DNS caching.
+ * For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.
+ * Of course, this requires explicit support in the DNS server (wildcard).
+ */
+static const char *
+hostname_randomize(const char *hostname, struct gc_arena *gc)
+{
+#define n_rnd_bytes 6
+
+    uint8_t rnd_bytes[n_rnd_bytes];
+    const char *rnd_str;
+    struct buffer hname = alloc_buf_gc(strlen(hostname) + sizeof(rnd_bytes) * 2 + 4, gc);
+
+    prng_bytes(rnd_bytes, sizeof(rnd_bytes));
+    rnd_str = format_hex_ex(rnd_bytes, sizeof(rnd_bytes), 40, 0, NULL, gc);
+    buf_printf(&hname, "%s.%s", rnd_str, hostname);
+    return BSTR(&hname);
+#undef n_rnd_bytes
+}
+
+/*
+ * Translate IPv4/IPv6 addr or hostname into struct addrinfo
+ * If resolve error, try again for resolve_retry_seconds seconds.
+ */
+int
+openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname,
+                    int resolve_retry_seconds, struct signal_info *sig_info, int ai_family,
+                    struct addrinfo **res)
+{
+    struct addrinfo hints;
+    int status;
+    struct signal_info sigrec = { 0 };
+    msglvl_t msglevel = (flags & GETADDR_FATAL) ? M_FATAL : D_RESOLVE_ERRORS;
+    struct gc_arena gc = gc_new();
+    const char *print_hostname;
+    const char *print_servname;
+
+    ASSERT(res);
+
+    ASSERT(hostname || servname);
+    ASSERT(!(flags & GETADDR_HOST_ORDER));
+
+    if (servname)
+    {
+        print_servname = servname;
+    }
+    else
+    {
+        print_servname = "";
+    }
+
+    if (flags & GETADDR_MSG_VIRT_OUT)
+    {
+        msglevel |= M_MSG_VIRT_OUT;
+    }
+
+    if ((flags & (GETADDR_FATAL_ON_SIGNAL | GETADDR_WARN_ON_SIGNAL)) && !sig_info)
+    {
+        sig_info = &sigrec;
+    }
+
+    /* try numeric ip addr first */
+    CLEAR(hints);
+    hints.ai_flags = AI_NUMERICHOST;
+
+    if (flags & GETADDR_PASSIVE)
+    {
+        hints.ai_flags |= AI_PASSIVE;
+    }
+
+    if (flags & GETADDR_DATAGRAM)
+    {
+        hints.ai_socktype = SOCK_DGRAM;
+    }
+    else
+    {
+        hints.ai_socktype = SOCK_STREAM;
+    }
+
+    /* if hostname is not set, we want to bind to 'ANY', with
+     * the correct address family - v4-only or v6/v6-dual-stack */
+    if (!hostname)
+    {
+        hints.ai_family = ai_family;
+    }
+
+    status = getaddrinfo(hostname, servname, &hints, res);
+
+    if (status != 0)                      /* parse as numeric address failed? */
+    {
+        const int fail_wait_interval = 5; /* seconds */
+        /* Add +4 to cause integer division rounding up (1 + 4) = 5, (0+4)/5=0 */
+        int resolve_retries =
+            (flags & GETADDR_TRY_ONCE) ? 1 : ((resolve_retry_seconds + 4) / fail_wait_interval);
+        const char *fmt;
+        msglvl_t level = 0;
+
+        /* this is not a numeric IP, therefore force resolution using the
+         * provided ai_family */
+        hints.ai_family = ai_family;
+
+        if (hostname && (flags & GETADDR_RANDOMIZE))
+        {
+            hostname = hostname_randomize(hostname, &gc);
+        }
+
+        if (hostname)
+        {
+            print_hostname = hostname;
+        }
+        else
+        {
+            print_hostname = "undefined";
+        }
+
+        fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)";
+        if ((flags & GETADDR_MENTION_RESOLVE_RETRY) && !resolve_retry_seconds)
+        {
+            fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)"
+                  "(I would have retried this name query if you had "
+                  "specified the --resolv-retry option.)";
+        }
+
+        if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL)
+        {
+            msg(msglevel, "RESOLVE: Cannot parse IP address: %s:%s (%s)", print_hostname,
+                print_servname, gai_strerror(status));
+            goto done;
+        }
+
+#ifdef ENABLE_MANAGEMENT
+        if (flags & GETADDR_UPDATE_MANAGEMENT_STATE)
+        {
+            if (management)
+            {
+                management_set_state(management, OPENVPN_STATE_RESOLVE, NULL, NULL, NULL, NULL,
+                                     NULL);
+            }
+        }
+#endif
+
+        /*
+         * Resolve hostname
+         */
+        while (true)
+        {
+#ifndef _WIN32
+            /* force resolv.conf reload */
+            res_init();
+#endif
+            /* try hostname lookup */
+            hints.ai_flags &= ~AI_NUMERICHOST;
+            dmsg(D_SOCKET_DEBUG, "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d", flags,
+                 hints.ai_family, hints.ai_socktype);
+            status = getaddrinfo(hostname, servname, &hints, res);
+
+            if (sig_info)
+            {
+                get_signal(&sig_info->signal_received);
+                if (sig_info->signal_received) /* were we interrupted by a signal? */
+                {
+                    /* why are we overwriting SIGUSR1 ? */
+                    if (signal_reset(sig_info, SIGUSR1) == SIGUSR1) /* ignore SIGUSR1 */
+                    {
+                        msg(level, "RESOLVE: Ignored SIGUSR1 signal received during "
+                                   "DNS resolution attempt");
+                    }
+                    else
+                    {
+                        /* turn success into failure (interrupted syscall) */
+                        if (0 == status)
+                        {
+                            ASSERT(res);
+                            freeaddrinfo(*res);
+                            *res = NULL;
+                            status = EAI_AGAIN; /* = temporary failure */
+                            errno = EINTR;
+                        }
+                        goto done;
+                    }
+                }
+            }
+
+            /* success? */
+            if (0 == status)
+            {
+                break;
+            }
+
+            /* resolve lookup failed, should we
+             * continue or fail? */
+            level = msglevel;
+            if (resolve_retries > 0)
+            {
+                level = D_RESOLVE_ERRORS;
+            }
+
+            msg(level, fmt, print_hostname, print_servname, getaddrinfo_addr_family_name(ai_family),
+                gai_strerror(status));
+
+            if (--resolve_retries <= 0)
+            {
+                goto done;
+            }
+
+            management_sleep(fail_wait_interval);
+        }
+
+        ASSERT(res);
+
+        /* hostname resolve succeeded */
+
+        /*
+         * Do not choose an IP Addresse by random or change the order *
+         * of IP addresses, doing so will break RFC 3484 address selection *
+         */
+    }
+    else
+    {
+        /* IP address parse succeeded */
+        if (flags & GETADDR_RANDOMIZE)
+        {
+            msg(M_WARN, "WARNING: ignoring --remote-random-hostname because the "
+                        "hostname is an IP address");
+        }
+    }
+
+done:
+    if (sig_info && sig_info->signal_received)
+    {
+        msglvl_t level = 0;
+        if (flags & GETADDR_FATAL_ON_SIGNAL)
+        {
+            level = M_FATAL;
+        }
+        else if (flags & GETADDR_WARN_ON_SIGNAL)
+        {
+            level = M_WARN;
+        }
+        msg(level, "RESOLVE: signal received during DNS resolution attempt");
+    }
+
+    gc_free(&gc);
+    return status;
+}
+
+static int
+get_addr_generic(sa_family_t af, unsigned int flags, const char *hostname, void *network,
+                 unsigned int *netbits, int resolve_retry_seconds, struct signal_info *sig_info,
+                 msglvl_t msglevel)
+{
+    char *endp, *sep, *var_host = NULL;
+    struct addrinfo *ai = NULL;
+    unsigned long bits;
+    uint8_t max_bits;
+    int ret = -1;
+
+    if (!hostname)
+    {
+        msg(M_NONFATAL, "Can't resolve null hostname!");
+        goto out;
+    }
+
+    /* assign family specific default values */
+    switch (af)
+    {
+        case AF_INET:
+            bits = 0;
+            max_bits = sizeof(in_addr_t) * 8;
+            break;
+
+        case AF_INET6:
+            bits = 64;
+            max_bits = sizeof(struct in6_addr) * 8;
+            break;
+
+        default:
+            msg(M_WARN, "Unsupported AF family passed to getaddrinfo for %s (%d)", hostname, af);
+            goto out;
+    }
+
+    /* we need to modify the hostname received as input, but we don't want to
+     * touch it directly as it might be a constant string.
+     *
+     * Therefore, we clone the string here and free it at the end of the
+     * function */
+    var_host = strdup(hostname);
+    if (!var_host)
+    {
+        msg(M_NONFATAL | M_ERRNO, "Can't allocate hostname buffer for getaddrinfo");
+        goto out;
+    }
+
+    /* check if this hostname has a /bits suffix */
+    sep = strchr(var_host, '/');
+    if (sep)
+    {
+        bits = strtoul(sep + 1, &endp, 10);
+        if ((*endp != '\0') || (bits > max_bits))
+        {
+            msg(msglevel, "IP prefix '%s': invalid '/bits' spec (%s)", hostname, sep + 1);
+            goto out;
+        }
+        *sep = '\0';
+    }
+
+    ret = openvpn_getaddrinfo(flags & ~GETADDR_HOST_ORDER, var_host, NULL, resolve_retry_seconds,
+                              sig_info, af, &ai);
+    if ((ret == 0) && network)
+    {
+        struct in6_addr *ip6;
+        in_addr_t *ip4;
+
+        if (af != ai->ai_family)
+        {
+            msg(msglevel, "Can't parse %s as IPv%d address", var_host, (af == AF_INET) ? 4 : 6);
+            ret = -1;
+            goto out;
+        }
+
+        switch (af)
+        {
+            case AF_INET:
+                ip4 = network;
+                *ip4 = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;
+
+                if (flags & GETADDR_HOST_ORDER)
+                {
+                    *ip4 = ntohl(*ip4);
+                }
+                break;
+
+            case AF_INET6:
+                ip6 = network;
+                *ip6 = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
+                break;
+
+            default:
+                /* can't get here because 'af' was previously checked */
+                msg(M_WARN, "Unsupported AF family for %s (%d)", var_host, af);
+                goto out;
+        }
+    }
+
+    if (netbits)
+    {
+        *netbits = (unsigned int)bits;
+    }
+
+    /* restore '/' separator, if any */
+    if (sep)
+    {
+        *sep = '/';
+    }
+out:
+    if (ai)
+    {
+        freeaddrinfo(ai);
+    }
+    free(var_host);
+
+    return ret;
+}
+
+in_addr_t
+getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds, bool *succeeded,
+        struct signal_info *sig_info)
+{
+    in_addr_t addr = { 0 };
+    int status;
+
+    status = get_addr_generic(AF_INET, flags, hostname, &addr, NULL, resolve_retry_seconds,
+                              sig_info, M_WARN);
+    if (status == 0)
+    {
+        if (succeeded)
+        {
+            *succeeded = true;
+        }
+        return addr;
+    }
+    else
+    {
+        if (succeeded)
+        {
+            *succeeded = false;
+        }
+        return 0;
+    }
+}
+
+in_addr_t
+get_ip_addr(const char *ip_string, msglvl_t msglevel, bool *error)
+{
+    unsigned int flags = GETADDR_HOST_ORDER;
+    bool succeeded = false;
+    in_addr_t ret;
+
+    if (msglevel & M_FATAL)
+    {
+        flags |= GETADDR_FATAL;
+    }
+
+    ret = getaddr(flags, ip_string, 0, &succeeded, NULL);
+    if (!succeeded && error)
+    {
+        *error = true;
+    }
+    return ret;
+}
+
+bool
+get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits,
+              msglvl_t msglevel)
+{
+    if (get_addr_generic(AF_INET6, GETADDR_RESOLVE, hostname, network, netbits, 0, NULL, msglevel)
+        < 0)
+    {
+        return false;
+    }
+
+    return true; /* parsing OK, values set */
+}
+
+/*
+ * get_cached_dns_entry return 0 on success and -1
+ * otherwise. (like getaddrinfo)
+ */
+int
+get_cached_dns_entry(struct cached_dns_entry *dns_cache, const char *hostname, const char *servname,
+                     int ai_family, unsigned int resolve_flags, struct addrinfo **ai)
+{
+    struct cached_dns_entry *ph;
+    unsigned int flags;
+
+    /* Only use flags that are relevant for the structure */
+    flags = resolve_flags & GETADDR_CACHE_MASK;
+
+    for (ph = dns_cache; ph; ph = ph->next)
+    {
+        if (streqnull(ph->hostname, hostname) && streqnull(ph->servname, servname)
+            && ph->ai_family == ai_family && ph->flags == flags)
+        {
+            *ai = ph->ai;
+            return 0;
+        }
+    }
+    return -1;
+}
+
+
+static int
+do_preresolve_host(struct context *c, const char *hostname, const char *servname, const int af,
+                   const unsigned int flags)
+{
+    struct addrinfo *ai;
+    int status;
+
+    if (get_cached_dns_entry(c->c1.dns_cache, hostname, servname, af, flags, &ai) == 0)
+    {
+        /* entry already cached, return success */
+        return 0;
+    }
+
+    status = openvpn_getaddrinfo(flags, hostname, servname, c->options.resolve_retry_seconds, NULL,
+                                 af, &ai);
+    if (status == 0)
+    {
+        struct cached_dns_entry *ph;
+
+        ALLOC_OBJ_CLEAR_GC(ph, struct cached_dns_entry, &c->gc);
+        ph->ai = ai;
+        ph->hostname = hostname;
+        ph->servname = servname;
+        ph->flags = flags & GETADDR_CACHE_MASK;
+
+        if (!c->c1.dns_cache)
+        {
+            c->c1.dns_cache = ph;
+        }
+        else
+        {
+            struct cached_dns_entry *prev = c->c1.dns_cache;
+            while (prev->next)
+            {
+                prev = prev->next;
+            }
+            prev->next = ph;
+        }
+
+        gc_addspecial(ai, &gc_freeaddrinfo_callback, &c->gc);
+    }
+    return status;
+}
+
+void
+do_preresolve(struct context *c)
+{
+    struct connection_list *l = c->options.connection_list;
+    const unsigned int preresolve_flags = GETADDR_RESOLVE | GETADDR_UPDATE_MANAGEMENT_STATE
+                                          | GETADDR_MENTION_RESOLVE_RETRY | GETADDR_FATAL;
+
+
+    for (int i = 0; i < l->len; ++i)
+    {
+        int status;
+        const char *remote;
+        unsigned int flags = preresolve_flags;
+
+        struct connection_entry *ce = l->array[i];
+
+        if (proto_is_dgram(ce->proto))
+        {
+            flags |= GETADDR_DATAGRAM;
+        }
+
+        if (c->options.sockflags & SF_HOST_RANDOMIZE)
+        {
+            flags |= GETADDR_RANDOMIZE;
+        }
+
+        if (c->options.ip_remote_hint)
+        {
+            remote = c->options.ip_remote_hint;
+        }
+        else
+        {
+            remote = ce->remote;
+        }
+
+        /* HTTP remote hostname does not need to be resolved */
+        if (!ce->http_proxy_options)
+        {
+            status = do_preresolve_host(c, remote, ce->remote_port, ce->af, flags);
+            if (status != 0)
+            {
+                goto err;
+            }
+        }
+
+        /* Preresolve proxy */
+        if (ce->http_proxy_options)
+        {
+            status = do_preresolve_host(c, ce->http_proxy_options->server,
+                                        ce->http_proxy_options->port, ce->af, preresolve_flags);
+
+            if (status != 0)
+            {
+                goto err;
+            }
+        }
+
+        if (ce->socks_proxy_server)
+        {
+            status =
+                do_preresolve_host(c, ce->socks_proxy_server, ce->socks_proxy_port, ce->af, flags);
+            if (status != 0)
+            {
+                goto err;
+            }
+        }
+
+        if (ce->bind_local)
+        {
+            flags |= GETADDR_PASSIVE;
+            flags &= ~GETADDR_RANDOMIZE;
+
+            for (int j = 0; j < ce->local_list->len; j++)
+            {
+                const struct local_entry *le = ce->local_list->array[j];
+
+                if (!le->local)
+                {
+                    continue;
+                }
+
+                status = do_preresolve_host(c, le->local, le->port, ce->af, flags);
+                if (status != 0)
+                {
+                    goto err;
+                }
+            }
+        }
+    }
+    return;
+
+err:
+    throw_signal_soft(SIGHUP, "Preresolving failed");
+}
diff --git a/src/openvpn/resolve.h b/src/openvpn/resolve.h
new file mode 100644
index 0000000..9069af7
--- /dev/null
+++ b/src/openvpn/resolve.h
@@ -0,0 +1,106 @@ 
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single TCP/UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file
+ * DNS resolution
+ */
+#ifndef RESOLVE_H
+#define RESOLVE_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "syshead.h"
+
+#include "sig.h"
+
+#define GETADDR_RESOLVE                 (1u << 0)
+#define GETADDR_FATAL                   (1u << 1)
+#define GETADDR_HOST_ORDER              (1u << 2)
+#define GETADDR_MENTION_RESOLVE_RETRY   (1u << 3)
+#define GETADDR_FATAL_ON_SIGNAL         (1u << 4)
+#define GETADDR_WARN_ON_SIGNAL          (1u << 5)
+#define GETADDR_MSG_VIRT_OUT            (1u << 6)
+#define GETADDR_TRY_ONCE                (1u << 7)
+#define GETADDR_UPDATE_MANAGEMENT_STATE (1u << 8)
+#define GETADDR_RANDOMIZE               (1u << 9)
+#define GETADDR_PASSIVE                 (1u << 10)
+#define GETADDR_DATAGRAM                (1u << 11)
+
+#define GETADDR_CACHE_MASK (GETADDR_DATAGRAM | GETADDR_PASSIVE)
+
+/**
+ * Convert sockflags/getaddr_flags into getaddr_flags
+ */
+unsigned int sf2gaf(const unsigned int getaddr_flags, const unsigned int sockflags);
+
+/**
+ * Translate an IPv4 addr or hostname from string form to in_addr_t
+ *
+ * In case of resolve error, it will try again for
+ * resolve_retry_seconds seconds.
+ */
+in_addr_t getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds,
+                  bool *succeeded, struct signal_info *sig_info);
+
+in_addr_t get_ip_addr(const char *ip_string, msglvl_t msglevel, bool *error);
+
+/**
+ * Translate an IPv6 addr or hostname from string form to in6_addr
+ */
+bool get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits,
+                   msglvl_t msglevel);
+
+static inline bool
+ipv6_addr_safe_hexplusbits(const char *ipv6_prefix_spec)
+{
+    return get_ipv6_addr(ipv6_prefix_spec, NULL, NULL, M_WARN);
+}
+
+int openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname,
+                        int resolve_retry_seconds, struct signal_info *sig_info, int ai_family,
+                        struct addrinfo **res);
+
+void do_preresolve(struct context *c);
+
+/* struct to hold preresolved host names */
+struct cached_dns_entry
+{
+    const char *hostname;
+    const char *servname;
+    int ai_family;
+    unsigned int flags;
+    struct addrinfo *ai;
+    struct cached_dns_entry *next;
+};
+
+/**
+ * get_cached_dns_entry return 0 on success and -1
+ * otherwise. (like getaddrinfo())
+ */
+int get_cached_dns_entry(struct cached_dns_entry *dns_cache, const char *hostname,
+                         const char *servname, int ai_family,
+                         unsigned int resolve_flags, struct addrinfo **ai);
+
+#endif /* RESOLVE_H */
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index 8ea745d..8e828ae 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -42,6 +42,7 @@ 
 #include "options.h"
 #include "networking.h"
 #include "integer.h"
+#include "resolve.h"
 
 #include "memdbg.h"
 
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index 0f66ad51..dec4229 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -38,6 +38,7 @@ 
 #include "manage.h"
 #include "openvpn.h"
 #include "forward.h"
+#include "resolve.h"
 
 #include "memdbg.h"
 
@@ -54,366 +55,6 @@ 
     return false;
 }
 
-/*
- * Convert sockflags/getaddr_flags into getaddr_flags
- */
-static unsigned int
-sf2gaf(const unsigned int getaddr_flags, const unsigned int sockflags)
-{
-    if (sockflags & SF_HOST_RANDOMIZE)
-    {
-        return getaddr_flags | GETADDR_RANDOMIZE;
-    }
-    else
-    {
-        return getaddr_flags;
-    }
-}
-
-/*
- * Functions related to the translation of DNS names to IP addresses.
- */
-static int
-get_addr_generic(sa_family_t af, unsigned int flags, const char *hostname, void *network,
-                 unsigned int *netbits, int resolve_retry_seconds, struct signal_info *sig_info,
-                 msglvl_t msglevel)
-{
-    char *endp, *sep, *var_host = NULL;
-    struct addrinfo *ai = NULL;
-    unsigned long bits;
-    uint8_t max_bits;
-    int ret = -1;
-
-    if (!hostname)
-    {
-        msg(M_NONFATAL, "Can't resolve null hostname!");
-        goto out;
-    }
-
-    /* assign family specific default values */
-    switch (af)
-    {
-        case AF_INET:
-            bits = 0;
-            max_bits = sizeof(in_addr_t) * 8;
-            break;
-
-        case AF_INET6:
-            bits = 64;
-            max_bits = sizeof(struct in6_addr) * 8;
-            break;
-
-        default:
-            msg(M_WARN, "Unsupported AF family passed to getaddrinfo for %s (%d)", hostname, af);
-            goto out;
-    }
-
-    /* we need to modify the hostname received as input, but we don't want to
-     * touch it directly as it might be a constant string.
-     *
-     * Therefore, we clone the string here and free it at the end of the
-     * function */
-    var_host = strdup(hostname);
-    if (!var_host)
-    {
-        msg(M_NONFATAL | M_ERRNO, "Can't allocate hostname buffer for getaddrinfo");
-        goto out;
-    }
-
-    /* check if this hostname has a /bits suffix */
-    sep = strchr(var_host, '/');
-    if (sep)
-    {
-        bits = strtoul(sep + 1, &endp, 10);
-        if ((*endp != '\0') || (bits > max_bits))
-        {
-            msg(msglevel, "IP prefix '%s': invalid '/bits' spec (%s)", hostname, sep + 1);
-            goto out;
-        }
-        *sep = '\0';
-    }
-
-    ret = openvpn_getaddrinfo(flags & ~GETADDR_HOST_ORDER, var_host, NULL, resolve_retry_seconds,
-                              sig_info, af, &ai);
-    if ((ret == 0) && network)
-    {
-        struct in6_addr *ip6;
-        in_addr_t *ip4;
-
-        if (af != ai->ai_family)
-        {
-            msg(msglevel, "Can't parse %s as IPv%d address", var_host, (af == AF_INET) ? 4 : 6);
-            ret = -1;
-            goto out;
-        }
-
-        switch (af)
-        {
-            case AF_INET:
-                ip4 = network;
-                *ip4 = ((struct sockaddr_in *)ai->ai_addr)->sin_addr.s_addr;
-
-                if (flags & GETADDR_HOST_ORDER)
-                {
-                    *ip4 = ntohl(*ip4);
-                }
-                break;
-
-            case AF_INET6:
-                ip6 = network;
-                *ip6 = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
-                break;
-
-            default:
-                /* can't get here because 'af' was previously checked */
-                msg(M_WARN, "Unsupported AF family for %s (%d)", var_host, af);
-                goto out;
-        }
-    }
-
-    if (netbits)
-    {
-        *netbits = (unsigned int)bits;
-    }
-
-    /* restore '/' separator, if any */
-    if (sep)
-    {
-        *sep = '/';
-    }
-out:
-    if (ai)
-    {
-        freeaddrinfo(ai);
-    }
-    free(var_host);
-
-    return ret;
-}
-
-in_addr_t
-getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds, bool *succeeded,
-        struct signal_info *sig_info)
-{
-    in_addr_t addr = { 0 };
-    int status;
-
-    status = get_addr_generic(AF_INET, flags, hostname, &addr, NULL, resolve_retry_seconds,
-                              sig_info, M_WARN);
-    if (status == 0)
-    {
-        if (succeeded)
-        {
-            *succeeded = true;
-        }
-        return addr;
-    }
-    else
-    {
-        if (succeeded)
-        {
-            *succeeded = false;
-        }
-        return 0;
-    }
-}
-
-bool
-get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits,
-              msglvl_t msglevel)
-{
-    if (get_addr_generic(AF_INET6, GETADDR_RESOLVE, hostname, network, netbits, 0, NULL, msglevel)
-        < 0)
-    {
-        return false;
-    }
-
-    return true; /* parsing OK, values set */
-}
-
-static inline bool
-streqnull(const char *a, const char *b)
-{
-    if (a == NULL && b == NULL)
-    {
-        return true;
-    }
-    else if (a == NULL || b == NULL)
-    {
-        return false;
-    }
-    else
-    {
-        return streq(a, b);
-    }
-}
-
-/*
- * get_cached_dns_entry return 0 on success and -1
- * otherwise. (like getaddrinfo)
- */
-static int
-get_cached_dns_entry(struct cached_dns_entry *dns_cache, const char *hostname, const char *servname,
-                     int ai_family, unsigned int resolve_flags, struct addrinfo **ai)
-{
-    struct cached_dns_entry *ph;
-    unsigned int flags;
-
-    /* Only use flags that are relevant for the structure */
-    flags = resolve_flags & GETADDR_CACHE_MASK;
-
-    for (ph = dns_cache; ph; ph = ph->next)
-    {
-        if (streqnull(ph->hostname, hostname) && streqnull(ph->servname, servname)
-            && ph->ai_family == ai_family && ph->flags == flags)
-        {
-            *ai = ph->ai;
-            return 0;
-        }
-    }
-    return -1;
-}
-
-
-static int
-do_preresolve_host(struct context *c, const char *hostname, const char *servname, const int af,
-                   const unsigned int flags)
-{
-    struct addrinfo *ai;
-    int status;
-
-    if (get_cached_dns_entry(c->c1.dns_cache, hostname, servname, af, flags, &ai) == 0)
-    {
-        /* entry already cached, return success */
-        return 0;
-    }
-
-    status = openvpn_getaddrinfo(flags, hostname, servname, c->options.resolve_retry_seconds, NULL,
-                                 af, &ai);
-    if (status == 0)
-    {
-        struct cached_dns_entry *ph;
-
-        ALLOC_OBJ_CLEAR_GC(ph, struct cached_dns_entry, &c->gc);
-        ph->ai = ai;
-        ph->hostname = hostname;
-        ph->servname = servname;
-        ph->flags = flags & GETADDR_CACHE_MASK;
-
-        if (!c->c1.dns_cache)
-        {
-            c->c1.dns_cache = ph;
-        }
-        else
-        {
-            struct cached_dns_entry *prev = c->c1.dns_cache;
-            while (prev->next)
-            {
-                prev = prev->next;
-            }
-            prev->next = ph;
-        }
-
-        gc_addspecial(ai, &gc_freeaddrinfo_callback, &c->gc);
-    }
-    return status;
-}
-
-void
-do_preresolve(struct context *c)
-{
-    struct connection_list *l = c->options.connection_list;
-    const unsigned int preresolve_flags = GETADDR_RESOLVE | GETADDR_UPDATE_MANAGEMENT_STATE
-                                          | GETADDR_MENTION_RESOLVE_RETRY | GETADDR_FATAL;
-
-
-    for (int i = 0; i < l->len; ++i)
-    {
-        int status;
-        const char *remote;
-        unsigned int flags = preresolve_flags;
-
-        struct connection_entry *ce = l->array[i];
-
-        if (proto_is_dgram(ce->proto))
-        {
-            flags |= GETADDR_DATAGRAM;
-        }
-
-        if (c->options.sockflags & SF_HOST_RANDOMIZE)
-        {
-            flags |= GETADDR_RANDOMIZE;
-        }
-
-        if (c->options.ip_remote_hint)
-        {
-            remote = c->options.ip_remote_hint;
-        }
-        else
-        {
-            remote = ce->remote;
-        }
-
-        /* HTTP remote hostname does not need to be resolved */
-        if (!ce->http_proxy_options)
-        {
-            status = do_preresolve_host(c, remote, ce->remote_port, ce->af, flags);
-            if (status != 0)
-            {
-                goto err;
-            }
-        }
-
-        /* Preresolve proxy */
-        if (ce->http_proxy_options)
-        {
-            status = do_preresolve_host(c, ce->http_proxy_options->server,
-                                        ce->http_proxy_options->port, ce->af, preresolve_flags);
-
-            if (status != 0)
-            {
-                goto err;
-            }
-        }
-
-        if (ce->socks_proxy_server)
-        {
-            status =
-                do_preresolve_host(c, ce->socks_proxy_server, ce->socks_proxy_port, ce->af, flags);
-            if (status != 0)
-            {
-                goto err;
-            }
-        }
-
-        if (ce->bind_local)
-        {
-            flags |= GETADDR_PASSIVE;
-            flags &= ~GETADDR_RANDOMIZE;
-
-            for (int j = 0; j < ce->local_list->len; j++)
-            {
-                struct local_entry *le = ce->local_list->array[j];
-
-                if (!le->local)
-                {
-                    continue;
-                }
-
-                status = do_preresolve_host(c, le->local, le->port, ce->af, flags);
-                if (status != 0)
-                {
-                    goto err;
-                }
-            }
-        }
-    }
-    return;
-
-err:
-    throw_signal_soft(SIGHUP, "Preresolving failed");
-}
-
 static int
 socket_get_sndbuf(socket_descriptor_t sd)
 {
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index 1a532e1..15934f9 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -62,17 +62,6 @@ 
 /* convert a packet_size_type from network to host order */
 #define ntohps(x) ntohs(x)
 
-/* struct to hold preresolved host names */
-struct cached_dns_entry
-{
-    const char *hostname;
-    const char *servname;
-    int ai_family;
-    unsigned int flags;
-    struct addrinfo *ai;
-    struct cached_dns_entry *next;
-};
-
 /* IP addresses which are persistent across SIGUSR1s */
 struct link_socket_addr
 {
@@ -362,8 +351,6 @@ 
 
 void link_socket_init_phase2(struct context *c, struct link_socket *sock);
 
-void do_preresolve(struct context *c);
-
 void link_socket_close(struct link_socket *sock);
 
 #ifdef ENABLE_MANAGEMENT
diff --git a/src/openvpn/socket_util.c b/src/openvpn/socket_util.c
index 0194f38..5d405ac 100644
--- a/src/openvpn/socket_util.c
+++ b/src/openvpn/socket_util.c
@@ -490,271 +490,6 @@ 
     return ""; /* Make the compiler happy */
 }
 
-/**
- * Small helper function for openvpn_getaddrinfo to print the address
- * family when resolving fails
- */
-static const char *
-getaddrinfo_addr_family_name(int af)
-{
-    switch (af)
-    {
-        case AF_INET:
-            return "[AF_INET]";
-
-        case AF_INET6:
-            return "[AF_INET6]";
-    }
-    return "";
-}
-
-/*
- * Prepend a random string to hostname to prevent DNS caching.
- * For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.
- * Of course, this requires explicit support in the DNS server (wildcard).
- */
-static const char *
-hostname_randomize(const char *hostname, struct gc_arena *gc)
-{
-#define n_rnd_bytes 6
-
-    uint8_t rnd_bytes[n_rnd_bytes];
-    const char *rnd_str;
-    struct buffer hname = alloc_buf_gc(strlen(hostname) + sizeof(rnd_bytes) * 2 + 4, gc);
-
-    prng_bytes(rnd_bytes, sizeof(rnd_bytes));
-    rnd_str = format_hex_ex(rnd_bytes, sizeof(rnd_bytes), 40, 0, NULL, gc);
-    buf_printf(&hname, "%s.%s", rnd_str, hostname);
-    return BSTR(&hname);
-#undef n_rnd_bytes
-}
-
-/*
- * Translate IPv4/IPv6 addr or hostname into struct addrinfo
- * If resolve error, try again for resolve_retry_seconds seconds.
- */
-int
-openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname,
-                    int resolve_retry_seconds, struct signal_info *sig_info, int ai_family,
-                    struct addrinfo **res)
-{
-    struct addrinfo hints;
-    int status;
-    struct signal_info sigrec = { 0 };
-    msglvl_t msglevel = (flags & GETADDR_FATAL) ? M_FATAL : D_RESOLVE_ERRORS;
-    struct gc_arena gc = gc_new();
-    const char *print_hostname;
-    const char *print_servname;
-
-    ASSERT(res);
-
-    ASSERT(hostname || servname);
-    ASSERT(!(flags & GETADDR_HOST_ORDER));
-
-    if (servname)
-    {
-        print_servname = servname;
-    }
-    else
-    {
-        print_servname = "";
-    }
-
-    if (flags & GETADDR_MSG_VIRT_OUT)
-    {
-        msglevel |= M_MSG_VIRT_OUT;
-    }
-
-    if ((flags & (GETADDR_FATAL_ON_SIGNAL | GETADDR_WARN_ON_SIGNAL)) && !sig_info)
-    {
-        sig_info = &sigrec;
-    }
-
-    /* try numeric ip addr first */
-    CLEAR(hints);
-    hints.ai_flags = AI_NUMERICHOST;
-
-    if (flags & GETADDR_PASSIVE)
-    {
-        hints.ai_flags |= AI_PASSIVE;
-    }
-
-    if (flags & GETADDR_DATAGRAM)
-    {
-        hints.ai_socktype = SOCK_DGRAM;
-    }
-    else
-    {
-        hints.ai_socktype = SOCK_STREAM;
-    }
-
-    /* if hostname is not set, we want to bind to 'ANY', with
-     * the correct address family - v4-only or v6/v6-dual-stack */
-    if (!hostname)
-    {
-        hints.ai_family = ai_family;
-    }
-
-    status = getaddrinfo(hostname, servname, &hints, res);
-
-    if (status != 0)                      /* parse as numeric address failed? */
-    {
-        const int fail_wait_interval = 5; /* seconds */
-        /* Add +4 to cause integer division rounding up (1 + 4) = 5, (0+4)/5=0 */
-        int resolve_retries =
-            (flags & GETADDR_TRY_ONCE) ? 1 : ((resolve_retry_seconds + 4) / fail_wait_interval);
-        const char *fmt;
-        msglvl_t level = 0;
-
-        /* this is not a numeric IP, therefore force resolution using the
-         * provided ai_family */
-        hints.ai_family = ai_family;
-
-        if (hostname && (flags & GETADDR_RANDOMIZE))
-        {
-            hostname = hostname_randomize(hostname, &gc);
-        }
-
-        if (hostname)
-        {
-            print_hostname = hostname;
-        }
-        else
-        {
-            print_hostname = "undefined";
-        }
-
-        fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)";
-        if ((flags & GETADDR_MENTION_RESOLVE_RETRY) && !resolve_retry_seconds)
-        {
-            fmt = "RESOLVE: Cannot resolve host address: %s:%s%s (%s)"
-                  "(I would have retried this name query if you had "
-                  "specified the --resolv-retry option.)";
-        }
-
-        if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL)
-        {
-            msg(msglevel, "RESOLVE: Cannot parse IP address: %s:%s (%s)", print_hostname,
-                print_servname, gai_strerror(status));
-            goto done;
-        }
-
-#ifdef ENABLE_MANAGEMENT
-        if (flags & GETADDR_UPDATE_MANAGEMENT_STATE)
-        {
-            if (management)
-            {
-                management_set_state(management, OPENVPN_STATE_RESOLVE, NULL, NULL, NULL, NULL,
-                                     NULL);
-            }
-        }
-#endif
-
-        /*
-         * Resolve hostname
-         */
-        while (true)
-        {
-#ifndef _WIN32
-            /* force resolv.conf reload */
-            res_init();
-#endif
-            /* try hostname lookup */
-            hints.ai_flags &= ~AI_NUMERICHOST;
-            dmsg(D_SOCKET_DEBUG, "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d", flags,
-                 hints.ai_family, hints.ai_socktype);
-            status = getaddrinfo(hostname, servname, &hints, res);
-
-            if (sig_info)
-            {
-                get_signal(&sig_info->signal_received);
-                if (sig_info->signal_received) /* were we interrupted by a signal? */
-                {
-                    /* why are we overwriting SIGUSR1 ? */
-                    if (signal_reset(sig_info, SIGUSR1) == SIGUSR1) /* ignore SIGUSR1 */
-                    {
-                        msg(level, "RESOLVE: Ignored SIGUSR1 signal received during "
-                                   "DNS resolution attempt");
-                    }
-                    else
-                    {
-                        /* turn success into failure (interrupted syscall) */
-                        if (0 == status)
-                        {
-                            ASSERT(res);
-                            freeaddrinfo(*res);
-                            *res = NULL;
-                            status = EAI_AGAIN; /* = temporary failure */
-                            errno = EINTR;
-                        }
-                        goto done;
-                    }
-                }
-            }
-
-            /* success? */
-            if (0 == status)
-            {
-                break;
-            }
-
-            /* resolve lookup failed, should we
-             * continue or fail? */
-            level = msglevel;
-            if (resolve_retries > 0)
-            {
-                level = D_RESOLVE_ERRORS;
-            }
-
-            msg(level, fmt, print_hostname, print_servname, getaddrinfo_addr_family_name(ai_family),
-                gai_strerror(status));
-
-            if (--resolve_retries <= 0)
-            {
-                goto done;
-            }
-
-            management_sleep(fail_wait_interval);
-        }
-
-        ASSERT(res);
-
-        /* hostname resolve succeeded */
-
-        /*
-         * Do not choose an IP Addresse by random or change the order *
-         * of IP addresses, doing so will break RFC 3484 address selection *
-         */
-    }
-    else
-    {
-        /* IP address parse succeeded */
-        if (flags & GETADDR_RANDOMIZE)
-        {
-            msg(M_WARN, "WARNING: ignoring --remote-random-hostname because the "
-                        "hostname is an IP address");
-        }
-    }
-
-done:
-    if (sig_info && sig_info->signal_received)
-    {
-        msglvl_t level = 0;
-        if (flags & GETADDR_FATAL_ON_SIGNAL)
-        {
-            level = M_FATAL;
-        }
-        else if (flags & GETADDR_WARN_ON_SIGNAL)
-        {
-            level = M_WARN;
-        }
-        msg(level, "RESOLVE: signal received during DNS resolution attempt");
-    }
-
-    gc_free(&gc);
-    return status;
-}
-
 /*
  * We do our own inet_aton because the glibc function
  * isn't very good about error checking.
diff --git a/src/openvpn/socket_util.h b/src/openvpn/socket_util.h
index 13f5962..be934fab 100644
--- a/src/openvpn/socket_util.h
+++ b/src/openvpn/socket_util.h
@@ -110,43 +110,6 @@ 
 void setenv_link_socket_actual(struct env_set *es, const char *name_prefix,
                                const struct link_socket_actual *act, const unsigned int flags);
 
-/*
- * DNS resolution
- */
-
-#define GETADDR_RESOLVE                 (1u << 0)
-#define GETADDR_FATAL                   (1u << 1)
-#define GETADDR_HOST_ORDER              (1u << 2)
-#define GETADDR_MENTION_RESOLVE_RETRY   (1u << 3)
-#define GETADDR_FATAL_ON_SIGNAL         (1u << 4)
-#define GETADDR_WARN_ON_SIGNAL          (1u << 5)
-#define GETADDR_MSG_VIRT_OUT            (1u << 6)
-#define GETADDR_TRY_ONCE                (1u << 7)
-#define GETADDR_UPDATE_MANAGEMENT_STATE (1u << 8)
-#define GETADDR_RANDOMIZE               (1u << 9)
-#define GETADDR_PASSIVE                 (1u << 10)
-#define GETADDR_DATAGRAM                (1u << 11)
-
-#define GETADDR_CACHE_MASK (GETADDR_DATAGRAM | GETADDR_PASSIVE)
-
-/**
- * Translate an IPv4 addr or hostname from string form to in_addr_t
- *
- * In case of resolve error, it will try again for
- * resolve_retry_seconds seconds.
- */
-in_addr_t getaddr(unsigned int flags, const char *hostname, int resolve_retry_seconds,
-                  bool *succeeded, struct signal_info *sig_info);
-
-/**
- * Translate an IPv6 addr or hostname from string form to in6_addr
- */
-bool get_ipv6_addr(const char *hostname, struct in6_addr *network, unsigned int *netbits,
-                   msglvl_t msglevel);
-
-int openvpn_getaddrinfo(unsigned int flags, const char *hostname, const char *servname,
-                        int resolve_retry_seconds, struct signal_info *sig_info, int ai_family,
-                        struct addrinfo **res);
 
 /* return values of openvpn_inet_aton */
 #define OIA_HOSTNAME 0
diff --git a/src/openvpn/tun.c b/src/openvpn/tun.c
index f2a4394..d8cedda 100644
--- a/src/openvpn/tun.c
+++ b/src/openvpn/tun.c
@@ -39,7 +39,7 @@ 
 #include "fdmisc.h"
 #include "common.h"
 #include "run_command.h"
-#include "socket_util.h"
+#include "resolve.h"
 #include "manage.h"
 #include "route.h"
 #include "win32.h"