Browse code

add support for --dns option

As a first step towards DNS configuration in openvpn and a unified way
to push DNS related settings to clients in v2 and v3, this commit adds
support for parsing the new --dns option. Later commits will add support
for setting up DNS on different platforms.

For now, --dns and DNS related --dhcp-option can be used together for
smoother transition. Settings from --dns will override ones --dhcp-option
where applicable.

For detailed information about the option consult the documentation in
this commit.

Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
Acked-by: Frank Lichtenheld <frank@lichtenheld.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20220323143452.1100446-1-heiko@ist.eigentlich.net>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg23997.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Heiko Hund authored on 2022/03/23 23:34:52
Showing 13 changed files
... ...
@@ -154,6 +154,65 @@ configuration.
154 154
 --connect-timeout n
155 155
   See ``--server-poll-timeout``.
156 156
 
157
+--dns args
158
+  Client DNS configuration to be used with the connection.
159
+
160
+  Valid syntaxes:
161
+  ::
162
+
163
+     dns search-domains domain [domain ...]
164
+     dns server n address addr[:port] [addr[:port]]
165
+     dns server n resolve-domains|exclude-domains domain [domain ...]
166
+     dns server n dnssec yes|optional|no
167
+     dns server n transport DoH|DoT|plain
168
+     dns server n sni server-name
169
+
170
+  The ``--dns search-domains`` directive takes one or more domain names
171
+  to be added as DNS domain suffixes. If it is repeated multiple times within
172
+  a configuration the domains are appended, thus e.g. domain names pushed by
173
+  a server will amend locally defined ones.
174
+
175
+  The ``--dns server`` directive is used to configure DNS server ``n``.
176
+  The server id ``n`` must be a value between -128 and 127. For pushed
177
+  DNS server options it must be between 0 and 127. The server id is used
178
+  to group options and also for ordering the list of configured DNS servers;
179
+  lower numbers come first. DNS servers being pushed to a client replace
180
+  already configured DNS servers with the same server id.
181
+
182
+  The ``address`` option configures the IPv4 and / or IPv6 address of
183
+  the DNS server. Optionally a port can be appended after a colon. IPv6
184
+  addresses need to be enclosed in brackets if a port is appended.
185
+
186
+  The ``resolve-domains`` and ``exclude-domains`` options take one or
187
+  more DNS domains which are explicitly resolved or explicitly not resolved
188
+  by a server. Only one of the options can be configured for a server.
189
+  ``resolve-domains`` is used to define a split-dns setup, where only
190
+  given domains are resolved by a server. ``exclude-domains`` is used to
191
+  define domains which will never be resolved by a server (e.g. domains
192
+  which can only be resolved locally). Systems which do not support fine
193
+  grained DNS domain configuration, will ignore these settings.
194
+
195
+  The ``dnssec`` option is used to configure validation of DNSSEC records.
196
+  While the exact semantics may differ for resolvers on different systems,
197
+  ``yes`` likely makes validation mandatory, ``no`` disables it, and ``optional``
198
+  uses it opportunistically.
199
+
200
+  The ``transport`` option enables DNS-over-HTTPS (``DoH``) or DNS-over-TLS (``DoT``)
201
+  for a DNS server. The ``sni`` option can be used with them to specify the
202
+  ``server-name`` for TLS server name indication.
203
+
204
+  Each server has to have at least one address configured for a configuration
205
+  to be valid. All the other options can be omitted.
206
+
207
+  Note that not all options may be supported on all platforms. As soon support
208
+  for different systems is implemented, information will be added here how
209
+  unsupported options are treated.
210
+
211
+  The ``--dns`` option will eventually obsolete the ``--dhcp-option`` directive.
212
+  Until then it will replace configuration at the places ``--dhcp-option`` puts it,
213
+  so that ``--dns`` overrides ``--dhcp-option``. Thus, ``--dns`` can be used today
214
+  to migrate from ``--dhcp-option``.
215
+
157 216
 --explicit-exit-notify n
158 217
   In UDP client mode or point-to-point mode, send server/peer an exit
159 218
   notification if tunnel is restarted or OpenVPN process is exited. In
... ...
@@ -588,6 +588,25 @@ instances.
588 588
     netsh.exe calls which sometimes just do not work right with interface
589 589
     names). Set prior to ``--up`` or ``--down`` script execution.
590 590
 
591
+:code:`dns_*`
592
+    The ``--dns`` configuration options will be made available to script
593
+    execution through this set of environment variables. Variables appear
594
+    only if the corresponding option has a value assigned. For the semantics
595
+    of each individual variable, please refer to the documentation for ``--dns``.
596
+
597
+    ::
598
+
599
+       dns_search_domain_{n}
600
+       dns_server_{n}_address4
601
+       dns_server_{n}_port4
602
+       dns_server_{n}_address6
603
+       dns_server_{n}_port6
604
+       dns_server_{n}_resolve_domain_{m}
605
+       dns_server_{n}_exclude_domain_{m}
606
+       dns_server_{n}_dnssec
607
+       dns_server_{n}_transport
608
+       dns_server_{n}_sni
609
+
591 610
 :code:`foreign_option_{n}`
592 611
     An option pushed via ``--push`` to a client which does not natively
593 612
     support it, such as ``--dhcp-option`` on a non-Windows system, will be
... ...
@@ -412,7 +412,7 @@ fast hardware. SSL/TLS authentication must be used in this mode.
412 412
 
413 413
   This is a partial list of options which can currently be pushed:
414 414
   ``--route``, ``--route-gateway``, ``--route-delay``,
415
-  ``--redirect-gateway``, ``--ip-win32``, ``--dhcp-option``,
415
+  ``--redirect-gateway``, ``--ip-win32``, ``--dhcp-option``, ``--dns``,
416 416
   ``--inactive``, ``--ping``, ``--ping-exit``, ``--ping-restart``,
417 417
   ``--setenv``, ``--auth-token``, ``--persist-key``, ``--persist-tun``,
418 418
   ``--echo``, ``--comp-lzo``, ``--socket-flags``, ``--sndbuf``,
