Browse code

dns: apply settings via script on unixoid systems

This introduces a new script hook, the dns-updown, and implements such a
command script for a few popular systems (and a default for the not so
popular ones). Like the name suggests this hook is soleley for dealing
with modifying how names are resolved when the VPN pushes some --dns
settings.

The default dns updown command is part of the distribution and is
installed with openvpn. You can change the path the command is located
at as a compile time option, defaults to libexecdir.

You can compile-time disable that the default dns-updown hook is
run by passing --disable-dns-updown-by-default to configure or
ccmake ENABLE_DNS_UPDOWN_BY_DEFAULT to OFF.

There's also a new runtime option --dns-updown, which can run a custom
command, force running the default when disabled or disable execution
of the dns-updown altogether.

Change-Id: Ifbe4ffb44d3bfcaa50adb38cacb3436fcdc71b10
Signed-off-by: Heiko Hund <heiko@ist.eigentlich.net>
Acked-by: Gert Doering <gert@greenie.muc.de>
Message-Id: <20250514135334.14377-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg31639.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Heiko Hund authored on 2025/05/14 22:53:27
Showing 15 changed files
... ...
@@ -49,6 +49,7 @@ doc/openvpn.8.html
49 49
 /doc/doxygen/latex/
50 50
 /doc/doxygen/openvpn.doxyfile
51 51
 distro/systemd/*.service
52
+distro/dns-scripts/dns-updown
52 53
 sample/sample-keys/sample-ca/
53 54
 vendor/cmocka_build
54 55
 vendor/dist
... ...
@@ -41,7 +41,10 @@ option(ENABLE_PKCS11 "BUILD with pkcs11-helper" ON)
41 41
 option(USE_WERROR "Treat compiler warnings as errors (-Werror)" ON)
42 42
 option(FAKE_ANDROID "Target Android but do not use actual cross compile/Android cmake to build for simple compile checks on Linux")
43 43
 
44
-set(PLUGIN_DIR /usr/local/lib/openvpn/plugins CACHE FILEPATH "Location of the plugin directory")
44
+option(ENABLE_DNS_UPDOWN_BY_DEFAULT "Run --dns-updown hook by default" ON)
45
+set(DNS_UPDOWN_PATH "${CMAKE_INSTALL_PREFIX}/libexec/openvpn/dns-updown" CACHE STRING "Default location for the DNS up/down script")
46
+
47
+set(PLUGIN_DIR "${CMAKE_INSTALL_PREFIX}/lib/openvpn/plugins" CACHE FILEPATH "Location of the plugin directory")
45 48
 
46 49
 # Create machine readable compile commands
47 50
 option(ENABLE_COMPILE_COMMANDS "Generate compile_commands.json and a symlink for clangd to find it" OFF)
... ...
@@ -601,6 +604,8 @@ add_executable(openvpn ${SOURCE_FILES})
601 601
 
602 602
 add_library_deps(openvpn)
603 603
 
604
+target_compile_options(openvpn PRIVATE -DDEFAULT_DNS_UPDOWN=\"${DNS_UPDOWN_PATH}\")
605
+
604 606
 if(MINGW)
605 607
     target_compile_options(openvpn PRIVATE -municode -UUNICODE)
606 608
     target_link_options(openvpn PRIVATE -municode)
... ...
@@ -35,6 +35,9 @@
35 35
 /* Enable LZO compression library */
36 36
 #cmakedefine ENABLE_LZO
37 37
 
38
+/* Enable dns-updown script hook */
39
+#cmakedefine ENABLE_DNS_UPDOWN
40
+
38 41
 /* Enable NTLMv2 proxy support */
39 42
 #define ENABLE_NTLM 1
40 43
 
... ...
@@ -96,6 +96,13 @@ AC_ARG_ENABLE(
96 96
 )
97 97
 
98 98
 AC_ARG_ENABLE(
99
+	[dns-updown-by-default],
100
+	[AS_HELP_STRING([--disable-dns-updown-by-default], [disable running --dns-updown by default @<:@default=yes@:>@])],
101
+	,
102
+	[enable_dns_updown_by_default="yes"]
103
+)
104
+
105
+AC_ARG_ENABLE(
99 106
 	[ntlm],
100 107
 	[AS_HELP_STRING([--disable-ntlm], [disable NTLMv2 proxy support @<:@default=yes@:>@])],
101 108
 	,
... ...
@@ -315,37 +322,50 @@ else
315 315
 	plugindir="\${libdir}/openvpn/plugins"
316 316
 fi
317 317
 
318
+AC_ARG_VAR([SCRIPTDIR], [Path of script directory @<:@default=PKGLIBEXECDIR@:>@])
319
+if test -n "${SCRIPTDIR}"; then
320
+	scriptdir="${SCRIPTDIR}"
321
+else
322
+	scriptdir="\${pkglibexecdir}"
323
+fi
324
+
318 325
 AC_DEFINE_UNQUOTED([TARGET_ALIAS], ["${host}"], [A string representing our host])
319
-AM_CONDITIONAL([TARGET_LINUX], [false])
326
+AM_CONDITIONAL([ENABLE_DNS_UPDOWN],[true])
320 327
 case "$host" in
321 328
 	*-*-linux*)
322 329
 		AC_DEFINE([TARGET_LINUX], [1], [Are we running on Linux?])
323
-		AM_CONDITIONAL([TARGET_LINUX], [true])
324 330
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["L"], [Target prefix])
331
+		AC_SUBST([DNS_UPDOWN_TYPE], ["systemd"])
325 332
 		have_sitnl="yes"
326 333
 		pkg_config_required="yes"
327 334
 		;;
328 335
 	*-*-solaris*)
