From 7f236c79570642d466c5acab890b26c3a07f4f7a Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 8 May 2020 23:15:50 -0600 Subject: wg-quick: support dns search domains If DNS= has an IP in it, treat it as a DNS server. If DNS= has a non-IP in it, treat it as a DNS search domain. Signed-off-by: Jason A. Donenfeld --- contrib/dns-hatchet/hatchet.bash | 5 ++++- contrib/highlighter/highlighter.c | 12 +++++------ src/man/wg-quick.8 | 3 ++- src/wg-quick/android.c | 43 +++++++++++++++++++++++++-------------- src/wg-quick/darwin.bash | 30 ++++++++++++++++++++++----- src/wg-quick/freebsd.bash | 11 +++++++--- src/wg-quick/linux.bash | 11 +++++++--- src/wg-quick/openbsd.bash | 9 ++++++-- 8 files changed, 88 insertions(+), 36 deletions(-) diff --git a/contrib/dns-hatchet/hatchet.bash b/contrib/dns-hatchet/hatchet.bash index 5857cc1..bc4d090 100644 --- a/contrib/dns-hatchet/hatchet.bash +++ b/contrib/dns-hatchet/hatchet.bash @@ -2,7 +2,9 @@ set_dns() { [[ ${#DNS[@]} -gt 0 ]] || return 0 if [[ $(resolvconf --version 2>/dev/null) == openresolv\ * ]]; then - printf 'nameserver %s\n' "${DNS[@]}" | cmd resolvconf -a "$INTERFACE" -m 0 -x + { printf 'nameserver %s\n' "${DNS[@]}" + [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}" + } | cmd resolvconf -a "$INTERFACE" -m 0 -x else echo "[#] mount \`${DNS[*]}' /etc/resolv.conf" >&2 [[ -e /etc/resolv.conf ]] || touch /etc/resolv.conf @@ -15,6 +17,7 @@ set_dns() { _EOF printf 'nameserver %s\n' "${DNS[@]}" + [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}" } | unshare -m --propagation shared bash -c "$(cat <<-_EOF set -e context="\$(stat -c %C /etc/resolv.conf 2>/dev/null)" || unset context diff --git a/contrib/highlighter/highlighter.c b/contrib/highlighter/highlighter.c index e0d4e04..d89feda 100644 --- a/contrib/highlighter/highlighter.c +++ b/contrib/highlighter/highlighter.c @@ -337,11 +337,6 @@ static bool is_valid_network(string_span_t s) return is_valid_ipv4(s) || is_valid_ipv6(s); } -static bool is_valid_dns(string_span_t s) -{ - return is_valid_ipv4(s) || is_valid_ipv6(s); -} - enum field { InterfaceSection, PrivateKey, @@ -451,7 +446,12 @@ static void highlight_multivalue_value(struct highlight_span_array *ret, const s { switch (section) { case DNS: - append_highlight_span(ret, parent.s, s, is_valid_dns(s) ? HighlightIP : HighlightError); + if (is_valid_ipv4(s) || is_valid_ipv6(s)) + append_highlight_span(ret, parent.s, s, HighlightIP); + else if (is_valid_hostname(s)) + append_highlight_span(ret, parent.s, s, HighlightHost); + else + append_highlight_span(ret, parent.s, s, HighlightError); break; case Address: case AllowedIPs: { diff --git a/src/man/wg-quick.8 b/src/man/wg-quick.8 index eca3b48..c693a89 100644 --- a/src/man/wg-quick.8 +++ b/src/man/wg-quick.8 @@ -76,7 +76,8 @@ Address \(em a comma-separated list of IP (v4 or v6) addresses (optionally with to be assigned to the interface. May be specified multiple times. .IP \(bu DNS \(em a comma-separated list of IP (v4 or v6) addresses to be set as the interface's -DNS servers. May be specified multiple times. Upon bringing the interface up, this runs +DNS servers, or non-IP hostnames to be set as the interface's DNS search domains. May be +specified multiple times. Upon bringing the interface up, this runs `resolvconf -a tun.\fIINTERFACE\fP -m 0 -x` and upon bringing it down, this runs `resolvconf -d tun.\fIINTERFACE\fP`. If these particular invocations of .BR resolvconf (8) diff --git a/src/wg-quick/android.c b/src/wg-quick/android.c index 540925a..49ae1e3 100644 --- a/src/wg-quick/android.c +++ b/src/wg-quick/android.c @@ -837,37 +837,49 @@ static void set_dnses(unsigned int netid, const char *dnses) if (len > (1<<16)) return; _cleanup_free_ char *mutable = xstrdup(dnses); - _cleanup_free_ char *shell_arglist = xmalloc(len * 4 + 1); - _cleanup_free_ char *function_arglist = xmalloc(len * 4 + 1); + _cleanup_free_ char *dns_shell_arglist = xmalloc(len * 4 + 1); + _cleanup_free_ char *dns_search_shell_arglist = xmalloc(len * 4 + 1); + _cleanup_free_ char *dns_function_arglist = xmalloc(len * 4 + 1); + _cleanup_free_ char *dns_search_function_arglist = xmalloc(len * 4 + 1); _cleanup_free_ char *arg = xmalloc(len + 4); _cleanup_free_ char **dns_list = NULL; + _cleanup_free_ char **dns_search_list = NULL; _cleanup_binder_ AIBinder *handle = NULL; - size_t dns_list_size = 0; + _cleanup_regfree_ regex_t regex_ipnothost = { 0 }; + size_t dns_list_size = 0, dns_search_list_size = 0; + bool is_ip; if (!len) return; + + xregcomp(®ex_ipnothost, "^[a-zA-Z0-9_=+.-]{1,15}$", REG_EXTENDED | REG_NOSUB); for (char *dns = strtok(mutable, ", \t\n"); dns; dns = strtok(NULL, ", \t\n")) { if (strchr(dns, '\'') || strchr(dns, '\\')) continue; - ++dns_list_size; + ++*(!regexec(®ex_ipnothost, dns, 0, NULL, 0) ? &dns_list_size : &dns_search_list_size); } if (!dns_list_size) return; dns_list = xcalloc(dns_list_size + 1, sizeof(*dns_list)); + dns_search_list = xcalloc(dns_search_list_size + 1, sizeof(*dns_search_list)); free(mutable); mutable = xstrdup(dnses); - shell_arglist[0] = '\0'; - function_arglist[0] = '\0'; + dns_shell_arglist[0] = '\0'; + dns_search_shell_arglist[0] = '\0'; + dns_function_arglist[0] = '\0'; + dns_search_function_arglist[0] = '\0'; dns_list_size = 0; + dns_search_list_size = 0; for (char *dns = strtok(mutable, ", \t\n"); dns; dns = strtok(NULL, ", \t\n")) { if (strchr(dns, '\'') || strchr(dns, '\\')) continue; + is_ip = !regexec(®ex_ipnothost, dns, 0, NULL, 0); snprintf(arg, len + 3, "'%s' ", dns); - strncat(shell_arglist, arg, len * 4 - 1); - snprintf(arg, len + 2, function_arglist[0] == '\0' ? "%s" : ", %s", dns); - strncat(function_arglist, arg, len * 4 - 1); - dns_list[dns_list_size++] = dns; + strncat(is_ip ? dns_shell_arglist : dns_search_shell_arglist, arg, len * 4 - 1); + snprintf(arg, len + 2, (is_ip ? dns_function_arglist[0] : dns_search_function_arglist[0]) == '\0' ? "%s" : ", %s", dns); + strncat(is_ip ? dns_function_arglist : dns_search_function_arglist, arg, len * 4 - 1); + *(is_ip ? &dns_list[dns_list_size++] : &dns_search_list[dns_search_list_size++]) = dns; } if ((handle = dnsresolver_get_handle())) { @@ -889,15 +901,16 @@ static void set_dnses(unsigned int netid, const char *dnses) .base_timeout_msec = DNSRESOLVER_BASE_TIMEOUT, .retry_count = DNSRESOLVER_RETRY_COUNT, .servers = dns_list, - .domains = (char *[]){NULL}, + .domains = dns_search_list, .tls_name = "", .tls_servers = (char *[]){NULL}, .tls_fingerprints = (char *[]){NULL} }; - printf("[#] ::dnsResolver->setResolverConfiguration(%u, [%s], [], %d, %d, %d, %d, %d, %d, [], [])\n", - netid, function_arglist, DNSRESOLVER_SAMPLE_VALIDITY, DNSRESOLVER_SUCCESS_THRESHOLD, - DNSRESOLVER_MIN_SAMPLES, DNSRESOLVER_MAX_SAMPLES, DNSRESOLVER_BASE_TIMEOUT, DNSRESOLVER_RETRY_COUNT); + printf("[#] ::dnsResolver->setResolverConfiguration(%u, [%s], [%s], %d, %d, %d, %d, %d, %d, [], [])\n", + netid, dns_function_arglist, dns_search_function_arglist, DNSRESOLVER_SAMPLE_VALIDITY, + DNSRESOLVER_SUCCESS_THRESHOLD, DNSRESOLVER_MIN_SAMPLES, DNSRESOLVER_MAX_SAMPLES, + DNSRESOLVER_BASE_TIMEOUT, DNSRESOLVER_RETRY_COUNT); status = dnsresolver_set_resolver_configuration(handle, ¶ms); if (status != 0) { @@ -905,7 +918,7 @@ static void set_dnses(unsigned int netid, const char *dnses) exit(ENONET); } } else - cndc("resolver setnetdns %u '' %s", netid, shell_arglist); + cndc("resolver setnetdns %u '%s' %s", netid, dns_search_shell_arglist, dns_shell_arglist); } static void add_addr(const char *iface, const char *addr) diff --git a/src/wg-quick/darwin.bash b/src/wg-quick/darwin.bash index d9d07cf..cde1b54 100755 --- a/src/wg-quick/darwin.bash +++ b/src/wg-quick/darwin.bash @@ -18,6 +18,7 @@ INTERFACE="" ADDRESSES=( ) MTU="" DNS=( ) +DNS_SEARCH=( ) TABLE="" PRE_UP=( ) POST_UP=( ) @@ -43,7 +44,7 @@ die() { CONFIG_SEARCH_PATHS=( /etc/wireguard /usr/local/etc/wireguard ) parse_options() { - local interface_section=0 line key value stripped path + local interface_section=0 line key value stripped path v CONFIG_FILE="$1" if [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]]; then for path in "${CONFIG_SEARCH_PATHS[@]}"; do @@ -67,7 +68,9 @@ parse_options() { case "$key" in Address) ADDRESSES+=( ${value//,/ } ); continue ;; MTU) MTU="$value"; continue ;; - DNS) DNS+=( ${value//,/ } ); continue ;; + DNS) for v in ${value//,/ }; do + [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v ) + done; continue ;; Table) TABLE="$value"; continue ;; PreUp) PRE_UP+=( "$value" ); continue ;; PreDown) PRE_DOWN+=( "$value" ); continue ;; @@ -213,6 +216,7 @@ collect_endpoints() { } declare -A SERVICE_DNS +declare -A SERVICE_DNS_SEARCH collect_new_service_dns() { local service get_response local -A found_services @@ -223,10 +227,16 @@ collect_new_service_dns() { get_response="$(cmd networksetup -getdnsservers "$service")" [[ $get_response == *" "* ]] && get_response="Empty" [[ -n $get_response ]] && SERVICE_DNS["$service"]="$get_response" + get_response="$(cmd networksetup -getsearchdomains "$service")" + [[ $get_response == *" "* ]] && get_response="Empty" + [[ -n $get_response ]] && SERVICE_DNS_SEARCH["$service"]="$get_response" done; } < <(networksetup -listallnetworkservices) for service in "${!SERVICE_DNS[@]}"; do - [[ -n ${found_services["$service"]} ]] || unset SERVICE_DNS["$service"] + if ! [[ -n ${found_services["$service"]} ]]; then + unset SERVICE_DNS["$service"] + unset SERVICE_DNS_SEARCH["$service"] + fi done } @@ -287,7 +297,14 @@ set_dns() { for service in "${!SERVICE_DNS[@]}"; do while read -r response; do [[ $response == *Error* ]] && echo "$response" >&2 - done < <(cmd networksetup -setdnsservers "$service" "${DNS[@]}") + done < <( + cmd networksetup -setdnsservers "$service" "${DNS[@]}" + if [[ ${#DNS_SEARCH[@]} -eq 0 ]]; then + cmd networksetup -setsearchdomains "$service" Empty + else + cmd networksetup -setsearchdomains "$service" "${DNS_SEARCH[@]}" + fi + ) done } @@ -296,7 +313,10 @@ del_dns() { for service in "${!SERVICE_DNS[@]}"; do while read -r response; do [[ $response == *Error* ]] && echo "$response" >&2 - done < <(cmd networksetup -setdnsservers "$service" ${SERVICE_DNS["$service"]} || true) + done < <( + cmd networksetup -setdnsservers "$service" ${SERVICE_DNS["$service"]} || true + cmd networksetup -setsearchdomains "$service" ${SERVICE_DNS_SEARCH["$service"]} || true + ) done } diff --git a/src/wg-quick/freebsd.bash b/src/wg-quick/freebsd.bash index c390dcc..e1ee67f 100755 --- a/src/wg-quick/freebsd.bash +++ b/src/wg-quick/freebsd.bash @@ -16,6 +16,7 @@ INTERFACE="" ADDRESSES=( ) MTU="" DNS=( ) +DNS_SEARCH=( ) TABLE="" PRE_UP=( ) POST_UP=( ) @@ -60,7 +61,7 @@ clean_temp() { } parse_options() { - local interface_section=0 line key value stripped path + local interface_section=0 line key value stripped path v CONFIG_FILE="$1" if [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]]; then for path in "${CONFIG_SEARCH_PATHS[@]}"; do @@ -84,7 +85,9 @@ parse_options() { case "$key" in Address) ADDRESSES+=( ${value//,/ } ); continue ;; MTU) MTU="$value"; continue ;; - DNS) DNS+=( ${value//,/ } ); continue ;; + DNS) for v in ${value//,/ }; do + [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v ) + done; continue ;; Table) TABLE="$value"; continue ;; PreUp) PRE_UP+=( "$value" ); continue ;; PreDown) PRE_DOWN+=( "$value" ); continue ;; @@ -297,7 +300,9 @@ monitor_daemon() { HAVE_SET_DNS=0 set_dns() { [[ ${#DNS[@]} -gt 0 ]] || return 0 - printf 'nameserver %s\n' "${DNS[@]}" | cmd resolvconf -a "$INTERFACE" -x + { printf 'nameserver %s\n' "${DNS[@]}" + [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}" + } | cmd resolvconf -a "$INTERFACE" -x HAVE_SET_DNS=1 } diff --git a/src/wg-quick/linux.bash b/src/wg-quick/linux.bash index 7c2c002..1150be5 100755 --- a/src/wg-quick/linux.bash +++ b/src/wg-quick/linux.bash @@ -16,6 +16,7 @@ INTERFACE="" ADDRESSES=( ) MTU="" DNS=( ) +DNS_SEARCH=( ) TABLE="" PRE_UP=( ) POST_UP=( ) @@ -37,7 +38,7 @@ die() { } parse_options() { - local interface_section=0 line key value stripped + local interface_section=0 line key value stripped v CONFIG_FILE="$1" [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]] && CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf" [[ -e $CONFIG_FILE ]] || die "\`$CONFIG_FILE' does not exist" @@ -56,7 +57,9 @@ parse_options() { case "$key" in Address) ADDRESSES+=( ${value//,/ } ); continue ;; MTU) MTU="$value"; continue ;; - DNS) DNS+=( ${value//,/ } ); continue ;; + DNS) for v in ${value//,/ }; do + [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v ) + done; continue ;; Table) TABLE="$value"; continue ;; PreUp) PRE_UP+=( "$value" ); continue ;; PreDown) PRE_DOWN+=( "$value" ); continue ;; @@ -150,7 +153,9 @@ resolvconf_iface_prefix() { HAVE_SET_DNS=0 set_dns() { [[ ${#DNS[@]} -gt 0 ]] || return 0 - printf 'nameserver %s\n' "${DNS[@]}" | cmd resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x + { printf 'nameserver %s\n' "${DNS[@]}" + [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}" + } | cmd resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x HAVE_SET_DNS=1 } diff --git a/src/wg-quick/openbsd.bash b/src/wg-quick/openbsd.bash index 8d458d1..9584902 100755 --- a/src/wg-quick/openbsd.bash +++ b/src/wg-quick/openbsd.bash @@ -16,6 +16,7 @@ INTERFACE="" ADDRESSES=( ) MTU="" DNS=( ) +DNS_SEARCH=( ) TABLE="" PRE_UP=( ) POST_UP=( ) @@ -56,7 +57,9 @@ parse_options() { case "$key" in Address) ADDRESSES+=( ${value//,/ } ); continue ;; MTU) MTU="$value"; continue ;; - DNS) DNS+=( ${value//,/ } ); continue ;; + DNS) for v in ${value//,/ }; do + [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v ) + done; continue ;; Table) TABLE="$value"; continue ;; PreUp) PRE_UP+=( "$value" ); continue ;; PreDown) PRE_DOWN+=( "$value" ); continue ;; @@ -270,7 +273,9 @@ set_dns() { [[ ${#DNS[@]} -gt 0 ]] || return 0 # TODO: this is a horrible way of doing it. Has OpenBSD no resolvconf? cmd cp /etc/resolv.conf "/etc/resolv.conf.wg-quick-backup.$INTERFACE" - cmd printf 'nameserver %s\n' "${DNS[@]}" > /etc/resolv.conf + { cmd printf 'nameserver %s\n' "${DNS[@]}" + [[ ${#DNS_SEARCH[@]} -eq 0 ]] || cmd printf 'search %s\n' "${DNS_SEARCH[*]}" + } > /etc/resolv.conf } unset_dns() { -- cgit v1.2.3-59-g8ed1b