... ...
@@ -54,6 +54,7 @@ openvpn_SOURCES = \
54 54
 	crypto_openssl.c crypto_openssl.h \
55 55
 	crypto_mbedtls.c crypto_mbedtls.h \
56 56
 	dhcp.c dhcp.h \
57
+	dns.c dns.h \
57 58
 	env_set.c env_set.h \
58 59
 	errlevel.h \
59 60
 	error.c error.h \
60 61
new file mode 100644
... ...
@@ -0,0 +1,516 @@
0
+/*
1
+ *  OpenVPN -- An application to securely tunnel IP networks
2
+ *             over a single UDP port, with support for SSL/TLS-based
3
+ *             session authentication and key exchange,
4
+ *             packet encryption, packet authentication, and
5
+ *             packet compression.
6
+ *
7
+ *  Copyright (C) 2022 OpenVPN Inc <sales@openvpn.net>
8
+ *
9
+ *  This program is free software; you can redistribute it and/or modify
10
+ *  it under the terms of the GNU General Public License version 2
11
+ *  as published by the Free Software Foundation.
12
+ *
13
+ *  This program is distributed in the hope that it will be useful,
14
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ *  GNU General Public License for more details.
17
+ *
18
+ *  You should have received a copy of the GNU General Public License along
19
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
20
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
+ */
22
+
23
+#ifdef HAVE_CONFIG_H
24
+#include "config.h"
25
+#elif defined(_MSC_VER)
26
+#include "config-msvc.h"
27
+#endif
28
+
29
+#include "syshead.h"
30
+
31
+#include "dns.h"
32
+#include "socket.h"
33
+
34
+/**
35
+ * Parses a string as port and stores it
36
+ *
37
+ * @param   port        Pointer to in_port_t where the port value is stored
38
+ * @param   addr        Port number as string
39
+ * @return              True if parsing was successful
40
+ */
41
+static bool
42
+dns_server_port_parse(in_port_t *port, char *port_str)
43
+{
44
+    char *endptr;
45
+    errno = 0;
46
+    unsigned long tmp = strtoul(port_str, &endptr, 10);
47
+    if (errno || *endptr != '\0' || tmp == 0 || tmp > UINT16_MAX)
48
+    {
49
+        return false;
50
+    }
51
+    *port = (in_port_t)tmp;
52
+    return true;
53
+}
54
+
55
+bool
56
+dns_server_addr_parse(struct dns_server *server, const char *addr)
57
+{
58
+    if (!addr)
59
+    {
60
+        return false;
61
+    }
62
+
63
+    char addrcopy[INET6_ADDRSTRLEN] = {0};
64
+    size_t copylen = 0;
65
+    in_port_t port = 0;
66
+    int af;
67
+
68
+    char *first_colon = strchr(addr, ':');
69
+    char *last_colon = strrchr(addr, ':');
70
+
71
+    if (!first_colon || first_colon == last_colon)
72
+    {
73
+        /* IPv4 address with optional port, e.g. 1.2.3.4 or 1.2.3.4:853 */
74
+        if (last_colon)
75
+        {
76
+            if (last_colon == addr || !dns_server_port_parse(&port, last_colon + 1))
77
+            {
78
+                return false;
79
+            }
80
+            copylen = first_colon - addr;
81
+        }
82
+        af = AF_INET;
83
+    }
84
+    else
85
+    {
86
+        /* IPv6 address with optional port, e.g. ab::cd or [ab::cd]:853 */
87
+        if (addr[0] == '[')
88
+        {
89
+            addr += 1;
90
+            char *bracket = last_colon - 1;
91
+            if (*bracket != ']' || bracket == addr || !dns_server_port_parse(&port, last_colon + 1))
92
+            {
93
+                return false;
94
+            }
95
+            copylen = bracket - addr;
96
+        }
97
+        af = AF_INET6;
98
+    }
99
+
100
+    /* Copy the address part into a temporary buffer and use that */
101
+    if (copylen)
102
+    {
103
+        if (copylen >= sizeof(addrcopy))
104
+        {
105
+            return false;
106
+        }
107
+        strncpy(addrcopy, addr, copylen);
108
+        addr = addrcopy;
109
+    }
110
+
111
+    struct addrinfo *ai = NULL;
112
+    if (openvpn_getaddrinfo(0, addr, NULL, 0, NULL, af, &ai) != 0)
113
+    {
114
+        return false;
115
+    }
116
+
117
+    if (ai->ai_family == AF_INET)
118
+    {
119
+        struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr;
120
+        server->addr4_defined = true;
121
+        server->addr4.s_addr = ntohl(sin->sin_addr.s_addr);
122
+        server->port4 = port;
123
+    }
124
+    else
125
+    {
126
+        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr;
127
+        server->addr6_defined = true;
128
+        server->addr6 = sin6->sin6_addr;
129
+        server->port6 = port;
130
+    }
131
+
132
+    freeaddrinfo(ai);
133
+    return true;
134
+}
135
+
136
+void
137
+dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc)
138
+{
139
+    /* Fast forward to the end of the list */
140
+    while (*entry)
141
+    {
142
+        entry = &((*entry)->next);
143
+    }
144
+
145
+    /* Append all domains to the end of the list */
146
+    while (*domains)
147
+    {
148
+        ALLOC_OBJ_CLEAR_GC(*entry, struct dns_domain, gc);
149
+        struct dns_domain *new = *entry;
150
+        new->name = *domains++;
151
+        entry = &new->next;
152
+    }
153
+}
154
+
155
+bool
156
+dns_server_priority_parse(long *priority, const char *str, bool pulled)
157
+{
158
+    char *endptr;
159
+    const long min = pulled ? 0 : INT8_MIN;
160
+    const long max = INT8_MAX;
161
+    long prio = strtol(str, &endptr, 10);
162
+    if (*endptr != '\0' || prio < min || prio > max)
163
+    {
164
+        return false;
165
+    }
166
+    *priority = prio;
167
+    return true;
168
+}
169
+
170
+struct dns_server *
171
+dns_server_get(struct dns_server **entry, long priority, struct gc_arena *gc)
172
+{
173
+    struct dns_server *obj = *entry;
174
+    while (true)
175
+    {
176
+        if (!obj || obj->priority > priority)
177
+        {
178
+            ALLOC_OBJ_CLEAR_GC(*entry, struct dns_server, gc);
179
+            (*entry)->next = obj;
180
+            (*entry)->priority = priority;
181
+            return *entry;
182
+        }
183
+        else if (obj->priority == priority)
184
+        {
185
+            return obj;
186
+        }
187
+        entry = &obj->next;
188
+        obj = *entry;
189
+    }
190
+}
191
+
192
+bool
193
+dns_options_verify(int msglevel, const struct dns_options *o)
194
+{
195
+    const struct dns_server *server =
196
+        o->servers ? o->servers : o->servers_prepull;
197
+    while (server)
198
+    {
199
+        if (!server->addr4_defined && !server->addr6_defined)
200
+        {
201
+            msg(msglevel, "ERROR: dns server %ld does not have an address assigned", server->priority);
202
+            return false;
203
+        }
204
+        server = server->next;
205
+    }
206
+    return true;
207
+}
208
+
209
+static struct dns_domain *
210
+clone_dns_domains(const struct dns_domain *domain, struct gc_arena *gc)
211
+{
212
+    struct dns_domain *new_list = NULL;
213
+    struct dns_domain **new_entry = &new_list;
214
+
215
+    while (domain)
216
+    {
217
+        ALLOC_OBJ_CLEAR_GC(*new_entry, struct dns_domain, gc);
218
+        struct dns_domain *new_domain = *new_entry;
219
+        *new_domain = *domain;
220
+        new_entry = &new_domain->next;
221
+        domain = domain->next;
222
+    }
223
+
224
+    return new_list;
225
+}
226
+
227
+static struct dns_server *
228
+clone_dns_servers(const struct dns_server *server, struct gc_arena *gc)
229
+{
230
+    struct dns_server *new_list = NULL;
231
+    struct dns_server **new_entry = &new_list;
232
+
233
+    while (server)
234
+    {
235
+        ALLOC_OBJ_CLEAR_GC(*new_entry, struct dns_server, gc);
236
+        struct dns_server *new_server = *new_entry;
237
+        *new_server = *server;
238
+        new_server->domains = clone_dns_domains(server->domains, gc);
239
+        new_entry = &new_server->next;
240
+        server = server->next;
241
+    }
242
+
243
+    return new_list;
244
+}
245
+
246
+struct dns_options
247
+clone_dns_options(const struct dns_options o, struct gc_arena *gc)
248
+{
249
+    struct dns_options clone;
250
+    memset(&clone, 0, sizeof(clone));
251
+    clone.search_domains = clone_dns_domains(o.search_domains, gc);
252
+    clone.servers = clone_dns_servers(o.servers, gc);
253
+    clone.servers_prepull = clone_dns_servers(o.servers_prepull, gc);
254
+    return clone;
255
+}
256
+
257
+void
258
+dns_options_preprocess_pull(struct dns_options *o)
259
+{
260
+    o->servers_prepull = o->servers;
261
+    o->servers = NULL;
262
+}
263
+
264
+void
265
+dns_options_postprocess_pull(struct dns_options *o)
266
+{
267
+    struct dns_server **entry = &o->servers;
268
+    struct dns_server *server = *entry;
269
+    struct dns_server *server_pp = o->servers_prepull;
270
+
271
+    while (server && server_pp)
272
+    {
273
+        if (server->priority > server_pp->priority)
274
+        {
275
+            /* Merge static server in front of pulled one */
276
+            struct dns_server *next_pp = server_pp->next;
277
+            server_pp->next = server;
278
+            *entry = server_pp;
279
+            server = *entry;
280
+            server_pp = next_pp;
281
+        }
282
+        else if (server->priority == server_pp->priority)
283
+        {
284
+            /* Pulled server overrides static one */
285
+            server_pp = server_pp->next;
286
+        }
287
+        entry = &server->next;
288
+        server = *entry;
289
+    }
290
+
291
+    /* Append remaining local servers */
292
+    if (server_pp)
293
+    {
294
+        *entry = server_pp;
295
+    }
296
+
297
+    o->servers_prepull = NULL;
298
+}
299
+
300
+static const char *
301
+dnssec_value(const enum dns_security dnssec)
302
+{
303
+    switch (dnssec)
304
+    {
305
+        case DNS_SECURITY_YES:
306
+            return "yes";
307
+
308
+        case DNS_SECURITY_OPTIONAL:
309
+            return "optional";
310
+
311
+        case DNS_SECURITY_NO:
312
+            return "no";
313
+
314
+        default:
315
+            return "unset";
316
+    }
317
+}
318
+
319
+static const char *
320
+transport_value(const enum dns_server_transport transport)
321
+{
322
+    switch (transport)
323
+    {
324
+        case DNS_TRANSPORT_HTTPS:
325
+            return "DoH";
326
+
327
+        case DNS_TRANSPORT_TLS:
328
+            return "DoT";
329
+
330
+        case DNS_TRANSPORT_PLAIN:
331
+            return "plain";
332
+
333
+        default:
334
+            return "unset";
335
+    }
336
+}
337
+
338
+static void
339
+setenv_dns_option(struct env_set *es,
340
+                  const char *format, int i, int j,
341
+                  const char *value)
342
+{
343
+    char name[64];
344
+    bool name_ok = false;
345
+
346
+    if (j < 0)
347
+    {
348
+        name_ok = openvpn_snprintf(name, sizeof(name), format, i);
349
+    }
350
+    else
351
+    {
352
+        name_ok = openvpn_snprintf(name, sizeof(name), format, i, j);
353
+    }
354
+
355
+    if (!name_ok)
356
+    {
357
+        msg(M_WARN, "WARNING: dns option setenv name buffer overflow");
358
+    }
359
+
360
+    setenv_str(es, name, value);
361
+}
362
+
363
+void
364
+setenv_dns_options(const struct dns_options *o, struct env_set *es)
365
+{
366
+    struct gc_arena gc = gc_new();
367
+    const struct dns_server *s;
368
+    const struct dns_domain *d;
369
+    int i, j;
370
+
371
+    for (i = 1, d = o->search_domains; d != NULL; i++, d = d->next)
372
+    {
373
+        setenv_dns_option(es, "dns_search_domain_%d", i, -1, d->name);
374
+    }
375
+
376
+    for (i = 1, s = o->servers; s != NULL; i++, s = s->next)
377
+    {
378
+        if (s->addr4_defined)
379
+        {
380
+            setenv_dns_option(es, "dns_server_%d_address4", i, -1,
381
+                              print_in_addr_t(s->addr4.s_addr, 0, &gc));
382
+        }
383
+        if (s->port4)
384
+        {
385
+            setenv_dns_option(es, "dns_server_%d_port4", i, -1,
386
+                              print_in_port_t(s->port4, &gc));
387
+        }
388
+
389
+        if (s->addr6_defined)
390
+        {
391
+            setenv_dns_option(es, "dns_server_%d_address6", i, -1,
392
+                              print_in6_addr(s->addr6, 0, &gc));
393
+        }
394
+        if (s->port6)
395
+        {
396
+            setenv_dns_option(es, "dns_server_%d_port6", i, -1,
397
+                              print_in_port_t(s->port6, &gc));
398
+        }
399
+
400
+        if (s->domains)
401
+        {
402
+            const char *format = s->domain_type == DNS_RESOLVE_DOMAINS ?
403
+                                 "dns_server_%d_resolve_domain_%d" : "dns_server_%d_exclude_domain_%d";
404
+            for (j = 1, d = s->domains; d != NULL; j++, d = d->next)
405
+            {
406
+                setenv_dns_option(es, format, i, j, d->name);
407
+            }
408
+        }
409
+
410
+        if (s->dnssec)
411
+        {
412
+            setenv_dns_option(es, "dns_server_%d_dnssec", i, -1,
413
+                              dnssec_value(s->dnssec));
414
+        }
415
+
416
+        if (s->transport)
417
+        {
418
+            setenv_dns_option(es, "dns_server_%d_transport", i, -1,
419
+                              transport_value(s->transport));
420
+        }
421
+        if (s->sni)
422
+        {
423
+            setenv_dns_option(es, "dns_server_%d_sni", i, -1, s->sni);
424
+        }
425
+    }
426
+
427
+    gc_free(&gc);
428
+}
429
+
430
+void
431
+show_dns_options(const struct dns_options *o)
432
+{
433
+    struct gc_arena gc = gc_new();
434
+
435
+    int i = 1;
436
+    struct dns_server *server = o->servers_prepull ? o->servers_prepull : o->servers;
437
+    while (server)
438
+    {
439
+        msg(D_SHOW_PARMS, "  DNS server #%d:", i++);
440
+
441
+        if (server->addr4_defined)
442
+        {
443
+            const char *addr = print_in_addr_t(server->addr4.s_addr, 0, &gc);
444
+            if (server->port4)
445
+            {
446
+                const char *port = print_in_port_t(server->port4, &gc);
447
+                msg(D_SHOW_PARMS, "    address4 = %s:%s", addr, port);
448
+            }
449
+            else
450
+            {
451
+                msg(D_SHOW_PARMS, "    address4 = %s", addr);
452
+            }
453
+        }
454
+        if (server->addr6_defined)
455
+        {
456
+            const char *addr = print_in6_addr(server->addr6, 0, &gc);
457
+            if (server->port6)
458
+            {
459
+                const char *port = print_in_port_t(server->port6, &gc);
460
+                msg(D_SHOW_PARMS, "    address6 = [%s]:%s", addr, port);
461
+            }
462
+            else
463
+            {
464
+                msg(D_SHOW_PARMS, "    address6 = %s", addr);
465
+            }
466
+        }
467
+
468
+        if (server->dnssec)
469
+        {
470
+            msg(D_SHOW_PARMS, "    dnssec = %s", dnssec_value(server->dnssec));
471
+        }
472
+
473
+        if (server->transport)
474
+        {
475
+            msg(D_SHOW_PARMS, "    transport = %s", transport_value(server->transport));
476
+        }
477
+        if (server->sni)
478
+        {
479
+            msg(D_SHOW_PARMS, "    sni = %s", server->sni);
480
+        }
481
+
482
+        struct dns_domain *domain = server->domains;
483
+        if (domain)
484
+        {
485
+            if (server->domain_type == DNS_RESOLVE_DOMAINS)
486
+            {
487
+                msg(D_SHOW_PARMS, "    resolve domains:");
488
+            }
489
+            else
490
+            {
491
+                msg(D_SHOW_PARMS, "    exclude domains:");
492
+            }
493
+            while (domain)
494
+            {
495
+                msg(D_SHOW_PARMS, "      %s", domain->name);
496
+                domain = domain->next;
497
+            }
498
+        }
499
+
500
+        server = server->next;
501
+    }
502
+
503
+    struct dns_domain *search_domain = o->search_domains;
504
+    if (search_domain)
505
+    {
506
+        msg(D_SHOW_PARMS, "  DNS search domains:");
507
+        while (search_domain)
508
+        {
509
+            msg(D_SHOW_PARMS, "    %s", search_domain->name);
510
+            search_domain = search_domain->next;
511
+        }
512
+    }
513
+
514
+    gc_free(&gc);
515
+}
0 516
new file mode 100644
... ...
@@ -0,0 +1,164 @@
0
+/*
1
+ *  OpenVPN -- An application to securely tunnel IP networks
2
+ *             over a single UDP port, with support for SSL/TLS-based
3
+ *             session authentication and key exchange,
4
+ *             packet encryption, packet authentication, and
5
+ *             packet compression.
6
+ *
7
+ *  Copyright (C) 2022 OpenVPN Inc <sales@openvpn.net>
8
+ *
9
+ *  This program is free software; you can redistribute it and/or modify
10
+ *  it under the terms of the GNU General Public License version 2
11
+ *  as published by the Free Software Foundation.
12
+ *
13
+ *  This program is distributed in the hope that it will be useful,
14
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ *  GNU General Public License for more details.
17
+ *
18
+ *  You should have received a copy of the GNU General Public License along
19
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
20
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
+ */
22
+
23
+#ifndef DNS_H
24
+#define DNS_H
25
+
26
+#include "buffer.h"
27
+#include "env_set.h"
28
+
29
+enum dns_domain_type {
30
+    DNS_DOMAINS_UNSET,
31
+    DNS_RESOLVE_DOMAINS,
32
+    DNS_EXCLUDE_DOMAINS
33
+};
34
+
35
+enum dns_security {
36
+    DNS_SECURITY_UNSET,
37
+    DNS_SECURITY_NO,
38
+    DNS_SECURITY_YES,
39
+    DNS_SECURITY_OPTIONAL
40
+};
41
+
42
+enum dns_server_transport {
43
+    DNS_TRANSPORT_UNSET,
44
+    DNS_TRANSPORT_PLAIN,
45
+    DNS_TRANSPORT_HTTPS,
46
+    DNS_TRANSPORT_TLS
47
+};
48
+
49
+struct dns_domain {
50
+    struct dns_domain *next;
51
+    const char *name;
52
+};
53
+
54
+struct dns_server {
55
+    struct dns_server *next;
56
+    long priority;
57
+    bool addr4_defined;
58
+    bool addr6_defined;
59
+    struct in_addr addr4;
60
+    struct in6_addr addr6;
61
+    in_port_t port4;
62
+    in_port_t port6;
63
+    struct dns_domain *domains;
64
+    enum dns_domain_type domain_type;
65
+    enum dns_security dnssec;
66
+    enum dns_server_transport transport;
67
+    const char *sni;
68
+};
69
+
70
+struct dns_options {
71
+    struct dns_domain *search_domains;
72
+    struct dns_server *servers_prepull;
73
+    struct dns_server *servers;
74
+    struct gc_arena gc;
75
+};
76
+
77
+/**
78
+ * Parses a string DNS server priority and validates it.
79
+ *
80
+ * @param   priority    Pointer to where the priority should be stored
81
+ * @param   str         Priority string to parse
82
+ * @param   pulled      Whether this was pulled from a server
83
+ * @return              True if priority in string is valid
84
+ */
85
+bool dns_server_priority_parse(long *priority, const char *str, bool pulled);
86
+
87
+/**
88
+ * Find or create DNS server with priority in a linked list.
89
+ * The list is ordered by priority.
90
+ *
91
+ * @param   entry       Address of the first list entry pointer
92
+ * @param   priority    Priority of the DNS server to find / create
93
+ * @param   gc          The gc new list items should be allocated in
94
+ */
95
+struct dns_server * dns_server_get(struct dns_server **entry, long priority, struct gc_arena *gc);
96
+
97
+/**
98
+ * Appends DNS domain parameters to a linked list.
99
+ *
100
+ * @param   entry       Address of the first list entry pointer
101
+ * @param   domains     Address of the first domain parameter
102
+ * @param   gc          The gc the new list items should be allocated in
103
+ */
104
+void dns_domain_list_append(struct dns_domain **entry, char **domains, struct gc_arena *gc);
105
+
106
+/**
107
+ * Parses a string IPv4 or IPv6 address and optional colon separated port,
108
+ * into a in_addr or in6_addr respectively plus a in_port_t port.
109
+ *
110
+ * @param   server      Pointer to DNS server the address is parsed for
111
+ * @param   addr        Address as string
112
+ * @return              True if parsing was successful
113
+ */
114
+bool dns_server_addr_parse(struct dns_server *server, const char *addr);
115
+
116
+/**
117
+ * Checks validity of DNS options
118
+ *
119
+ * @param   msglevel    The message level to log errors with
120
+ * @param   o           Pointer to the DNS options to validate
121
+ * @return              True if no error was found
122
+ */
123
+bool dns_options_verify(int msglevel, const struct dns_options *o);
124
+
125
+/**
126
+ * Makes a deep copy of the passed DNS options.
127
+ *
128
+ * @param   o           Pointer to the DNS options to clone
129
+ * @param   gc          Pointer to the gc_arena to use for the clone
130
+ * @return              The dns_options clone
131
+ */
132
+struct dns_options clone_dns_options(const struct dns_options o, struct gc_arena *gc);
133
+
134
+/**
135
+ * Saves and resets the server options, so that pulled ones don't mix in.
136
+ *
137
+ * @param   o           Pointer to the DNS options to modify
138
+ */
139
+void dns_options_preprocess_pull(struct dns_options *o);
140
+
141
+/**
142
+ * Merges pulled DNS servers with static ones into an ordered list.
143
+ *
144
+ * @param   o           Pointer to the DNS options to modify
145
+ */
146
+void dns_options_postprocess_pull(struct dns_options *o);
147
+
148
+/**
149
+ * Puts the DNS options into an environment set.
150
+ *
151
+ * @param   o           Pointer to the DNS options to set
152
+ * @param   es          Pointer to the env_set to set the options into
153
+ */
154
+void setenv_dns_options(const struct dns_options *o, struct env_set *es);
155
+
156
+/**
157
+ * Prints configured DNS options.
158
+ *
159
+ * @param   o           Pointer to the DNS options to print
160
+ */
161
+void show_dns_options(const struct dns_options *o);
162
+
163
+#endif /* ifndef DNS_H */
... ...
@@ -269,6 +269,7 @@
269 269
     <ClCompile Include="cryptoapi.c" />