329 336
 		AC_DEFINE([TARGET_SOLARIS], [1], [Are we running on Solaris?])
330 337
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["S"], [Target prefix])
338
+		AC_SUBST([DNS_UPDOWN_TYPE], ["resolvconf_file"])
331 339
 		CPPFLAGS="$CPPFLAGS -D_XPG4_2"
332 340
 		test -x /bin/bash && SHELL="/bin/bash"
333 341
 		;;
334 342
 	*-*-openbsd*)
335 343
 		AC_DEFINE([TARGET_OPENBSD], [1], [Are we running on OpenBSD?])
336 344
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["O"], [Target prefix])
345
+		AC_SUBST([DNS_UPDOWN_TYPE], ["resolvconf_file"])
337 346
 		;;
338 347
 	*-*-freebsd*)
339 348
 		AC_DEFINE([TARGET_FREEBSD], [1], [Are we running on FreeBSD?])
340 349
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["F"], [Target prefix])
350
+		AC_SUBST([DNS_UPDOWN_TYPE], ["openresolv"])
341 351
 		;;
342 352
 	*-*-netbsd*)
343 353
 		AC_DEFINE([TARGET_NETBSD], [1], [Are we running NetBSD?])
344 354
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["N"], [Target prefix])
355
+		AC_SUBST([DNS_UPDOWN_TYPE], ["openresolv"])
345 356
 		;;
346 357
 	*-*-darwin*)
347 358
 		AC_DEFINE([TARGET_DARWIN], [1], [Are we running on Mac OS X?])
348 359
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["M"], [Target prefix])
360
+		AM_CONDITIONAL([ENABLE_DNS_UPDOWN], [false])
361
+		AC_SUBST([DNS_UPDOWN_TYPE], ["resolvconf_file"])
349 362
 		have_tap_header="yes"
350 363
 		ac_cv_type_struct_in_pktinfo=no
351 364
 		;;
... ...
@@ -353,6 +373,8 @@ case "$host" in
353 353
 		AC_DEFINE([TARGET_WIN32], [1], [Are we running WIN32?])
354 354
 		AC_DEFINE([ENABLE_DCO], [1], [DCO is always enabled on Windows])
355 355
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["W"], [Target prefix])
356
+		AM_CONDITIONAL([ENABLE_DNS_UPDOWN], [false])
357
+		AC_SUBST([DNS_UPDOWN_TYPE], ["windows"])
356 358
 		CPPFLAGS="${CPPFLAGS} -DWIN32_LEAN_AND_MEAN"
357 359
 		CPPFLAGS="${CPPFLAGS} -DNTDDI_VERSION=NTDDI_VISTA -D_WIN32_WINNT=_WIN32_WINNT_VISTA"
358 360
 		WIN32=yes
... ...
@@ -360,10 +382,12 @@ case "$host" in
360 360
 	*-*-dragonfly*)
361 361
 		AC_DEFINE([TARGET_DRAGONFLY], [1], [Are we running on DragonFlyBSD?])
362 362
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["D"], [Target prefix])
363
+		AC_SUBST([DNS_UPDOWN_TYPE], ["openresolv"])
363 364
 		;;
364 365
 	*-aix*)
365 366
 		AC_DEFINE([TARGET_AIX], [1], [Are we running AIX?])
366 367
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["A"], [Target prefix])
368
+		AC_SUBST([DNS_UPDOWN_TYPE], ["resolvconf_file"])
367 369
 		ROUTE="/usr/sbin/route"
368 370
 		have_tap_header="yes"
369 371
 		ac_cv_header_net_if_h="no"	# exists, but breaks things
... ...
@@ -371,10 +395,12 @@ case "$host" in
371 371
 	*-*-haiku*)
372 372
 		AC_DEFINE([TARGET_HAIKU], [1], [Are we running Haiku?])
373 373
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["H"], [Target prefix])
374
+		AC_SUBST([DNS_UPDOWN_TYPE], ["haikuos_file"])
374 375
 		LIBS="${LIBS} -lnetwork"
375 376
 		;;
376 377
 	*)
377 378
 		AC_DEFINE_UNQUOTED([TARGET_PREFIX], ["X"], [Target prefix])
379
+		AC_SUBST([DNS_UPDOWN_TYPE], ["resolvconf_file"])
378 380
 		have_tap_header="yes"
379 381
 		;;
380 382
 esac
... ...
@@ -1317,7 +1343,7 @@ test "${enable_debug}" = "yes" && AC_DEFINE([ENABLE_DEBUG], [1], [Enable debuggi
1317 1317
 test "${enable_small}" = "yes" && AC_DEFINE([ENABLE_SMALL], [1], [Enable smaller executable size])
1318 1318
 test "${enable_fragment}" = "yes" && AC_DEFINE([ENABLE_FRAGMENT], [1], [Enable internal fragmentation support])
1319 1319
 test "${enable_port_share}" = "yes" && AC_DEFINE([ENABLE_PORT_SHARE], [1], [Enable TCP Server port sharing])
1320
-
1320
+test "${enable_dns_updown_by_default}" = "yes" && AC_DEFINE([ENABLE_DNS_UPDOWN_BY_DEFAULT], [1], [Enable dns-updown hook by default])
1321 1321
 test "${enable_ntlm}" = "yes" && AC_DEFINE([ENABLE_NTLM], [1], [Enable NTLMv2 proxy support])
1322 1322
 test "${enable_crypto_ofb_cfb}" = "yes" && AC_DEFINE([ENABLE_OFB_CFB_MODE], [1], [Enable OFB and CFB cipher modes])
1323 1323
 if test "${have_export_keying_material}" = "yes"; then
... ...
@@ -1505,6 +1531,7 @@ AM_CONDITIONAL([OPENSSL_ENGINE], [test "${have_openssl_engine}" = "yes"])
1505 1505
 
1506 1506
 sampledir="\$(docdir)/sample"
1507 1507
 AC_SUBST([plugindir])
1508
+AC_SUBST([scriptdir])
1508 1509
 AC_SUBST([sampledir])
1509 1510
 
1510 1511
 AC_SUBST([systemdunitdir])
... ...
@@ -1541,6 +1568,7 @@ AC_CONFIG_FILES([
1541 1541
 	Makefile
1542 1542
 	distro/Makefile
1543 1543
 	distro/systemd/Makefile
1544
+	distro/dns-scripts/Makefile
1544 1545
 	doc/Makefile
1545 1546
 	doc/doxygen/Makefile
1546 1547
 	doc/doxygen/openvpn.doxyfile
... ...
@@ -13,3 +13,7 @@ MAINTAINERCLEANFILES = \
13 13
 	$(srcdir)/Makefile.in
14 14
 
15 15
 SUBDIRS = systemd
16
+
17
+if ENABLE_DNS_UPDOWN
18
+SUBDIRS += dns-scripts
19
+endif
16 20
new file mode 100644
... ...
@@ -0,0 +1,29 @@
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) 2002-2024 OpenVPN Inc <sales@openvpn.net>
8
+#
9
+
10
+MAINTAINERCLEANFILES = \
11
+	$(srcdir)/Makefile.in
12
+
13
+EXTRA_DIST = \
14
+	systemd-dns-updown.sh \
15
+	openresolv-dns-updown.sh \
16
+	haikuos_file-dns-updown.sh \
17
+	resolvconf_file-dns-updown.sh
18
+
19
+script_SCRIPTS = \
20
+	dns-updown
21
+
22
+CLEANFILES = $(script_SCRIPTS)
23
+
24
+dns-updown: @DNS_UPDOWN_TYPE@-dns-updown.sh
25
+	cp ${srcdir}/@DNS_UPDOWN_TYPE@-dns-updown.sh $@
26
+	chmod +x $@
27
+
28
+all: $(script_SCRIPTS)
0 29
new file mode 100644
... ...
@@ -0,0 +1,85 @@
0
+#!/bin/sh
1
+#
2
+# Simple OpenVPN up/down script for modifying Haiku OS resolv.conf
3
+# (C) Copyright 2024 OpenVPN Inc <sales@openvpn.net>
4
+#
5
+# SPDX-License-Identifier: BSD-2-Clause
6
+#
7
+# Example env from openvpn (most are not applied):
8
+#
9
+#   dev tun0
10
+#   script_type dns-up
11
+#   dns_search_domain_1 mycorp.in
12
+#   dns_search_domain_2 eu.mycorp.com
13
+#   dns_server_1_address_1 192.168.99.254
14
+#   dns_server_1_address_2 fd00::99:53
15
+#   dns_server_1_port_1 53
16
+#   dns_server_1_port_2 53
17
+#   dns_server_1_resolve_domain_1 mycorp.in
18
+#   dns_server_1_resolve_domain_2 eu.mycorp.com
19
+#   dns_server_1_dnssec true
20
+#   dns_server_1_transport DoH
21
+#   dns_server_1_sni dns.mycorp.in
22
+#
23
+
24
+set -e +u
25
+
26
+conly_standard_server_ports() {
27
+    i=1
28
+    while true; do
29
+        eval addr=\"\$dns_server_${n}_address_${i}\"
30
+        [ -n "$addr" ] || return 0
31
+
32
+        eval port=\"\$dns_server_${n}_port_${i}\"
33
+        [ -z "$port" -o "$port" = "53" ] || return 1
34
+
35
+        i=$(expr $i + 1)
36
+    done
37
+}
38
+
39
+onf=/boot/system/settings/network/resolv.conf
40
+test -e "$conf" || exit 1
41
+case "${script_type}" in
42
+dns-up)
43
+    n=1
44
+    while :; do
45
+        eval addr=\"\$dns_server_${n}_address_1\"
46
+        [ -n "$addr" ] || {
47
+            echo "setting DNS failed, no compatible server profile"
48
+            exit 1
49
+        }
50
+
51
+        # Skip server profiles which require DNSSEC,
52
+        # secure transport or use a custom port
53
+        eval dnssec=\"\$dns_server_${n}_dnssec\"
54
+        eval transport=\"\$dns_server_${n}_transport\"
55
+        [ -z "$transport" -o "$transport" = "plain" ] \
56
+            && [ -z "$dnssec" -o "$dnssec" = "no" ] \
57
+            && only_standard_server_ports && break
58
+
59
+        n=$(expr $n + 1)
60
+    done
61
+
62
+    eval addr1=\"\$dns_server_${n}_address_1\"
63
+    eval addr2=\"\$dns_server_${n}_address_2\"
64
+    eval addr3=\"\$dns_server_${n}_address_3\"
65
+    text="### openvpn ${dev} begin ###\n"
66
+    text="${text}nameserver $addr1\n"
67
+    test -z "$addr2" || text="${text}nameserver $addr2\n"
68
+    test -z "$addr3" || text="${text}nameserver $addr3\n"
69
+
70
+    test -z "$dns_search_domain_1" || {
71
+        for i in $(seq 1 6); do
72
+            eval domains=\"$domains\$dns_search_domain_${i} \" || break
73
+        done
74
+        text="${text}search $domains\n"
75
+    }
76
+    text="${text}### openvpn ${dev} end ###"
77
+    text="${text}\n$(cat ${conf})"
78
+
79
+    echo "${text}" > "${conf}"
80
+    ;;
81
+dns-down)
82
+    sed -i'' -e "/### openvpn ${dev} begin ###/,/### openvpn ${dev} end ###/d" "$conf"
83
+    ;;
84
+esac
0 85
new file mode 100644
... ...
@@ -0,0 +1,89 @@
0
+#!/bin/sh
1
+#
2
+# Simple OpenVPN up/down script for openresolv integration
3
+# (C) Copyright 2016 Baptiste Daroussin
4
+#               2024 OpenVPN Inc <sales@openvpn.net>
5
+#
6
+# SPDX-License-Identifier: BSD-2-Clause
7
+#
8
+# Example env from openvpn (most are not applied):
9
+#
10
+#   dev tun0
11
+#   script_type dns-up
12
+#   dns_search_domain_1 mycorp.in
13
+#   dns_search_domain_2 eu.mycorp.com
14
+#   dns_server_1_address_1 192.168.99.254
15
+#   dns_server_1_address_2 fd00::99:53
16
+#   dns_server_1_port_1 53
17
+#   dns_server_1_port_2 53
18
+#   dns_server_1_resolve_domain_1 mycorp.in
19
+#   dns_server_1_resolve_domain_2 eu.mycorp.com
20
+#   dns_server_1_dnssec true
21
+#   dns_server_1_transport DoH
22
+#   dns_server_1_sni dns.mycorp.in
23
+#
24
+
25
+set -e +u
26
+
27
+only_standard_server_ports() {
28
+    i=1
29
+    while true; do
30
+        eval addr=\"\$dns_server_${n}_address_${i}\"
31
+        [ -n "$addr" ] || return 0
32
+
33
+        eval port=\"\$dns_server_${n}_port_${i}\"
34
+        [ -z "$port" -o "$port" = "53" ] || return 1
35
+
36
+        i=$(expr $i + 1)
37
+    done
38
+}
39
+
40
+: ${script_type:=dns-down}
41
+case "${script_type}" in
42
+dns-up)
43
+    n=1
44
+    while :; do
45
+        eval addr=\"\$dns_server_${n}_address_1\"
46
+        [ -n "$addr" ] || {
47
+            echo "setting DNS failed, no compatible server profile"
48
+            exit 1
49
+        }
50
+
51
+        # Skip server profiles which require DNSSEC,
52
+        # secure transport or use a custom port
53
+        eval dnssec=\"\$dns_server_${n}_dnssec\"
54
+        eval transport=\"\$dns_server_${n}_transport\"
55
+        [ -z "$transport" -o "$transport" = "plain" ] \
56
+            && [ -z "$dnssec" -o "$dnssec" = "no" ] \
57
+            && only_standard_server_ports && break
58
+
59
+        n=$(expr $n + 1)
60
+    done
61
+
62
+    {
63
+        i=1
64
+        maxns=3
65
+        while :; do
66
+            maxns=$((maxns - 1))
67
+            [ $maxns -gt 0 ] || break
68
+            eval option=\"\$dns_server_${n}_address_${i}\" || break
69
+            [ "${option}" ] || break
70
+            i=$((i + 1))
71
+            echo "nameserver ${option}"
72
+        done
73
+        i=1
74
+        maxdom=6
75
+        while :; do
76
+            maxdom=$((maxdom - 1))
77
+            [ $maxdom -gt 0 ] || break
78
+            eval option=\"\$dns_search_domain_${i}\" || break
79
+            [ "${option}" ] || break
80
+            i=$((i + 1))
81
+            echo "search ${option}"
82
+        done
83
+    } | /sbin/resolvconf -a "${dev}"
84
+    ;;
85
+dns-down)
86
+    /sbin/resolvconf -d "${dev}" -f
87
+    ;;
88
+esac
0 89
new file mode 100644
... ...
@@ -0,0 +1,85 @@
0
+#!/bin/sh
1
+#
2
+# Simple OpenVPN up/down script for modifying /etc/resolv.conf
3
+# (C) Copyright 2024 OpenVPN Inc <sales@openvpn.net>
4
+#
5
+# SPDX-License-Identifier: BSD-2-Clause
6
+#
7
+# Example env from openvpn (most are not applied):
8
+#
9
+#   dev tun0
10
+#   script_type dns-up
11
+#   dns_search_domain_1 mycorp.in
12
+#   dns_search_domain_2 eu.mycorp.com
13
+#   dns_server_1_address_1 192.168.99.254
14
+#   dns_server_1_address_2 fd00::99:53
15
+#   dns_server_1_port_1 53
16
+#   dns_server_1_port_2 53
17
+#   dns_server_1_resolve_domain_1 mycorp.in
18
+#   dns_server_1_resolve_domain_2 eu.mycorp.com
19
+#   dns_server_1_dnssec true
20
+#   dns_server_1_transport DoH
21
+#   dns_server_1_sni dns.mycorp.in
22
+#
23
+
24
+set -e +u
25
+
26
+only_standard_server_ports() {
27
+    i=1
28
+    while true; do
29
+        eval addr=\"\$dns_server_${n}_address_${i}\"
30
+        [ -n "$addr" ] || return 0
31
+
32
+        eval port=\"\$dns_server_${n}_port_${i}\"
33
+        [ -z "$port" -o "$port" = "53" ] || return 1
34
+
35
+        i=$(expr $i + 1)
36
+    done
37
+}
38
+
39
+conf=/etc/resolv.conf
40
+test -e "$conf" || exit 1
41
+case "${script_type}" in
42
+dns-up)
43
+    n=1
44
+    while :; do
45
+        eval addr=\"\$dns_server_${n}_address_1\"
46
+        [ -n "$addr" ] || {
47
+            echo "setting DNS failed, no compatible server profile"
48
+            exit 1
49
+        }
50
+
51
+        # Skip server profiles which require DNSSEC,
52
+        # secure transport or use a custom port
53
+        eval dnssec=\"\$dns_server_${n}_dnssec\"
54
+        eval transport=\"\$dns_server_${n}_transport\"
55
+        [ -z "$transport" -o "$transport" = "plain" ] \
56
+            && [ -z "$dnssec" -o "$dnssec" = "no" ] \
57
+            && only_standard_server_ports && break
58
+
59
+        n=$(expr $n + 1)
60
+    done
61
+
62
+    eval addr1=\"\$dns_server_${n}_address_1\"
63
+    eval addr2=\"\$dns_server_${n}_address_2\"
64
+    eval addr3=\"\$dns_server_${n}_address_3\"
65
+    text="### openvpn ${dev} begin ###\n"
66
+    text="${text}nameserver $addr1\n"
67
+    test -z "$addr2" || text="${text}nameserver $addr2\n"
68
+    test -z "$addr3" || text="${text}nameserver $addr3\n"
69
+
70
+    test -z "$dns_search_domain_1" || {
71
+        for i in $(seq 1 6); do
72
+            eval domains=\"$domains\$dns_search_domain_${i} \" || break
73
+        done
74
+        text="${text}search $domains\n"
75
+    }
76
+    text="${text}### openvpn ${dev} end ###"
77
+    text="${text}\n$(cat ${conf})"
78
+
79
+    echo "${text}" > "${conf}"
80
+    ;;
81
+dns-down)
82
+    sed -i'' -e "/### openvpn ${dev} begin ###/,/### openvpn ${dev} end ###/d" "$conf"
83
+    ;;
84
+esac
0 85
new file mode 100644
... ...
@@ -0,0 +1,240 @@
0
+#!/bin/bash
1
+#
2
+# dns-updown - add/remove openvpn provided DNS information
3
+#
4
+# Copyright (C) 2024 OpenVPN Inc <sales@openvpn.net>
5
+#
6
+# SPDX-License-Identifier: GPL-2.0
7
+#
8
+# Add/remove openvpn DNS settings from the env into/from
9
+# the system. Supported backends in this order:
10
+#
11
+#   * systemd-resolved
12
+#   * resolvconf
13
+#   * /etc/resolv.conf file
14
+#
15
+# Example env from openvpn (not all are always applied):
16
+#
17
+#   dev tun0
18
+#   script_type dns-up
19
+#   dns_search_domain_1 mycorp.in
20
+#   dns_search_domain_2 eu.mycorp.com
21
+#   dns_server_1_address_1 192.168.99.254
22
+#   dns_server_1_address_2 fd00::99:53
23
+#   dns_server_1_port_1 53
24
+#   dns_server_1_port_2 53
25
+#   dns_server_1_resolve_domain_1 mycorp.in
26
+#   dns_server_1_resolve_domain_2 eu.mycorp.com
27
+#   dns_server_1_dnssec true
28
+#   dns_server_1_transport DoH
29
+#   dns_server_1_sni dns.mycorp.in
30
+#
31
+
32
+function do_resolved_servers {
33
+    local sni=""
34
+    local transport_var=dns_server_${n}_transport
35
+    local sni_var=dns_server_${n}_sni
36
+    [ "${!transport_var}" = "DoT" ] && sni="#${!sni_var}"
37
+
38
+    local i=1
39
+    local addrs=""
40
+    while :; do
41
+        local addr_var=dns_server_${n}_address_${i}
42
+        local addr="${!addr_var}"
43
+        [ -n "$addr" ] || break
44
+
45
+        local port_var=dns_server_${n}_port_${i}
46
+        if [ -n "${!port_var}" ]; then
47
+            if [[ "$addr" =~ : ]]; then
48
+                addr="[$addr]"
49
+            fi
50
+            addrs+="${addr}:${!port_var}${sni} "
51
+        else
52
+            addrs+="${addr}${sni} "
53
+        fi
54
+        i=$((i+1))
55
+    done
56
+
57
+    resolvectl dns "$dev" $addrs
58
+}
59
+
60
+function do_resolved_domains {
61
+    local list=""
62
+    for domain_var in ${!dns_search_domain_*}; do
63
+        list+="${!domain_var} "
64
+    done
65
+    local domain_var=dns_server_${n}_resolve_domain_1
66
+    if [ -z "${!domain_var}" ]; then
67
+        resolvectl default-route "$dev" true
68
+        list+="~."
69
+    else
70
+        resolvectl default-route "$dev" false
71
+        local i=1
72
+        while :; do
73
+            domain_var=dns_server_${n}_resolve_domain_${i}
74
+            [ -n "${!domain_var}" ] || break
75
+            # Add as split domain (~ prefix), if it doesn't already exist
76
+            [[ "$list" =~ (^| )"${!domain_var}"( |$) ]] \
77
+                || list+="~${!domain_var} "
78
+            i=$((i+1))
79
+        done
80
+    fi
81
+
82
+    resolvectl domain "$dev" $list
83
+}
84
+
85
+function do_resolved_dnssec {
86
+    local dnssec_var=dns_server_${n}_dnssec
87
+    if [ "${!dnssec_var}" = "optional" ]; then
88
+        resolvectl dnssec "$dev" allow-downgrade
89
+    elif [ "${!dnssec_var}" = "yes" ]; then
90
+        resolvectl dnssec "$dev" true
91
+    else
92
+        resolvectl dnssec "$dev" false
93
+    fi
94
+}
95
+
96
+function do_resolved_dnsovertls {
97
+    local transport_var=dns_server_${n}_transport
98
+    if [ "${!transport_var}" = "DoT" ]; then
99
+        resolvectl dnsovertls "$dev" true
100
+    else
101
+        resolvectl dnsovertls "$dev" false
102
+    fi
103
+}
104
+
105
+function do_resolved {
106
+    [[ "$(readlink /etc/resolv.conf)" =~ systemd ]] || return 1
107
+
108
+    n=1
109
+    while :; do
110
+        local addr_var=dns_server_${n}_address_1
111
+        [ -n "${!addr_var}" ] || {
112
+            echo "setting DNS failed, no compatible server profile"
113
+            return 1
114
+        }
115
+
116
+        # Skip server profiles which require DNS-over-HTTPS
117
+        local transport_var=dns_server_${n}_transport
118
+        [ -n "${!transport_var}" -a "${!transport_var}" = "DoH" ] || break
119
+
120
+        n=$((n+1))
121
+    done
122
+
123
+    if [ "$script_type" = "dns-up" ]; then
124
+        echo "setting DNS using resolvectl"
125
+        do_resolved_servers
126
+        do_resolved_domains
127
+        do_resolved_dnssec
128
+        do_resolved_dnsovertls
129
+    else
130
+        echo "unsetting DNS using resolvectl"
131
+        resolvectl revert "$dev"
132
+    fi
133
+
134
+    return 0
135
+}
136
+
137
+function only_standard_server_ports {
138
+    local i=1
139
+    while :; do
140
+        local addr_var=dns_server_${n}_address_${i}
141
+        [ -n "${!addr_var}" ] || return 0
142
+
143
+        local port_var=dns_server_${n}_port_${i}
144
+        [ -z "${!port_var}" -o "${!port_var}" = "53" ] || return 1
145
+
146
+        i=$((i+1))
147
+    done
148
+}
149
+
150
+function resolv_conf_compat_profile {
151
+    local n=1
152
+    while :; do
153
+        local server_addr_var=dns_server_${n}_address_1
154
+        [ -n "${!server_addr_var}" ] || {
155
+            echo "setting DNS failed, no compatible server profile"
156
+            exit 1
157
+        }
158
+
159
+        # Skip server profiles which require DNSSEC,
160
+        # secure transport or use a custom port
161
+        local dnssec_var=dns_server_${n}_dnssec
162
+        local transport_var=dns_server_${n}_transport
163
+        [ -z "${!transport_var}" -o "${!transport_var}" = "plain" ] \
164
+            && [ -z "${!dnssec_var}" -o "${!dnssec_var}" = "no" ] \
165
+            && only_standard_server_ports && break
166
+
167
+        n=$((n+1))
168
+    done
169
+    return $n
170
+}
171
+
172
+function do_resolvconf {
173
+    [ -x /sbin/resolvconf ] || return 1
174
+
175
+    resolv_conf_compat_profile
176
+    local n=$?
177
+
178
+    if [ "$script_type" = "dns-up" ]; then
179
+        echo "setting DNS using resolvconf"
180
+        local domains=""
181
+        for domain_var in ${!dns_search_domain_*}; do
182
+            domains+="${!domain_var} "
183
+        done
184
+        {
185
+            local maxns=3
186
+            local server_var=dns_server_${n}_address_*
187
+            for addr_var in ${!server_var}; do
188
+                [ $((maxns--)) -gt 0 ] || break
189
+                echo "nameserver ${!addr_var}"
190
+            done
191
+            [ -z "$domains" ] || echo "search $domains"
192
+        } | /sbin/resolvconf -a "$dev"
193
+    else
194
+        echo "unsetting DNS using resolvconf"
195
+        /sbin/resolvconf -d "$dev"
196
+    fi
197
+
198
+    return 0
199
+}
200
+
201
+function do_resolv_conf_file {
202
+    conf=/etc/resolv.conf
203
+    test -e "$conf" || exit 1
204
+
205
+    resolv_conf_compat_profile
206
+    local n=$?
207
+
208
+    if [ "$script_type" = "dns-up" ]; then
209
+        echo "setting DNS using resolv.conf file"
210
+
211
+        local addr1_var=dns_server_${n}_address_1
212
+        local addr2_var=dns_server_${n}_address_2
213
+        local addr3_var=dns_server_${n}_address_3
214
+        text="### openvpn ${dev} begin ###\n"
215
+        text="${text}nameserver ${!addr1_var}\n"
216
+        test -z "${!addr2_var}" || text="${text}nameserver ${!addr2_var}\n"
217
+        test -z "${!addr3_var}" || text="${text}nameserver ${!addr3_var}\n"
218
+
219
+        test -z "$dns_search_domain_1" || {
220
+            for i in $(seq 1 6); do
221
+                eval domains=\"$domains\$dns_search_domain_${i} \" || break
222
+            done
223
+            text="${text}search $domains\n"
224
+        }
225
+        text="${text}### openvpn ${dev} end ###"
226
+
227
+        sed -i "1i${text}" "$conf"
228
+    else
229
+        echo "unsetting DNS using resolv.conf file"
230
+        sed -i "/### openvpn ${dev} begin ###/,/### openvpn ${dev} end ###/d" "$conf"
231
+    fi
232
+
233
+    return 0
234
+}
235
+
236
+do_resolved || do_resolvconf || do_resolv_conf_file || {
237
+    echo "setting DNS failed, no method succeeded"
238
+    exit 1
239
+}
... ...
@@ -8,9 +8,13 @@ the OpenVPN process.
8 8
 Script Order of Execution
