From patchwork Thu Sep 17 03:12:23 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladislav Grishenko X-Patchwork-Id: 1461 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id OMD+AyVhY1+4EgAAIUCqbw (envelope-from ) for ; Thu, 17 Sep 2020 09:14:13 -0400 Received: from proxy16.mail.iad3b.rsapps.net ([172.31.255.6]) by director12.mail.ord1d.rsapps.net with LMTP id SHfnAyVhY1/PagAAIasKDg (envelope-from ) for ; Thu, 17 Sep 2020 09:14:13 -0400 Received: from smtp30.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy16.mail.iad3b.rsapps.net with LMTPS id kAwbOSRhY1/xYAAAPj+4aA (envelope-from ) for ; Thu, 17 Sep 2020 09:14:12 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp30.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=yandex-team.ru; dmarc=fail (p=none; dis=none) header.from=yandex-team.ru X-Suspicious-Flag: YES X-Classification-ID: ad06ecba-f8e7-11ea-9eb2-525400502618-1-1 Received: from [216.105.38.7] ([216.105.38.7:52848] helo=lists.sourceforge.net) by smtp30.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 6A/51-02192-321636F5; Thu, 17 Sep 2020 09:14:12 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1kItig-0001TK-F0; Thu, 17 Sep 2020 13:13:02 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kItie-0001T1-V1 for openvpn-devel@lists.sourceforge.net; Thu, 17 Sep 2020 13:13:00 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:To: From:Sender:Reply-To:Cc:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=e6SMpAFWO6Q/47WpqzC3CA/pyOhO3tGvX6TDati+2ck=; b=D9FPTwdTcGrW2lnxwPztf/0+wp Aokymw82ZhltYe/eovJAI9GDLokc6FPvNDwtxzyjbgp1fJXWf5bcAOhOcO8IZ7IvJE+voYRC1owV9 yX7r9Ox8YJZWqrsXss1G+KsJsCcL94Y6ThKjcJ1ApsHlYQUvkvIKXFRtSCtpXKSWWGvo=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=e6SMpAFWO6Q/47WpqzC3CA/pyOhO3tGvX6TDati+2ck=; b=Nqh+DE5Oh3LBHKveWWpt0CB6FS 8v6Qg7W1Ss25Vdmul0b7h3aaK4IA44q2dDLryOQ1AC+KmqvfdgRvohZWrqXfYkNqLm1ZjpNtWGGX0 PatI8AztMGRCdXAOqkvg130+40GpEhq2NuQBNJjNWFCyDgByzatLYGEui00lqfKydfeo=; Received: from forwardcorp1o.mail.yandex.net ([95.108.205.193]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92.2) id 1kItiQ-00EA2I-FZ for openvpn-devel@lists.sourceforge.net; Thu, 17 Sep 2020 13:13:00 +0000 Received: from iva8-d077482f1536.qloud-c.yandex.net (iva8-d077482f1536.qloud-c.yandex.net [IPv6:2a02:6b8:c0c:2f26:0:640:d077:482f]) by forwardcorp1o.mail.yandex.net (Yandex) with ESMTP id A44AC2E15B6 for ; Thu, 17 Sep 2020 16:12:29 +0300 (MSK) Received: from iva4-7c3d9abce76c.qloud-c.yandex.net (iva4-7c3d9abce76c.qloud-c.yandex.net [2a02:6b8:c0c:4e8e:0:640:7c3d:9abc]) by iva8-d077482f1536.qloud-c.yandex.net (mxbackcorp/Yandex) with ESMTP id 1rLOGBBpAm-CTvOYo7j; Thu, 17 Sep 2020 16:12:29 +0300 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yandex-team.ru; s=default; t=1600348349; bh=e6SMpAFWO6Q/47WpqzC3CA/pyOhO3tGvX6TDati+2ck=; h=In-Reply-To:Message-Id:References:Date:Subject:To:From; b=wp4PMy8cmJq7EravmvHRsL7KaLCW/wNYPGadIeSuonzhsdOOYYUsDBmlDOyXCnP+K XzXHdu6BLDg6c1Lrf/vhEhBMibcp8pFOYTUNqfG3mHwL2Udf9i8ulNi8j+Vp2FfiN2 UAgZDhT+IWi6qAl1gik1AGgNvSR3a1Fu+K8I1IBs= Received: from unknown (unknown [95.108.216.35]) by iva4-7c3d9abce76c.qloud-c.yandex.net (smtpcorp/Yandex) with ESMTPSA id g14cUwKUGD-CTleuIcM; Thu, 17 Sep 2020 16:12:29 +0300 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (Client certificate not present) From: Vladislav Grishenko To: openvpn-devel@lists.sourceforge.net Date: Thu, 17 Sep 2020 18:12:23 +0500 Message-Id: <20200917131223.11519-1-themiron@yandex-team.ru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200914171134.27490-1-themiron@yandex-team.ru> References: <20200914171134.27490-1-themiron@yandex-team.ru> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid X-Headers-End: 1kItiQ-00EA2I-FZ Subject: [Openvpn-devel] [PATCH v5] Add DNS SRV remote host discovery support X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox 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: remote server1.example.net 1194 udp remote server2.example.net 1194 udp remote server3.example.net 1194 udp remote server4.example.net 443 tcp now it's possible to specify just one --remote-srv: remote-srv example.net and configure following DNS SRV records: name prio weight port target _openvpn._udp.example.net IN SRV 10 60 1194 server1.example.net _openvpn._udp.example.net IN SRV 10 40 1194 server2.example.net _openvpn._udp.example.net IN SRV 10 0 1194 server3.example.net _openvpn._tcp.example.net 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. Tested on Linux/Glibc & Windows 10. v2: use DNS SRV for feature naming change option name to --remote-service-discovery add --remote-service-discovery [service] optional parameter support rewrite man, add algo/prio/weight description and examples fix random weight selection was not implicit remove pulled REMOTE_DISCOVERY support, not needed split windows/unix-specific parts into extra functions rename functions into servinfo scope, add doxygen comments when appropriate remove addrinfo hack, use servinfo containers of addrinfo list instead better proxy support (tcp mode not supported so far) log discovery attempts and results, if enabled v3: complete logic rewrite use separate --remote-srv [service] [proto] option remove fallback, same is achieved with additiona --remote/--remote-srv add "auto" protocol support to allow both udp & tcp hosts be discovered add support for tcp / http proxy (natively) man update v4: due RFC 2782 ambiguity, prefer to use all resolved DNS SRV records, even ones with weight 0 after the records containing weights greater than 0 were all selected, keep related code disabled for historical reasons. man update v5: rebase against upstream with connection advancing fix allow management skip/accept for exact remote service hosts as for --remote improve compability with a way "--persist-remote-ip" is handled ensure max line length is 80 Signed-off-by: Vladislav Grishenko --- configure.ac | 2 +- doc/man-sections/client-options.rst | 117 +++- doc/management-notes.txt | 6 + src/openvpn/Makefile.am | 2 +- src/openvpn/buffer.h | 5 - src/openvpn/errlevel.h | 1 + src/openvpn/init.c | 69 ++- src/openvpn/openvpn.vcxproj | 8 +- src/openvpn/options.c | 252 ++++++-- src/openvpn/options.h | 4 + src/openvpn/socket.c | 881 +++++++++++++++++++++++++++- src/openvpn/socket.h | 56 +- src/openvpn/syshead.h | 5 + 13 files changed, 1324 insertions(+), 84 deletions(-) diff --git a/configure.ac b/configure.ac index ebb32204..22088aaf 100644 --- a/configure.ac +++ b/configure.ac @@ -477,7 +477,7 @@ SOCKET_INCLUDES=" " AC_CHECK_HEADERS( - [net/if.h netinet/ip.h resolv.h sys/un.h net/if_utun.h sys/kern_control.h], + [net/if.h netinet/ip.h arpa/nameser.h resolv.h sys/un.h net/if_utun.h sys/kern_control.h], , , [[${SOCKET_INCLUDES}]] diff --git a/doc/man-sections/client-options.rst b/doc/man-sections/client-options.rst index af21fbcd..ca2f27a5 100644 --- a/doc/man-sections/client-options.rst +++ b/doc/man-sections/client-options.rst @@ -307,10 +307,117 @@ 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 `_: + :: + + 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 it is :code:`openvpn` registered service name. + + 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 + ```` 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 partucular DNS SRV record next resolves to multiple IP addresses, + OpenVPN will try them in the order that the system getaddrinfo() + presents them, so priorization 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 + _openvpn._udp.example.net IN SRV 10 60 1194 server1.example.net + _openvpn._udp.example.net IN SRV 10 40 1194 server2.example.net + _openvpn._udp.example.net IN SRV 10 0 1194 server3.example.net + _openvpn._tcp.example.net 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. + + 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. + When multiple ``--remote`` address/ports or ``--remote-srv`` records are + specified, or if connection profiles are being used, initially randomize + the order of the list as a kind of basic load-balancing measure. --remote-random-hostname Prepend a random string (6 bytes, 12 hex characters) to hostname to @@ -318,8 +425,8 @@ configuration. ".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..d06a4782 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 acceted 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 d1ad5c8f..af50f897 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; } @@ -456,10 +462,16 @@ init_connection_list(struct context *c) static void clear_remote_addrlist(struct link_socket_addr *lsa, bool free) { - if (lsa->remote_list && free) + if (lsa->service_list && free) + { + freeservinfo(lsa->service_list); + } + else if (lsa->remote_list && free) { freeaddrinfo(lsa->remote_list); } + lsa->service_list = NULL; + lsa->current_service = NULL; lsa->remote_list = NULL; lsa->current_remote = NULL; } @@ -492,6 +504,18 @@ 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 */ + else if (c->c1.link_socket_addr.current_service + && c->c1.link_socket_addr.current_service->next) + { + c->c1.link_socket_addr.current_service = + c->c1.link_socket_addr.current_service->next; + c->c1.link_socket_addr.remote_list = + c->c1.link_socket_addr.current_service->ai; + c->c1.link_socket_addr.current_remote = + c->c1.link_socket_addr.remote_list; + } else { /* FIXME (schwabe) fix the persist-remote-ip option for real, @@ -500,12 +524,14 @@ 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. + * skipped on the previous loop either by management or due + * inappropriate service protocol. * If so, clear the addrinfo 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, !c->options.resolve_in_advance); @@ -514,6 +540,17 @@ next_connection_entry(struct context *c) /* 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 if (c->c1.link_socket_addr.service_list) + { + c->c1.link_socket_addr.current_service = + c->c1.link_socket_addr.service_list; + c->c1.link_socket_addr.remote_list = + c->c1.link_socket_addr.current_service->ai; + c->c1.link_socket_addr.current_remote = + c->c1.link_socket_addr.remote_list; } else { @@ -549,6 +586,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)) { @@ -3646,7 +3689,10 @@ do_close_link_socket(struct context *c) && ( (c->options.persist_remote_ip) || ( 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_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)) ))) { @@ -4182,6 +4228,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 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console @@ -107,7 +107,7 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console @@ -122,7 +122,7 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console @@ -137,7 +137,7 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 4b22d3d9..3c310fc1 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" @@ -1446,6 +1447,7 @@ show_connection_entry(const struct connection_entry *o) SHOW_STR(local_port); SHOW_STR(remote); SHOW_STR(remote_port); + SHOW_BOOL(remote_srv); SHOW_BOOL(remote_float); SHOW_BOOL(bind_defined); SHOW_BOOL(bind_local); @@ -1980,6 +1982,61 @@ connection_entry_load_re(struct connection_entry *ce, const struct remote_entry { ce->af = re->af; } + ce->remote_srv = re->remote_srv; +} + +static bool +options_postprocess_verify_ce_proto(const struct options *options, + const struct connection_entry *ce) +{ + int level = 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(level, "--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(level, "--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(level, "--fragment can only be used with --proto udp"); + return false; + } +#endif + + if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification) + { + msg(level, "--explicit-exit-notify can only be used with --proto udp"); + return false; + } + + if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT) + { + msg(level, "--http-proxy MUST be used in TCP Client mode " + "(i.e. --proto tcp-client)"); + return false; + } + + return true; } static void @@ -2014,6 +2071,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec msg(M_USAGE, "--proto tcp is ambiguous in this context. Please specify --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 */ @@ -2023,9 +2085,10 @@ options_postprocess_verify_ce(const struct options *options, const struct connec 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) @@ -2068,7 +2131,8 @@ options_postprocess_verify_ce(const struct options *options, const struct connec msg(M_USAGE, "only one of --tun-mtu or --link-mtu may be defined (note that --ifconfig implies --link-mtu %d)", LINK_MTU_DEFAULT); } - if (!proto_is_udp(ce->proto) && options->mtu_test) + if (!proto_is_udp(ce->proto) && options->mtu_test + && ce->proto != PROTO_AUTO) { msg(M_USAGE, "--mtu-test only makes sense with --proto udp"); } @@ -2082,6 +2146,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec * 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)) @@ -2199,13 +2268,15 @@ options_postprocess_verify_ce(const struct options *options, const struct connec */ #ifdef ENABLE_FRAGMENT - if (!proto_is_udp(ce->proto) && ce->fragment) + if (!proto_is_udp(ce->proto) && ce->fragment + && 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) + if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification + && ce->proto != PROTO_AUTO) { msg(M_USAGE, "--explicit-exit-notify can only be used with --proto udp"); } @@ -2215,10 +2286,12 @@ options_postprocess_verify_ce(const struct options *options, const struct connec msg(M_USAGE, "--remote MUST be used in TCP Client mode"); } - if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT) + if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT + && ce->proto != PROTO_AUTO) { 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) { msg(M_USAGE, "--http-proxy not specified but other http proxy options present"); @@ -2280,6 +2353,10 @@ options_postprocess_verify_ce(const struct options *options, const struct connec { 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"); @@ -2794,6 +2871,59 @@ options_postprocess_verify_ce(const struct options *options, const struct connec uninit_options(&defaults); } +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->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) { @@ -2817,23 +2947,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; } @@ -2854,24 +2968,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 */ @@ -2939,6 +3035,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 @@ -5676,6 +5798,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; @@ -5690,7 +5813,50 @@ add_option(struct options *options, const sa_family_t af = ascii2af(p[3]); if (proto < 0) { - msg(msglevel, "remote: bad protocol associated with host %s: '%s'", p[1], p[3]); + msg(msglevel, "remote: bad protocol associated " + "with host %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) + { + 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; @@ -6223,7 +6389,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 877e9396..cf22ffea 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 @@ -787,6 +789,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 76bdbfc5..4c811b24 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -260,22 +260,37 @@ get_cached_dns_entry(struct cached_dns_entry *dns_cache, const char *servname, int ai_family, int resolve_flags, - struct addrinfo **ai) + struct addrinfo **ai, + struct servinfo **si) { struct cached_dns_entry *ph; int flags; /* Only use flags that are relevant for the structure */ - flags = resolve_flags & GETADDR_CACHE_MASK; + if (si) + { + flags = resolve_flags & GETADDR_CACHE_SERVICE_MASK; + } + else + { + flags = resolve_flags & GETADDR_CACHE_MASK; + } for (ph = dns_cache; ph; ph = ph->next) { if (streqnull(ph->hostname, hostname) && streqnull(ph->servname, servname) - && ph->ai_family == ai_family + && (si || ph->ai_family == ai_family) && ph->flags == flags) { - *ai = ph->ai; + if (si) + { + *si = ph->si; + } + else if (ai) + { + *ai = ph->ai; + } return 0; } } @@ -298,7 +313,7 @@ do_preresolve_host(struct context *c, servname, af, flags, - &ai) == 0) + &ai, NULL) == 0) { /* entry already cached, return success */ return 0; @@ -337,6 +352,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_dns_entry(c->c1.dns_cache, + hostname, + servname, + af, + flags, + NULL, &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,10 +461,34 @@ 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); + status = do_preresolve_host(c, remote, ce->remote_port, + ce->af, flags); if (status != 0) { goto err; @@ -417,7 +527,8 @@ do_preresolve(struct context *c) { flags |= GETADDR_PASSIVE; flags &= ~GETADDR_RANDOMIZE; - status = do_preresolve_host(c, ce->local, ce->local_port, ce->af, flags); + status = do_preresolve_host(c, ce->local, ce->local_port, + ce->af, flags); if (status != 0) { goto err; @@ -432,6 +543,612 @@ err: throw_signal_soft(SIGHUP, "Preresolving failed"); } +/* + * Allocates new service structure on heap and stores + * its initial host, port and proto values. + */ +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. + * Returns EAI_* status and service list on success in order of receive. + */ +static int +query_servinfo_win32(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: + return EAI_NONAME; /* HOST_NOT_FOUND */ + + case DNS_INFO_NO_RECORDS: + return EAI_NODATA; /* NO_DATA */ + + case DNS_ERROR_NO_DNS_SERVERS: + case DNS_ERROR_RCODE_FORMAT_ERROR: + case DNS_ERROR_RCODE_NOT_IMPLEMENTED: + case DNS_ERROR_RCODE_REFUSED: + return EAI_FAIL; /* NO_RECOVERY */ + + case ERROR_TIMEOUT: + case DNS_ERROR_RCODE_SERVER_FAILURE: + case DNS_ERROR_TRY_AGAIN_LATER: + return EAI_AGAIN; /* TRY_AGAIN */ + + default: + return EAI_NODATA; + } + + 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_NODATA; + } + +done: + DnsRecordListFree(pDnsRecord, DnsFreeParsedMessageFields); + return status; +} + +#else +/* + * Queries DNS SRV records for specified DNS domain. + * Returns EAI_* status and service list on success in order of receive. + */ +static int +query_servinfo(const char *domain, int proto, + struct servinfo *next, struct servinfo **res) +{ + unsigned char answer[NS_MAXMSG]; + int status; + + int n = res_query(domain, ns_c_in, ns_t_srv, answer, NS_MAXMSG); + 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: + return EAI_NONAME; + + case NO_ADDRESS: +#if NO_ADDRESS != NO_DATA + case NO_DATA: +#endif + return EAI_NODATA; + + case NO_RECOVERY: + return EAI_FAIL; + + case TRY_AGAIN: + return EAI_AGAIN; + } + return EAI_SYSTEM; + } + + ns_msg msg; + if (ns_initparse(answer, n, &msg) < 0) + { + return EAI_FAIL; + } + + 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_NODATA; + } + +done: + return status; +} +#endif + +/* + * Service structure compare function for server selection + * mechanism, defined in RFC 2782. + */ +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; +} + +/* + * Sort & order service list according server selection mechanism, + * defined in RFC 2782. + * Returns service list. Although specific elements can be freed, + * non-empty list can never become empty. + */ +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. + */ +#if 0 + /* + * Skip all the rest records with weight 0 after the last + * one with weight greater than 0. + */ + if (unordered.weight == 0 && si->weight) + { + freeservinfo(unordered.next); + unordered.next = NULL; + } + break; +#endif + } + weight -= si->weight; + } + } + } + + gc_free(&gc); + return ordered.next; +} + + +/* + * Resolves DNS SRV records for specified domain and service. + * Returns EAI_* status (like getaddrinfo) and service list, ordered + * according server selection mechanism, defined in RFC 2782. + */ +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; + } + +#ifdef _WIN32 + status = query_servinfo_win32(qname, proto[i].proto, list, &list); +#else + status = query_servinfo(qname, proto[i].proto, list, &list); +#endif + } + } + + if (list) + { + *res = sort_servinfo(list); + status = 0; + } + + return status; +} + +void +freeservinfo(struct servinfo *res) +{ + while (res) + { + struct servinfo *si = res; + res = res->next; + freeaddrinfo(si->ai); + 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. @@ -526,7 +1243,9 @@ openvpn_getaddrinfo(unsigned int flags, if ((flags & GETADDR_MENTION_RESOLVE_RETRY) && !resolve_retry_seconds) { - fmt = "RESOLVE: Cannot resolve host address: %s:%s (%s) (I would have retried this name query if you had specified the --resolv-retry option.)"; + fmt = "RESOLVE: Cannot resolve host address: %s:%s (%s) " + "(I would have retried this name query if you had " + "specified the --resolv-retry option.)"; } if (!(flags & GETADDR_RESOLVE) || status == EAI_FAIL) @@ -558,12 +1277,15 @@ openvpn_getaddrinfo(unsigned int flags, while (true) { #ifndef _WIN32 + /* force resolv.conf reload */ res_init(); #endif /* try hostname lookup */ hints.ai_flags &= ~AI_NUMERICHOST; - dmsg(D_SOCKET_DEBUG, "GETADDRINFO flags=0x%04x ai_family=%d ai_socktype=%d", - flags, hints.ai_family, hints.ai_socktype); + dmsg(D_SOCKET_DEBUG, + "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) @@ -573,7 +1295,8 @@ openvpn_getaddrinfo(unsigned int flags, { if (*signal_received == SIGUSR1) /* ignore SIGUSR1 */ { - msg(level, "RESOLVE: Ignored SIGUSR1 signal received during DNS resolution attempt"); + msg(level, "RESOLVE: Ignored SIGUSR1 signal received " + "during DNS resolution attempt"); *signal_received = 0; } else @@ -634,7 +1357,8 @@ openvpn_getaddrinfo(unsigned int flags, /* IP address parse succeeded */ if (flags & GETADDR_RANDOMIZE) { - msg(M_WARN, "WARNING: ignoring --remote-random-hostname because the hostname is an IP address"); + msg(M_WARN, "WARNING: ignoring --remote-random-hostname " + "because the hostname is an IP address"); } } @@ -1707,7 +2431,7 @@ resolve_bind_local(struct link_socket *sock, const sa_family_t af) sock->local_port, af, flags, - &sock->info.lsa->bind_local); + &sock->info.lsa->bind_local, NULL); if (status) { @@ -1790,7 +2514,7 @@ resolve_remote(struct link_socket *sock, sock->remote_host, sock->remote_port, sock->info.af, - flags, &ai); + flags, &ai, NULL); if (status) { status = openvpn_getaddrinfo(flags, sock->remote_host, sock->remote_port, @@ -1801,8 +2525,13 @@ resolve_remote(struct link_socket *sock, { sock->info.lsa->remote_list = ai; sock->info.lsa->current_remote = ai; + if (sock->info.lsa->current_service) + { + sock->info.lsa->current_service->ai = ai; + } - dmsg(D_SOCKET_DEBUG, "RESOLVE_REMOTE flags=0x%04x phase=%d rrs=%d sig=%d status=%d", + dmsg(D_SOCKET_DEBUG, + "RESOLVE_REMOTE flags=0x%04x phase=%d rrs=%d sig=%d status=%d", flags, phase, retry, @@ -1851,7 +2580,108 @@ 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_dns_entry(c->c1.dns_cache, + ce->remote, + ce->remote_port, + ce->af, + flags, NULL, &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", + np(si->hostname), np(si->servname), + proto2ascii(si->proto, ce->af, false)); + + 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) @@ -3161,16 +3991,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}, + {"tcp6", "TCPv6", AF_INET6, PROTO_TCP}, + {"auto6", "AUTOv6", AF_INET6, PROTO_AUTO}, }; bool @@ -3180,14 +4013,21 @@ proto_is_net(int proto) { ASSERT(0); } - return proto != PROTO_NONE; + return proto != PROTO_NONE && proto != PROTO_AUTO; } + bool 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) { @@ -3266,6 +4106,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..8a267581 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,13 +76,29 @@ 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; const char *servname; int ai_family; int flags; - struct addrinfo *ai; + union { + struct addrinfo *ai; + struct servinfo *si; + }; struct cached_dns_entry *next; }; @@ -106,6 +127,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 +362,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 +512,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 +559,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 +592,28 @@ 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 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 8342eae0..e5852753 100644 --- a/src/openvpn/syshead.h +++ b/src/openvpn/syshead.h @@ -38,6 +38,7 @@ #ifdef _WIN32 #include +#include #include #include #define sleep(x) Sleep((x)*1000) @@ -176,6 +177,10 @@ #include #endif +#ifdef HAVE_ARPA_NAMESER_H +#include +#endif + #ifdef HAVE_RESOLV_H #include #endif From patchwork Sun Sep 20 10:57:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Vladislav Grishenko X-Patchwork-Id: 1464 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director9.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id EIFKBoDCZ19rIAAAIUCqbw (envelope-from ) for ; Sun, 20 Sep 2020 16:58:40 -0400 Received: from proxy17.mail.iad3b.rsapps.net ([172.31.255.6]) by director9.mail.ord1d.rsapps.net with LMTP id +KkvBoDCZ18FLAAAalYnBA (envelope-from ) for ; Sun, 20 Sep 2020 16:58:40 -0400 Received: from smtp40.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy17.mail.iad3b.rsapps.net with LMTPS id kKyhOn/CZ18+fwAA5ccGVQ (envelope-from ) for ; Sun, 20 Sep 2020 16:58:39 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp40.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dkim=fail (signature verification failed) header.d=yandex-team.ru; dmarc=fail (p=none; dis=none) header.from=yandex-team.ru X-Suspicious-Flag: YES X-Classification-ID: 0e5034c4-fb84-11ea-869b-5254000cc6d4-1-1 Received: from [216.105.38.7] ([216.105.38.7:43434] helo=lists.sourceforge.net) by smtp40.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id EF/A3-29889-E72C76F5; Sun, 20 Sep 2020 16:58:39 -0400 Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1kK6P8-0003A5-3j; Sun, 20 Sep 2020 20:57:50 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kK6P6-00039x-IV for openvpn-devel@lists.sourceforge.net; Sun, 20 Sep 2020 20:57:48 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:To: From:Sender:Reply-To:Cc:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=7iQydbnqDjwGOzuDoElFi4yPpBSamPb/87trbJCVeCE=; b=gRMWf/5Qe4n9+B/np2db+XOG+D NEtfSeqYlKlVyf28HWc7kbNVCrfj2lXZZLJr46Uwwu4VZ1BxK5SUwVXzqTBvzo6tamz0+vJ/Hr93o pOnmQ3UUaXWei/1nYi5ejmN7gst+CthsjEl6pvZUZAB7VRw5tRSTxlA2Gfd5inYuY6OE=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc :MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=7iQydbnqDjwGOzuDoElFi4yPpBSamPb/87trbJCVeCE=; b=I0OSpEJH8hHm+u8ubKljwTsaKi NuNHXfED9kb/G4P1Fp3HFAl7o28icg+mLCn1Zb2AMYGpW2co0QHLBR+dOomNS3gMsDAQPWTKMIIri cgbTVu94MfWAGcRW+0a0TWqt/QzMx7w2rwuR9PjxkR1ZNyHkZl9RiNZOoI/1OS2/0zn4=; Received: from forwardcorp1p.mail.yandex.net ([77.88.29.217]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92.2) id 1kK6Ov-000wBu-U7 for openvpn-devel@lists.sourceforge.net; Sun, 20 Sep 2020 20:57:48 +0000 Received: from iva8-d077482f1536.qloud-c.yandex.net (iva8-d077482f1536.qloud-c.yandex.net [IPv6:2a02:6b8:c0c:2f26:0:640:d077:482f]) by forwardcorp1p.mail.yandex.net (Yandex) with ESMTP id B94D12E07F2 for ; Sun, 20 Sep 2020 23:57:21 +0300 (MSK) Received: from iva8-88b7aa9dc799.qloud-c.yandex.net (iva8-88b7aa9dc799.qloud-c.yandex.net [2a02:6b8:c0c:77a0:0:640:88b7:aa9d]) by iva8-d077482f1536.qloud-c.yandex.net (mxbackcorp/Yandex) with ESMTP id dIFYOkBqKM-vLv03nbO; Sun, 20 Sep 2020 23:57:21 +0300 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yandex-team.ru; s=default; t=1600635441; bh=7iQydbnqDjwGOzuDoElFi4yPpBSamPb/87trbJCVeCE=; h=In-Reply-To:Message-Id:References:Date:Subject:To:From; b=pVG0EgjUUnmi6nKH2rLwsqeHJNZnkYXRR8S9beURPss0WTcy6yEDnwu0ZrcDlOov1 q+rKL6z6j5a+wYhqNq0FJMRnR9H3VnO1r/1xrG5ps4kfrOeAvYuaDNkiLcmswExjnM vt6Pywe0zBEbrjaeOTesvSEt5cTVz7uTDbU+33Oc= Received: from 37.9.104.38-iva.dhcp.yndx.net (37.9.104.38-iva.dhcp.yndx.net [37.9.104.38]) by iva8-88b7aa9dc799.qloud-c.yandex.net (smtpcorp/Yandex) with ESMTPSA id 96MdP86UJo-vLmKjWqI; Sun, 20 Sep 2020 23:57:21 +0300 (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (Client certificate not present) From: Vladislav Grishenko To: openvpn-devel@lists.sourceforge.net Date: Mon, 21 Sep 2020 01:57:04 +0500 Message-Id: <20200920205704.18274-2-themiron@yandex-team.ru> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200920205704.18274-1-themiron@yandex-team.ru> References: <20200917131223.11519-1-themiron@yandex-team.ru> <20200920205704.18274-1-themiron@yandex-team.ru> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: yandex-team.ru] -0.0 SPF_PASS SPF: sender matches SPF record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid X-Headers-End: 1kK6Ov-000wBu-U7 Subject: [Openvpn-devel] [PATCH v6 2/2] Add DNS SRV remote host discovery support X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox 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: remote server1.example.net 1194 udp remote server2.example.net 1194 udp remote server3.example.net 1194 udp remote server4.example.net 443 tcp now it's possible to specify just one --remote-srv: remote-srv example.net and configure following DNS SRV records: name prio weight port target _openvpn._udp.example.net IN SRV 10 60 1194 server1.example.net _openvpn._udp.example.net IN SRV 10 40 1194 server2.example.net _openvpn._udp.example.net IN SRV 10 0 1194 server3.example.net _openvpn._tcp.example.net 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. Tested on Linux/Glibc & Windows 10. v2: use DNS SRV for feature naming change option name to --remote-service-discovery add --remote-service-discovery [service] optional parameter support rewrite man, add algo/prio/weight description and examples fix random weight selection was not implicit remove pulled REMOTE_DISCOVERY support, not needed split windows/unix-specific parts into extra functions rename functions into servinfo scope, add doxygen comments when appropriate remove addrinfo hack, use servinfo containers of addrinfo list instead better proxy support (tcp mode not supported so far) log discovery attempts and results, if enabled v3: complete logic rewrite use separate --remote-srv [service] [proto] option remove fallback, same is achieved with additional --remote/--remote-srv add "auto" protocol support to allow both udp & tcp hosts be discovered add support for tcp / http proxy (natively) man update v4: due RFC 2782 ambiguity, prefer to use all resolved DNS SRV records, even ones with weight 0 after the records containing weights greater than 0 were all selected, keep related code disabled for historical reasons. man update v5: rebase against upstream with connection advancing fix allow management skip/accept for exact remote service hosts as for --remote improve compatibility with a way "--persist-remote-ip" is handled ensure max line length is 80 v6: pick out code-style conformant changes into separate patch add more options checks and comments fix typos Signed-off-by: Vladislav Grishenko --- configure.ac | 2 +- doc/man-sections/client-options.rst | 117 +++- doc/management-notes.txt | 6 + src/openvpn/Makefile.am | 2 +- src/openvpn/buffer.h | 5 - src/openvpn/errlevel.h | 1 + src/openvpn/init.c | 66 ++- src/openvpn/openvpn.vcxproj | 8 +- src/openvpn/options.c | 262 +++++++-- src/openvpn/options.h | 4 + src/openvpn/socket.c | 857 +++++++++++++++++++++++++++- src/openvpn/socket.h | 56 +- src/openvpn/syshead.h | 5 + 13 files changed, 1312 insertions(+), 79 deletions(-) diff --git a/configure.ac b/configure.ac index ebb32204..22088aaf 100644 --- a/configure.ac +++ b/configure.ac @@ -477,7 +477,7 @@ SOCKET_INCLUDES=" " AC_CHECK_HEADERS( - [net/if.h netinet/ip.h resolv.h sys/un.h net/if_utun.h sys/kern_control.h], + [net/if.h netinet/ip.h arpa/nameser.h resolv.h sys/un.h net/if_utun.h sys/kern_control.h], , , [[${SOCKET_INCLUDES}]] diff --git a/doc/man-sections/client-options.rst b/doc/man-sections/client-options.rst index af21fbcd..4445b4a8 100644 --- a/doc/man-sections/client-options.rst +++ b/doc/man-sections/client-options.rst @@ -307,10 +307,117 @@ 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 `_: + :: + + 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 it is :code:`openvpn` registered service name. + + 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 + ```` 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 + _openvpn._udp.example.net IN SRV 10 60 1194 server1.example.net + _openvpn._udp.example.net IN SRV 10 40 1194 server2.example.net + _openvpn._udp.example.net IN SRV 10 0 1194 server3.example.net + _openvpn._tcp.example.net 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. + + 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. + When multiple ``--remote`` address/ports or ``--remote-srv`` records are + specified, or if connection profiles are being used, initially randomize + the order of the list as a kind of basic load-balancing measure. --remote-random-hostname Prepend a random string (6 bytes, 12 hex characters) to hostname to @@ -318,8 +425,8 @@ configuration. ".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 31ecadcc..3b9259a3 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; } @@ -456,10 +462,16 @@ init_connection_list(struct context *c) static void clear_remote_addrlist(struct link_socket_addr *lsa, bool free) { - if (lsa->remote_list && free) + if (lsa->service_list && free) + { + freeservinfo(lsa->service_list); + } + else if (lsa->remote_list && free) { freeaddrinfo(lsa->remote_list); } + lsa->service_list = NULL; + lsa->current_service = NULL; lsa->remote_list = NULL; lsa->current_remote = NULL; } @@ -492,6 +504,18 @@ 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 */ + else if (c->c1.link_socket_addr.current_service + && c->c1.link_socket_addr.current_service->next) + { + c->c1.link_socket_addr.current_service = + c->c1.link_socket_addr.current_service->next; + c->c1.link_socket_addr.remote_list = + c->c1.link_socket_addr.current_service->ai; + c->c1.link_socket_addr.current_remote = + c->c1.link_socket_addr.remote_list; + } else { /* FIXME (schwabe) fix the persist-remote-ip option for real, @@ -500,12 +524,14 @@ 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. + * skipped on the previous loop either by management or due + * inappropriate service protocol. * If so, clear the addrinfo 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, !c->options.resolve_in_advance); @@ -514,6 +540,17 @@ next_connection_entry(struct context *c) /* 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 if (c->c1.link_socket_addr.service_list) + { + c->c1.link_socket_addr.current_service = + c->c1.link_socket_addr.service_list; + c->c1.link_socket_addr.remote_list = + c->c1.link_socket_addr.current_service->ai; + c->c1.link_socket_addr.current_remote = + c->c1.link_socket_addr.remote_list; } else { @@ -549,6 +586,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)) { @@ -3648,6 +3691,8 @@ 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)) ))) { @@ -4183,6 +4228,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 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console @@ -107,7 +107,7 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console @@ -122,7 +122,7 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console @@ -137,7 +137,7 @@ - legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;%(AdditionalDependencies) + legacy_stdio_definitions.lib;Ncrypt.lib;libssl.lib;libcrypto.lib;lzo2.lib;pkcs11-helper.dll.lib;gdi32.lib;ws2_32.lib;wininet.lib;crypt32.lib;iphlpapi.lib;winmm.lib;Fwpuclnt.lib;Rpcrt4.lib;setupapi.lib;dnsapi.lib;%(AdditionalDependencies) $(OPENSSL_HOME)/lib;$(LZO_HOME)/lib;$(PKCS11H_HOME)/lib;%(AdditionalLibraryDirectories) Console diff --git a/src/openvpn/options.c b/src/openvpn/options.c index ed4229c0..e1672239 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" @@ -1446,6 +1447,7 @@ show_connection_entry(const struct connection_entry *o) SHOW_STR(local_port); SHOW_STR(remote); SHOW_STR(remote_port); + SHOW_BOOL(remote_srv); SHOW_BOOL(remote_float); SHOW_BOOL(bind_defined); SHOW_BOOL(bind_local); @@ -1980,6 +1982,64 @@ 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 level = 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(level, "--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(level, "--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(level, "--fragment can only be used with --proto udp"); + return false; + } +#endif + + if (!proto_is_udp(ce->proto) && ce->explicit_exit_notification) + { + msg(level, "--explicit-exit-notify can only be used with --proto udp"); + return false; + } + + if ((ce->http_proxy_options) && ce->proto != PROTO_TCP_CLIENT) + { + msg(level, "--http-proxy MUST be used in TCP Client mode " + "(i.e. --proto tcp-client)"); + return false; + } + + return true; } static void @@ -2016,6 +2076,11 @@ options_postprocess_verify_ce(const struct options *options, "Please specify --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 */ @@ -2025,9 +2090,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) @@ -2071,7 +2137,9 @@ options_postprocess_verify_ce(const struct options *options, "(note that --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"); } @@ -2085,6 +2153,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)) @@ -2206,13 +2279,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"); } @@ -2222,7 +2299,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 tcp-client)"); @@ -2290,6 +2369,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"); @@ -2804,28 +2887,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 P2MP - if (o->client) - { - if (ce->proto == PROTO_TCP) - { - ce->proto = PROTO_TCP_CLIENT; - } - } -#endif + bool result = true; if (ce->proto == PROTO_TCP_CLIENT && !ce->local && !ce->local_port_defined && !ce->bind_defined) @@ -2847,25 +2916,10 @@ 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)) { - ce->flags |= CE_DISABLED; - } - - /* - * If --mssfix is supplied without a parameter, default - * it to --fragment value, if --fragment is specified. - */ - if (o->ce.mssfix_default) - { -#ifdef ENABLE_FRAGMENT - if (ce->fragment) - { - ce->mssfix = ce->fragment; - } -#else - msg(M_USAGE, "--mssfix must specify a parameter"); -#endif + result = false; } /* our socks code is not fully IPv6 enabled yet (TCP works, UDP not) @@ -2886,6 +2940,53 @@ options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce) ce->af = AF_INET; } + 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 P2MP + if (o->client) + { + if (ce->proto == PROTO_TCP) + { + ce->proto = PROTO_TCP_CLIENT; + } + } +#endif + + if (!options_postprocess_mutate_ce_proto(o, ce)) + { + ce->flags |= CE_DISABLED; + } + + /* + * If --mssfix is supplied without a parameter, default + * it to --fragment value, if --fragment is specified. + */ + if (o->ce.mssfix_default) + { +#ifdef ENABLE_FRAGMENT + if (ce->fragment) + { + ce->mssfix = ce->fragment; + } +#else + msg(M_USAGE, "--mssfix must specify a parameter"); +#endif + } + /* * Set MTU defaults */ @@ -2953,6 +3054,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 @@ -5690,6 +5817,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; @@ -5726,6 +5854,48 @@ add_option(struct options *options, 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) + { + connection_entry_load_re(&options->ce, &re); + } + } else if (streq(p[0], "resolv-retry") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); @@ -6239,7 +6409,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 877e9396..cf22ffea 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 @@ -787,6 +789,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 296fb294..4c811b24 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -260,22 +260,37 @@ get_cached_dns_entry(struct cached_dns_entry *dns_cache, const char *servname, int ai_family, int resolve_flags, - struct addrinfo **ai) + struct addrinfo **ai, + struct servinfo **si) { struct cached_dns_entry *ph; int flags; /* Only use flags that are relevant for the structure */ - flags = resolve_flags & GETADDR_CACHE_MASK; + if (si) + { + flags = resolve_flags & GETADDR_CACHE_SERVICE_MASK; + } + else + { + flags = resolve_flags & GETADDR_CACHE_MASK; + } for (ph = dns_cache; ph; ph = ph->next) { if (streqnull(ph->hostname, hostname) && streqnull(ph->servname, servname) - && ph->ai_family == ai_family + && (si || ph->ai_family == ai_family) && ph->flags == flags) { - *ai = ph->ai; + if (si) + { + *si = ph->si; + } + else if (ai) + { + *ai = ph->ai; + } return 0; } } @@ -298,7 +313,7 @@ do_preresolve_host(struct context *c, servname, af, flags, - &ai) == 0) + &ai, NULL) == 0) { /* entry already cached, return success */ return 0; @@ -337,6 +352,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_dns_entry(c->c1.dns_cache, + hostname, + servname, + af, + flags, + NULL, &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 +461,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 +543,612 @@ err: throw_signal_soft(SIGHUP, "Preresolving failed"); } +/* + * Allocates new service structure on heap and stores + * its initial host, port and proto values. + */ +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. + * Returns EAI_* status and service list on success in order of receive. + */ +static int +query_servinfo_win32(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: + return EAI_NONAME; /* HOST_NOT_FOUND */ + + case DNS_INFO_NO_RECORDS: + return EAI_NODATA; /* NO_DATA */ + + case DNS_ERROR_NO_DNS_SERVERS: + case DNS_ERROR_RCODE_FORMAT_ERROR: + case DNS_ERROR_RCODE_NOT_IMPLEMENTED: + case DNS_ERROR_RCODE_REFUSED: + return EAI_FAIL; /* NO_RECOVERY */ + + case ERROR_TIMEOUT: + case DNS_ERROR_RCODE_SERVER_FAILURE: + case DNS_ERROR_TRY_AGAIN_LATER: + return EAI_AGAIN; /* TRY_AGAIN */ + + default: + return EAI_NODATA; + } + + 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_NODATA; + } + +done: + DnsRecordListFree(pDnsRecord, DnsFreeParsedMessageFields); + return status; +} + +#else +/* + * Queries DNS SRV records for specified DNS domain. + * Returns EAI_* status and service list on success in order of receive. + */ +static int +query_servinfo(const char *domain, int proto, + struct servinfo *next, struct servinfo **res) +{ + unsigned char answer[NS_MAXMSG]; + int status; + + int n = res_query(domain, ns_c_in, ns_t_srv, answer, NS_MAXMSG); + 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: + return EAI_NONAME; + + case NO_ADDRESS: +#if NO_ADDRESS != NO_DATA + case NO_DATA: +#endif + return EAI_NODATA; + + case NO_RECOVERY: + return EAI_FAIL; + + case TRY_AGAIN: + return EAI_AGAIN; + } + return EAI_SYSTEM; + } + + ns_msg msg; + if (ns_initparse(answer, n, &msg) < 0) + { + return EAI_FAIL; + } + + 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_NODATA; + } + +done: + return status; +} +#endif + +/* + * Service structure compare function for server selection + * mechanism, defined in RFC 2782. + */ +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; +} + +/* + * Sort & order service list according server selection mechanism, + * defined in RFC 2782. + * Returns service list. Although specific elements can be freed, + * non-empty list can never become empty. + */ +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. + */ +#if 0 + /* + * Skip all the rest records with weight 0 after the last + * one with weight greater than 0. + */ + if (unordered.weight == 0 && si->weight) + { + freeservinfo(unordered.next); + unordered.next = NULL; + } + break; +#endif + } + weight -= si->weight; + } + } + } + + gc_free(&gc); + return ordered.next; +} + + +/* + * Resolves DNS SRV records for specified domain and service. + * Returns EAI_* status (like getaddrinfo) and service list, ordered + * according server selection mechanism, defined in RFC 2782. + */ +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; + } + +#ifdef _WIN32 + status = query_servinfo_win32(qname, proto[i].proto, list, &list); +#else + status = query_servinfo(qname, proto[i].proto, list, &list); +#endif + } + } + + if (list) + { + *res = sort_servinfo(list); + status = 0; + } + + return status; +} + +void +freeservinfo(struct servinfo *res) +{ + while (res) + { + struct servinfo *si = res; + res = res->next; + freeaddrinfo(si->ai); + 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 +1283,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) @@ -1715,7 +2431,7 @@ resolve_bind_local(struct link_socket *sock, const sa_family_t af) sock->local_port, af, flags, - &sock->info.lsa->bind_local); + &sock->info.lsa->bind_local, NULL); if (status) { @@ -1798,7 +2514,7 @@ resolve_remote(struct link_socket *sock, sock->remote_host, sock->remote_port, sock->info.af, - flags, &ai); + flags, &ai, NULL); if (status) { status = openvpn_getaddrinfo(flags, sock->remote_host, sock->remote_port, @@ -1809,6 +2525,10 @@ resolve_remote(struct link_socket *sock, { sock->info.lsa->remote_list = ai; sock->info.lsa->current_remote = ai; + if (sock->info.lsa->current_service) + { + sock->info.lsa->current_service->ai = ai; + } dmsg(D_SOCKET_DEBUG, "RESOLVE_REMOTE flags=0x%04x phase=%d rrs=%d sig=%d status=%d", @@ -1860,7 +2580,108 @@ 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_dns_entry(c->c1.dns_cache, + ce->remote, + ce->remote_port, + ce->af, + flags, NULL, &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", + np(si->hostname), np(si->servname), + proto2ascii(si->proto, ce->af, false)); + + 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) @@ -3170,16 +3991,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 @@ -3189,7 +4013,7 @@ proto_is_net(int proto) { ASSERT(0); } - return proto != PROTO_NONE; + return proto != PROTO_NONE && proto != PROTO_AUTO; } bool @@ -3198,6 +4022,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) { @@ -3276,6 +4106,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..8a267581 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,13 +76,29 @@ 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; const char *servname; int ai_family; int flags; - struct addrinfo *ai; + union { + struct addrinfo *ai; + struct servinfo *si; + }; struct cached_dns_entry *next; }; @@ -106,6 +127,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 +362,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 +512,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 +559,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 +592,28 @@ 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 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 8342eae0..e5852753 100644 --- a/src/openvpn/syshead.h +++ b/src/openvpn/syshead.h @@ -38,6 +38,7 @@ #ifdef _WIN32 #include +#include #include #include #define sleep(x) Sleep((x)*1000) @@ -176,6 +177,10 @@ #include #endif +#ifdef HAVE_ARPA_NAMESER_H +#include +#endif + #ifdef HAVE_RESOLV_H #include #endif