270 270
     <ClCompile Include="env_set.c" />
271 271
     <ClCompile Include="dhcp.c" />
272
+    <ClCompile Include="dns.c" />
272 273
     <ClCompile Include="error.c" />
273 274
     <ClCompile Include="event.c" />
274 275
     <ClCompile Include="fdmisc.c" />
... ...
@@ -352,6 +353,7 @@
352 352
     <ClInclude Include="crypto_openssl.h" />
353 353
     <ClInclude Include="cryptoapi.h" />
354 354
     <ClInclude Include="dhcp.h" />
355
+    <ClInclude Include="dns.h" />
355 356
     <ClInclude Include="env_set.h" />
356 357
     <ClInclude Include="errlevel.h" />
357 358
     <ClInclude Include="error.h" />
... ...
@@ -444,4 +446,4 @@
444 444
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
445 445
   <ImportGroup Label="ExtensionTargets">
446 446
   </ImportGroup>
447
-</Project>
448 447
\ No newline at end of file
448
+</Project>
... ...
@@ -39,6 +39,9 @@
39 39
     <ClCompile Include="dhcp.c">
40 40
       <Filter>Source Files</Filter>
41 41
     </ClCompile>
42
+    <ClCompile Include="dns.c">
43
+      <Filter>Source Files</Filter>
44
+    </ClCompile>
42 45
     <ClCompile Include="error.c">