9 9
 -------------------------
10 10
 
11
+#. ``--dns-updown``
12
+
13
+   Executed after TCP/UDP socket bind and TUN/TAP open, before ``--up``.
14
+
11 15
 #. ``--up``
12 16
 
13
-   Executed after TCP/UDP socket bind and TUN/TAP open.
17
+   Executed after TCP/UDP socket bind and TUN/TAP open, after ``--dns-updown``.
14 18
 
15 19
 #. ``--tls-verify``
16 20
 
... ...
@@ -38,9 +42,13 @@ Script Order of Execution
38 38
 
39 39
    Executed in ``--mode server`` mode on client instance shutdown.
40 40
 
41
+#. ``--dns-updown``
42
+
43
+   Executed before TCP/UDP and TUN/TAP close, before ``--down``.
44
+
41 45
 #. ``--down``
42 46
 
43
-   Executed after TCP/UDP and TUN/TAP close.
47
+   Executed after TCP/UDP and TUN/TAP close, after ``--dns-updown``.
44 48
 
45 49
 #. ``--learn-address``
46 50
 
... ...
@@ -171,7 +179,7 @@ SCRIPT HOOKS
171 171
         client-crresponse cmd
172 172
 
173 173
   OpenVPN will write the response of the client into a temporary file.
174
-  The filename will be passed as an argument to ``cmd``, and the file will be
174
+  The filename will be passed as an argument to ``cmd``, and the file will
175 175
   automatically deleted by OpenVPN after the script returns.
