Hello and happy holidays, Is there a chance to get back to this patch since v9 was acked and minor fix for undefined EAI_NODATA on FreeBSD was applied?
-- Best Regards, Vladislav Grishenko > -----Original Message----- > From: Vladislav Grishenko <themi...@yandex-team.ru> > Sent: Friday, December 4, 2020 9:15 PM > To: openvpn-devel@lists.sourceforge.net > Cc: g...@greenie.muc.de > Subject: [Openvpn-devel] [PATCH v10] Add DNS SRV remote host discovery > support > > DNS SRV remote host discovery allows to have multiple OpenVPN servers for > a single domain w/o explicit profile enumeration, to move services from > host to host with little fuss, and to designate hosts as primary servers > for a service and others as backups. > Feature has been asked several times already, should be useful in case of > substantial number of clients & servers deployed. > > Patch introduces "--remote-srv domain [service] [proto]" option. > 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 RFC2782 (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. > > Example: instead of multiple --remote in order, now it's possible to > specify just one --remote-srv and configure DNS SRV records: > > remote-srv example.net > > 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 DNS SRV name can't be resolved or no valid records were returned, > client will move on to the next connection entry. > > v10: > add get_cached_srv_entry() for servinfo vs addrinfo cache split > add check for mixed --remote and --remote-srv > add doxygen dns srv functions comments > use query_servinfo() for both unix and windows > fix undefined NS_MAXMSG issue on macOS > fix undefined EAI_NODATA issue on FreeBSD > fix man > > Signed-off-by: Vladislav Grishenko <themi...@yandex-team.ru> > --- > configure.ac | 2 +- > doc/man-sections/client-options.rst | 121 +++- > doc/management-notes.txt | 6 + > src/openvpn/Makefile.am | 2 +- > src/openvpn/buffer.h | 5 - > src/openvpn/errlevel.h | 1 + > src/openvpn/init.c | 79 ++- > src/openvpn/openvpn.vcxproj | 8 +- > src/openvpn/options.c | 286 +++++++-- > src/openvpn/options.h | 4 + > src/openvpn/socket.c | 875 +++++++++++++++++++++++++++- > src/openvpn/socket.h | 54 ++ > src/openvpn/syshead.h | 5 + > 13 files changed, 1374 insertions(+), 74 deletions(-) > > diff --git a/configure.ac b/configure.ac > index 1ab8fe59..d110ba98 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -470,7 +470,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 af21fbcd..d2cafd00 100644 > --- a/doc/man-sections/client-options.rst > +++ b/doc/man-sections/client-options.rst > @@ -307,10 +307,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 > @@ -318,8 +429,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 "infinite" to retry indefinitely. > > diff --git a/doc/management-notes.txt b/doc/management-notes.txt > index 61daaf07..d9d7394d 100644 > --- a/doc/management-notes.txt > +++ b/doc/management-notes.txt > @@ -851,6 +851,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: > > 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/errlevel.h b/src/openvpn/errlevel.h > index 5663f841..39f96e7d 100644 > --- a/src/openvpn/errlevel.h > +++ b/src/openvpn/errlevel.h > @@ -92,6 +92,7 @@ > #define D_PS_PROXY LOGLEV(3, 44, 0) /* messages related to --port- > share option */ > #define D_PF_INFO LOGLEV(3, 45, 0) /* packet filter informational > messages */ > #define D_IFCONFIG LOGLEV(3, 0, 0) /* show ifconfig info (don't > mute) */ > +#define D_RESOLVE LOGLEV(3, 46, 0) /* show hostname resolve info > */ > > #define D_SHOW_PARMS LOGLEV(4, 50, 0) /* show all parameters on > program initiation */ > #define D_SHOW_OCC LOGLEV(4, 51, 0) /* show options compatibility > string */ > diff --git a/src/openvpn/init.c b/src/openvpn/init.c > index 2291a89a..34873cf4 100644 > --- a/src/openvpn/init.c > +++ b/src/openvpn/init.c > @@ -352,7 +352,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) > @@ -365,6 +370,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; > } > @@ -464,6 +470,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 > */ > @@ -492,6 +515,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 > { > /* FIXME (schwabe) fix the persist-remote-ip option for real, > @@ -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)) > { > @@ -3652,10 +3703,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 */ > @@ -4187,6 +4241,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 */ > diff --git a/src/openvpn/openvpn.vcxproj b/src/openvpn/openvpn.vcxproj > index 3863854b..1f317fb0 100644 > --- a/src/openvpn/openvpn.vcxproj > +++ b/src/openvpn/openvpn.vcxproj > @@ -92,7 +92,7 @@ > </ClCompile> > <ResourceCompile /> > <Link> > - > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDep en > dencies> > + > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies)</Ad diti > onalDependencies> > > <AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS1 > 1H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> > <SubSystem>Console</SubSystem> > </Link> > @@ -107,7 +107,7 @@ > </ClCompile> > <ResourceCompile /> > <Link> > - > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDep en > dencies> > + > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies)</Ad diti > onalDependencies> > > <AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS1 > 1H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> > <SubSystem>Console</SubSystem> > </Link> > @@ -122,7 +122,7 @@ > </ClCompile> > <ResourceCompile /> > <Link> > - > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDep en > dencies> > + > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies)</Ad diti > onalDependencies> > > <AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS1 > 1H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> > <SubSystem>Console</SubSystem> > </Link> > @@ -137,7 +137,7 @@ > </ClCompile> > <ResourceCompile /> > <Link> > - > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies)</AdditionalDep en > dencies> > + > <AdditionalDependencies>legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;l ibcryp > to.lib;lzo2.lib;pkcs11- > helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;win mm.lib;F > wpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies)</Ad diti > onalDependencies> > > <AdditionalLibraryDirectories>$(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS1 > 1H_HOME)/lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> > <SubSystem>Console</SubSystem> > </Link> > diff --git a/src/openvpn/options.c b/src/openvpn/options.c > index 21f8d494..edb73f16 100644 > --- a/src/openvpn/options.c > +++ b/src/openvpn/options.c > @@ -121,6 +121,7 @@ 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-srv domain [service] : Perform DNS SRV remote host discovery.\n" > "--remote-random : If multiple --remote options specified, 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" > @@ -1444,6 +1445,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); > @@ -1978,6 +1980,67 @@ 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 (!proto_is_udp(ce->proto) && ce->explicit_exit_notification) > + { > + msg(msglevel, > + "--explicit-exit-notify can only be used with --proto udp"); > + return false; > + } > + > + 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 > @@ -2015,6 +2078,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"); > + } > + > /* > * Sanity check on daemon/inetd modes > */ > @@ -2024,9 +2092,10 @@ options_postprocess_verify_ce(const struct options > *options, > msg(M_USAGE, "only one of --daemon or --inetd may be specified"); > } > > - if (options->inetd && (ce->local || ce->remote)) > + if (options->inetd && (ce->local || ce->remote || ce->remote_srv)) > { > - msg(M_USAGE, "--local or --remote cannot be used with --inetd"); > + msg(M_USAGE, > + "--local or --remote or --remote_srv cannot be used with --inetd"); > } > > if (options->inetd && ce->proto == PROTO_TCP_CLIENT) > @@ -2072,7 +2141,9 @@ options_postprocess_verify_ce(const struct options > *options, > "--ifconfig implies --link-mtu %d)", LINK_MTU_DEFAULT); > } > > - 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"); > } > @@ -2086,6 +2157,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)) > @@ -2210,13 +2286,17 @@ 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"); > } > #endif > > - if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification) > + /* Defer validation for --remote-srv with auto protocol */ > + if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification > + && !(ce->remote_srv && ce->proto == PROTO_AUTO)) > { > msg(M_USAGE, > "--explicit-exit-notify can only be used with --proto udp"); > @@ -2227,7 +2307,9 @@ 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 " > @@ -2297,6 +2379,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"); > @@ -2815,6 +2901,64 @@ options_postprocess_verify_ce(const struct options > *options, > uninit_options(&defaults); > } > > +/* 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) > +{ > + bool result = true; > + > + if (ce->proto == PROTO_TCP_CLIENT && !ce->local > + && !ce->local_port_defined && !ce->bind_defined) > + { > + ce->bind_local = false; > + } > + > + if (ce->proto == PROTO_UDP && ce->socks_proxy_server && !ce->local > + && !ce->local_port_defined && !ce->bind_defined) > + { > + ce->bind_local = false; > + } > + > + if (!ce->bind_local) > + { > + ce->local_port = NULL; > + } > + > + /* if protocol forcing is enabled, disable all protocols > + * except for the forced one > + */ > + 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; > + } > + > + return result; > +} > + > static void > options_postprocess_mutate_ce(struct options *o, struct connection_entry > *ce) > { > @@ -2838,27 +2982,7 @@ options_postprocess_mutate_ce(struct options *o, > struct connection_entry *ce) > } > #endif > > - if (ce->proto == PROTO_TCP_CLIENT && !ce->local > - && !ce->local_port_defined && !ce->bind_defined) > - { > - ce->bind_local = false; > - } > - > - if (ce->proto == PROTO_UDP && ce->socks_proxy_server && !ce->local > - && !ce->local_port_defined && !ce->bind_defined) > - { > - ce->bind_local = false; > - } > - > - if (!ce->bind_local) > - { > - ce->local_port = NULL; > - } > - > - /* if protocol forcing is enabled, disable all protocols > - * except for the forced one > - */ > - if (o->proto_force >= 0 && o->proto_force != ce->proto) > + if (!options_postprocess_mutate_ce_proto(o, ce)) > { > ce->flags |= CE_DISABLED; > } > @@ -2879,24 +3003,6 @@ options_postprocess_mutate_ce(struct options *o, > struct connection_entry *ce) > #endif > } > > - /* 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; > - } > - > /* > * Set MTU defaults > */ > @@ -2964,6 +3070,32 @@ options_postprocess_mutate_ce(struct options *o, > struct connection_entry *ce) > } > } > > +/* > + * 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)) > + { > + o->ce = ce; > + return true; > + } > + > + return false; > +} > + > #ifdef _WIN32 > /* If iservice is in use, we need def1 method for redirect-gateway */ > static void > @@ -5621,7 +5753,9 @@ 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; > } > @@ -5699,6 +5833,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; > > @@ -5733,6 +5868,63 @@ 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); > } > } > @@ -6249,7 +6441,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; > diff --git a/src/openvpn/options.h b/src/openvpn/options.h > index 5b6d9441..51432a26 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_srv; > bool remote_float; > bool bind_defined; > bool bind_ipv6_only; > @@ -152,6 +153,7 @@ struct remote_entry > const char *remote_port; > int proto; > sa_family_t af; > + bool remote_srv; > }; > > #define CONNECTION_LIST_SIZE 64 > @@ -790,6 +792,8 @@ char *options_string_extract_option(const char > *options_string, > > void options_postprocess(struct options *options); > > +bool options_mutate_ce_servinfo(struct options *o, struct servinfo *si); > + > void pre_pull_save(struct options *o); > > void pre_pull_restore(struct options *o, struct gc_arena *gc); > diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c > index 97750681..31e8fe9a 100644 > --- a/src/openvpn/socket.c > +++ b/src/openvpn/socket.c > @@ -282,6 +282,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, > @@ -337,6 +368,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) > { > @@ -375,8 +477,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); > @@ -434,6 +559,630 @@ 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 > +/** > + * 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) > +{ > + 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 > + > +/** > + * 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. > @@ -568,8 +1317,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) > @@ -1862,7 +2612,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_NODATA; > + } > > + 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) > @@ -3172,16 +4025,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}, > }; > > bool > @@ -3191,7 +4047,7 @@ proto_is_net(int proto) > { > ASSERT(0); > } > - return proto != PROTO_NONE; > + return proto != PROTO_NONE && proto != PROTO_AUTO; > } > > bool > @@ -3200,6 +4056,12 @@ proto_is_dgram(int proto) > return proto_is_udp(proto); > } > > +bool > +proto_is_stream(int proto) > +{ > + return proto_is_tcp(proto); > +} > + > bool > proto_is_udp(int proto) > { > @@ -3278,6 +4140,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, " "); > diff --git a/src/openvpn/socket.h b/src/openvpn/socket.h > index 7aeae527..fa55a4b6 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 DNS SRV discovery. > + */ > +#define OPENVPN_SERVICE "openvpn" > + > /* > * Number of seconds that "resolv-retry infinite" > * represents. > @@ -71,6 +76,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; > @@ -78,6 +96,7 @@ struct cached_dns_entry { > int ai_family; > int flags; > struct addrinfo *ai; > + struct servinfo *si; > struct cached_dns_entry *next; > }; > > @@ -106,6 +125,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 */ > }; > > @@ -338,6 +360,8 @@ void link_socket_init_phase2(struct link_socket *sock, > > void do_preresolve(struct context *c); > > +void do_resolve_service(struct context *c); > + > void socket_adjust_frame_parameters(struct frame *frame, int proto); > > void frame_adjust_path_mtu(struct frame *frame, int pmtu, int proto); > @@ -486,6 +510,7 @@ socket_descriptor_t > socket_do_accept(socket_descriptor_t sd, > bool proto_is_net(int proto); > > bool proto_is_dgram(int proto); > +bool proto_is_stream(int proto); > > bool proto_is_udp(int proto); > > @@ -532,8 +557,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 > @@ -561,6 +590,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 */ > + > +inline static void > +gc_freeaddrinfo_callback(void *addr) > +{ > + freeaddrinfo((struct addrinfo *) addr); > +} > + > +inline static void > +gc_freeservinfo_callback(void *addr) > +{ > + freeservinfo((struct servinfo *) addr); > +} > + > /* > * Transport protocol naming and other details. > */ > @@ -575,6 +628,7 @@ enum proto_num { > PROTO_TCP, > PROTO_TCP_SERVER, > PROTO_TCP_CLIENT, > + PROTO_AUTO, > PROTO_N > }; > > diff --git a/src/openvpn/syshead.h b/src/openvpn/syshead.h > index 2ad5afc2..bec4a8a5 100644 > --- a/src/openvpn/syshead.h > +++ b/src/openvpn/syshead.h > @@ -38,6 +38,7 @@ > > #ifdef _WIN32 > #include <windows.h> > +#include <windns.h> > #include <winsock2.h> > #include <tlhelp32.h> > #define sleep(x) Sleep((x)*1000) > @@ -176,6 +177,10 @@ > #include <netinet/in.h> > #endif > > +#ifdef HAVE_ARPA_NAMESER_H > +#include <arpa/nameser.h> > +#endif > + > #ifdef HAVE_RESOLV_H > #include <resolv.h> > #endif > -- > 2.17.1 > > > > _______________________________________________ > Openvpn-devel mailing list > Openvpn-devel@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/openvpn-devel _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel