]> git.ipfire.org Git - ipfire-2.x.git/blobdiff - src/initscripts/init.d/unbound
unbound: Allow list of INSECURE_ZONES being set in sysconfig
[ipfire-2.x.git] / src / initscripts / init.d / unbound
index 8e6881e4d9ce961b1343040f315f5f3066f1ec61..01a560d4044ebc2fd4914c0e43287fe5d50854d5 100644 (file)
@@ -3,25 +3,23 @@
 
 # Description : Unbound DNS resolver boot script for IPfire
 # Author      : Marcel Lorenz <marcel.lorenz@ipfire.org>
-#
-# Comment     : This init script additional starts the dhcpd watcher daemon
-#               if DNS-Update (RFC2136) in web interface enabled
 
 . /etc/sysconfig/rc
 . ${rc_functions}
 
-if [[ ! -d /run/var ]]; then mkdir /run/var; fi;
+TEST_DOMAIN="ipfire.org"
 
-CONTROL_INTERFACE_FILE=1
-CONTROL_ACCESS_FILE=1
-USE_CUSTOM_FORWARDS=0
-ENABLE_DNSSEC=1
+# This domain will never validate
+TEST_DOMAIN_FAIL="dnssec-failed.org"
 
-# Unbound daemon pid file
-PIDFILE=/var/run/unbound.pid
+INSECURE_ZONES=
+USE_FORWARDERS=1
 
-# Watcher deamon pid file must be the same in unbound main init script
-WAPIDFILE=/var/run/unbound_dhcpd.pid
+# Cache any local zones for 60 seconds
+LOCAL_TTL=60
+
+# Load optional configuration
+[ -e "/etc/sysconfig/unbound" ] && . /etc/sysconfig/unbound
 
 function cidr() {
     local cidr nbits IFS;
@@ -47,132 +45,364 @@ function cidr() {
     echo "${cidr}/${nbits}"
 }
 
+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 "$(</var/ipfire/red/dns${i})"
+       done | 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
+
+               # 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 "${broken_forwarders}" -a -z "${forwarders}" ]; then
+                       boot_mesg "Falling back to recursor mode" ${WARNING}
+                       echo_warning
+
+               elif [ -n "${forwarders}" ]; then
+                       boot_mesg "Configuring upstream name server(s): ${forwarders:1}" ${INFO}
+                       echo_ok
+
+                       echo "${forwarders}" > /var/ipfire/red/dns
+                       unbound-control -q forward ${forwarders}
+                       return 0
+               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
+
+       while IFS="," read -r enabled address hostname domainname; 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
+
+               # 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 remark
+               while IFS="," read -r enabled zone server remark; 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}"
+                                       ;;
+                       esac
+
+                       echo "forward-zone:"
+                       echo "  name: ${zone}"
+                       echo "  forward-addr: ${server}"
+                       echo
+               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
+
+       # Large systems with more than 2GB of RAM
+       if [ ${mem} -ge 2048 ]; then
+               mem=128
+
+       # Small systems with less than 256MB of RAM
+       elif [ ${mem} -le 256 ]; then
+               mem=8
+
+       # Everything else
+       else
+               mem=32
+       fi
+
+       (
+               config_header
+
+               # We run one thread per processor
+               echo "num-threads: ${processors}"
+
+               # 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"
+       ) > /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}
+
+       # 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
+
+       # Return 0 if validating
+       ns_is_validating ${ns} && return 0
+
+       local errors
+       for rr in DNSKEY DS RRSIG; do
+               if ! ns_forwards_${rr} ${ns}; 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
+
+       # Is DNSSEC-aware
+       return 2
+}
+
+# Sends an A query to the nameserver w/o DNSSEC
+ns_is_online() {
+       local ns=${1}
+
+       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}
+
+       dig @${ns} A ${TEST_DOMAIN_FAIL} | grep -q SERVFAIL
+}
+
+# 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}
+
+       dig @${ns} DNSKEY ${TEST_DOMAIN} | grep -qv SOA
+}
+
+ns_forwards_DS() {
+       local ns=${1}
+
+       dig @${ns} DS ${TEST_DOMAIN} | grep -qv SOA
+}
+
+ns_forwards_RRSIG() {
+       local ns=${1}
+
+       dig @${ns} +dnssec A ${TEST_DOMAIN} | grep -q RRSIG
+}
+
+ns_supports_tcp() {
+       local ns=${1}
+
+       dig @${ns} +tcp A ${TEST_DOMAIN} >/dev/null || return 1
+}
+
 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
 