176 176
 
177 177
   The response is passed as is from the client. The script needs to check
... ...
@@ -233,6 +241,31 @@ SCRIPT HOOKS
233 233
   The ``--client-disconnect`` command is not passed any extra arguments
234 234
   (only those arguments specified in cmd, if any).
235 235
 
236
+--dns-updown cmd
237
+  Run command ``cmd``, instead of the default DNS up/down command that comes
238
+  with openvpn. If ``cmd`` is ``disable`` the ``--dns-updown`` command is not run.
239
+
240
+  If you write your own command, please make sure to ignore ``--dns``
241
+  server profiles that cannot be applied. Port, DNSSEC and secure transport
242
+  settings need to be adhered to. If split DNS is not possible a full redirect
243
+  can be used as a fallback. If not all of the server addresses or search domains
244
+  can be configured, apply them in the order they are listed in.
245
+
246
+  Note that ``--dns-updown`` is not supported on all platforms. On Windows DNS
247
+  will always be set by the service. On Android DNS will be passed via management
248
+  interface.
249
+
250
+  Note that DNS-related ``--dhcp-option``\ s might be converted so that they are
251
+  available to this hook if no ``--dns`` options exist. If any ``--dns server``
252
+  option is present, DNS-related ``--dhcp-option``\ s will always be ignored.
253
+  If an ``--up`` script is defined, foreign_option env vars will be generated
254
+  from ``--dns`` options and passed to the script. The default ``--dns-updown``
255
+  command is not run if an ``--up`` script is defined. Both is done for backward
256
+  compatibility. In case you want to run the ``--dns-updown`` command even if
257
+  there is an ``--up`` defined, you can define a custom command or use ``force``
258
+  as ``cmd`` to run the default command. No DNS env vars will be passed to ``--up``
259
+  in this case.
260
+
236 261
 --down cmd
