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