43 46
       <Filter>Source Files</Filter>
44 47
     </ClCompile>
... ...
@@ -296,6 +299,9 @@
296 296
     <ClInclude Include="dhcp.h">
297 297
       <Filter>Header Files</Filter>
298 298
     </ClInclude>
299
+    <ClInclude Include="dns.h">
300
+      <Filter>Header Files</Filter>
301
+    </ClInclude>
299 302
     <ClInclude Include="errlevel.h">
300 303
       <Filter>Header Files</Filter>
301 304
     </ClInclude>
... ...
@@ -535,4 +541,4 @@
535 535
       <Filter>Resource Files</Filter>
536 536
     </Manifest>
537 537
   </ItemGroup>
538
-</Project>
539 538
\ No newline at end of file
539
+</Project>
... ...
@@ -499,6 +499,16 @@ static const char usage_message[] =
499 499
     "                  ignore or reject causes the option to be allowed, removed or\n"
500 500
     "                  rejected with error. May be specified multiple times, and\n"
501 501
     "                  each filter is applied in the order of appearance.\n"
502
+    "--dns server <n> <option> <value> [value ...] : Configure option for DNS server #n\n"
503
+    "                  Valid options are :\n"
504
+    "                  address <addr[:port]> [addr[:port]] : server address 4/6\n"
505
+    "                  resolve-domains <domain> [domain ...] : split domains\n"
506
+    "                  exclude-domains <domain> [domain ...] : domains not to resolve\n"
507
+    "                  dnssec <yes|no|optional> : option to use DNSSEC\n"
508
+    "                  type <DoH|DoT> : query server over HTTPS / TLS\n"
509
+    "                  sni <domain> : DNS server name indication\n"
510
+    "--dns search-domains <domain> [domain ...]:\n"
511
+    "                  Add domains to DNS domain search list\n"
502 512
     "--auth-retry t  : How to handle auth failures.  Set t to\n"
