@@ -468,7 +468,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}]]
@@ -462,10 +462,121 @@ configuration.
specification (4/6 suffix), OpenVPN will try both IPv4 and IPv6
addresses, in the order getaddrinfo() returns them.
+--remote-srv args
+ Remote DNS SRV domain, service name and protocol.
+
+ Valid syntaxes:
+ ::
+
+ remote-srv domain
+ remote-srv domain service
+ remote-srv domain service proto
+
+ The ``service`` and ``proto`` arguments are optional. Client will try
+ to resolve DNS SRV record ``_service._proto.domain`` and use returned
+ DNS SRV records as remote server list ordered by server selection
+ mechanism defined in `RFC 2782 <https://tools.ietf.org/html/rfc2782>`_:
+ ::
+
+ A client MUST attempt to contact the target host with the
+ lowest-numbered priority field value it can reach, target hosts
+ with the same priority SHOULD be tried in an order defined by the
+ weight field.
+ The weight field specifies a relative weight for entries with the
+ same priority. Larger weights SHOULD be given a proportionately
+ higher probability of being selected.
+ Domain administrators SHOULD use Weight 0 when there isn't any
+ server selection to do. In the presence of records containing
+ weights greater than 0, records with Weight 0 SHOULD have a very
+ small chance of being selected.
+
+ *Note:*
+ OpenVPN server selection mechanism implementation indeed will give
+ records with weight of zero a very small chance of being selected
+ first, but never skip them.
+
+ The ``service`` argument indicates the symbolic name of the desired
+ service. By default service is the registered service name
+ :code:`openvpn`.
+
+ The ``proto`` argument indicates the symbolic name of the desired
+ protocol and the protocol to use when connecting with the remote, and
+ may either be :code:`tcp`, :code:`udp` or special value :code:`auto`
+ used by default to try both. In this case all the discovered remote
+ hosts will be ordered by server selection mechanism regardless their
+ protocol. To enforce IPv4 or IPv6 connections add a :code:`4` or
+ :code:`6` suffix; like :code:`udp4` / :code:`udp6` / :code:`tcp4` /
+ :code:`tcp6` / :code:`auto4` / :code:`auto6`.
+
+ On the client, multiple ``--remote-srv`` options may be specified for
+ redundancy, each referring to a different DNS SRV record name, in the
+ order specified by the list of ``--remote-srv`` options. Specifying
+ multiple ``--remote-srv`` options for this purpose is a special case
+ of the more general connection-profile feature. See the
+ ``<connection>`` documentation below.
+
+ The client will move on to the next DNS SRV record in the ordered list,
+ in the event of connection failure. Note that at any given time, the
+ OpenVPN client will at most be connected to one server.
+
+ If particular DNS SRV record next resolves to multiple IP addresses,
+ OpenVPN will try them in the order that the system getaddrinfo()
+ presents them, so prioritization and DNS randomization is done by the
+ system library. Unless an IP version is forced by the protocol
+ specification (4/6 suffix), OpenVPN will try both IPv4 and IPv6
+ addresses, in the order getaddrinfo() returns them.
+
+ Examples:
+ ::
+
+ remote-srv example.net
+ remote-srv example.net openvpn
+ remote-srv example.net openvpn tcp
+
+ Example of DNS SRV records:
+ ::
+
+ name prio weight port target
+ $ORIGIN example.net.
+ _openvpn._udp IN SRV 10 60 1194 server1.example.net.
+ _openvpn._udp IN SRV 10 40 1194 server2.example.net.
+ _openvpn._udp IN SRV 10 0 1194 server3.example.net.
+ _openvpn._tcp IN SRV 20 0 443 server4.example.net.
+
+ For ``--remote-srv example.net`` following will happen in order:
+
+ 1. The client will first try to resolve ``_openvpn._udp.example.net``
+ and ``_openvpn._tcp.example.net``.
+ 2. Records ``server1.example.net:1194``, ``server2.example.net:1194``
+ and ``server3.example.net:1194`` will be selected before record
+ ``server4.example.net:443`` as their priority 10 is smaller than 20.
+ 3. Records ``server1.example.net:1194``, ``server2.example.net:1194``
+ and ``server3.example.net:1194`` will be randomly selected with
+ weight probability: first will be either ``server1.example.net:1194``
+ with 60% probability or ``server2.example.net:1194`` with 40% or
+ ``server3.example.net:1194`` with almost zero probability.
+ 4. If ``server1.example.net:1194`` was selected, the second record will
+ be either ``server2.example.net:1194`` with almost 100% probability
+ or ``server3.example.net:1194`` with almost 0%.
+ 5. If ``server2.example.net:1194`` was selected, the third record will
+ be the only last record of priority 10 - ``server3.example.net:1194``.
+ 6. Record ``server4.example.net:443`` will be the last one selected as
+ the only record with priority 20.
+ 7. Each of the resulting ```target:port`` remote hosts will be resolved
+ and accessed if its protocol has no conflict with the rest of the
+ OpenVPN options.
+
+ If a DNS SRV name cannot be resolved or no valid records are returned,
+ the client will move on to the next connection entry.
+
+ For more information on DNS SRV see https://tools.ietf.org/html/rfc2782
+
--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
- kind of basic load-balancing measure.
+ Randomises the order of ``--remote-srv``, ``remote-srv`` and connection
+ profiles on start. This randomisation of these entries is a kind of basic
+ load-balancing measure. Note that implementing DNS SRV entries (with a
+ short TTL if it should be dynamic) and using ``--remote-srv`` is a better
+ alternative for client-side load balancing.
--remote-random-hostname
Prepend a random string (6 bytes, 12 hex characters) to hostname to
@@ -473,8 +584,8 @@ configuration.
"<random-chars>.foo.bar.gov".
--resolv-retry n
- If hostname resolve fails for ``--remote``, retry resolve for ``n``
- seconds before failing.
+ If hostname resolve fails for ``--remote`` or ``--remote-srv``, retry
+ resolve for ``n`` seconds before failing.
Set ``n`` to :code:`infinite` to retry indefinitely.
@@ -807,6 +807,12 @@ the above notification, use this command:
remote MOD vpn.otherexample.com 1234
+DNS SRV discovery domain and service name with protocol code:`auto`
+can't be overriden, but either accepted or skipped only.
+Example of such notification:
+
+ >REMOTE:example.com,openvpn,auto
+
To accept the same host and port as the client would ordinarily
have connected to, use this command:
@@ -155,5 +155,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 -lbcrypt
+openvpn_LDADD += -lgdi32 -lws2_32 -lwininet -lcrypt32 -liphlpapi -lwinmm -lfwpuclnt -lrpcrt4 -lncrypt -lsetupapi -lbcrypt -ldnsapi
endif
@@ -198,11 +198,6 @@ bool buf_init_debug(struct buffer *buf, int offset, const char *file, int line);
/* inline functions */
-static inline void
-gc_freeaddrinfo_callback(void *addr)
-{
- freeaddrinfo((struct addrinfo *) addr);
-}
/** Return an empty struct buffer */
static inline struct buffer
@@ -90,6 +90,7 @@
#define D_ROUTE_QUOTA LOGLEV(3, 42, 0) /* show route quota exceeded messages */
#define D_OSBUF LOGLEV(3, 43, 0) /* show socket/tun/tap buffer sizes */
#define D_PS_PROXY LOGLEV(3, 44, 0) /* messages related to --port-share option */
+#define D_RESOLVE LOGLEV(3, 46, 0) /* show hostname resolve info */
#define D_IFCONFIG LOGLEV(3, 0, 0) /* show ifconfig info (don't mute) */
#define D_DCO LOGLEV(3, 0, 0) /* show DCO related messages */
@@ -350,7 +350,12 @@ management_callback_remote_cmd(void *arg, const char **p)
}
else if (!strcmp(p[1], "MOD") && p[2] && p[3])
{
- if (strlen(p[2]) < RH_HOST_LEN && strlen(p[3]) < RH_PORT_LEN)
+ if (ce->remote_srv && ce->proto == PROTO_AUTO)
+ {
+ /* can't mutate --remote-srv into --remote without protocol */
+ ret = false;
+ }
+ else if (strlen(p[2]) < RH_HOST_LEN && strlen(p[3]) < RH_PORT_LEN)
{
struct remote_host_store *rhs = c->options.rh_store;
if (!rhs)
@@ -363,6 +368,7 @@ management_callback_remote_cmd(void *arg, const char **p)
ce->remote = rhs->host;
ce->remote_port = rhs->port;
+ ce->remote_srv = false;
flags = CE_MAN_QUERY_REMOTE_MOD;
ret = true;
}
@@ -462,6 +468,23 @@ clear_remote_addrlist(struct link_socket_addr *lsa, bool free)
lsa->current_remote = NULL;
}
+/*
+ * Clear the remote service list
+ */
+static void
+clear_remote_servlist(struct link_socket_addr *lsa, bool free)
+{
+ if (lsa->service_list && free)
+ {
+ freeservinfo(lsa->service_list);
+ }
+ lsa->service_list = NULL;
+ lsa->current_service = NULL;
+
+ /* clear addrinfo objects as well */
+ clear_remote_addrlist(lsa, free);
+}
+
/*
* Increment to next connection entry
*/
@@ -491,6 +514,24 @@ next_connection_entry(struct context *c)
c->c1.link_socket_addr.current_remote =
c->c1.link_socket_addr.current_remote->ai_next;
}
+ /* Check if there is another resolved service to try for
+ * the current connection unless persist-remote-ip was
+ * requested and current service already has an address */
+ else if (c->c1.link_socket_addr.current_service
+ && c->c1.link_socket_addr.current_service->next
+ && !(c->options.persist_remote_ip
+ && c->c1.link_socket_addr.remote_list))
+ {
+ c->c1.link_socket_addr.current_service =
+ c->c1.link_socket_addr.current_service->next;
+
+ /* Clear addrinfo object of the previous service */
+ if (c->c1.link_socket_addr.remote_list)
+ {
+ clear_remote_addrlist(&c->c1.link_socket_addr,
+ !c->options.resolve_in_advance);
+ }
+ }
else
{
c->options.advance_next_remote = false;
@@ -500,20 +541,24 @@ next_connection_entry(struct context *c)
*/
if (!c->options.persist_remote_ip)
{
- /* Connection entry addrinfo objects might have been
+ /* Connection entry addr/servinfo objects might have been
* resolved earlier but the entry itself might have been
- * skipped by management on the previous loop.
- * If so, clear the addrinfo objects as close_instance does
+ * skipped on the previous loop either by management or
+ * due inappropriate service protocol.
+ * Clear the addr/servinfo objects as close_instance does.
*/
- if (c->c1.link_socket_addr.remote_list)
+ if (c->c1.link_socket_addr.remote_list
+ || c->c1.link_socket_addr.service_list)
{
- clear_remote_addrlist(&c->c1.link_socket_addr,
+ clear_remote_servlist(&c->c1.link_socket_addr,
!c->options.resolve_in_advance);
}
/* close_instance should have cleared the addrinfo objects */
ASSERT(c->c1.link_socket_addr.current_remote == NULL);
ASSERT(c->c1.link_socket_addr.remote_list == NULL);
+ ASSERT(c->c1.link_socket_addr.current_service == NULL);
+ ASSERT(c->c1.link_socket_addr.service_list == NULL);
}
else
{
@@ -549,6 +594,12 @@ next_connection_entry(struct context *c)
}
c->options.ce = *ce;
+ if (ce_defined && c->c1.link_socket_addr.current_service)
+ {
+ /* map in current service */
+ struct servinfo *si = c->c1.link_socket_addr.current_service;
+ ce_defined = options_mutate_ce_servinfo(&c->options, si);
+ }
#ifdef ENABLE_MANAGEMENT
if (ce_defined && management && management_query_remote_enabled(management))
{
@@ -3699,10 +3750,13 @@ do_close_link_socket(struct context *c)
( c->sig->source != SIG_SOURCE_HARD
&& ((c->c1.link_socket_addr.current_remote
&& c->c1.link_socket_addr.current_remote->ai_next)
+ || (c->c1.link_socket_addr.current_service
+ && c->c1.link_socket_addr.current_service->next)
|| c->options.no_advance))
)))
{
- clear_remote_addrlist(&c->c1.link_socket_addr, !c->options.resolve_in_advance);
+ clear_remote_servlist(&c->c1.link_socket_addr,
+ !c->options.resolve_in_advance);
}
/* Clear the remote actual address when persist_remote_ip is not in use */
@@ -4234,6 +4288,17 @@ init_instance(struct context *c, const struct env_set *env, const unsigned int f
/* map in current connection entry */
next_connection_entry(c);
+ /* map in current remote service */
+ if (c->options.ce.remote_srv)
+ {
+ do_resolve_service(c);
+ if (IS_SIG(c))
+ {
+ goto sig;
+ }
+ update_options_ce_post(&c->options);
+ }
+
/* link_socket_mode allows CM_CHILD_TCP
* instances to inherit acceptable fds
* from a top-level parent */
@@ -166,7 +166,7 @@
</ClCompile>
<ResourceCompile />
<Link>
- <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;Advapi32.lib</AdditionalDependencies>
+ <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;Advapi32.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
</Link>
@@ -183,7 +183,7 @@
</ClCompile>
<ResourceCompile />
<Link>
- <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;Advapi32.lib</AdditionalDependencies>
+ <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;Advapi32.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
</Link>
@@ -200,7 +200,7 @@
</ClCompile>
<ResourceCompile />
<Link>
- <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;Advapi32.lib</AdditionalDependencies>
+ <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;Advapi32.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
</Link>
@@ -218,7 +218,8 @@
</ClCompile>
<ResourceCompile />
<Link>
- <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;Advapi32.lib</AdditionalDependencies>
+ <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;Advapi32.lib</AdditionalDependencies>
+ <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)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
<CETCompat>true</CETCompat>
@@ -237,7 +238,7 @@
</ClCompile>
<ResourceCompile />
<Link>
- <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;Advapi32.lib</AdditionalDependencies>
+ <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;Advapi32.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
<CETCompat>true</CETCompat>
@@ -256,7 +257,7 @@
</ClCompile>
<ResourceCompile />
<Link>
- <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;Advapi32.lib</AdditionalDependencies>
+ <AdditionalDependencies>Ncrypt.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;Advapi32.lib</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<SubSystem>Console</SubSystem>
</Link>
@@ -465,4 +466,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
@@ -128,7 +128,9 @@ static const char usage_message[] =
"Tunnel Options:\n"
"--local host : Local host name or ip address. Implies --bind.\n"
"--remote host [port] : Remote host name or ip address.\n"
- "--remote-random : If multiple --remote options specified, choose one randomly.\n"
+ "--remote-srv domain [service] : Perform DNS SRV remote host discovery.\n"
+ "--remote-random : If multiple --remote or --remote-srv options specified,\n"
+ " choose one randomly.\n"
"--remote-random-hostname : Add a random string to remote DNS name.\n"
"--mode m : Major mode, m = 'p2p' (default, point-to-point) or 'server'.\n"
"--proto p : Use protocol p for communicating with peer.\n"
@@ -161,7 +163,7 @@ static const char usage_message[] =
" up is a file containing username/password on 2 lines, or\n"
" 'stdin' to prompt for console.\n"
"--socks-proxy-retry : Retry indefinitely on Socks proxy errors.\n"
- "--resolv-retry n: If hostname resolve fails for --remote, retry\n"
+ "--resolv-retry n: If hostname resolve fails for --remote or --remote-srv, retry\n"
" resolve for n seconds before failing (disabled by default).\n"
" Set n=\"infinite\" to retry indefinitely.\n"
"--float : Allow remote to change its IP address/port, such as through\n"
@@ -1695,6 +1697,7 @@ show_connection_entry(const struct connection_entry *o)
SHOW_STR(local_port);
SHOW_STR(remote);
SHOW_STR(remote_port);
+ SHOW_BOOL(remote_srv);
SHOW_BOOL(remote_float);
SHOW_BOOL(bind_defined);
SHOW_BOOL(bind_local);
@@ -2246,6 +2249,56 @@ connection_entry_load_re(struct connection_entry *ce, const struct remote_entry
{
ce->af = re->af;
}
+ ce->remote_srv = re->remote_srv;
+}
+
+/* Part of options_postprocess_verify_ce that can be used
+ * in runtime after connection entry proto/hostname change.
+ */
+static bool
+options_postprocess_verify_ce_proto(const struct options *options,
+ const struct connection_entry *ce)
+{
+ int msglevel = M_WARN|M_NOPREFIX|M_OPTERR;
+
+ /*
+ * Sanity check on --local, --remote, and --ifconfig
+ */
+
+ if (proto_is_net(ce->proto)
+ && string_defined_equal(ce->local, ce->remote)
+ && string_defined_equal(ce->local_port, ce->remote_port))
+ {
+ msg(msglevel, "--remote and --local addresses are the same");
+ return false;
+ }
+
+ if (string_defined_equal(ce->remote, options->ifconfig_local)
+ || string_defined_equal(ce->remote, options->ifconfig_remote_netmask))
+ {
+ msg(msglevel, "--local and --remote addresses must be distinct from --ifconfig addresses");
+ return false;
+ }
+
+ /*
+ * Check that protocol options make sense.
+ */
+
+#ifdef ENABLE_FRAGMENT
+ if (!proto_is_udp(ce->proto) && ce->fragment)
+ {
+ msg(msglevel, "--fragment can only be used with --proto udp");
+ return false;
+ }
+#endif
+
+ if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT)
+ {
+ msg(msglevel, "--http-proxy MUST be used in TCP Client mode (i.e. --proto tcp-client)");
+ return false;
+ }
+
+ return true;
}
static void
@@ -2322,6 +2375,11 @@ options_postprocess_verify_ce(const struct options *options,
"--proto tcp-server or --proto tcp-client");
}
+ if (ce->proto == PROTO_AUTO && !ce->remote_srv)
+ {
+ msg(M_USAGE, "--proto auto can be only used with --remote-srv");
+ }
+
if (options->lladdr && dev != DEV_TYPE_TAP)
{
msg(M_USAGE, "--lladdr can only be used in --dev tap mode");
@@ -2335,7 +2393,9 @@ options_postprocess_verify_ce(const struct options *options,
msg(M_USAGE, "only one of --tun-mtu or --link-mtu may be defined");
}
- if (!proto_is_udp(ce->proto) && options->mtu_test)
+ /* Defer validation for --remote-srv with auto protocol */
+ if (!proto_is_udp(ce->proto) && options->mtu_test
+ && !(ce->remote_srv && ce->proto == PROTO_AUTO))
{
msg(M_USAGE, "--mtu-test only makes sense with --proto udp");
}
@@ -2347,6 +2407,11 @@ options_postprocess_verify_ce(const struct options *options,
* Sanity check on --local, --remote, and --ifconfig
*/
+ if (ce->remote_srv && options->ip_remote_hint)
+ {
+ msg(M_USAGE, "--ip-remote-hint can't be used with --remote-srv");
+ }
+
if (proto_is_net(ce->proto)
&& string_defined_equal(ce->local, ce->remote)
&& string_defined_equal(ce->local_port, ce->remote_port))
@@ -2470,7 +2535,9 @@ options_postprocess_verify_ce(const struct options *options,
*/
#ifdef ENABLE_FRAGMENT
- if (!proto_is_udp(ce->proto) && ce->fragment)
+ /* Defer validation for --remote-srv with auto protocol */
+ if (!proto_is_udp(ce->proto) && ce->fragment
+ && !(ce->remote_srv && ce->proto == PROTO_AUTO))
{
msg(M_USAGE, "--fragment can only be used with --proto udp");
}
@@ -2481,11 +2548,11 @@ options_postprocess_verify_ce(const struct options *options,
msg(M_USAGE, "--remote MUST be used in TCP Client mode");
}
- if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT)
+ /* Defer validation for --remote-srv with auto protocol */
+ if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT
+ && !(ce->remote_srv && ce->proto == PROTO_AUTO))
{
- msg(M_USAGE,
- "--http-proxy MUST be used in TCP Client mode (i.e. --proto "
- "tcp-client)");
+ msg(M_USAGE, "--http-proxy MUST be used in TCP Client mode (i.e. --proto tcp-client)");
}
if ((ce->http_proxy_options) && !ce->http_proxy_options->server)
@@ -2552,6 +2619,10 @@ options_postprocess_verify_ce(const struct options *options,
{
msg(M_USAGE, "--remote cannot be used with --mode server");
}
+ if (ce->remote_srv)
+ {
+ msg(M_USAGE, "--remote-srv cannot be used with --mode server");
+ }
if (!ce->bind_local)
{
msg(M_USAGE, "--nobind cannot be used with --mode server");
@@ -3067,26 +3138,14 @@ options_postprocess_verify_ce(const struct options *options,
uninit_options(&defaults);
}
-static void
-options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce)
+/* Part of options_postprocess_mutate_ce that can be used
+ * in runtime after connection entry proto/hostname change.
+ */
+static bool
+options_postprocess_mutate_ce_proto(struct options *o,
+ struct connection_entry *ce)
{
- const int dev = dev_type_enum(o->dev, o->dev_type);
-
- if (o->server_defined || o->server_bridge_defined || o->server_bridge_proxy_dhcp)
- {
- if (ce->proto == PROTO_TCP)
- {
- ce->proto = PROTO_TCP_SERVER;
- }
- }
-
- if (o->client)
- {
- if (ce->proto == PROTO_TCP)
- {
- ce->proto = PROTO_TCP_CLIENT;
- }
- }
+ bool result = true;
/* an option is present that requires local bind to enabled */
bool need_bind = ce->local || ce->local_port_defined || ce->bind_defined;
@@ -3110,7 +3169,61 @@ options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce)
/* if protocol forcing is enabled, disable all protocols
* except for the forced one
*/
- if (o->proto_force >= 0 && o->proto_force != ce->proto)
+ if (o->proto_force >= 0 && o->proto_force != ce->proto
+ && !(ce->remote_srv && ce->proto == PROTO_AUTO))
+ {
+ result = false;
+ }
+
+ /* our socks code is not fully IPv6 enabled yet (TCP works, UDP not)
+ * so fall back to IPv4-only (trac #1221)
+ */
+ if (ce->socks_proxy_server && proto_is_udp(ce->proto) && ce->af != AF_INET)
+ {
+ if (ce->af == AF_INET6)
+ {
+ msg(M_INFO, "WARNING: '--proto udp6' is not compatible with "
+ "'--socks-proxy' today. Forcing IPv4 mode.");
+ }
+ else
+ {
+ msg(M_INFO, "NOTICE: dual-stack mode for '--proto udp' does not "
+ "work correctly with '--socks-proxy' today. Forcing IPv4.");
+ }
+ ce->af = AF_INET;
+ }
+
+ if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification)
+ {
+ msg(M_WARN, "NOTICE: --explicit-exit-notify ignored for --proto tcp");
+ ce->explicit_exit_notification = 0;
+ }
+
+ return result;
+}
+
+static void
+options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce)
+{
+ const int dev = dev_type_enum(o->dev, o->dev_type);
+
+ if (o->server_defined || o->server_bridge_defined || o->server_bridge_proxy_dhcp)
+ {
+ if (ce->proto == PROTO_TCP)
+ {
+ ce->proto = PROTO_TCP_SERVER;
+ }
+ }
+
+ if (o->client)
+ {
+ if (ce->proto == PROTO_TCP)
+ {
+ ce->proto = PROTO_TCP_CLIENT;
+ }
+ }
+
+ if (!options_postprocess_mutate_ce_proto(o, ce))
{
ce->flags |= CE_DISABLED;
}
@@ -3214,12 +3327,32 @@ options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce)
connection_entry_preload_key(&ce->tls_crypt_v2_file,
&ce->tls_crypt_v2_file_inline, &o->gc);
}
+}
- if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification)
+/*
+ * Merges servinfo's hostname, servname and proto into current connection
+ * entry if possible and doesn't conflict with the options.
+ * Used by runtime DNS SRV discovery.
+ */
+bool
+options_mutate_ce_servinfo(struct options *o, struct servinfo *si)
+{
+ struct connection_entry ce = o->ce;
+
+ ASSERT(ce.remote_srv);
+
+ ce.remote = si->hostname;
+ ce.remote_port = si->servname;
+ ce.proto = si->proto;
+
+ if (options_postprocess_mutate_ce_proto(o, &ce)
+ && options_postprocess_verify_ce_proto(o, &ce))
{
- msg(M_WARN, "NOTICE: --explicit-exit-notify ignored for --proto tcp");
- ce->explicit_exit_notification = 0;
+ o->ce = ce;
+ return true;
}
+
+ return false;
}
#ifdef _WIN32
@@ -6062,7 +6195,8 @@ add_option(struct options *options,
OPT_P_CONNECTION, option_types_found, es);
if (!sub.ce.remote)
{
- msg(msglevel, "Each 'connection' block must contain exactly one 'remote' directive");
+ msg(msglevel, "Each 'connection' block must contain exactly one 'remote' "
+ "or 'remote-srv' directive");
uninit_options(&sub);
goto err;
}
@@ -6140,6 +6274,7 @@ add_option(struct options *options,
{
struct remote_entry re;
re.remote = re.remote_port = NULL;
+ re.remote_srv = false;
re.proto = -1;
re.af = 0;
@@ -6174,6 +6309,61 @@ add_option(struct options *options,
}
else if (permission_mask & OPT_P_CONNECTION)
{
+ if (options->ce.remote && options->ce.remote_srv)
+ {
+ msg(msglevel, "Each 'connection' block must contain exactly one 'remote' "
+ "or 'remote-srv' directive");
+ goto err;
+ }
+ connection_entry_load_re(&options->ce, &re);
+ }
+ }
+ else if (streq(p[0], "remote-srv") && p[1] && !p[4])
+ {
+ struct remote_entry re;
+ re.remote = NULL;
+ re.remote_port = OPENVPN_SERVICE;
+ re.remote_srv = true;
+ re.proto = PROTO_AUTO;
+ re.af = 0;
+
+ VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION);
+ re.remote = p[1];
+ if (p[2])
+ {
+ re.remote_port = p[2];
+ if (p[3])
+ {
+ const int proto = ascii2proto(p[3]);
+ const sa_family_t af = ascii2af(p[3]);
+ if (proto < 0)
+ {
+ msg(msglevel,
+ "remote-srv: bad protocol associated with domain %s: "
+ "'%s'", p[1], p[3]);
+ goto err;
+ }
+ re.proto = proto;
+ re.af = af;
+ }
+ }
+ if (permission_mask & OPT_P_GENERAL)
+ {
+ struct remote_entry *e = alloc_remote_entry(options, msglevel);
+ if (!e)
+ {
+ goto err;
+ }
+ *e = re;
+ }
+ else if (permission_mask & OPT_P_CONNECTION)
+ {
+ if (options->ce.remote && !options->ce.remote_srv)
+ {
+ msg(msglevel, "Each 'connection' block must contain exactly one 'remote' "
+ "or 'remote-srv' directive");
+ goto err;
+ }
connection_entry_load_re(&options->ce, &re);
}
}
@@ -6691,7 +6881,7 @@ add_option(struct options *options,
int proto_force;
VERIFY_PERMISSION(OPT_P_GENERAL);
proto_force = ascii2proto(p[1]);
- if (proto_force < 0)
+ if (proto_force < 0 || proto_force == PROTO_AUTO)
{
msg(msglevel, "Bad --proto-force protocol: '%s'", p[1]);
goto err;
@@ -105,6 +105,7 @@ struct connection_entry
const char *remote_port;
const char *local;
const char *remote;
+ bool remote_srv;
bool remote_float;
bool bind_defined;
bool bind_ipv6_only;
@@ -177,6 +178,7 @@ struct remote_entry
const char *remote_port;
int proto;
sa_family_t af;
+ bool remote_srv;
};
#define CONNECTION_LIST_SIZE 64
@@ -832,6 +834,8 @@ void options_postprocess(struct options *options, struct env_set *es);
bool options_postprocess_pull(struct options *o, struct env_set *es);
+bool options_mutate_ce_servinfo(struct options *o, struct servinfo *si);
+
void pre_connect_restore(struct options *o, struct gc_arena *gc);
bool apply_push_options(struct options *options,
@@ -271,6 +271,37 @@ get_cached_dns_entry(struct cached_dns_entry *dns_cache,
return -1;
}
+/*
+ * get_cached_srv_entry return 0 on success and -1
+ * otherwise. (like getservinfo)
+ */
+static int
+get_cached_srv_entry(struct cached_dns_entry *dns_cache,
+ const char *hostname,
+ const char *servname,
+ int ai_family,
+ int resolve_flags,
+ struct servinfo **si)
+{
+ struct cached_dns_entry *ph;
+ int flags;
+
+ /* Only use flags that are relevant for the structure */
+ flags = resolve_flags & GETADDR_CACHE_SERVICE_MASK;
+ flags |= GETADDR_SERVICE;
+
+ for (ph = dns_cache; ph; ph = ph->next)
+ {
+ if (streqnull(ph->hostname, hostname)
+ && streqnull(ph->servname, servname)
+ && ph->flags == flags)
+ {
+ *si = ph->si;
+ return 0;
+ }
+ }
+ return -1;
+}
static int
do_preresolve_host(struct context *c,
@@ -326,6 +357,77 @@ do_preresolve_host(struct context *c,
return status;
}
+static int
+do_preresolve_service(struct context *c,
+ const char *hostname,
+ const char *servname,
+ const int af,
+ const int flags,
+ bool preresolve_host)
+{
+ struct servinfo *si;
+ int status;
+
+ if (get_cached_srv_entry(c->c1.dns_cache,
+ hostname,
+ servname,
+ af,
+ flags,
+ &si) == 0)
+ {
+ /* entry already cached, return success */
+ return 0;
+ }
+
+ status = openvpn_getservinfo(flags, hostname, servname,
+ c->options.resolve_retry_seconds, NULL,
+ af, &si);
+ if (status == 0)
+ {
+ struct cached_dns_entry *ph;
+
+ ALLOC_OBJ_CLEAR_GC(ph, struct cached_dns_entry, &c->gc);
+ ph->si = si;
+ ph->hostname = hostname;
+ ph->servname = servname;
+ ph->flags = flags & GETADDR_CACHE_SERVICE_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(si, &gc_freeservinfo_callback, &c->gc);
+
+ /* preresolve service targets unless disabled */
+ if (preresolve_host)
+ {
+ while (si)
+ {
+ int host_flags = flags & ~GETADDR_PROTO_MASK;
+ if (proto_is_dgram(si->proto))
+ {
+ host_flags |= GETADDR_DATAGRAM;
+ }
+ /* ignore errors */
+ do_preresolve_host(c, si->hostname, si->servname,
+ af, host_flags);
+ si = si->next;
+ }
+ }
+ }
+ return status;
+}
+
void
do_preresolve(struct context *c)
{
@@ -364,8 +466,31 @@ do_preresolve(struct context *c)
remote = ce->remote;
}
+ /* Preresolve remote services */
+ if (ce->remote_srv)
+ {
+ int service_flags = flags|GETADDR_SERVICE;
+
+ if (proto_is_dgram(ce->proto) || ce->proto == PROTO_AUTO)
+ {
+ service_flags |= GETADDR_DATAGRAM;
+ }
+ if (proto_is_stream(ce->proto) || ce->proto == PROTO_AUTO)
+ {
+ service_flags |= GETADDR_STREAM;
+ }
+
+ /* HTTP remote hostnames does not need to be resolved */
+ status = do_preresolve_service(c, remote, ce->remote_port,
+ ce->af, service_flags,
+ !ce->http_proxy_options);
+ if (status != 0)
+ {
+ goto err;
+ }
+ }
/* HTTP remote hostname does not need to be resolved */
- if (!ce->http_proxy_options)
+ else if (!ce->http_proxy_options)
{
status = do_preresolve_host(c, remote, ce->remote_port,
ce->af, flags);
@@ -423,6 +548,637 @@ err:
throw_signal_soft(SIGHUP, "Preresolving failed");
}
+/**
+ * Allocates new service structure on heap and stores
+ * its initial values.
+ *
+ * @param hostname - The peer host name or ip address
+ * @param port - The peer TCP/UDP port
+ * @param proto - Protocol for communicating with the peer
+ *
+ * @return A pointer to servinfo structure or NULL in case of
+ * allocation error.
+ */
+static struct servinfo *
+alloc_servinfo(const char *hostname, uint16_t port, int proto)
+{
+ size_t namesize = hostname ? strlen(hostname) + 1 : 0;
+
+ char servname[sizeof("65535")];
+ openvpn_snprintf(servname, sizeof(servname), "%u", port);
+ size_t servsize = strlen(servname) + 1;
+
+ struct servinfo *si = calloc(1, sizeof(*si) + namesize + servsize);
+ if (si)
+ {
+ if (hostname)
+ {
+ si->hostname = (char *)(si + 1);
+ memcpy((char *)si->hostname, hostname, namesize);
+ }
+ si->servname = (char *)(si + 1) + namesize;
+ memcpy((char *)si->servname, servname, servsize);
+ si->proto = proto;
+ }
+ return si;
+}
+
+#ifdef _WIN32
+/**
+ * Queries DNS SRV records for specified DNS domain.
+ *
+ * @param domain - The DNS domain name
+ * @param proto - TCP/UDP protocol for communicating with the peer
+ * @param next - The servinfo structure list to be chained to
+ * the resulting servinfo list
+ * @param res - The pointer to the resulting servinfo list
+ *
+ * @return 0 on success, a EAI-* status code otherwise
+ */
+static int
+query_servinfo(const char *domain, int proto,
+ struct servinfo *next, struct servinfo **res)
+{
+ PDNS_RECORD pDnsRecord;
+ int status;
+
+ ASSERT(res);
+
+ DNS_STATUS DnsStatus = DnsQuery(domain, DNS_TYPE_SRV, DNS_QUERY_STANDARD,
+ NULL, &pDnsRecord, NULL);
+ dmsg(D_SOCKET_DEBUG, "DNSQUERY type=%d domain=%s result=%d",
+ DNS_TYPE_SRV, domain, DnsStatus);
+ switch (DnsStatus)
+ {
+ case ERROR_SUCCESS:
+ break;
+
+ case DNS_ERROR_RCODE_NAME_ERROR:
+ case DNS_INFO_NO_RECORDS:
+ return EAI_NONAME; /* HOST_NOT_FOUND */
+
+ 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_FAIL;
+ }
+
+ struct servinfo *list = NULL, *first = NULL;
+ 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)
+ {
+ struct servinfo *si = alloc_servinfo(rdata->pNameTarget,
+ rdata->wPort,
+ proto);
+ if (!si)
+ {
+ freeservinfo(list);
+ status = EAI_MEMORY;
+ goto done;
+ }
+ si->prio = rdata->wPriority;
+ si->weight = rdata->wWeight;
+ si->next = list, list = si;
+ if (!first)
+ {
+ first = si;
+ }
+ }
+ }
+ }
+ if (list)
+ {
+ first->next = next;
+ *res = list;
+ status = 0;
+ }
+ else
+ {
+ status = EAI_FAIL;
+ }
+
+done:
+ DnsRecordListFree(pDnsRecord, DnsFreeRecordList);
+ return status;
+}
+
+#else /* ifdef _WIN32 */
+/**
+ * Queries DNS SRV records for specified DNS domain.
+ *
+ * @param domain - The DNS domain name
+ * @param proto - TCP/UDP protocol for communicating with the peer
+ * @param next - The servinfo structure list to be chained to
+ * the resulting servinfo list
+ * @param res - The pointer to the resulting servinfo list
+ *
+ * @return 0 on success, a EAI-* status code otherwise
+ */
+
+#ifdef TARGET_OPENBSD
+/* OpenBSD replaced 'everywhere else' ns_xxx enums with uppercased #defines */
+#define ns_c_in C_IN
+#define ns_t_srv T_SRV
+#endif
+
+static int
+query_servinfo(const char *domain, int proto,
+ struct servinfo *next, struct servinfo **res)
+{
+ unsigned char answer[65535];
+ int status;
+
+ int n = res_query(domain, ns_c_in, ns_t_srv, answer, sizeof(answer));
+ dmsg(D_SOCKET_DEBUG, "RES_QUERY class=%d type=%d domain=%s result=%d",
+ ns_c_in, ns_t_srv, domain, n);
+ if (n < 0)
+ {
+ switch (h_errno)
+ {
+ case HOST_NOT_FOUND:
+ case NO_ADDRESS:
+#if NO_ADDRESS != NO_DATA
+ case NO_DATA:
+#endif
+ return EAI_NONAME;
+
+ 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;
+ }
+
+ struct servinfo *list = NULL, *first = NULL;
+ 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);
+ char name[NS_MAXDNAME];
+
+ if (ns_rr_rdlen(rr) > 6
+ && dn_expand(ns_msg_base(msg), ns_msg_end(msg),
+ rdata + 6, name, sizeof(name)) > 0 && *name)
+ {
+ struct servinfo *si = alloc_servinfo(name,
+ ns_get16(rdata + 4),
+ proto);
+ if (!si)
+ {
+ freeservinfo(list);
+ status = EAI_MEMORY;
+ goto done;
+ }
+ si->prio = ns_get16(rdata);
+ si->weight = ns_get16(rdata + 2);
+ si->next = list, list = si;
+ if (!first)
+ {
+ first = si;
+ }
+ }
+ }
+ }
+ if (list)
+ {
+ first->next = next;
+ *res = list;
+ status = 0;
+ }
+ else
+ {
+ status = EAI_FAIL;
+ }
+
+done:
+ return status;
+}
+#endif /* ifdef _WIN32 */
+
+/**
+ * Servinfo qsort compare function for the server selection
+ * mechanism, defined in RFC 2782.
+ *
+ * @param a - Pointer to first servinfo structure
+ * @param b - Pointer to second servinfo structure
+ *
+ * @return
+ * @li 0, if equal
+ * @li 1, if "a" should be ordered after "b"
+ * @li -1, if "a" should be ordered before "b"
+ */
+static int
+servinfo_cmp(const void *a, const void *b)
+{
+ const struct servinfo *ae = *(struct servinfo **)a;
+ const struct servinfo *be = *(struct servinfo **)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;
+ }
+
+ /* else keep received order, can't be equal */
+ return ae->order > be->order ? -1 : 1;
+}
+
+/**
+ * Sorts service list according the server selection mechanism,
+ * defined in RFC 2782.
+ *
+ * @param list - The pointer to servinfo list
+ *
+ * @return A pointer to the sorted servinfo list
+ */
+static struct servinfo *
+sort_servinfo(struct servinfo *list)
+{
+ struct servinfo ordered, *tail = &ordered;
+ int count = 0;
+ struct gc_arena gc = gc_new();
+
+ ASSERT(list);
+
+ /* count and number entries in reverse order */
+ for (struct servinfo *si = list; si; si = si->next)
+ {
+ si->order = count++;
+ }
+
+ struct servinfo **sorted;
+ ALLOC_ARRAY_CLEAR_GC(sorted, struct servinfo *, count, &gc);
+ for (struct servinfo *si = list; si; si = si->next)
+ {
+ sorted[si->order] = si;
+ }
+
+ /* sort records by priority and zero weight */
+ qsort(sorted, count, sizeof(sorted[0]), servinfo_cmp);
+
+ /* apply weighted selection mechanism */
+ ordered.next = NULL;
+ for (int i = 0; i < count; )
+ {
+ struct servinfo 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 servinfo *prev = &unordered;
+ i < count && sorted[i]->prio == unordered.prio; i++)
+ {
+ unordered.weight += sorted[i]->weight;
+
+ /* add entry to the tail of unordered list */
+ sorted[i]->next = NULL;
+ prev->next = sorted[i], prev = sorted[i];
+ }
+
+ /* process the unordered list */
+ while (unordered.next)
+ {
+ /* choose a uniform random number between 0 and the sum
+ * computed (inclusive) */
+ int weight = get_random() % (unordered.weight + 1);
+
+ /* 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 servinfo *si = unordered.next, *prev = &unordered;
+ si; prev = si, si = si->next)
+ {
+ /* selected entry is the next one to be contacted */
+ if (si->weight >= weight)
+ {
+ unordered.weight -= si->weight;
+
+ /* move entry to the ordered list */
+ prev->next = si->next;
+ si->next = NULL;
+ tail->next = si, tail = si;
+
+ /*
+ * RFC 2782 is ambiguous, it says:
+ * In the presence of records containing weights greater
+ * than 0, records with weight 0 should have a very
+ * small chance of being selected.
+ * According that, within the same priority, after all
+ * records containing weights greater than 0 were selected,
+ * the rest of records with weight 0 should be skipped.
+ * At the same time, it says:
+ * The following algorithm SHOULD be used to order the
+ * SRV RRs of the same priority:
+ * ...
+ * Continue the ordering process until there are no
+ * unordered SRV RRs.
+ * This means records with wight 0 should always be
+ * selected, as last ones in worst case.
+ *
+ * We implement the second option and do not skip any of
+ * the records with unordered.weight == 0 after the last
+ * one with weight greater than 0.
+ */
+ }
+ weight -= si->weight;
+ }
+ }
+ }
+
+ gc_free(&gc);
+ return ordered.next;
+}
+
+/**
+ * Resolves DNS SRV records for given domain and service names.
+ *
+ * @param domain - The DNS SRV domain name
+ * @param service - The DNS SRV service name
+ * @param flags - GETADDR_* DNS resultion flags
+ * @param res - The pointer to the resulting servinfo list
+ *
+ * @return 0 on success, a EAI-* status code otherwise
+ */
+static int
+getservinfo(const char *domain,
+ const char *service,
+ int flags,
+ struct servinfo **res)
+{
+ static const struct {
+ int flags;
+ int proto;
+ const char *name;
+ } proto[] = {
+ { GETADDR_DATAGRAM, PROTO_UDP, "udp" },
+ { GETADDR_STREAM, PROTO_TCP_CLIENT, "tcp" }
+ };
+ struct servinfo *list = NULL;
+ int status = EAI_SOCKTYPE;
+
+ ASSERT(res);
+
+ if (!domain)
+ {
+ return EAI_NONAME;
+ }
+ if (!service)
+ {
+ return EAI_SERVICE;
+ }
+
+ int proto_flags = flags & GETADDR_PROTO_MASK;
+ for (int i = 0; i < SIZE(proto); i++)
+ {
+ if (proto_flags & proto[i].flags)
+ {
+ proto_flags &= ~proto[i].flags;
+
+ char qname[256];
+ if (!openvpn_snprintf(qname, sizeof(qname), "_%s._%s.%s",
+ service, proto[i].name, domain))
+ {
+ freeservinfo(list);
+ return EAI_MEMORY;
+ }
+
+ status = query_servinfo(qname, proto[i].proto, list, &list);
+ }
+ }
+
+ if (list)
+ {
+ *res = sort_servinfo(list);
+ status = 0;
+ }
+
+ return status;
+}
+
+void
+freeservinfo(struct servinfo *res)
+{
+ while (res)
+ {
+ struct servinfo *si = res;
+ res = res->next;
+ free(si);
+ }
+}
+
+/*
+ * Translate IPv4/IPv6 hostname and service name into struct servinfo
+ * If resolve error, try again for resolve_retry_seconds seconds.
+ */
+int
+openvpn_getservinfo(unsigned int flags,
+ const char *hostname,
+ const char *servname,
+ int resolve_retry_seconds,
+ volatile int *signal_received,
+ int family,
+ struct servinfo **res)
+{
+ int status;
+ int sigrec = 0;
+ int 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 (hostname)
+ {
+ print_hostname = hostname;
+ }
+ else
+ {
+ print_hostname = "undefined";
+ }
+
+ 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))
+ && !signal_received)
+ {
+ signal_received = &sigrec;
+ }
+
+ 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;
+ int level = 0;
+
+ fmt = "RESOLVE: Cannot resolve remote service: %s:%s (%s)";
+ if ((flags & GETADDR_MENTION_RESOLVE_RETRY)
+ && !resolve_retry_seconds)
+ {
+ fmt = "RESOLVE: Cannot resolve remote service: %s:%s (%s) "
+ "(I would have retried this name query if you had "
+ "specified the --resolv-retry option.)";
+ }
+
+#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 service
+ */
+ while (true)
+ {
+#ifndef _WIN32
+ /* force resolv.conf reload */
+ res_init();
+#endif
+ dmsg(D_SOCKET_DEBUG, "GETSERVINFO flags=0x%04x family=%d %s:%s",
+ flags, family, print_hostname, print_servname);
+ status = getservinfo(hostname, servname, flags, res);
+
+ 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);
+ freeservinfo(*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,
+ gai_strerror(status));
+
+ if (--resolve_retries <= 0)
+ {
+ goto done;
+ }
+
+ management_sleep(fail_wait_interval);
+ }
+
+ ASSERT(res);
+
+ /* service resolve succeeded */
+
+done:
+ if (signal_received && *signal_received)
+ {
+ int 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;
+}
+
/*
* Translate IPv4/IPv6 addr or hostname into struct addrinfo
* If resolve error, try again for resolve_retry_seconds seconds.
@@ -557,8 +1313,9 @@ openvpn_getaddrinfo(unsigned int flags,
/* 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);
+ "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d %s:%s",
+ flags, hints.ai_family, hints.ai_socktype,
+ print_hostname, print_servname);
status = getaddrinfo(hostname, servname, &hints, res);
if (signal_received)
@@ -1816,7 +2573,110 @@ done:
gc_free(&gc);
}
+void
+do_resolve_service(struct context *c)
+{
+ struct connection_entry *ce = &c->options.ce;
+ if (ce->remote_srv
+ && !c->c1.link_socket_addr.service_list)
+ {
+ if (ce->remote)
+ {
+ unsigned int flags = sf2gaf(GETADDR_RESOLVE
+ |GETADDR_UPDATE_MANAGEMENT_STATE
+ |GETADDR_SERVICE,
+ c->options.sockflags);
+ int retry = 0;
+ int status = -1;
+ struct servinfo *si;
+
+ if (proto_is_dgram(ce->proto) || ce->proto == PROTO_AUTO)
+ {
+ flags |= GETADDR_DATAGRAM;
+ }
+ if (proto_is_stream(ce->proto) || ce->proto == PROTO_AUTO)
+ {
+ flags |= GETADDR_STREAM;
+ }
+
+ if (c->options.sockflags & SF_HOST_RANDOMIZE)
+ {
+ flags |= GETADDR_RANDOMIZE;
+ }
+
+ if (c->options.resolve_retry_seconds == RESOLV_RETRY_INFINITE)
+ {
+ flags |= (GETADDR_TRY_ONCE | GETADDR_FATAL);
+ retry = 0;
+ }
+ else if (c->options.resolve_retry_seconds)
+ {
+ flags |= GETADDR_FATAL;
+ retry = c->options.resolve_retry_seconds;
+ }
+ else
+ {
+ flags |= (GETADDR_FATAL | GETADDR_MENTION_RESOLVE_RETRY);
+ retry = 0;
+ }
+
+ status = get_cached_srv_entry(c->c1.dns_cache,
+ ce->remote,
+ ce->remote_port,
+ ce->af,
+ flags, &si);
+ if (status)
+ {
+ status = openvpn_getservinfo(flags, ce->remote, ce->remote_port,
+ retry, &c->sig->signal_received,
+ ce->af, &si);
+ }
+
+ if (status == 0)
+ {
+ c->c1.link_socket_addr.service_list = si;
+ c->c1.link_socket_addr.current_service = NULL;
+
+ /* advance to the first appropriate service */
+ while (si)
+ {
+ /* map in current service */
+ if (!c->c1.link_socket_addr.current_service
+ && options_mutate_ce_servinfo(&c->options, si))
+ {
+ c->c1.link_socket_addr.current_service = si;
+ }
+
+ /* log discovered service hosts */
+ msg(D_RESOLVE,
+ "Resolved remote service host: %s:%s,%s prio %u weight %u",
+ np(si->hostname), np(si->servname),
+ proto2ascii(si->proto, ce->af, false),
+ si->prio, si->weight);
+
+ si = si->next;
+ }
+ if (!c->c1.link_socket_addr.current_service)
+ {
+ status = EAI_NONAME;
+ }
+
+ dmsg(D_SOCKET_DEBUG,
+ "RESOLVE_SERVICE flags=0x%04x rrs=%d sig=%d status=%d",
+ flags,
+ retry,
+ c->sig->signal_received,
+ status);
+ }
+
+ if (!c->sig->signal_received && status != 0)
+ {
+ c->sig->signal_received = SIGUSR1;
+ }
+ }
+ }
+}
struct link_socket *
link_socket_new(void)
@@ -3077,16 +3937,19 @@ static const struct proto_names proto_names[] = {
{"tcp-server", "TCP_SERVER", AF_UNSPEC, PROTO_TCP_SERVER},
{"tcp-client", "TCP_CLIENT", AF_UNSPEC, PROTO_TCP_CLIENT},
{"tcp", "TCP", AF_UNSPEC, PROTO_TCP},
+ {"auto", "AUTO", AF_UNSPEC, PROTO_AUTO},
/* force IPv4 */
{"udp4", "UDPv4", AF_INET, PROTO_UDP},
{"tcp4-server", "TCPv4_SERVER", AF_INET, PROTO_TCP_SERVER},
{"tcp4-client", "TCPv4_CLIENT", AF_INET, PROTO_TCP_CLIENT},
{"tcp4", "TCPv4", AF_INET, PROTO_TCP},
+ {"auto4", "AUTOv4", AF_INET, PROTO_AUTO},
/* force IPv6 */
{"udp6", "UDPv6", AF_INET6, PROTO_UDP},
{"tcp6-server", "TCPv6_SERVER", AF_INET6, PROTO_TCP_SERVER},
{"tcp6-client", "TCPv6_CLIENT", AF_INET6, PROTO_TCP_CLIENT},
{"tcp6", "TCPv6", AF_INET6, PROTO_TCP},
+ {"auto6", "AUTOv6", AF_INET6, PROTO_AUTO},
};
int
@@ -3147,6 +4010,11 @@ proto2ascii_all(struct gc_arena *gc)
for (i = 0; i < SIZE(proto_names); ++i)
{
+ if (proto_names[i].proto == PROTO_NONE
+ || proto_names[i].proto == PROTO_AUTO)
+ {
+ continue;
+ }
if (i)
{
buf_printf(&out, " ");
@@ -41,6 +41,11 @@
*/
#define OPENVPN_PORT "1194"
+/*
+ * OpenVPN's default service name for DNS SRV discovery.
+ */
+#define OPENVPN_SERVICE "openvpn"
+
/*
* Number of seconds that "resolv-retry infinite"
* represents.
@@ -72,6 +77,19 @@ struct openvpn_sockaddr
} addr;
};
+/* struct to hold resolved service targets */
+struct servinfo
+{
+ const char *hostname;
+ const char *servname;
+ int proto;
+ int order;
+ unsigned short prio;
+ unsigned short weight;
+ struct addrinfo *ai;
+ struct servinfo *next;
+};
+
/* struct to hold preresolved host names */
struct cached_dns_entry {
const char *hostname;
@@ -79,6 +97,7 @@ struct cached_dns_entry {
int ai_family;
int flags;
struct addrinfo *ai;
+ struct servinfo *si;
struct cached_dns_entry *next;
};
@@ -107,6 +126,9 @@ struct link_socket_addr
struct addrinfo *remote_list; /* complete remote list */
struct addrinfo *current_remote; /* remote used in the
* current connection attempt */
+ struct servinfo *service_list; /* complete service list */
+ struct servinfo *current_service; /* service used in the
+ * current connection attempt */
struct link_socket_actual actual; /* reply to this address */
};
@@ -333,6 +355,8 @@ void link_socket_init_phase2(struct context *c);
void do_preresolve(struct context *c);
+void do_resolve_service(struct context *c);
+
void link_socket_close(struct link_socket *sock);
void sd_close(socket_descriptor_t *sd);
@@ -513,8 +537,12 @@ 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_STREAM (1<<12)
+#define GETADDR_SERVICE (1<<13)
+#define GETADDR_PROTO_MASK (GETADDR_DATAGRAM|GETADDR_STREAM)
#define GETADDR_CACHE_MASK (GETADDR_DATAGRAM|GETADDR_PASSIVE)
+#define GETADDR_CACHE_SERVICE_MASK (GETADDR_PROTO_MASK|GETADDR_SERVICE)
/**
* Translate an IPv4 addr or hostname from string form to in_addr_t
@@ -542,6 +570,30 @@ int openvpn_getaddrinfo(unsigned int flags,
int ai_family,
struct addrinfo **res);
+int openvpn_getservinfo(unsigned int flags,
+ const char *hostname,
+ const char *servname,
+ int resolve_retry_seconds,
+ volatile int *signal_received,
+ int family,
+ struct servinfo **res);
+
+void freeservinfo(struct servinfo *res);
+
+/* Inline functions */
+
+static inline void
+gc_freeaddrinfo_callback(void *addr)
+{
+ freeaddrinfo((struct addrinfo *) addr);
+}
+
+static inline void
+gc_freeservinfo_callback(void *addr)
+{
+ freeservinfo((struct servinfo *) addr);
+}
+
/*
* Transport protocol naming and other details.
*/
@@ -556,6 +608,7 @@ enum proto_num {
PROTO_TCP,
PROTO_TCP_SERVER,
PROTO_TCP_CLIENT,
+ PROTO_AUTO,
PROTO_N
};
@@ -563,7 +616,7 @@ static inline bool
proto_is_net(int proto)
{
ASSERT(proto >= 0 && proto < PROTO_N);
- return proto != PROTO_NONE;
+ return proto != PROTO_NONE && proto != PROTO_AUTO;
}
/**
@@ -596,6 +649,16 @@ proto_is_tcp(int proto)
return proto == PROTO_TCP_CLIENT || proto == PROTO_TCP_SERVER;
}
+/**
+ * @brief Return if the protocol is stream (TCP)
+ *
+ */
+static inline bool
+proto_is_stream(int proto)
+{
+ return proto_is_tcp(proto);
+}
+
int ascii2proto(const char *proto_name);
@@ -38,6 +38,7 @@
#ifdef _WIN32
#include <windows.h>
+#include <windns.h>
#include <winsock2.h>
#include <tlhelp32.h>
#define sleep(x) Sleep((x)*1000)
@@ -143,6 +144,10 @@
#include <netinet/in.h>
#endif
+#ifdef HAVE_ARPA_NAMESER_H
+#include <arpa/nameser.h>
+#endif
+
#ifdef HAVE_RESOLV_H
#include <resolv.h>
#endif