#!/usr/bin/bash # # dns-updown - add/remove openvpn provided DNS information # # Copyright (C) 2024-2026 OpenVPN Inc # # SPDX-License-Identifier: GPL-2.0 # # Add/remove openvpn DNS settings from the env into/from # the system. Supported backends in this order: # # * systemd-resolved # * resolvconf # * /etc/resolv.conf file # # Example env from openvpn (not all are always applied): # # dns_vars_file /tmp/openvpn_dvf_58b95c0c97b2db43afb5d745f986c53c.tmp # # or # # dev tun0 # script_type dns-up # dns_search_domain_1 mycorp.in # dns_search_domain_2 eu.mycorp.com # dns_server_1_address_1 192.168.99.254 # dns_server_1_address_2 fd00::99:53 # dns_server_1_port_1 53 # dns_server_1_port_2 53 # dns_server_1_resolve_domain_1 mycorp.in # dns_server_1_resolve_domain_2 eu.mycorp.com # dns_server_1_dnssec true # dns_server_1_transport DoH # dns_server_1_sni dns.mycorp.in # [ -z "${dns_vars_file}" ] || . "${dns_vars_file}" function do_resolved_servers { local sni="" local transport_var=dns_server_${n}_transport local sni_var=dns_server_${n}_sni [ "${!transport_var}" = "DoT" ] && sni="#${!sni_var}" local i=1 local addrs="" while :; do local addr_var=dns_server_${n}_address_${i} local addr="${!addr_var}" [ -n "$addr" ] || break local port_var=dns_server_${n}_port_${i} if [ -n "${!port_var}" ]; then if [[ "$addr" =~ : ]]; then addr="[$addr]" fi addrs+="${addr}:${!port_var}${sni} " else addrs+="${addr}${sni} " fi i=$((i+1)) done resolvectl dns "$dev" $addrs } function do_resolved_domains { local list="" for domain_var in ${!dns_search_domain_*}; do list+="${!domain_var} " done local domain_var=dns_server_${n}_resolve_domain_1 if [ -z "${!domain_var}" ]; then resolvectl default-route "$dev" true list+="~." else resolvectl default-route "$dev" false local i=1 while :; do domain_var=dns_server_${n}_resolve_domain_${i} [ -n "${!domain_var}" ] || break # Add as split domain (~ prefix), if it doesn't already exist [[ "$list" =~ (^| )"${!domain_var}"( |$) ]] \ || list+="~${!domain_var} " i=$((i+1)) done fi resolvectl domain "$dev" $list } function do_resolved_dnssec { local dnssec_var=dns_server_${n}_dnssec if [ "${!dnssec_var}" = "optional" ]; then resolvectl dnssec "$dev" allow-downgrade elif [ "${!dnssec_var}" = "yes" ]; then resolvectl dnssec "$dev" true else resolvectl dnssec "$dev" false fi } function do_resolved_dnsovertls { local transport_var=dns_server_${n}_transport if [ "${!transport_var}" = "DoT" ]; then resolvectl dnsovertls "$dev" true else resolvectl dnsovertls "$dev" false fi } function do_resolved { [[ "$(readlink /etc/resolv.conf)" =~ systemd ]] || return 1 n=1 while :; do local addr_var=dns_server_${n}_address_1 [ -n "${!addr_var}" ] || { echo "setting DNS failed, no compatible server profile" return 1 } # Skip server profiles which require DNS-over-HTTPS local transport_var=dns_server_${n}_transport [ -n "${!transport_var}" -a "${!transport_var}" = "DoH" ] || break n=$((n+1)) done if [ "$script_type" = "dns-up" ]; then echo "setting DNS using resolvectl" do_resolved_servers do_resolved_domains do_resolved_dnssec do_resolved_dnsovertls else echo "unsetting DNS using resolvectl" resolvectl revert "$dev" fi return 0 } function only_standard_server_ports { local i=1 while :; do local addr_var=dns_server_${n}_address_${i} [ -n "${!addr_var}" ] || return 0 local port_var=dns_server_${n}_port_${i} [ -z "${!port_var}" -o "${!port_var}" = "53" ] || return 1 i=$((i+1)) done } function resolv_conf_compat_profile { local n=1 while :; do local server_addr_var=dns_server_${n}_address_1 [ -n "${!server_addr_var}" ] || { echo "setting DNS failed, no compatible server profile" exit 1 } # Skip server profiles which require DNSSEC, # secure transport or use a custom port local dnssec_var=dns_server_${n}_dnssec local transport_var=dns_server_${n}_transport [ -z "${!transport_var}" -o "${!transport_var}" = "plain" ] \ && [ -z "${!dnssec_var}" -o "${!dnssec_var}" = "no" ] \ && only_standard_server_ports && break n=$((n+1)) done return $n } function do_resolvconf { [ -x /sbin/resolvconf ] || return 1 resolv_conf_compat_profile local n=$? if [ "$script_type" = "dns-up" ]; then echo "setting DNS using resolvconf" local domains="" for domain_var in ${!dns_search_domain_*}; do domains+="${!domain_var} " done { local i=1 local maxns=3 while [ "${i}" -le "${maxns}" ]; do local addr_var=dns_server_${n}_address_${i} [ -n "${!addr_var}" ] || break echo "nameserver ${!addr_var}" i=$((i+1)) done [ -z "$domains" ] || echo "search $domains" } | /sbin/resolvconf -a "$dev" else echo "unsetting DNS using resolvconf" /sbin/resolvconf -d "$dev" fi return 0 } function do_resolv_conf_file { conf=/etc/resolv.conf test -e "$conf" || exit 1 resolv_conf_compat_profile local n=$? if [ "$script_type" = "dns-up" ]; then echo "setting DNS using resolv.conf file" local addr1_var=dns_server_${n}_address_1 local addr2_var=dns_server_${n}_address_2 local addr3_var=dns_server_${n}_address_3 text="### openvpn ${dev} begin ###\n" text="${text}nameserver ${!addr1_var}\n" test -z "${!addr2_var}" || text="${text}nameserver ${!addr2_var}\n" test -z "${!addr3_var}" || text="${text}nameserver ${!addr3_var}\n" test -z "$dns_search_domain_1" || { for i in $(seq 1 6); do eval domains=\"$domains\$dns_search_domain_${i} \" || break done text="${text}search $domains\n" } text="${text}### openvpn ${dev} end ###" sed -i "1i${text}" "$conf" else echo "unsetting DNS using resolv.conf file" sed -i "/### openvpn ${dev} begin ###/,/### openvpn ${dev} end ###/d" "$conf" fi return 0 } do_resolved || do_resolvconf || do_resolv_conf_file || { echo "setting DNS failed, no method succeeded" exit 1 }