503 513
     "                  none (default), interact, or nointeract.\n"
504 514
     "--static-challenge t e : Enable static challenge/response protocol using\n"
... ...
@@ -786,6 +796,7 @@ init_options(struct options *o, const bool init_gc)
786 786
     if (init_gc)
787 787
     {
788 788
         gc_init(&o->gc);
789
+        gc_init(&o->dns_options.gc);
789 790
         o->gc_owned = true;
790 791
     }
791 792
     o->mode = MODE_POINT_TO_POINT;
... ...
@@ -891,6 +902,7 @@ uninit_options(struct options *o)
891 891
     if (o->gc_owned)
892 892
     {
893 893
         gc_free(&o->gc);
894
+        gc_free(&o->dns_options.gc);
894 895
     }
895 896
 }
896 897
 
... ...
@@ -994,6 +1006,11 @@ setenv_settings(struct env_set *es, const struct options *o)
994 994
     {
995 995
         setenv_connection_entry(es, &o->ce, 1);
996 996
     }
997
+
998
+    if (!o->pull)
999
+    {
1000
+        setenv_dns_options(&o->dns_options, es);
1001
+    }
997 1002
 }
998 1003
 
999 1004
 static in_addr_t
... ...
@@ -1268,6 +1285,64 @@ dhcp_option_address_parse(const char *name, const char *parm, in_addr_t *array,
1268 1268
     }
