diff --git a/configure.ac b/configure.ac
index f8279924..67251bed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -477,7 +477,7 @@ SOCKET_INCLUDES="
"
AC_CHECK_HEADERS(
- [net/if.h netinet/ip.h resolv.h sys/un.h net/if_utun.h sys/kern_control.h],
+ [net/if.h netinet/ip.h arpa/nameser.h resolv.h sys/un.h net/if_utun.h sys/kern_control.h],
,
,
[[${SOCKET_INCLUDES}]]
diff --git a/doc/man-sections/client-options.rst b/doc/man-sections/client-options.rst
index ec1e3b11..53e6580e 100644
--- a/doc/man-sections/client-options.rst
+++ b/doc/man-sections/client-options.rst
@@ -299,6 +299,32 @@ configuration.
specification (4/6 suffix), OpenVPN will try both IPv4 and IPv6
addresses, in the order getaddrinfo() returns them.
+--remote-discovery
+ Perform rfc2782 remote host discovery using specified ``host`` and
+ ``proto`` values.
+ OpenVPN will try to resolve `` _openvpn._proto.host`` name via DNS
+ and use returned DNS SRV target and port records as OpenVPN servers
+ addresses in the order specified by Priority and Weight of that
+ records. If one record resolves to multiple IP addresses, OpenVPN
+ will try them in the order that the system getaddrinfo() presents
+ them. If discovery is failed, usual ``host`` and ``port`` connection
+ will be tried as a fallback.
+
+ Example:
+ ::
+
+ remote example.net 1194 udp
+ remote example.net 443 tcp
+ remote-discovery
+
+ DNS zone:
+ ::
+
+ _openvpn._udp.example.net IN SRV 10 60 1194 server1.example.net
+ _openvpn._udp.example.net IN SRV 10 40 1194 server2.example.net
+ _openvpn._udp.example.net IN SRV 20 0 1194 server3.example.net
+ _openvpn._tcp.example.net IN SRV 10 0 443 server4.example.net
+
--remote-random
When multiple ``--remote`` address/ports are specified, or if connection
profiles are being used, initially randomize the order of the list as a
diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am
index 37b002c6..f7632047 100644
--- a/src/openvpn/Makefile.am
+++ b/src/openvpn/Makefile.am
@@ -143,5 +143,5 @@ openvpn_LDADD = \
$(OPTIONAL_INOTIFY_LIBS)
if WIN32
openvpn_SOURCES += openvpn_win32_resources.rc block_dns.c block_dns.h ring_buffer.h
-openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt -lsetupapi
+openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt -lsetupapi -ldnsapi
endif
diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h
index 1722ffd5..ae0cf0eb 100644
--- a/src/openvpn/buffer.h
+++ b/src/openvpn/buffer.h
@@ -198,11 +198,6 @@ bool buf_init_debug(struct buffer *buf, int offset, const char *file, int line);
/* inline functions */
-inline static void
-gc_freeaddrinfo_callback(void *addr)
-{
- freeaddrinfo((struct addrinfo *) addr);
-}
/** Return an empty struct buffer */
static inline struct buffer
diff --git a/src/openvpn/init.c b/src/openvpn/init.c
index a785934a..043f4475 100644
--- a/src/openvpn/init.c
+++ b/src/openvpn/init.c
@@ -365,6 +365,7 @@ management_callback_remote_cmd(void *arg, const char **p)
ce->remote = rhs->host;
ce->remote_port = rhs->port;
+ ce->remote_discovery = false;
flags = CE_MAN_QUERY_REMOTE_MOD;
ret = true;
}
@@ -458,7 +459,7 @@ clear_remote_addrlist(struct link_socket_addr *lsa, bool free)
{
if (lsa->remote_list && free)
{
- freeaddrinfo(lsa->remote_list);
+ openvpn_freeaddrinfo(lsa->remote_list);
}
lsa->remote_list = NULL;
lsa->current_remote = NULL;
@@ -3411,6 +3412,10 @@ do_init_socket_1(struct context *c, const int mode)
sockflags |= SF_PORT_SHARE;
}
#endif
+ if (c->options.ce.remote_discovery)
+ {
+ sockflags |= SF_HOST_DISCOVERY;
+ }
link_socket_init_phase1(c->c2.link_socket,
c->options.ce.local,
@@ -3652,7 +3657,7 @@ do_close_link_socket(struct context *c)
{
if (c->c1.link_socket_addr.bind_local && !c->options.resolve_in_advance)
{
- freeaddrinfo(c->c1.link_socket_addr.bind_local);
+ openvpn_freeaddrinfo(c->c1.link_socket_addr.bind_local);
}
c->c1.link_socket_addr.bind_local = NULL;
diff --git a/src/openvpn/manage.c b/src/openvpn/manage.c
index 898cb3b3..f0d1e76e 100644
--- a/src/openvpn/manage.c
+++ b/src/openvpn/manage.c
@@ -2556,7 +2556,7 @@ man_settings_close(struct man_settings *ms)
{
if (ms->local)
{
- freeaddrinfo(ms->local);
+ openvpn_freeaddrinfo(ms->local);
}
free(ms->write_peer_info_file);
CLEAR(*ms);
diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj
index 5367979d..996c0160 100644
--- a/src/openvpn/openvpn.vcxproj
+++ b/src/openvpn/openvpn.vcxproj
@@ -92,7 +92,7 @@
- legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;%(AdditionalDependencies)
+ legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;dnsapi.lib;%(AdditionalDependencies)
$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)
Console
@@ -107,7 +107,7 @@
- legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies)
+ legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies)
$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)
Console
@@ -122,7 +122,7 @@
- legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;%(AdditionalDependencies)
+ legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;dnsapi.lib;%(AdditionalDependencies)
$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)
Console
@@ -137,7 +137,7 @@
- legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies)
+ legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies)
$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)
Console
diff --git a/src/openvpn/options.c b/src/openvpn/options.c
index 8bf82c57..79e2a61f 100644
--- a/src/openvpn/options.c
+++ b/src/openvpn/options.c
@@ -123,6 +123,7 @@ static const char usage_message[] =
"--remote host [port] : Remote host name or ip address.\n"
"--remote-random : If multiple --remote options specified, choose one randomly.\n"
"--remote-random-hostname : Add a random string to remote DNS name.\n"
+ "--remote-discovery : Perform rfc2782 remote host discovery.\n"
"--mode m : Major mode, m = 'p2p' (default, point-to-point) or 'server'.\n"
"--proto p : Use protocol p for communicating with peer.\n"
" p = udp (default), tcp-server, or tcp-client\n"
@@ -1446,6 +1447,7 @@ show_connection_entry(const struct connection_entry *o)
SHOW_STR(local_port);
SHOW_STR(remote);
SHOW_STR(remote_port);
+ SHOW_BOOL(remote_discovery);
SHOW_BOOL(remote_float);
SHOW_BOOL(bind_defined);
SHOW_BOOL(bind_local);
@@ -6672,6 +6674,11 @@ add_option(struct options *options,
VERIFY_PERMISSION(OPT_P_GENERAL);
options->sockflags |= SF_HOST_RANDOMIZE;
}
+ else if (streq(p[0], "remote-discovery") && !p[2])
+ {
+ VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION);
+ options->ce.remote_discovery = true;
+ }
else if (streq(p[0], "setenv") && p[1] && !p[3])
{
VERIFY_PERMISSION(OPT_P_GENERAL);
@@ -6679,6 +6686,10 @@ add_option(struct options *options,
{
options->sockflags |= SF_HOST_RANDOMIZE;
}
+ if (streq(p[1], "REMOTE_DISCOVERY"))
+ {
+ options->ce.remote_discovery = true;
+ }
else if (streq(p[1], "GENERIC_CONFIG"))
{
msg(msglevel, "this is a generic configuration and cannot directly be used");
diff --git a/src/openvpn/options.h b/src/openvpn/options.h
index 877e9396..26f8e791 100644
--- a/src/openvpn/options.h
+++ b/src/openvpn/options.h
@@ -92,6 +92,7 @@ struct connection_entry
const char *remote_port;
const char *local;
const char *remote;
+ bool remote_discovery;
bool remote_float;
bool bind_defined;
bool bind_ipv6_only;
diff --git a/src/openvpn/ps.c b/src/openvpn/ps.c
index 2089e6b9..40343fe0 100644
--- a/src/openvpn/ps.c
+++ b/src/openvpn/ps.c
@@ -841,7 +841,7 @@ port_share_open(const char *host,
host, port, 0, NULL, AF_INET, &ai);
ASSERT(status==0);
hostaddr = *((struct sockaddr_in *) ai->ai_addr);
- freeaddrinfo(ai);
+ openvpn_freeaddrinfo(ai);
/*
* Make a socket for foreground and background processes
diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c
index c486327b..8e9bf737 100644
--- a/src/openvpn/socket.c
+++ b/src/openvpn/socket.c
@@ -62,14 +62,17 @@ static unsigned int
sf2gaf(const unsigned int getaddr_flags,
const unsigned int sockflags)
{
+ unsigned int flags = getaddr_flags;
+
if (sockflags & SF_HOST_RANDOMIZE)
{
- return getaddr_flags | GETADDR_RANDOMIZE;
+ flags |= GETADDR_RANDOMIZE;
}
- else
+ if (sockflags & SF_HOST_DISCOVERY)
{
- return getaddr_flags;
+ flags |= GETADDR_DISCOVERY;
}
+ return flags;
}
/*
@@ -183,7 +186,7 @@ get_addr_generic(sa_family_t af, unsigned int flags, const char *hostname,
*sep = '/';
}
out:
- freeaddrinfo(ai);
+ openvpn_freeaddrinfo(ai);
free(var_host);
return ret;
@@ -366,6 +369,11 @@ do_preresolve(struct context *c)
flags |= GETADDR_RANDOMIZE;
}
+ if (ce->remote_discovery)
+ {
+ flags |= GETADDR_DISCOVERY;
+ }
+
if (c->options.ip_remote_hint)
{
remote = c->options.ip_remote_hint;
@@ -416,7 +424,7 @@ do_preresolve(struct context *c)
if (ce->bind_local)
{
flags |= GETADDR_PASSIVE;
- flags &= ~GETADDR_RANDOMIZE;
+ flags &= ~(GETADDR_RANDOMIZE|GETADDR_DISCOVERY);
status = do_preresolve_host(c, ce->local, ce->local_port, ce->af, flags);
if (status != 0)
{
@@ -432,6 +440,363 @@ err:
throw_signal_soft(SIGHUP, "Preresolving failed");
}
+/* Struct to hold resolved srv records */
+struct srvinfo
+{
+ const char *hostname;
+ const char *servname;
+ unsigned short prio;
+ unsigned short weight;
+ int order;
+ struct srvinfo *next;
+};
+
+static int
+rfc2782_cmp(const void *a, const void *b)
+{
+ const struct srvinfo *ae = *(struct srvinfo **)a;
+ const struct srvinfo *be = *(struct srvinfo **)b;
+
+ /* lowest-numbered priority first */
+ if (ae->prio != be->prio)
+ {
+ return ae->prio < be->prio ? -1 : 1;
+ }
+
+ /* zero-weighted first */
+ if ((ae->weight == 0 && be->weight)
+ || (ae->weight && be->weight == 0))
+ {
+ return ae->weight < be->weight ? -1 : 1;
+ }
+
+ /* keep received order */
+ return ae->order < be->order ? -1 : 1;
+}
+
+static struct srvinfo *
+rfc2782_sort(struct srvinfo *list, int count)
+{
+ struct srvinfo head, *tail, **sorted;
+ struct gc_arena gc = gc_new();
+
+ ASSERT(list);
+ ASSERT(count);
+
+ head.next = NULL;
+ tail = &head;
+
+ /* sort records by priority and zero weight */
+ ALLOC_ARRAY_CLEAR_GC(sorted, struct srvinfo *, count, &gc);
+ for (struct srvinfo *e = list; e; e = e->next)
+ {
+ ASSERT(e->order < count);
+ sorted[e->order] = e;
+ }
+ qsort(sorted, count, sizeof(sorted[0]), rfc2782_cmp);
+
+ /* apply weighted selection mechanism */
+ for (int i = 0; i < count;)
+ {
+ struct srvinfo unordered;
+
+ /* compute the sum of the weights of records of the same
+ * priority and put them in the unordered list */
+ unordered.prio = sorted[i]->prio;
+ unordered.weight = 0;
+ unordered.next = NULL;
+ for (struct srvinfo *prev = &unordered; i < count && sorted[i]->prio == unordered.prio; i++)
+ {
+ struct srvinfo *e = sorted[i];
+
+ unordered.weight += e->weight;
+
+ /* add entry to the tail of unordered list */
+ e->next = NULL;
+ prev->next = e, prev = e;
+ }
+
+ /* process the unordered list */
+ while (unordered.next)
+ {
+ /* choose a uniform random number between 0 and the sum
+ * computed (inclusive) */
+ int weight = unordered.weight ? get_random() % unordered.weight : 0;
+
+ /* select the entries whose running sum value is the first
+ * in the selected order which is greater than or equal
+ * to the random number selected */
+ for (struct srvinfo *e = unordered.next, *prev = &unordered; e; prev = e, e = e->next)
+ {
+ /* selected entry is the next one to be contacted */
+ if (e->weight >= weight)
+ {
+ unordered.weight -= e->weight;
+
+ /* move entry to the ordered list */
+ prev->next = e->next;
+ e->next = NULL;
+ tail->next = e, tail = e;
+
+ /* in the presence of records containing weights greater
+ * than 0, records with weight 0 should have a very small
+ * chance of being selected, so skip them all after the
+ * last weighted */
+ if (unordered.weight == 0 && e->weight)
+ {
+ unordered.next = NULL;
+ }
+ break;
+ }
+ weight -= e->weight;
+ }
+ }
+ }
+
+ gc_free(&gc);
+ return head.next;
+}
+
+/*
+ * Translated hostname, service and protocol into struct srvinfo.
+ * Multiple records are ordered per rfc2782.
+ */
+static int
+openvpn_getsrvinfo(unsigned int flags,
+ const char *hostname,
+ const char *servname,
+ struct srvinfo **res,
+ struct gc_arena *gc)
+{
+ struct srvinfo *list = NULL;
+#ifdef _WIN32
+ char qname[DNS_MAX_NAME_BUFFER_LENGTH];
+#else
+ char qname[NS_MAXDNAME];
+#endif
+ int status;
+ int count = 0;
+
+ ASSERT(res);
+
+ ASSERT(hostname && servname);
+ ASSERT(!(flags & GETADDR_HOST_ORDER));
+
+ if (!openvpn_snprintf(qname, sizeof(qname), "_%s._%s.%s", servname,
+ (flags & GETADDR_DATAGRAM) ? "udp" : "tcp", hostname))
+ {
+ return EAI_MEMORY;
+ }
+
+#ifdef _WIN32
+ PDNS_RECORD pDnsRecord;
+ DNS_STATUS DnsStatus = DnsQuery(qname, DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &pDnsRecord, NULL);
+ dmsg(D_SOCKET_DEBUG, "DNSQUERY type=%d dname=%s result=%d",
+ DNS_TYPE_SRV, qname, DnsStatus);
+ switch (DnsStatus)
+ {
+ case ERROR_SUCCESS:
+ break;
+
+ case DNS_ERROR_RCODE_NAME_ERROR:
+ return EAI_NONAME; /* HOST_NOT_FOUND */
+
+ case DNS_INFO_NO_RECORDS:
+ return EAI_NODATA; /* NO_DATA */
+
+ case DNS_ERROR_NO_DNS_SERVERS:
+ case DNS_ERROR_RCODE_FORMAT_ERROR:
+ case DNS_ERROR_RCODE_NOT_IMPLEMENTED:
+ case DNS_ERROR_RCODE_REFUSED:
+ return EAI_FAIL; /* NO_RECOVERY */
+
+ case ERROR_TIMEOUT:
+ case DNS_ERROR_RCODE_SERVER_FAILURE:
+ case DNS_ERROR_TRY_AGAIN_LATER:
+ return EAI_AGAIN; /* TRY_AGAIN */
+
+ default:
+ return EAI_NODATA;
+ }
+
+ for (PDNS_RECORD rr = pDnsRecord; rr; rr = rr->pNext)
+ {
+ if (rr->wType == DNS_TYPE_SRV)
+ {
+ PDNS_SRV_DATA rdata = &rr->Data.Srv;
+
+ if (rr->wDataLength >= sizeof(DNS_SRV_DATA) && *rdata->pNameTarget)
+ {
+ char port[sizeof("65535")];
+ openvpn_snprintf(port, sizeof(port), "%u", rdata->wPort);
+
+ struct srvinfo *e;
+ ALLOC_OBJ_CLEAR_GC(e, struct srvinfo, gc);
+ e->hostname = string_alloc(rdata->pNameTarget, gc);
+ e->servname = string_alloc(port, gc);
+ e->prio = rdata->wPriority;
+ e->weight = rdata->wWeight;
+ e->order = count++;
+ e->next = list, list = e;
+ }
+ }
+ }
+ DnsRecordListFree(pDnsRecord, DnsFreeParsedMessageFields);
+#else
+ unsigned char answer[NS_MAXMSG];
+ int n = res_query(qname, ns_c_in, ns_t_srv, answer, NS_MAXMSG);
+ dmsg(D_SOCKET_DEBUG, "RES_QUERY class=%d type=%d dname=%s result=%d",
+ ns_c_in, ns_t_srv, qname, n);
+ if (n < 0)
+ {
+ switch (h_errno)
+ {
+ case HOST_NOT_FOUND:
+ return EAI_NONAME;
+
+ case NO_ADDRESS:
+#if NO_ADDRESS != NO_DATA
+ case NO_DATA:
+#endif
+ return EAI_NODATA;
+
+ case NO_RECOVERY:
+ return EAI_FAIL;
+
+ case TRY_AGAIN:
+ return EAI_AGAIN;
+ }
+ return EAI_SYSTEM;
+ }
+
+ ns_msg msg;
+ if (ns_initparse(answer, n, &msg) < 0)
+ {
+ return EAI_FAIL;
+ }
+
+ for (int i = 0; i < ns_msg_count(msg, ns_s_an); i++)
+ {
+ ns_rr rr;
+
+ if (ns_parserr(&msg, ns_s_an, i, &rr) == 0 && ns_rr_type(rr) == ns_t_srv)
+ {
+ const unsigned char *rdata = ns_rr_rdata(rr);
+
+ if (ns_rr_rdlen(rr) > 6
+ && dn_expand(ns_msg_base(msg), ns_msg_end(msg),
+ rdata + 6, qname, sizeof(qname)) > 0 && *qname)
+ {
+ char port[sizeof("65535")];
+ openvpn_snprintf(port, sizeof(port), "%u", ns_get16(rdata + 4));
+
+ struct srvinfo *e;
+ ALLOC_OBJ_CLEAR_GC(e, struct srvinfo, gc);
+ e->hostname = string_alloc(qname, gc);
+ e->servname = string_alloc(port, gc);
+ e->prio = ns_get16(rdata);
+ e->weight = ns_get16(rdata + 2);
+ e->order = count++;
+ e->next = list, list = e;
+ }
+ }
+ }
+#endif
+
+ if (list)
+ {
+ *res = rfc2782_sort(list, count);
+ status = *res ? 0 : EAI_NODATA;
+ }
+ else
+ {
+ status = EAI_NODATA;
+ }
+ return status;
+}
+
+/*
+ * Struct to hold chainable copy of struct addinfo.
+ * Must be binary compatible (except size) with original.
+ */
+struct addrinfo_chained
+{
+ struct addrinfo ai;
+ struct openvpn_sockaddr addr;
+ char canonname[0];
+};
+
+/*
+ * System getaddrinfo() returns one or more addrinfo structures.
+ * openvpn_getaddinfo() chains them into one in case DNS SRV
+ * resolve is performed, but unfortunately it can't be safely done
+ * (freed) with musl libc, refer
+ * https://git.musl-libc.org/cgit/musl/commit/?id=d1395c43c019aec6b855cf3c656bf47c8a719e7f
+ * Use own copy of each addrinfo to workaround this and possibly
+ * other getaddrinfo()/freeaddrinfo() implementations.
+ */
+int
+getaddrinfo_chained(const char *node, const char *service,
+ const struct addrinfo *hints,
+ struct addrinfo **res)
+{
+ int status = getaddrinfo(node, service, hints, res);
+ if (status == 0 && res && *res)
+ {
+ struct addrinfo_chained head, *tail = &head;
+
+ head.ai.ai_next = NULL;
+ for (struct addrinfo *ai = *res; ai; ai = ai->ai_next)
+ {
+ socklen_t addrlen = ai->ai_addr ? ai->ai_addrlen : 0;
+ size_t namelen = ai->ai_canonname ? strlen(ai->ai_canonname) + 1 : 0;
+
+ /* allocate self-contained struct with enough space for optional data */
+ struct addrinfo_chained *ac = calloc(1, sizeof(*ac) + addrlen + namelen);
+ if (!ac)
+ {
+ openvpn_freeaddrinfo(head.ai.ai_next);
+ head.ai.ai_next = NULL;
+ status = EAI_MEMORY;
+ break;
+ }
+
+ /* deep clone the structure and its members */
+ memcpy(&ac->ai, ai, sizeof(ac->ai));
+ if (ai->ai_addr)
+ {
+ memcpy(&ac->addr.addr.sa, ai->ai_addr, addrlen);
+ ac->ai.ai_addr = &ac->addr.addr.sa;
+ }
+ if (ai->ai_canonname)
+ {
+ memcpy(ac->canonname, ai->ai_canonname, namelen);
+ ac->ai.ai_canonname = ac->canonname;
+ }
+
+ /* add clone to the tail */
+ ac->ai.ai_next = NULL;
+ tail->ai.ai_next = &ac->ai, tail = ac;
+ }
+
+ /* original list is cloned, can be freed now */
+ freeaddrinfo(*res);
+ *res = head.ai.ai_next;
+ }
+ return status;
+}
+
+void
+openvpn_freeaddrinfo(struct addrinfo *res)
+{
+ while (res)
+ {
+ struct addrinfo_chained *ac = (struct addrinfo_chained *)res;
+ res = res->ai_next;
+ free(ac);
+ }
+}
+
/*
* Translate IPv4/IPv6 addr or hostname into struct addrinfo
* If resolve error, try again for resolve_retry_seconds seconds.
@@ -497,7 +862,7 @@ openvpn_getaddrinfo(unsigned int flags,
hints.ai_socktype = SOCK_STREAM;
}
- status = getaddrinfo(hostname, servname, &hints, res);
+ status = getaddrinfo_chained(hostname, servname, &hints, res);
if (status != 0) /* parse as numeric address failed? */
{
@@ -555,16 +920,17 @@ openvpn_getaddrinfo(unsigned int flags,
/*
* Resolve hostname
*/
- while (true)
- {
+ struct srvinfo *targets = NULL;
#ifndef _WIN32
- res_init();
+ 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);
+
+ /* try srv lookup */
+ while (flags & GETADDR_DISCOVERY)
+ {
+ dmsg(D_SOCKET_DEBUG, "GETSRVINFO flags=0x%04x hostname=%s",
+ flags, np(hostname));
+ status = openvpn_getsrvinfo(flags, hostname, OPENVPN_SERVICE, &targets, &gc);
if (signal_received)
{
@@ -581,9 +947,6 @@ openvpn_getaddrinfo(unsigned int flags,
/* turn success into failure (interrupted syscall) */
if (0 == status)
{
- ASSERT(res);
- freeaddrinfo(*res);
- *res = NULL;
status = EAI_AGAIN; /* = temporary failure */
errno = EINTR;
}
@@ -592,8 +955,8 @@ openvpn_getaddrinfo(unsigned int flags,
}
}
- /* success? */
- if (0 == status)
+ /* success and fails are expected, fallback to usual resolve */
+ if (EAI_AGAIN != status)
{
break;
}
@@ -614,10 +977,127 @@ openvpn_getaddrinfo(unsigned int flags,
if (--resolve_retries <= 0)
{
- goto done;
+ /* can't retry, fallback to usual resolve */
+ break;
}
management_sleep(fail_wait_interval);
+#ifndef _WIN32
+ res_init();
+#endif
+ }
+
+ /* no targets due not discovering or discovery fail,
+ * use specified hostname/sername as target instead */
+ if (!targets)
+ {
+ ALLOC_OBJ_CLEAR_GC(targets, struct srvinfo, &gc);
+ targets->hostname = hostname;
+ targets->servname = servname;
+ }
+
+ /* try to resolve all collected targets */
+ struct addrinfo **res_tail = res;
+ while (targets)
+ {
+ /* 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);
+ level = 0;
+
+ if (targets->hostname)
+ {
+ print_hostname = targets->hostname;
+ }
+ else
+ {
+ print_hostname = "undefined";
+ }
+
+ /* try hostname lookup */
+ while (true)
+ {
+ hints.ai_flags &= ~AI_NUMERICHOST;
+ dmsg(D_SOCKET_DEBUG, "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d hostname=%s:%s",
+ flags, hints.ai_family, hints.ai_socktype,
+ np(targets->hostname), np(targets->servname));
+ status = getaddrinfo_chained(targets->hostname, targets->servname, &hints, res_tail);
+
+ if (signal_received)
+ {
+ get_signal(signal_received);
+ if (*signal_received) /* were we interrupted by a signal? */
+ {
+ if (*signal_received == SIGUSR1) /* ignore SIGUSR1 */
+ {
+ msg(level, "RESOLVE: Ignored SIGUSR1 signal received during DNS resolution attempt");
+ *signal_received = 0;
+ }
+ else
+ {
+ /* turn success into failure (interrupted syscall) */
+ if (0 == status)
+ {
+ ASSERT(res);
+ openvpn_freeaddrinfo(*res);
+ *res = NULL;
+ status = EAI_AGAIN; /* = temporary failure */
+ errno = EINTR;
+ }
+ goto done;
+ }
+ }
+ }
+
+ /* success? */
+ if (0 == status)
+ {
+ /* handle next target if possible */
+ targets = targets->next;
+
+ /* add subsequent results to the tail */
+ while (*res_tail)
+ {
+ res_tail = &(*res_tail)->ai_next;
+ }
+ 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,
+ gai_strerror(status));
+
+ if (--resolve_retries <= 0)
+ {
+ /* can't retry, handle next target if possible */
+ targets = targets->next;
+
+ /* error makes no sense for the next target
+ * or reset error if there're accumulated results */
+ if (targets || res_tail != res)
+ {
+ status = 0;
+ break;
+ }
+
+ goto done;
+ }
+
+ management_sleep(fail_wait_interval);
+#ifndef _WIN32
+ res_init();
+#endif
+ }
}
ASSERT(res);
@@ -636,6 +1116,10 @@ openvpn_getaddrinfo(unsigned int flags,
{
msg(M_WARN, "WARNING: ignoring --remote-random-hostname because the hostname is an IP address");
}
+ if (flags & GETADDR_DISCOVERY)
+ {
+ msg(M_WARN, "WARNING: ignoring --remote-discovery because the hostname is an IP address");
+ }
}
done:
@@ -1350,13 +1834,13 @@ socket_listen_accept(socket_descriptor_t sd,
{
msg(M_ERR, "TCP: close socket failed (new_sd)");
}
- freeaddrinfo(ai);
+ openvpn_freeaddrinfo(ai);
}
else
{
if (ai)
{
- freeaddrinfo(ai);
+ openvpn_freeaddrinfo(ai);
}
break;
}
@@ -2232,7 +2716,7 @@ phase2_socks_client(struct link_socket *sock, struct signal_info *sig_info)
addr_zero_host(&sock->info.lsa->actual.dest);
if (sock->info.lsa->remote_list)
{
- freeaddrinfo(sock->info.lsa->remote_list);
+ openvpn_freeaddrinfo(sock->info.lsa->remote_list);
sock->info.lsa->current_remote = NULL;
sock->info.lsa->remote_list = NULL;
}
diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h
index 7aeae527..7e70c481 100644
--- a/src/openvpn/socket.h
+++ b/src/openvpn/socket.h
@@ -40,6 +40,11 @@
*/
#define OPENVPN_PORT "1194"
+/*
+ * OpenVPN's default service name for rfc2782 discovery.
+ */
+#define OPENVPN_SERVICE "openvpn"
+
/*
* Number of seconds that "resolv-retry infinite"
* represents.
@@ -210,6 +215,7 @@ struct link_socket
#define SF_PORT_SHARE (1<<2)
#define SF_HOST_RANDOMIZE (1<<3)
#define SF_GETADDRINFO_DGRAM (1<<4)
+#define SF_HOST_DISCOVERY (1<<5)
unsigned int sockflags;
int mark;
const char *bind_dev;
@@ -532,6 +538,7 @@ bool unix_socket_get_peer_uid_gid(const socket_descriptor_t sd, int *uid, int *g
#define GETADDR_RANDOMIZE (1<<9)
#define GETADDR_PASSIVE (1<<10)
#define GETADDR_DATAGRAM (1<<11)
+#define GETADDR_DISCOVERY (1<<12)
#define GETADDR_CACHE_MASK (GETADDR_DATAGRAM|GETADDR_PASSIVE)
@@ -561,6 +568,14 @@ int openvpn_getaddrinfo(unsigned int flags,
int ai_family,
struct addrinfo **res);
+void openvpn_freeaddrinfo(struct addrinfo *res);
+
+inline static void
+gc_freeaddrinfo_callback(void *addr)
+{
+ openvpn_freeaddrinfo((struct addrinfo *) addr);
+}
+
/*
* Transport protocol naming and other details.
*/
diff --git a/src/openvpn/syshead.h b/src/openvpn/syshead.h
index 8342eae0..e5852753 100644
--- a/src/openvpn/syshead.h
+++ b/src/openvpn/syshead.h
@@ -38,6 +38,7 @@
#ifdef _WIN32
#include
+#include
#include
#include
#define sleep(x) Sleep((x)*1000)
@@ -176,6 +177,10 @@
#include
#endif
+#ifdef HAVE_ARPA_NAMESER_H
+#include
+#endif
+
#ifdef HAVE_RESOLV_H
#include
#endif