237 262
   Run command ``cmd`` after TUN/TAP device close (post ``--user`` UID
238 263
   change and/or ``--chroot`` ). ``cmd`` consists of a path to script (or
... ...
@@ -659,7 +692,7 @@ instances.
659 659
     names). Set prior to ``--up`` or ``--down`` script execution.
660 660
 
661 661
 :code:`dns_*`
662
-    The ``--dns`` configuration options will be made available to script
662
+    The ``--dns`` configuration options will be made available to ``--dns-updown``
663 663
     execution through this set of environment variables. Variables appear
664 664
     only if the corresponding option has a value assigned. For the semantics
665 665
     of each individual variable, please refer to the documentation for ``--dns``.
... ...
@@ -30,7 +30,8 @@ AM_CFLAGS = \
30 30
 	$(OPTIONAL_LZ4_CFLAGS) \
31 31
 	$(OPTIONAL_PKCS11_HELPER_CFLAGS) \
32 32
 	$(OPTIONAL_INOTIFY_CFLAGS) \
33
-	-DPLUGIN_LIBDIR=\"${plugindir}\"
33
+	-DPLUGIN_LIBDIR=\"${plugindir}\" \
34
+	-DDEFAULT_DNS_UPDOWN=\"${scriptdir}/dns-updown\"
34 35
 