-         if [[ -f ${PIDFILE} ]]; then
-           log_warning_msg "Unbound daemon is running with Process ID $(cat ${PIDFILE})"
-         else
-           eval $(/usr/local/bin/readhash /var/ipfire/ethernet/settings)
-           #ARGS="$CUSTOM_ARGS"
-           #[ "$DOMAIN_NAME_GREEN" != "" ] && ARGS="$ARGS -s $DOMAIN_NAME_GREEN"
-
-           echo > /var/ipfire/red/resolv.conf # Clear it
-           if [ -e "/var/ipfire/red/dns1" ]; then
-              DNS1=$(cat /var/ipfire/red/dns1 2>/dev/null)
-             if [ ! -z ${DNS1} ]; then
-               echo "nameserver ${DNS1}" >> /var/ipfire/red/resolv.conf
-               NAMESERVERS="${DNS1} "
-             fi
-           fi
-           if [ -e "/var/ipfire/red/dns2" ]; then
-             DNS2=$(cat /var/ipfire/red/dns2 2>/dev/null)
-             if [ ! -z ${DNS2} ]; then
-               echo "nameserver ${DNS2}" >> /var/ipfire/red/resolv.conf
-               NAMESERVERS+="${DNS2} "
-             fi
-           fi
-
-           # create unbound interfaces.conf
-           if [ ${CONTROL_INTERFACE_FILE} = 1 ]; then
-                 echo -n > /etc/unbound/interfaces.conf # Clear it
-             if [ ! -z ${GREEN_ADDRESS} ]; then
-               echo "interface: ${GREEN_ADDRESS}" >> /etc/unbound/interfaces.conf
-             fi
-             if [ ! -z ${BLUE_ADDRESS} ]; then
-               echo "interface: ${BLUE_ADDRESS}" >> /etc/unbound/interfaces.conf
-             fi
-               if [ ! -z ${ORANGE_ADDRESS} ]; then
-               echo "interface: ${ORANGE_ADDRESS}" >> /etc/unbound/interfaces.conf
-             fi
-           fi
-
-           # create unbound access.conf
-           if [ ${CONTROL_ACCESS_FILE} = 1 ]; then
-             echo -n > /etc/unbound/access.conf # Clear it
-             if [ ! -z ${GREEN_ADDRESS} ]; then
-               echo "access-control: $(cidr ${GREEN_ADDRESS} ${GREEN_NETMASK}) allow" >> /etc/unbound/access.conf
-             fi
-             if [ ! -z ${BLUE_ADDRESS} ]; then
-               echo "access-control: $(cidr ${BLUE_ADDRESS} ${BLUE_NETMASK}) allow" >> /etc/unbound/access.conf
-             fi
-             if [ ! -z ${ORANGE_ADDRESS} ]; then
-               echo "access-control: $(cidr ${ORANGE_ADDRESS} ${ORANGE_NETMASK}) allow" >> /etc/unbound/access.conf
-             fi
-           fi
-
-           # create unbound dnssec.conf
-           echo -n > /etc/unbound/dnssec.conf # Clear it
-           if [ ${ENABLE_DNSSEC} = 1 ]; then
-             echo "    # dessec enabled per default" >> /etc/unbound/dnssec.conf
-             echo "    # no necessary config options in this file" >> /etc/unbound/dnssec.conf
-           else
-             echo "    # dnssec now disabled" >> /etc/unbound/dnssec.conf
-             echo "    module-config: iterator" >> /etc/unbound/dnssec.conf
-             echo "    val-permissive-mode: yes" >> /etc/unbound/dnssec.conf
-           fi
-
-           # create zone file for internal ipfire domain 
-           unbound-zone
-
-           boot_mesg "Starting Unbound DNS proxy..."
-           unbound-anchor
-           loadproc /usr/sbin/unbound
-
-           # start dhcpd watcher daemon if DNS-Update (RFC2136) activated
-           eval $(/usr/local/bin/readhash /var/ipfire/dhcp/settings)
-           if [[ ${DNS_UPDATE_ENABLED} = on && ! -f ${WAPIDFILE} ]]; then
-             /etc/rc.d/init.d/unbound-dhcpd start
-           fi
-
-           # use setup configured DNS servers 
-           if [ "${USE_CUSTOM_FORWARDS}" -eq 0 ]; then
-              unbound-control forward_add +i . ${NAMESERVERS} &> /dev/null
-           fi;
-
-           FORWADRS=$(unbound-control list_forwards |sed 's|. IN forward ||g'|sed 's|+i ||g')
-           if [ "${USE_CUSTOM_FORWARDS}" -eq 0 ]; then
-             boot_mesg "Using DNS server(s): ${FORWADRS}"
-           else
-             boot_mesg "Using custom DNS server(s): ${FORWADRS}"
-           fi
-           if [ ${ENABLE_DNSSEC} = 1 ]; then
-             boot_mesg "DNSSEC is enabled!"
-           else
-             boot_mesg "DNSSEC is disabled!"
-           fi
-         fi 
-         ;;
+               eval $(/usr/local/bin/readhash /var/ipfire/ethernet/settings)
 
-       stop)
+               # Create control keys at first run
+               if [ ! -r "/etc/unbound/unbound_control.key" ]; then
+                       unbound-control-setup -d /etc/unbound &>/dev/null
+               fi
+
+               # Update configuration files
+               write_tuning_conf
+               write_forward_conf
 
-         if [[ -f ${PIDFILE} ]]; then
-           # stop dhcpd watcher daemon if activted
-           if [[ -f ${WAPIDFILE} ]]; then
-             /etc/rc.d/init.d/unbound-dhcpd stop
-           fi
-           # stop Unbound daemon
-           boot_mesg "Stopping Unbound DNS proxy..."
-           killproc -p "/var/run/unbound.pid" /usr/sbin/unbound
-         else
-           log_warning_msg "Unbound daemon is not running..."
-         fi
-         ;;
+               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
+               ;;
+
+       stop)
+               boot_mesg "Stopping Unbound DNS Proxy..."
+               killproc /usr/sbin/unbound
+               ;;
 
        restart)
-         $0 stop
-         sleep 1
-         $0 start
-         ;;
+               $0 stop
+               sleep 1
+               $0 start
+               ;;
 
        status)
-         statusproc /usr/sbin/unbound
-         ;;
+               statusproc /usr/sbin/unbound
+               ;;
+
+       update-forwarders)
+               update_forwarders
+               ;;
+
+       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"
+                               ;;
+               esac
+
+               if ns_supports_tcp ${ns}; then
+                       echo "${ns} supports TCP fallback"
+               else
+                       echo "${ns} does not support TCP fallback"
+               fi
+
+               exit ${ret}
+               ;;
 
        *)
-         echo "Usage: $0 {start|stop|restart|status}"
-         exit 1
-         ;;
+               echo "Usage: $0 {start|stop|restart|status|update-forwarders|test-name-server}"
+               exit 1
+               ;;
 esac
 
 # End $rc_base/init.d/unbound