08007f50a31a0ae51875e9cc9319f9f21f13c1d2
[ipfire-2.x.git] / src / initscripts / system / unbound
1 #!/bin/sh
2 # Begin $rc_base/init.d/unbound
3
4 # Description : Unbound DNS resolver boot script for IPfire
5 # Author      : Marcel Lorenz <marcel.lorenz@ipfire.org>
6
7 . /etc/sysconfig/rc
8 . ${rc_functions}
9
10 TEST_DOMAIN="ipfire.org"
11
12 # This domain will never validate
13 TEST_DOMAIN_FAIL="dnssec-failed.org"
14
15 INSECURE_ZONES=
16 USE_FORWARDERS=1
17
18 # Cache any local zones for 60 seconds
19 LOCAL_TTL=60
20
21 # EDNS buffer size
22 EDNS_DEFAULT_BUFFER_SIZE=4096
23
24 # Load optional configuration
25 [ -e "/etc/sysconfig/unbound" ] && . /etc/sysconfig/unbound
26
27 function cidr() {
28     local cidr nbits IFS;
29     IFS=. read -r i1 i2 i3 i4 <<< ${1}
30     IFS=. read -r m1 m2 m3 m4 <<< ${2}
31     cidr=$(printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$((i2 & m2))" "$((i3 & m3))" "$((i4 & m4))")
32     nbits=0
33     IFS=.
34     for dec in $2 ; do
35         case $dec in
36             255) let nbits+=8;;
37             254) let nbits+=7;;
38             252) let nbits+=6;;
39             248) let nbits+=5;;
40             240) let nbits+=4;;
41             224) let nbits+=3;;
42             192) let nbits+=2;;
43             128) let nbits+=1;;
44             0);;
45             *) echo "Error: $dec is not recognised"; exit 1
46         esac
47     done
48     echo "${cidr}/${nbits}"
49 }
50
51 ip_address_revptr() {
52         local addr=${1}
53
54         local a1 a2 a3 a4
55         IFS=. read -r a1 a2 a3 a4 <<< ${addr}
56
57         echo "${a4}.${a3}.${a2}.${a1}.in-addr.arpa"
58 }
59
60 read_name_servers() {
61         local i
62         for i in 1 2; do
63                 echo "$(</var/ipfire/red/dns${i})"
64         done 2>/dev/null | xargs echo
65 }
66
67 config_header() {
68         echo "# This file is automatically generated and any changes"
69         echo "# will be overwritten. DO NOT EDIT!"
70         echo
71 }
72
73 update_forwarders() {
74         if [ "${USE_FORWARDERS}" = "1" -a -e "/var/ipfire/red/active" ]; then
75                 local forwarders
76                 local broken_forwarders
77
78                 local ns
79                 for ns in $(read_name_servers); do
80                         test_name_server ${ns} &>/dev/null
81                         case "$?" in
82                                 # Only use DNSSEC-validating or DNSSEC-aware name servers
83                                 0|2)
84                                         forwarders="${forwarders} ${ns}"
85                                         ;;
86                                 *)
87                                         broken_forwarders="${broken_forwarders} ${ns}"
88                                         ;;
89                         esac
90                 done
91
92                 # Determine EDNS buffer size
93                 local new_edns_buffer_size=${EDNS_DEFAULT_BUFFER_SIZE}
94
95                 for ns in ${forwarders}; do
96                         local edns_buffer_size=$(ns_determine_edns_buffer_size ${ns})
97                         if [ -n "${edns_buffer_size}" ]; then
98                                 if [ ${edns_buffer_size} -lt ${new_edns_buffer_size} ]; then
99                                         new_edns_buffer_size=${edns_buffer_size}
100                                 fi
101                         fi
102                 done
103
104                 if [ ${new_edns_buffer_size} -lt ${EDNS_DEFAULT_BUFFER_SIZE} ]; then
105                         boot_mesg "EDNS buffer size reduced to ${new_edns_buffer_size}" ${WARNING}
106                         echo_warning
107
108                         unbound-control -q set_option edns-buffer-size: ${new_edns_buffer_size}
109                 fi
110
111                 # Show warning for any broken upstream name servers
112                 if [ -n "${broken_forwarders}" ]; then
113                         boot_mesg "Ignoring broken upstream name server(s): ${broken_forwarders:1}" ${WARNING}
114                         echo_warning
115                 fi
116
117                 if [ -n "${forwarders}" ]; then
118                         boot_mesg "Configuring upstream name server(s): ${forwarders:1}" ${INFO}
119                         echo_ok
120
121                         # Make sure DNSSEC is activated
122                         enable_dnssec
123
124                         echo "${forwarders}" > /var/ipfire/red/dns
125                         unbound-control -q forward ${forwarders}
126                         return 0
127
128                 # In case we have found no working forwarders
129                 else
130                         # Test if the recursor mode is available
131                         if can_resolve_root +bufsize=${new_edns_buffer_size}; then
132                                 # Make sure DNSSEC is activated
133                                 enable_dnssec
134
135                                 boot_mesg "Falling back to recursor mode" ${WARNING}
136                                 echo_warning
137
138                         # If not, we set DNSSEC in permissive mode and allow using all recursors
139                         elif [ -n "${broken_forwarders}" ]; then
140                                 disable_dnssec
141
142                                 boot_mesg "DNSSEC has been set to permissive mode" ${FAILURE}
143                                 echo_failure
144
145                                 echo "${broken_forwarders}" > /var/ipfire/red/dns
146                                 unbound-control -q forward ${broken_forwarders}
147                                 return 0
148                         fi
149                 fi
150         fi
151
152         # If forwarders cannot be used we run in recursor mode
153         echo "local recursor" > /var/ipfire/red/dns
154         unbound-control -q forward off
155 }
156
157 own_hostname() {
158         local hostname=$(hostname -f)
159         # 1.1.1.1 is reserved for unused green, skip this
160         if [ -n "${GREEN_ADDRESS}" -a "${GREEN_ADDRESS}" != "1.1.1.1" ]; then
161                 unbound-control -q local_data "${hostname} ${LOCAL_TTL} IN A ${GREEN_ADDRESS}"
162         fi
163
164         local address
165         for address in ${GREEN_ADDRESS} ${BLUE_ADDRESS} ${ORANGE_ADDRESS}; do
166                 [ -n "${address}" ] || continue
167                 [ "${address}" = "1.1.1.1" ] && continue
168
169                 address=$(ip_address_revptr ${address})
170                 unbound-control -q local_data "${address} ${LOCAL_TTL} IN PTR ${hostname}"
171         done
172 }
173
174 update_hosts() {
175         local enabled address hostname domainname
176
177         while IFS="," read -r enabled address hostname domainname; do
178                 [ "${enabled}" = "on" ] || continue
179
180                 # Build FQDN
181                 local fqdn="${hostname}.${domainname}"
182
183                 unbound-control -q local_data "${fqdn} ${LOCAL_TTL} IN A ${address}"
184
185                 # Skip reverse resolution if the address equals the GREEN address
186                 [ "${address}" = "${GREEN_ADDRESS}" ] && continue
187
188                 # Add RDNS
189                 address=$(ip_address_revptr ${address})
190                 unbound-control -q local_data "${address} ${LOCAL_TTL} IN PTR ${fqdn}"
191         done < /var/ipfire/main/hosts
192 }
193
194 write_forward_conf() {
195         (
196                 config_header
197
198                 local insecure_zones="${INSECURE_ZONES}"
199
200                 local enabled zone server servers remark
201                 while IFS="," read -r enabled zone servers remark; do
202                         # Line must be enabled.
203                         [ "${enabled}" = "on" ] || continue
204
205                         # Zones that end with .local are commonly used for internal
206                         # zones and therefore not signed
207                         case "${zone}" in
208                                 *.local)
209                                         insecure_zones="${insecure_zones} ${zone}"
210                                         ;;
211                         esac
212
213                         # Reverse-lookup zones must be stubs
214                         case "${zone}" in
215                                 *.in-addr.arpa)
216                                         echo "stub-zone:"
217                                         echo "  name: ${zone}"
218                                         for server in ${servers//|/ }; do
219                                                 if [[ ${server} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
220                                                         echo "  stub-addr: ${server}"
221                                                 else
222                                                         echo "  stub-host: ${server}"
223                                                 fi
224                                         done
225                                         echo
226                                         echo "server:"
227                                         echo "  local-zone: \"${zone}\" transparent"
228                                         echo
229                                         ;;
230                                 *)
231                                         echo "forward-zone:"
232                                         echo "  name: ${zone}"
233                                         for server in ${servers//|/ }; do
234                                                 if [[ ${server} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
235                                                         echo "  forward-addr: ${server}"
236                                                 else
237                                                         echo "  forward-host: ${server}"
238                                                 fi
239                                         done
240                                         echo
241                                         ;;
242                         esac
243                 done < /var/ipfire/dnsforward/config
244
245                 if [ -n "${insecure_zones}" ]; then
246                         echo "server:"
247
248                         for zone in ${insecure_zones}; do
249                                 echo "  domain-insecure: ${zone}"
250                         done
251                 fi
252         ) > /etc/unbound/forward.conf
253 }
254
255 write_tuning_conf() {
256         # https://www.unbound.net/documentation/howto_optimise.html
257
258         # Determine number of online processors
259         local processors=$(getconf _NPROCESSORS_ONLN)
260
261         # Determine number of slabs
262         local slabs=1
263         while [ ${slabs} -lt ${processors} ]; do
264                 slabs=$(( ${slabs} * 2 ))
265         done
266
267         # Determine amount of system memory
268         local mem=$(get_memory_amount)
269
270         # In the worst case scenario, unbound can use double the
271         # amount of memory allocated to a cache due to malloc overhead
272
273         # Even larger systems with more than 8GB of RAM
274         if [ ${mem} -ge 8192 ]; then
275                 mem=1024
276
277         # Extra large systems with more than 4GB of RAM
278         elif [ ${mem} -ge 4096 ]; then
279                 mem=512
280
281         # Large systems with more than 2GB of RAM
282         elif [ ${mem} -ge 2048 ]; then
283                 mem=256
284
285         # Medium systems with more than 1GB of RAM
286         elif [ ${mem} -ge 1024 ]; then
287                 mem=128
288
289         # Small systems with less than 256MB of RAM
290         elif [ ${mem} -le 256 ]; then
291                 mem=16
292
293         # Everything else
294         else
295                 mem=64
296         fi
297
298         (
299                 config_header
300
301                 # We run one thread per processor
302                 echo "num-threads: ${processors}"
303                 echo "so-reuseport: yes"
304
305                 # Adjust number of slabs
306                 echo "infra-cache-slabs: ${slabs}"
307                 echo "key-cache-slabs: ${slabs}"
308                 echo "msg-cache-slabs: ${slabs}"
309                 echo "rrset-cache-slabs: ${slabs}"
310
311                 # Slice up the cache
312                 echo "rrset-cache-size: $(( ${mem} / 2 ))m"
313                 echo "msg-cache-size: $(( ${mem} / 4 ))m"
314                 echo "key-cache-size: $(( ${mem} / 4 ))m"
315
316                 # Increase parallel queries
317                 echo "outgoing-range: 8192"
318                 echo "num-queries-per-thread: 4096"
319
320                 # Use larger send/receive buffers
321                 echo "so-sndbuf: 4m"
322                 echo "so-rcvbuf: 4m"
323         ) > /etc/unbound/tuning.conf
324 }
325
326 get_memory_amount() {
327         local key val unit
328
329         while read -r key val unit; do
330                 case "${key}" in
331                         MemTotal:*)
332                                 # Convert to MB
333                                 echo "$(( ${val} / 1024 ))"
334                                 break
335                                 ;;
336                 esac
337         done < /proc/meminfo
338 }
339
340 test_name_server() {
341         local ns=${1}
342         local args
343
344         # Return codes:
345         # 0     DNSSEC validating
346         # 1     Error: unreachable, etc.
347         # 2     DNSSEC aware
348         # 3     NOT DNSSEC-aware
349
350         # Exit when the server is not reachable
351         ns_is_online ${ns} || return 1
352
353         # Determine the maximum edns buffer size that works
354         local edns_buffer_size=$(ns_determine_edns_buffer_size ${ns})
355         if [ -n "${edns_buffer_size}" ]; then
356                 args="${args} +bufsize=${edns_buffer_size}"
357         fi
358
359         local errors
360         for rr in DNSKEY DS RRSIG; do
361                 if ! ns_forwards_${rr} ${ns} ${args}; then
362                         errors="${errors} ${rr}"
363                 fi
364         done
365
366         if [ -n "${errors}" ]; then
367                 echo >&2 "Unable to retrieve the following resource records from ${ns}: ${errors:1}"
368                 return 3
369         fi
370
371         if ns_is_validating ${ns} ${args}; then
372                 # Return 0 if validating
373                 return 0
374         else
375                 # Is DNSSEC-aware
376                 return 2
377         fi
378 }
379
380 # Sends an A query to the nameserver w/o DNSSEC
381 ns_is_online() {
382         local ns=${1}
383         shift
384
385         dig @${ns} +nodnssec A ${TEST_DOMAIN} $@ >/dev/null
386 }
387
388 # Resolving ${TEST_DOMAIN_FAIL} will fail if the nameserver is validating
389 ns_is_validating() {
390         local ns=${1}
391         shift
392
393         if ! dig @${ns} A ${TEST_DOMAIN_FAIL} $@ | grep -q SERVFAIL; then
394                 return 1
395         else
396                 # Determine if NS replies with "ad" data flag if DNSSEC enabled
397                 dig @${ns} +dnssec SOA ${TEST_DOMAIN} $@ | awk -F: '/\;\;\ flags\:/ { s=1; if (/\ ad/) s=0; exit s }'
398         fi
399 }
400
401 # Checks if we can retrieve the DNSKEY for this domain.
402 # dig will print the SOA if nothing was found
403 ns_forwards_DNSKEY() {
404         local ns=${1}
405         shift
406
407         dig @${ns} DNSKEY ${TEST_DOMAIN} $@ | grep -qv SOA
408 }
409
410 ns_forwards_DS() {
411         local ns=${1}
412         shift
413
414         dig @${ns} DS ${TEST_DOMAIN} $@ | grep -qv SOA
415 }
416
417 ns_forwards_RRSIG() {
418         local ns=${1}
419         shift
420
421         dig @${ns} +dnssec A ${TEST_DOMAIN} $@ | grep -q RRSIG
422 }
423
424 ns_supports_tcp() {
425         local ns=${1}
426         shift
427
428         dig @${ns} +tcp A ${TEST_DOMAIN} $@ >/dev/null || return 1
429 }
430
431 ns_determine_edns_buffer_size() {
432         local ns=${1}
433         shift
434
435         local b
436         for b in 4096 2048 1500 1480 1464 1400 1280 512; do
437                 if dig @${ns} +dnssec +bufsize=${b} A ${TEST_DOMAIN} $@ >/dev/null; then
438                         echo "${b}"
439                         return 0
440                 fi
441         done
442
443         return 1
444 }
445
446 get_root_nameservers() {
447         while read -r hostname ttl record address; do
448                 # Searching for A records
449                 [ "${record}" = "A" ] || continue
450
451                 echo "${address}"
452         done < /etc/unbound/root.hints
453 }
454
455 can_resolve_root() {
456         local ns
457         for ns in $(get_root_nameservers); do
458                 if dig @${ns} +dnssec SOA . $@ >/dev/null; then
459                         return 0
460                 fi
461         done
462
463         # none of the servers was reachable
464         return 1
465 }
466
467 enable_dnssec() {
468         local status=$(unbound-control get_option val-permissive-mode)
469
470         # Log DNSSEC status
471         echo "on" > /var/ipfire/red/dnssec-status
472
473         # Don't do anything if DNSSEC is already activated
474         [ "${status}" = "no" ] && return 0
475
476         # Activate DNSSEC and flush cache with any stale and unvalidated data
477         unbound-control -q set_option val-permissive-mode: no
478         unbound-control -q flush_zone .
479 }
480
481 disable_dnssec() {
482         # Log DNSSEC status
483         echo "off" > /var/ipfire/red/dnssec-status
484
485         unbound-control -q set_option val-permissive-mode: yes
486 }
487
488 fix_time_if_dns_fail() {
489         # If DNS still not work try to init ntp with
490         # hardcoded ntp.ipfire.org (81.3.27.46)
491         if [ -e /var/ipfire/red/active ]; then
492                 host 0.ipfire.pool.ntp.org > /dev/null 2>&1
493                 if [ "${?}" != "0" ]; then
494                         boot_mesg "DNS still not functioning... Trying to sync time with ntp.ipfire.org (81.3.27.46)..."
495                         loadproc /usr/local/bin/settime 81.3.27.46
496                 fi
497         fi
498 }
499
500 case "$1" in
501         start)
502                 # Print a nicer messagen when unbound is already running
503                 if pidofproc -s unbound; then
504                         statusproc /usr/sbin/unbound
505                         exit 0
506                 fi
507
508                 eval $(/usr/local/bin/readhash /var/ipfire/ethernet/settings)
509
510                 # Create control keys at first run
511                 if [ ! -r "/etc/unbound/unbound_control.key" ]; then
512                         unbound-control-setup -d /etc/unbound &>/dev/null
513                 fi
514
515                 # Update configuration files
516                 write_tuning_conf
517                 write_forward_conf
518
519                 boot_mesg "Starting Unbound DNS Proxy..."
520                 loadproc /usr/sbin/unbound || exit $?
521
522                 # Make own hostname resolveable
523                 own_hostname
524
525                 # Update any known forwarding name servers
526                 update_forwarders
527
528                 # Update hosts
529                 update_hosts
530
531                 fix_time_if_dns_fail
532                 ;;
533
534         stop)
535                 boot_mesg "Stopping Unbound DNS Proxy..."
536                 killproc /usr/sbin/unbound
537                 ;;
538
539         restart)
540                 $0 stop
541                 sleep 1
542                 $0 start
543                 ;;
544
545         status)
546                 statusproc /usr/sbin/unbound
547                 ;;
548
549         update-forwarders)
550                 # Do not try updating forwarders when unbound is not running
551                 if ! pgrep unbound &>/dev/null; then
552                         exit 0
553                 fi
554
555                 update_forwarders
556
557                 unbound-control flush_negative > /dev/null
558                 unbound-control flush_bogus > /dev/null
559
560                 fix_time_if_dns_fail
561                 ;;
562
563         test-name-server)
564                 ns=${2}
565
566                 test_name_server ${ns}
567                 ret=${?}
568
569                 case "${ret}" in
570                         0)
571                                 echo "${ns} is validating"
572                                 ;;
573                         2)
574                                 echo "${ns} is DNSSEC-aware"
575                                 ;;
576                         3)
577                                 echo "${ns} is NOT DNSSEC-aware"
578                                 ;;
579                         *)
580                                 echo "Test failed for an unknown reason"
581                                 exit ${ret}
582                                 ;;
583                 esac
584
585                 if ns_supports_tcp ${ns}; then
586                         echo "${ns} supports TCP fallback"
587                 else
588                         echo "${ns} does not support TCP fallback"
589                 fi
590
591                 edns_buffer_size=$(ns_determine_edns_buffer_size ${ns})
592                 if [ -n "${edns_buffer_size}" ]; then
593                         echo "EDNS buffer size for ${ns}: ${edns_buffer_size}"
594                 fi
595
596                 exit ${ret}
597                 ;;
598
599         *)
600                 echo "Usage: $0 {start|stop|restart|status|update-forwarders|test-name-server}"
601                 exit 1
602                 ;;
603 esac
604
605 # End $rc_base/init.d/unbound