35 36
 if WIN32
36 37
 # we want unicode entry point but not the macro
... ...
@@ -30,6 +30,7 @@
30 30
 #include "dns.h"
31 31
 #include "socket.h"
32 32
 #include "options.h"
33
+#include "run_command.h"
33 34
 
34 35
 #ifdef _WIN32
35 36
 #include "win32.h"
... ...
@@ -262,6 +263,8 @@ clone_dns_options(const struct dns_options *o, struct gc_arena *gc)
262 262
     clone.search_domains = clone_dns_domains(o->search_domains, gc);
263 263
     clone.servers = clone_dns_servers(o->servers, gc);
264 264
     clone.servers_prepull = clone_dns_servers(o->servers_prepull, gc);
265
+    clone.updown = o->updown;
266
+    clone.user_set_updown = o->user_set_updown;
265 267
 
266 268
     return clone;
267 269
 }
... ...
@@ -548,6 +551,54 @@ run_up_down_service(bool add, const struct options *o, const struct tuntap *tt)
548 548
     send_msg_iservice(o->msg_channel, &nrpt, sizeof(nrpt), &ack, "DNS");
549 549
 }
550 550
 
551
+#else /* ifdef _WIN32 */
552
+
553
+static void
554
+updown_env_set(bool up, const struct dns_options *o, const struct tuntap *tt, struct env_set *es)
555
+{
556
+    setenv_str(es, "dev", tt->actual_name);
557
+    setenv_str(es, "script_type", up ? "dns-up" : "dns-down");
558
+    setenv_dns_options(o, es);
559
+}
560
+
561
+static int
562
+do_run_up_down_command(bool up, const struct dns_options *o, const struct tuntap *tt)
563
+{
564
+    struct gc_arena gc = gc_new();
565
+    struct argv argv = argv_new();
566
+    struct env_set *es = env_set_create(&gc);
567
+
568
+    updown_env_set(up, o, tt, es);
569
+
570
+    argv_printf(&argv, "%s", o->updown);
571
+    argv_msg(M_INFO, &argv);
572
+    int res;
573
+    if (o->user_set_updown)
574
+    {
575
+        res = openvpn_run_script(&argv, es, S_EXITCODE, "dns updown");
576
+    }
577
+    else
578
+    {
579
+        res = openvpn_execve_check(&argv, es, S_EXITCODE, "WARNING: Failed running dns updown");
580
+    }
581
+    argv_free(&argv);
582
+    gc_free(&gc);
583
+    return res;
584
+}
585
+
586
+static void
587
+run_up_down_command(bool up, struct options *o, const struct tuntap *tt)
588
+{
589
+    if (!o->dns_options.updown)
590
+    {
591
+        return;
592
+    }
593
+
594
+    int status;
595
+    status = do_run_up_down_command(up, &o->dns_options, tt);
596
+    msg(M_INFO, "dns %s command exited with status %d", up ? "up" : "down", status);
597
+}
598
+
551 599
 #endif /* _WIN32 */