1269 1269
 }
1270 1270
 
1271
+/*
1272
+ * If DNS options are set use these for TUN/TAP options as well.
1273
+ * Applies to DNS, DNS6 and DOMAIN-SEARCH.
1274
+ * Existing options will be discarded.
1275
+ */
1276
+static void
1277
+tuntap_options_copy_dns(struct options *o)
1278
+{
1279
+    struct tuntap_options *tt = &o->tuntap_options;
1280
+    struct dns_options *dns = &o->dns_options;
1281
+
1282
+    if (dns->search_domains)
1283
+    {
1284
+        tt->domain_search_list_len = 0;
1285
+        const struct dns_domain *domain = dns->search_domains;
1286
+        while (domain && tt->domain_search_list_len < N_SEARCH_LIST_LEN)
1287
+        {
1288
+            tt->domain_search_list[tt->domain_search_list_len++] = domain->name;
1289
+            domain = domain->next;
1290
+        }
1291
+        if (domain)
1292
+        {
1293
+            msg(M_WARN, "WARNING: couldn't copy all --dns search-domains to --dhcp-option");
1294
+        }
1295
+    }
1296
+
1297
+    if (dns->servers)
1298
+    {
1299
+        tt->dns_len = 0;
1300
+        tt->dns6_len = 0;
1301
+        bool overflow = false;
1302
+        const struct dns_server *server = dns->servers;
1303
+        while (server)
1304
+        {
1305
+            if (server->addr4_defined && tt->dns_len < N_DHCP_ADDR)
1306
+            {
1307
+                tt->dns[tt->dns_len++] = server->addr4.s_addr;
1308
+            }
1309
+            else
1310
+            {
1311
+                overflow = true;
1312
+            }
1313
+            if (server->addr6_defined && tt->dns6_len < N_DHCP_ADDR)
1314
+            {
1315
+                tt->dns6[tt->dns6_len++] = server->addr6;
1316
+            }
1317
+            else
1318
+            {
1319
+                overflow = true;
1320
+            }
1321
+            server = server->next;
1322
+        }
1323
+        if (overflow)
1324
+        {
1325
+            msg(M_WARN, "WARNING: couldn't copy all --dns server addresses to --dhcp-option");
1326
+        }
1327
+    }
1328
+}
1271 1329
 #endif /* if defined(_WIN32) || defined(TARGET_ANDROID) */
