#!/bin/sh # Begin $rc_base/init.d/unbound # Description : Unbound DNS resolver boot script for IPfire # Author : Marcel Lorenz . /etc/sysconfig/rc . ${rc_functions} TEST_DOMAIN="ipfire.org" # This domain will never validate TEST_DOMAIN_FAIL="dnssec-failed.org" INSECURE_ZONES= USE_FORWARDERS=1 ENABLE_SAFE_SEARCH=off # Cache any local zones for 60 seconds LOCAL_TTL=60 # EDNS buffer size EDNS_DEFAULT_BUFFER_SIZE=4096 # Load optional configuration [ -e "/etc/sysconfig/unbound" ] && . /etc/sysconfig/unbound ip_address_revptr() { local addr=${1} local a1 a2 a3 a4 IFS=. read -r a1 a2 a3 a4 <<< ${addr} echo "${a4}.${a3}.${a2}.${a1}.in-addr.arpa" } read_name_servers() { local i for i in 1 2; do echo "$(/dev/null | xargs echo } config_header() { echo "# This file is automatically generated and any changes" echo "# will be overwritten. DO NOT EDIT!" echo } update_forwarders() { if [ "${USE_FORWARDERS}" = "1" -a -e "/var/ipfire/red/active" ]; then local forwarders local broken_forwarders local ns for ns in $(read_name_servers); do test_name_server ${ns} &>/dev/null case "$?" in # Only use DNSSEC-validating or DNSSEC-aware name servers 0|2) forwarders="${forwarders} ${ns}" ;; *) broken_forwarders="${broken_forwarders} ${ns}" ;; esac done # Determine EDNS buffer size local new_edns_buffer_size=${EDNS_DEFAULT_BUFFER_SIZE} for ns in ${forwarders}; do local edns_buffer_size=$(ns_determine_edns_buffer_size ${ns}) if [ -n "${edns_buffer_size}" ]; then if [ ${edns_buffer_size} -lt ${new_edns_buffer_size} ]; then new_edns_buffer_size=${edns_buffer_size} fi fi done if [ ${new_edns_buffer_size} -lt ${EDNS_DEFAULT_BUFFER_SIZE} ]; then boot_mesg "EDNS buffer size reduced to ${new_edns_buffer_size}" ${WARNING} echo_warning unbound-control -q set_option edns-buffer-size: ${new_edns_buffer_size} fi # Show warning for any broken upstream name servers if [ -n "${broken_forwarders}" ]; then boot_mesg "Ignoring broken upstream name server(s): ${broken_forwarders:1}" ${WARNING} echo_warning fi if [ -n "${forwarders}" ]; then boot_mesg "Configuring upstream name server(s): ${forwarders:1}" ${INFO} echo_ok # Make sure DNSSEC is activated enable_dnssec echo "${forwarders}" > /var/ipfire/red/dns unbound-control -q forward ${forwarders} return 0 # In case we have found no working forwarders else # Test if the recursor mode is available if can_resolve_root +bufsize=${new_edns_buffer_size}; then # Make sure DNSSEC is activated enable_dnssec boot_mesg "Falling back to recursor mode" ${WARNING} echo_warning # If not, we set DNSSEC in permissive mode and allow using all recursors elif [ -n "${broken_forwarders}" ]; then disable_dnssec boot_mesg "DNSSEC has been set to permissive mode" ${FAILURE} echo_failure echo "${broken_forwarders}" > /var/ipfire/red/dns unbound-control -q forward ${broken_forwarders} return 0 fi fi fi # If forwarders cannot be used we run in recursor mode echo "local recursor" > /var/ipfire/red/dns unbound-control -q forward off } own_hostname() { local hostname=$(hostname -f) # 1.1.1.1 is reserved for unused green, skip this if [ -n "${GREEN_ADDRESS}" -a "${GREEN_ADDRESS}" != "1.1.1.1" ]; then unbound-control -q local_data "${hostname} ${LOCAL_TTL} IN A ${GREEN_ADDRESS}" fi local address for address in ${GREEN_ADDRESS} ${BLUE_ADDRESS} ${ORANGE_ADDRESS}; do [ -n "${address}" ] || continue [ "${address}" = "1.1.1.1" ] && continue address=$(ip_address_revptr ${address}) unbound-control -q local_data "${address} ${LOCAL_TTL} IN PTR ${hostname}" done } update_hosts() { local enabled address hostname domainname generateptr while IFS="," read -r enabled address hostname domainname generateptr; do [ "${enabled}" = "on" ] || continue # Build FQDN local fqdn="${hostname}.${domainname}" unbound-control -q local_data "${fqdn} ${LOCAL_TTL} IN A ${address}" # Skip reverse resolution if the address equals the GREEN address [ "${address}" = "${GREEN_ADDRESS}" ] && continue # Skip reverse resolution if user requested not to do so [ "${generateptr}" = "off" ] && continue # Add RDNS address=$(ip_address_revptr ${address}) unbound-control -q local_data "${address} ${LOCAL_TTL} IN PTR ${fqdn}" done < /var/ipfire/main/hosts } write_forward_conf() { ( config_header local insecure_zones="${INSECURE_ZONES}" local enabled zone server servers remark disable_dnssec rest while IFS="," read -r enabled zone servers remark disable_dnssec rest; do # Line must be enabled. [ "${enabled}" = "on" ] || continue # Zones that end with .local are commonly used for internal # zones and therefore not signed case "${zone}" in *.local) insecure_zones="${insecure_zones} ${zone}" ;; *) if [ "${disable_dnssec}" = "on" ]; then insecure_zones="${insecure_zones} ${zone}" fi ;; esac # Reverse-lookup zones must be stubs case "${zone}" in *.in-addr.arpa) echo "stub-zone:" echo " name: ${zone}" for server in ${servers//|/ }; do if [[ ${server} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo " stub-addr: ${server}" else echo " stub-host: ${server}" fi done echo echo "server:" echo " local-zone: \"${zone}\" transparent" echo ;; *) echo "forward-zone:" echo " name: ${zone}" for server in ${servers//|/ }; do if [[ ${server} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo " forward-addr: ${server}" else echo " forward-host: ${server}" fi done echo ;; esac done < /var/ipfire/dnsforward/config if [ -n "${insecure_zones}" ]; then echo "server:" for zone in ${insecure_zones}; do echo " domain-insecure: ${zone}" done fi ) > /etc/unbound/forward.conf } write_tuning_conf() { # https://www.unbound.net/documentation/howto_optimise.html # Determine number of online processors local processors=$(getconf _NPROCESSORS_ONLN) # Determine number of slabs local slabs=1 while [ ${slabs} -lt ${processors} ]; do slabs=$(( ${slabs} * 2 )) done # Determine amount of system memory local mem=$(get_memory_amount) # In the worst case scenario, unbound can use double the # amount of memory allocated to a cache due to malloc overhead # Even larger systems with more than 8GB of RAM if [ ${mem} -ge 8192 ]; then mem=1024 # Extra large systems with more than 4GB of RAM elif [ ${mem} -ge 4096 ]; then mem=512 # Large systems with more than 2GB of RAM elif [ ${mem} -ge 2048 ]; then mem=256 # Medium systems with more than 1GB of RAM elif [ ${mem} -ge 1024 ]; then mem=128 # Small systems with less than 256MB of RAM elif [ ${mem} -le 256 ]; then mem=16 # Everything else else mem=64 fi ( config_header # We run one thread per processor echo "num-threads: ${processors}" echo "so-reuseport: yes" # Adjust number of slabs echo "infra-cache-slabs: ${slabs}" echo "key-cache-slabs: ${slabs}" echo "msg-cache-slabs: ${slabs}" echo "rrset-cache-slabs: ${slabs}" # Slice up the cache echo "rrset-cache-size: $(( ${mem} / 2 ))m" echo "msg-cache-size: $(( ${mem} / 4 ))m" echo "key-cache-size: $(( ${mem} / 4 ))m" # Increase parallel queries echo "outgoing-range: 8192" echo "num-queries-per-thread: 4096" # Use larger send/receive buffers echo "so-sndbuf: 4m" echo "so-rcvbuf: 4m" ) > /etc/unbound/tuning.conf } get_memory_amount() { local key val unit while read -r key val unit; do case "${key}" in MemTotal:*) # Convert to MB echo "$(( ${val} / 1024 ))" break ;; esac done < /proc/meminfo } test_name_server() { local ns=${1} local args # Return codes: # 0 DNSSEC validating # 1 Error: unreachable, etc. # 2 DNSSEC aware # 3 NOT DNSSEC-aware # Exit when the server is not reachable ns_is_online ${ns} || return 1 # Determine the maximum edns buffer size that works local edns_buffer_size=$(ns_determine_edns_buffer_size ${ns}) if [ -n "${edns_buffer_size}" ]; then args="${args} +bufsize=${edns_buffer_size}" fi local errors for rr in DNSKEY DS RRSIG; do if ! ns_forwards_${rr} ${ns} ${args}; then errors="${errors} ${rr}" fi done if [ -n "${errors}" ]; then echo >&2 "Unable to retrieve the following resource records from ${ns}: ${errors:1}" return 3 fi if ns_is_validating ${ns} ${args}; then # Return 0 if validating return 0 else # Is DNSSEC-aware return 2 fi } # Sends an A query to the nameserver w/o DNSSEC ns_is_online() { local ns=${1} shift dig @${ns} +nodnssec A ${TEST_DOMAIN} $@ >/dev/null } # Resolving ${TEST_DOMAIN_FAIL} will fail if the nameserver is validating ns_is_validating() { local ns=${1} shift if ! dig @${ns} A ${TEST_DOMAIN_FAIL} $@ | grep -q SERVFAIL; then return 1 else # Determine if NS replies with "ad" data flag if DNSSEC enabled dig @${ns} +dnssec SOA ${TEST_DOMAIN} $@ | awk -F: '/\;\;\ flags\:/ { s=1; if (/\ ad/) s=0; exit s }' fi } # Checks if we can retrieve the DNSKEY for this domain. # dig will print the SOA if nothing was found ns_forwards_DNSKEY() { local ns=${1} shift dig @${ns} DNSKEY ${TEST_DOMAIN} $@ | grep -qv SOA } ns_forwards_DS() { local ns=${1} shift dig @${ns} DS ${TEST_DOMAIN} $@ | grep -qv SOA } ns_forwards_RRSIG() { local ns=${1} shift dig @${ns} +dnssec A ${TEST_DOMAIN} $@ | grep -q RRSIG } ns_supports_tcp() { local ns=${1} shift dig @${ns} +tcp A ${TEST_DOMAIN} $@ >/dev/null || return 1 } ns_determine_edns_buffer_size() { local ns=${1} shift local b for b in 4096 2048 1500 1480 1464 1400 1280 512; do if dig @${ns} +dnssec +bufsize=${b} A ${TEST_DOMAIN} $@ >/dev/null; then echo "${b}" return 0 fi done return 1 } get_root_nameservers() { while read -r hostname ttl record address; do # Searching for A records [ "${record}" = "A" ] || continue echo "${address}" done < /etc/unbound/root.hints } can_resolve_root() { local ns for ns in $(get_root_nameservers); do if dig @${ns} +dnssec SOA . $@ >/dev/null; then return 0 fi done # none of the servers was reachable return 1 } enable_dnssec() { local status=$(unbound-control get_option val-permissive-mode) # Log DNSSEC status echo "on" > /var/ipfire/red/dnssec-status # Don't do anything if DNSSEC is already activated [ "${status}" = "no" ] && return 0 # Activate DNSSEC and flush cache with any stale and unvalidated data unbound-control -q set_option val-permissive-mode: no unbound-control -q flush_zone . } disable_dnssec() { # Log DNSSEC status echo "off" > /var/ipfire/red/dnssec-status unbound-control -q set_option val-permissive-mode: yes } fix_time_if_dns_fail() { # If DNS still not work try to init ntp with # hardcoded ntp.ipfire.org (81.3.27.46) if [ -e /var/ipfire/red/active ]; then host 0.ipfire.pool.ntp.org > /dev/null 2>&1 if [ "${?}" != "0" ]; then boot_mesg "DNS still not functioning... Trying to sync time with ntp.ipfire.org (81.3.27.46)..." loadproc /usr/local/bin/settime 81.3.27.46 fi fi } # Sets up Safe Search for various search engines write_safe_search_conf() { local google_tlds=( google.ad google.ae google.al google.am google.as google.at google.az google.ba google.be google.bf google.bg google.bi google.bj google.bs google.bt google.by google.ca google.cat google.cd google.cf google.cg google.ch google.ci google.cl google.cm google.cn google.co.ao google.co.bw google.co.ck google.co.cr google.co.id google.co.il google.co.in google.co.jp google.co.ke google.co.kr google.co.ls google.com google.co.ma google.com.af google.com.ag google.com.ai google.com.ar google.com.au google.com.bd google.com.bh google.com.bn google.com.bo google.com.br google.com.bz google.com.co google.com.cu google.com.cy google.com.do google.com.ec google.com.eg google.com.et google.com.fj google.com.gh google.com.gi google.com.gt google.com.hk google.com.jm google.com.kh google.com.kw google.com.lb google.com.ly google.com.mm google.com.mt google.com.mx google.com.my google.com.na google.com.nf google.com.ng google.com.ni google.com.np google.com.om google.com.pa google.com.pe google.com.pg google.com.ph google.com.pk google.com.pr google.com.py google.com.qa google.com.sa google.com.sb google.com.sg google.com.sl google.com.sv google.com.tj google.com.tr google.com.tw google.com.ua google.com.uy google.com.vc google.com.vn google.co.mz google.co.nz google.co.th google.co.tz google.co.ug google.co.uk google.co.uz google.co.ve google.co.vi google.co.za google.co.zm google.co.zw google.cv google.cz google.de google.dj google.dk google.dm google.dz google.ee google.es google.fi google.fm google.fr google.ga google.ge google.gg google.gl google.gm google.gp google.gr google.gy google.hn google.hr google.ht google.hu google.ie google.im google.iq google.is google.it google.je google.jo google.kg google.ki google.kz google.la google.li google.lk google.lt google.lu google.lv google.md google.me google.mg google.mk google.ml google.mn google.ms google.mu google.mv google.mw google.ne google.nl google.no google.nr google.nu google.pl google.pn google.ps google.pt google.ro google.rs google.ru google.rw google.sc google.se google.sh google.si google.sk google.sm google.sn google.so google.sr google.st google.td google.tg google.tk google.tl google.tm google.tn google.to google.tt google.vg google.vu google.ws ) ( # Nothing to do if safe search is not enabled if [ "${ENABLE_SAFE_SEARCH}" != "on" ]; then exit 0 fi # This all belongs into the server: section echo "server:" # Bing echo " local-zone: bing.com transparent" echo " local-data: \"www.bing.com CNAME strict.bing.com.\"" # DuckDuckGo echo " local-zone: duckduckgo.com transparent" echo " local-data: \"duckduckgo.com CNAME safe.duckduckgo.com.\"" # Google local domain for domain in ${google_tlds[@]}; do echo " local-zone: ${domain} transparent" echo " local-data: \"www.${domain} CNAME forcesafesearch.google.com.\"" done # Yandex echo " local-zone: yandex.ru transparent" echo " local-data: \"yandex.ru A 213.180.193.56\"" ) > /etc/unbound/safe-search.conf } case "$1" in start) # Print a nicer messagen when unbound is already running if pidofproc -s unbound; then statusproc /usr/sbin/unbound exit 0 fi eval $(/usr/local/bin/readhash /var/ipfire/ethernet/settings) # Update configuration files write_tuning_conf write_forward_conf write_safe_search_conf boot_mesg "Starting Unbound DNS Proxy..." loadproc /usr/sbin/unbound || exit $? # Make own hostname resolveable own_hostname # Update any known forwarding name servers update_forwarders # Update hosts update_hosts fix_time_if_dns_fail ;; stop) boot_mesg "Stopping Unbound DNS Proxy..." killproc /usr/sbin/unbound ;; restart) $0 stop sleep 1 $0 start ;; status) statusproc /usr/sbin/unbound ;; update-forwarders) # Do not try updating forwarders when unbound is not running if ! pgrep unbound &>/dev/null; then exit 0 fi update_forwarders unbound-control flush_negative > /dev/null unbound-control flush_bogus > /dev/null fix_time_if_dns_fail ;; test-name-server) ns=${2} test_name_server ${ns} ret=${?} case "${ret}" in 0) echo "${ns} is validating" ;; 2) echo "${ns} is DNSSEC-aware" ;; 3) echo "${ns} is NOT DNSSEC-aware" ;; *) echo "Test failed for an unknown reason" exit ${ret} ;; esac if ns_supports_tcp ${ns}; then echo "${ns} supports TCP fallback" else echo "${ns} does not support TCP fallback" fi edns_buffer_size=$(ns_determine_edns_buffer_size ${ns}) if [ -n "${edns_buffer_size}" ]; then echo "EDNS buffer size for ${ns}: ${edns_buffer_size}" fi exit ${ret} ;; *) echo "Usage: $0 {start|stop|restart|status|update-forwarders|test-name-server}" exit 1 ;; esac # End $rc_base/init.d/unbound