552 600
 
553 601
 void
... ...
@@ -666,5 +717,7 @@ run_dns_up_down(bool up, struct options *o, const struct tuntap *tt)
666 666
 
667 667
 #ifdef _WIN32
668 668
     run_up_down_service(up, o, tt);
669
+#else
670
+    run_up_down_command(up, o, tt);
669 671
 #endif /* ifdef _WIN32 */
670 672
 }
... ...
@@ -73,6 +73,8 @@ struct dns_options {
73 73
     struct dns_server *servers_prepull;
74 74
     struct dns_server *servers;
75 75
     struct gc_arena gc;
76
+    const char *updown;
77
+    bool user_set_updown;
76 78
 };
77 79
 
78 80
 /**
... ...
@@ -526,10 +526,12 @@ static const char usage_message[] =
526 526
     "                  address <addr[:port]> [addr[:port] ...] : server addresses 4/6\n"
527 527
     "                  resolve-domains <domain> [domain ...] : split domains\n"
528 528
     "                  dnssec <yes|no|optional> : option to use DNSSEC\n"
529
-    "                  type <DoH|DoT> : query server over HTTPS / TLS\n"
529
+    "                  transport <DoH|DoT> : query server over HTTPS / TLS\n"
530 530
     "                  sni <domain> : DNS server name indication\n"
531 531
     "--dns search-domains <domain> [domain ...]:\n"
532 532
     "                  Add domains to DNS domain search list\n"
533
+    "--dns-updown cmd|force|disable : Run cmd as user defined dns config command,\n"
534
+    "                  force running the default script or disable running it.\n"
533 535
     "--auth-retry t  : How to handle auth failures.  Set t to\n"
534 536
     "                  none (default), interact, or nointeract.\n"
535 537
     "--static-challenge t e [<scrv1|concat>]: Enable static challenge/response protocol using\n"
... ...
@@ -918,6 +920,10 @@ init_options(struct options *o, const bool init_gc)
918 918
 #ifndef ENABLE_DCO
919 919
     o->disable_dco = true;
920 920
 #endif /* ENABLE_DCO */
921
+
922
+#ifdef ENABLE_DNS_UPDOWN_BY_DEFAULT
923
+    o->dns_options.updown = DEFAULT_DNS_UPDOWN;
924
+#endif /* ENABLE_DNS_UPDOWN_BY_DEFAULT */
921 925
 }
922 926
 
923 927
 void
... ...
@@ -8046,6 +8052,39 @@ add_option(struct options *options,
8046 8046
         to->ip_win32_defined = true;
8047 8047
     }
8048 8048
 #endif /* ifdef _WIN32 */
8049
+    else if (streq(p[0], "dns-updown") && p[1])
8050
+    {
8051
+        VERIFY_PERMISSION(OPT_P_SCRIPT);
8052
+        if (!no_more_than_n_args(msglevel, p, 2, NM_QUOTE_HINT))
8053
+        {
8054
+            goto err;
8055
+        }
8056
+        struct dns_options *dns = &options->dns_options;
8057
+        if (streq(p[1], "disable"))
8058
+        {
8059
+            dns->updown = NULL;
8060
+            dns->user_set_updown = false;
8061
+        }
8062
+        else if (streq(p[1], "force"))
8063
+        {
8064
+            /* force dns-updown run, even if a --up script is defined */
8065
+            if (dns->user_set_updown == false)
8066
+            {
8067
+                dns->updown = DEFAULT_DNS_UPDOWN;
8068
+                dns->user_set_updown = true;
8069
+            }
8070
+        }
8071
+        else
8072
+        {
8073
+            if (streq(dns->updown, DEFAULT_DNS_UPDOWN))
8074
+            {
8075
+                /* Unset the default command to prevent warnings */
8076
+                dns->updown = NULL;
8077
+            }
8078
+            set_user_script(options, &dns->updown, p[1], p[0], false);
8079
+            dns->user_set_updown = true;
8080
+        }
8081
+    }
8049 8082
     else if (streq(p[0], "dns") && p[1])
8050 8083
     {
8051 8084
         VERIFY_PERMISSION(OPT_P_DHCPDNS);