#!/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 FORCE_TCP=off FORCE_TLS=off # Cache any local zones for 60 seconds LOCAL_TTL=60 # Load optional configuration [ -e "/etc/sysconfig/unbound" ] && . /etc/sysconfig/unbound DIG_ARGS=() if [ "${FORCE_TCP}" = "on" ]; then DIG_ARGS+=( "+tcp" ) fi 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 } check_red_has_carrier_and_ip() { # Interface configured ? [ ! -e "/var/ipfire/red/iface" ] && return 0; # Interface present ? [ ! -e "/sys/class/net/$(/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 # 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; 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 } remove_forwarders() { enable_dnssec 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 # Force using TLS for upstream servers only if [ "${FORCE_TLS}" = "on" ]; then echo "# Force using TLS for upstream servers only" echo "server:" echo " tls-upstream: yes" echo # Force using TCP for upstream servers only elif [ "${FORCE_TCP}" = "on" ]; then echo "# Force using TCP for upstream servers only" echo "server:" echo " tcp-upstream: yes" echo fi 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 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 # Make all reverse lookup zones transparent case "${zone}" in *.in-addr.arpa) echo "server:" echo " local-zone: \"${zone}\" transparent" 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 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 "${DIG_ARGS[@]}" @${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 "${DIG_ARGS[@]}" @${ns} A ${TEST_DOMAIN_FAIL} $@ | grep -q SERVFAIL; then return 1 else # Determine if NS replies with "ad" data flag if DNSSEC enabled dig "${DIG_ARGS[@]}" @${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 "${DIG_ARGS[@]}" @${ns} DNSKEY ${TEST_DOMAIN} $@ | grep -qv SOA } ns_forwards_DS() { local ns=${1} shift dig "${DIG_ARGS[@]}" @${ns} DS ${TEST_DOMAIN} $@ | grep -qv SOA } ns_forwards_RRSIG() { local ns=${1} shift dig "${DIG_ARGS[@]}" @${ns} +dnssec A ${TEST_DOMAIN} $@ | grep -q RRSIG } ns_supports_tcp() { local ns=${1} shift # If TCP is forced we know by now if the server responds to it if [ "${FORCE_TCP}" = "on" ]; then return 0 fi dig "${DIG_ARGS[@]}" @${ns} +tcp A ${TEST_DOMAIN} $@ >/dev/null || 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 "${DIG_ARGS[@]}" @${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) check_red_has_carrier_and_ip if [ -e "/var/ipfire/red/iface" -a "${?}" = "1" ]; 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 } resolve() { local hostname="${1}" local found=0 local ns for ns in $(read_name_servers); do local answer for answer in $(dig "${DIG_ARGS[@]}" +short "@${ns}" A "${hostname}"); do found=1 # Filter out non-IP addresses if [[ ! "${answer}" =~ \.$ ]]; then echo "${answer}" fi done # End loop when we have got something [ ${found} -eq 1 ] && break done } # Sets up Safe Search for various search engines update_safe_search() { 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 ) # Cleanup previous settings unbound-control local_zone_remove "bing.com" >/dev/null unbound-control local_zone_remove "duckduckgo.com" >/dev/null unbound-control local_zone_remove "yandex.com" >/dev/null unbound-control local_zone_remove "yandex.ru" >/dev/null unbound-control local_zone_remove "youtube.com" >/dev/null local domain for domain in ${google_tlds[@]}; do unbound-control local_zone_remove "${domain}" done >/dev/null # Nothing to do if safe search is not enabled if [ "${ENABLE_SAFE_SEARCH}" != "on" ]; then return 0 fi # Bing unbound-control bing.com transparent >/dev/null for address in $(resolve "strict.bing.com"); do unbound-control local_data "www.bing.com ${LOCAL_TTL} IN A ${address}" done >/dev/null # DuckDuckGo unbound-control local_zone duckduckgo.com typetransparent >/dev/null for address in $(resolve "safe.duckduckgo.com"); do unbound-control local_data "duckduckgo.com ${LOCAL_TTL} IN A ${address}" done >/dev/null # Google local addresses="$(resolve "forcesafesearch.google.com")" for domain in ${google_tlds[@]}; do unbound-control local_zone "${domain}" transparent >/dev/null for address in ${addresses}; do unbound-control local_data: "www.${domain} ${LOCAL_TTL} IN A ${address}" done >/dev/null done # Yandex for domain in yandex.com yandex.ru; do unbound-control local_zone "${domain}" typetransparent >/dev/null for address in $(resolve "familysearch.${domain}"); do unbound-control local_data "${domain} ${LOCAL_TTL} IN A ${address}" done >/dev/null done # YouTube unbound-control local_zone youtube.com transparent >/dev/null for address in $(resolve "restrictmoderate.youtube.com"); do unbound-control local_data "www.youtube.com ${LOCAL_TTL} IN A ${address}" done >/dev/null return 0 } 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 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 # Install Safe Search rules when the system is already online if [ -e "/var/ipfire/red/active" ]; then update_safe_search fi # 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 ;; remove-forwarders) # Do not try updating forwarders when unbound is not running if ! pgrep unbound &>/dev/null; then exit 0 fi remove_forwarders unbound-control flush_negative > /dev/null unbound-control flush_bogus > /dev/null ;; resolve) resolve "${2}" ;; update-safe-search) update_safe_search ;; *) echo "Usage: $0 {start|stop|restart|status|update-forwarders|remove-forwarders|resolve|update-safe-search}" exit 1 ;; esac # End $rc_base/init.d/unbound