1272 1330
 
1273 1331
 #ifndef ENABLE_SMALL
... ...
@@ -1698,6 +1773,8 @@ show_settings(const struct options *o)
1698 1698
         print_client_nat_list(o->client_nat, D_SHOW_PARMS);
1699 1699
     }
1700 1700
 
1701
+    show_dns_options(&o->dns_options);
1702
+
1701 1703
 #ifdef ENABLE_MANAGEMENT
1702 1704
     SHOW_STR(management_addr);
1703 1705
     SHOW_STR(management_port);
... ...
@@ -3093,6 +3170,8 @@ options_postprocess_verify(const struct options *o)
3093 3093
     {
3094 3094
         options_postprocess_verify_ce(o, &o->ce);
3095 3095
     }
3096
+
3097
+    dns_options_verify(M_FATAL, &o->dns_options);
3096 3098
 }
3097 3099
 
3098 3100
 /**
... ...
@@ -3341,6 +3420,16 @@ options_postprocess_mutate(struct options *o)
3341 3341
      * Save certain parms before modifying options during connect, especially
3342 3342
      * when using --pull
3343 3343
      */
3344
+    if (o->pull)
3345
+    {
3346
+        dns_options_preprocess_pull(&o->dns_options);
3347
+    }
3348
+#if defined(_WIN32) || defined(TARGET_ANDROID)
3349
+    else
3350
+    {
3351
+        tuntap_options_copy_dns(o);
3352
+    }
3353
+#endif
3344 3354
     pre_connect_save(o);
3345 3355
 }
3346 3356
 
... ...
@@ -3686,6 +3775,25 @@ options_postprocess(struct options *options)
3686 3686
 }
3687 3687
 
3688 3688
 /*
3689
+ * Sanity check on options after more options were pulled from server.
3690
+ * Also time to modify some options based on other options.
3691
+ */
3692
+bool
3693
+options_postprocess_pull(struct options *o, struct env_set *es)
3694
+{
3695
+    bool success = dns_options_verify(D_PUSH_ERRORS, &o->dns_options);
3696
+    if (success)
3697
+    {
3698
+        dns_options_postprocess_pull(&o->dns_options);
3699
+        setenv_dns_options(&o->dns_options, es);
3700
+#if defined(_WIN32) || defined(TARGET_ANDROID)
3701
+        tuntap_options_copy_dns(o);
3702
+#endif
3703
+    }
3704
+    return success;
3705
+}
3706
+
3707
+/*
3689 3708
  * Save/Restore certain option defaults before --pull is applied.
3690 3709
  */
3691 3710
 
... ...
@@ -3716,6 +3824,8 @@ pre_connect_save(struct options *o)
3716 3716
     o->pre_connect->route_default_gateway = o->route_default_gateway;
3717 3717
     o->pre_connect->route_ipv6_default_gateway = o->route_ipv6_default_gateway;
3718 3718
 
3719
+    o->pre_connect->dns_options = clone_dns_options(o->dns_options, &o->gc);
3720
+
3719 3721
     /* NCP related options that can be overwritten by a push */
3720 3722
     o->pre_connect->ciphername = o->ciphername;
3721 3723
     o->pre_connect->authname = o->authname;
... ...
@@ -3766,6 +3876,12 @@ pre_connect_restore(struct options *o, struct gc_arena *gc)
3766 3766
         o->route_default_gateway = pp->route_default_gateway;
3767 3767
         o->route_ipv6_default_gateway = pp->route_ipv6_default_gateway;
3768 3768
 
3769
+        /* Free DNS options and reset them to pre-pull state */
3770
+        gc_free(&o->dns_options.gc);
3771
+        struct gc_arena dns_gc = gc_new();
3772
+        o->dns_options = clone_dns_options(pp->dns_options, &dns_gc);
3773
+        o->dns_options.gc = dns_gc;
3774
+
3769 3775
         if (pp->client_nat_defined)
