@@ -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
@@ -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 \
@@ -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)
{
@@ -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);
@@ -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)
@@ -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"
@@ -55,6 +55,7 @@
#include "tun_afunix.h"
#include "schedule.h"
#include "options_string.h"
+#include "resolve.h"
#include "memdbg.h"
@@ -44,6 +44,7 @@
#include "dco.h"
#include "push.h"
#include "multi.h"
+#include "resolve.h"
#include "memdbg.h"
@@ -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
}
/*
@@ -723,8 +723,6 @@
unsigned int imported_protocol_flags;
};
-#define streq(x, y) (!strcmp((x), (y)))
-
/*
* Option classes.
*/
@@ -31,6 +31,7 @@
#include "error.h"
#include "socket_util.h"
#include "otime.h"
+#include "resolve.h"
#include "memdbg.h"
@@ -32,6 +32,7 @@
#include "socket.h"
#include "fdmisc.h"
#include "crypto.h"
+#include "resolve.h"
#include "ps.h"
#include "memdbg.h"
@@ -37,6 +37,7 @@
#include "memdbg.h"
#include "ssl_util.h"
#include "options_util.h"
+#include "resolve.h"
/*
* Auth username/password
new file mode 100644
@@ -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");
+}
new file mode 100644
@@ -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 */
@@ -42,6 +42,7 @@
#include "options.h"
#include "networking.h"
#include "integer.h"
+#include "resolve.h"
#include "memdbg.h"
@@ -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)
{
@@ -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
@@ -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.
@@ -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
@@ -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"