3770 3776
         {
3771 3777
             cnol_check_alloc(o);
... ...
@@ -7569,6 +7685,111 @@ add_option(struct options *options,
7569 7569
         to->ip_win32_defined = true;
7570 7570
     }
7571 7571
 #endif /* ifdef _WIN32 */
7572
+    else if (streq(p[0], "dns") && p[1])
7573
+    {
7574
+        VERIFY_PERMISSION(OPT_P_DEFAULT);
7575
+
7576
+        if (streq(p[1], "search-domains") && p[2])
7577
+        {
7578
+            dns_domain_list_append(&options->dns_options.search_domains, &p[2], &options->dns_options.gc);
7579
+        }
7580
+        else if (streq(p[1], "server") && p[2] && p[3] && p[4])
7581
+        {
7582
+            long priority;
7583
+            if (!dns_server_priority_parse(&priority, p[2], pull_mode)) {
7584
+                msg(msglevel, "--dns server: invalid priority value '%s'", p[2]);
7585
+                goto err;
7586
+            }
7587
+
7588
+            struct dns_server *server = dns_server_get(&options->dns_options.servers, priority, &options->dns_options.gc);
7589
+
7590
+            if (streq(p[3], "address") && !p[6])
7591
+            {
7592
+                for (int i = 4; p[i]; i++)
7593
+                {
7594
+                    if(!dns_server_addr_parse(server, p[i]))
7595
+                    {
7596
+                        msg(msglevel, "--dns server %ld: malformed or duplicate address '%s'", priority, p[i]);
7597
+                        goto err;
7598
+                    }
7599
+                }
7600
+            }
7601
+            else if (streq(p[3], "resolve-domains"))
7602
+            {
7603
+                if (server->domain_type == DNS_EXCLUDE_DOMAINS)
7604
+                {
7605
+                    msg(msglevel, "--dns server %ld: cannot use resolve-domains and exclude-domains", priority);
7606
+                    goto err;
7607
+                }
7608
+                server->domain_type = DNS_RESOLVE_DOMAINS;
7609
+                dns_domain_list_append(&server->domains, &p[4], &options->dns_options.gc);
7610
+            }
7611
+            else if (streq(p[3], "exclude-domains"))
7612
+            {
7613
+                if (server->domain_type == DNS_RESOLVE_DOMAINS)
7614
+                {
7615
+                    msg(msglevel, "--dns server %ld: cannot use exclude-domains and resolve-domains", priority);
7616
+                    goto err;
7617
+                }
7618
+                server->domain_type = DNS_EXCLUDE_DOMAINS;
7619
+                dns_domain_list_append(&server->domains, &p[4], &options->dns_options.gc);
7620
+            }
7621
+            else if (streq(p[3], "dnssec") && !p[5])
7622
+            {
7623
+                if (streq(p[4], "yes"))
7624
+                {
7625
+                    server->dnssec = DNS_SECURITY_YES;
7626
+                }
7627
+                else if (streq(p[4], "no"))
7628
+                {
7629
+                    server->dnssec = DNS_SECURITY_NO;
7630
+                }
7631
+                else if (streq(p[4], "optional"))
7632
+                {
7633
+                    server->dnssec = DNS_SECURITY_OPTIONAL;
7634
+                }
7635
+                else
7636
+                {
7637
+                    msg(msglevel, "--dns server %ld: malformed dnssec value '%s'", priority, p[4]);
7638
+                    goto err;
7639
+                }
7640
+            }
7641
+            else if (streq(p[3], "transport") && !p[5])
7642
+            {
7643
+                if (streq(p[4], "plain"))
7644
+                {
7645
+                    server->transport = DNS_TRANSPORT_PLAIN;
7646
+                }
7647
+                else if (streq(p[4], "DoH"))
7648
+                {
7649
+                    server->transport = DNS_TRANSPORT_HTTPS;
7650
+                }
7651
+                else if (streq(p[4], "DoT"))
7652
+                {
7653
+                    server->transport = DNS_TRANSPORT_TLS;
7654
+                }
7655
+                else
7656
+                {
7657
+                    msg(msglevel, "--dns server %ld: malformed transport value '%s'", priority, p[4]);
7658
+                    goto err;
7659
+                }
7660
+            }
7661
+            else if (streq(p[3], "sni") && !p[5])
7662
+            {
7663
+                server->sni = p[4];
7664
+            }
7665
+            else
7666
+            {
7667
+                msg(msglevel, "--dns server %ld: unknown option type '%s' or missing or unknown parameter", priority, p[3]);
7668
+                goto err;
7669
+            }
7670
+        }
7671
+        else
7672
+        {
7673
+            msg(msglevel, "--dns: unknown option type '%s' or missing or unknown parameter", p[1]);
7674
+            goto err;
7675
+        }
7676
+    }
7572 7677
 #if defined(_WIN32) || defined(TARGET_ANDROID)
7573 7678
     else if (streq(p[0], "dhcp-option") && p[1])
7574 7679
     {
... ...
@@ -42,6 +42,7 @@
42 42
 #include "pushlist.h"
43 43
 #include "clinat.h"
44 44
 #include "crypto_backend.h"
45
+#include "dns.h"
45 46
 
46 47
 
47 48
 /*
... ...
@@ -76,6 +77,8 @@ struct options_pre_connect
76 76
     bool client_nat_defined;
77 77
     struct client_nat_option_list *client_nat;
78 78
 
79
+    struct dns_options dns_options;
80
+
79 81
     const char* ciphername;
80 82
     const char* authname;
81 83
 
... ...
@@ -277,6 +280,8 @@ struct options
277 277
 
278 278
     struct remote_host_store *rh_store;
279 279
 
280
+    struct dns_options dns_options;
281
+
280 282
     bool remote_random;
281 283
     const char *ipchange;
282 284
     const char *dev;
... ...
@@ -807,6 +812,8 @@ char *options_string_extract_option(const char *options_string,
807 807
 
808 808
 void options_postprocess(struct options *options);
809 809
 
810
+bool options_postprocess_pull(struct options *o, struct env_set *es);
811
+
810 812
 void pre_connect_save(struct options *o);
811 813
 
812 814
 void pre_connect_restore(struct options *o, struct gc_arena *gc);
... ...
@@ -459,6 +459,10 @@ incoming_push_message(struct context *c, const struct buffer *buffer)
459 459
         /* delay bringing tun/tap up until --push parms received from remote */
460 460
         if (status == PUSH_MSG_REPLY)
461 461
         {
462
+            if (!options_postprocess_pull(&c->options, c->c2.es))
463
+            {
464
+                goto error;
465
+            }
462 466
             if (!do_up(c, true, c->options.push_option_types_found))
463 467
             {
464 468
                 msg(D_PUSH_ERRORS, "Failed to open tun/tap interface");
... ...
@@ -2875,6 +2875,17 @@ print_in6_addr(struct in6_addr a6, unsigned int flags, struct gc_arena *gc)
2875 2875
     return BSTR(&out);
2876 2876
 }
2877 2877
 
2878
+/*
2879
+ * Convert an in_port_t in host byte order to a string
2880
+ */
2881
+const char *
2882
+print_in_port_t(in_port_t port, struct gc_arena *gc)
2883
+{
2884
+    struct buffer buffer = alloc_buf_gc(8, gc);
2885
+    buf_printf(&buffer, "%hu", port);
2886
+    return BSTR(&buffer);
2887
+}
2888
+
2878 2889
 #ifndef UINT8_MAX
2879 2890
 #define UINT8_MAX 0xff
2880 2891
 #endif
... ...
@@ -389,6 +389,8 @@ const char *print_in_addr_t(in_addr_t addr, unsigned int flags, struct gc_arena
389 389
 
390 390
 const char *print_in6_addr(struct in6_addr addr6, unsigned int flags, struct gc_arena *gc);
391 391
 
392
+const char *print_in_port_t(in_port_t port, struct gc_arena *gc);
393
+
392 394
 struct in6_addr add_in6_addr( struct in6_addr base, uint32_t add );
393 395
 
394 396
 #define SA_IP_PORT        (1<<0)