]> git.ipfire.org Git - ipfire-2.x.git/blob - src/initscripts/system/unbound
70cb6edd3284923f93855e1575767075fef75487
[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 # Cache any local zones for 60 seconds
16 LOCAL_TTL=60
17
18 # Load configuration
19 eval $(/usr/local/bin/readhash /var/ipfire/dns/settings)
20
21 DIG_ARGS=()
22
23 if [ "${PROTO}" = "TCP" ]; then
24 DIG_ARGS+=( "+tcp" )
25 fi
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 check_red_has_carrier_and_ip() {
44 # Interface configured ?
45 [ ! -e "/var/ipfire/red/iface" ] && return 0;
46
47 # Interface present ?
48 [ ! -e "/sys/class/net/$(</var/ipfire/red/iface)" ] && return 0;
49
50 # has carrier ?
51 [ ! "$(</sys/class/net/$(</var/ipfire/red/iface)/carrier)" = "1" ] && return 0;
52
53 # has ip ?
54 [ "$(ip address show dev $(</var/ipfire/red/iface) | grep "inet")" = "" ] && return 0;
55
56 return 1;
57 }
58
59 config_header() {
60 echo "# This file is automatically generated and any changes"
61 echo "# will be overwritten. DO NOT EDIT!"
62 echo
63 }
64
65 update_forwarders() {
66 check_red_has_carrier_and_ip
67 if [ "${?}" = "1" ]; then
68 local forwarders
69 local broken_forwarders
70
71 local ns
72 for ns in $(read_name_servers); do
73 test_name_server ${ns} &>/dev/null
74 case "$?" in
75 # Only use DNSSEC-validating or DNSSEC-aware name servers
76 0|2)
77 forwarders="${forwarders} ${ns}"
78 ;;
79 *)
80 broken_forwarders="${broken_forwarders} ${ns}"
81 ;;
82 esac
83 done
84
85 # Show warning for any broken upstream name servers
86 if [ -n "${broken_forwarders}" ]; then
87 boot_mesg "Ignoring broken upstream name server(s): ${broken_forwarders:1}" ${WARNING}
88 echo_warning
89 fi
90
91 if [ -n "${forwarders}" ]; then
92 boot_mesg "Configuring upstream name server(s): ${forwarders:1}" ${INFO}
93 echo_ok
94
95 # Make sure DNSSEC is activated
96 enable_dnssec
97
98 echo "${forwarders}" > /var/ipfire/red/dns
99 unbound-control -q forward ${forwarders}
100 return 0
101
102 # In case we have found no working forwarders
103 else
104 # Test if the recursor mode is available
105 if can_resolve_root; then
106 # Make sure DNSSEC is activated
107 enable_dnssec
108
109 boot_mesg "Falling back to recursor mode" ${WARNING}
110 echo_warning
111
112 # If not, we set DNSSEC in permissive mode and allow using all recursors
113 elif [ -n "${broken_forwarders}" ]; then
114 disable_dnssec
115
116 boot_mesg "DNSSEC has been set to permissive mode" ${FAILURE}
117 echo_failure
118
119 echo "${broken_forwarders}" > /var/ipfire/red/dns
120 unbound-control -q forward ${broken_forwarders}
121 return 0
122 fi
123 fi
124 fi
125
126 # If forwarders cannot be used we run in recursor mode
127 echo "local recursor" > /var/ipfire/red/dns
128 unbound-control -q forward off
129 }
130
131 remove_forwarders() {
132 enable_dnssec
133 echo "local recursor" > /var/ipfire/red/dns
134 unbound-control -q forward off
135
136 }
137
138 own_hostname() {
139 local hostname=$(hostname -f)
140 # 1.1.1.1 is reserved for unused green, skip this
141 if [ -n "${GREEN_ADDRESS}" -a "${GREEN_ADDRESS}" != "1.1.1.1" ]; then
142 unbound-control -q local_data "${hostname} ${LOCAL_TTL} IN A ${GREEN_ADDRESS}"
143 fi
144
145 local address
146 for address in ${GREEN_ADDRESS} ${BLUE_ADDRESS} ${ORANGE_ADDRESS}; do
147 [ -n "${address}" ] || continue
148 [ "${address}" = "1.1.1.1" ] && continue
149
150 address=$(ip_address_revptr ${address})
151 unbound-control -q local_data "${address} ${LOCAL_TTL} IN PTR ${hostname}"
152 done
153 }
154
155 update_hosts() {
156 local enabled address hostname domainname generateptr
157
158 while IFS="," read -r enabled address hostname domainname generateptr; do
159 [ "${enabled}" = "on" ] || continue
160
161 # Build FQDN
162 local fqdn="${hostname}.${domainname}"
163
164 unbound-control -q local_data "${fqdn} ${LOCAL_TTL} IN A ${address}"
165
166 # Skip reverse resolution if the address equals the GREEN address
167 [ "${address}" = "${GREEN_ADDRESS}" ] && continue
168
169 # Skip reverse resolution if user requested not to do so
170 [ "${generateptr}" = "off" ] && continue
171
172 # Add RDNS
173 address=$(ip_address_revptr ${address})
174 unbound-control -q local_data "${address} ${LOCAL_TTL} IN PTR ${fqdn}"
175 done < /var/ipfire/main/hosts
176 }
177
178 write_forward_conf() {
179 (
180 config_header
181
182 # Force using TCP for upstream servers only
183 if [ "${PROTO}" = "TCP" ]; then
184 echo "# Force using TCP for upstream servers only"
185 echo "server:"
186 echo " tcp-upstream: yes"
187 echo
188 fi
189
190 local insecure_zones=""
191
192 local enabled zone server servers remark disable_dnssec rest
193 while IFS="," read -r enabled zone servers remark disable_dnssec rest; do
194 # Line must be enabled.
195 [ "${enabled}" = "on" ] || continue
196
197 # Zones that end with .local are commonly used for internal
198 # zones and therefore not signed
199 case "${zone}" in
200 *.local)
201 insecure_zones="${insecure_zones} ${zone}"
202 ;;
203 *)
204 if [ "${disable_dnssec}" = "on" ]; then
205 insecure_zones="${insecure_zones} ${zone}"
206 fi
207 ;;
208 esac
209
210 echo "stub-zone:"
211 echo " name: ${zone}"
212 for server in ${servers//|/ }; do
213 if [[ ${server} =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
214 echo " stub-addr: ${server}"
215 else
216 echo " stub-host: ${server}"
217 fi
218 done
219 echo
220
221 # Make all reverse lookup zones transparent
222 case "${zone}" in
223 *.in-addr.arpa)
224 echo "server:"
225 echo " local-zone: \"${zone}\" transparent"
226 echo
227 ;;
228 esac
229 done < /var/ipfire/dnsforward/config
230
231 if [ -n "${insecure_zones}" ]; then
232 echo "server:"
233
234 for zone in ${insecure_zones}; do
235 echo " domain-insecure: ${zone}"
236 done
237 fi
238
239 echo "forward-zone:"
240 echo " name: \".\""
241
242 # Force using TLS only
243 if [ "${PROTO}" = "TLS" ]; then
244 echo " forward-tls-upstream: yes"
245 fi
246
247 # Add upstream name servers
248 local id address tls_hostname enabled remark
249 while IFS="," read -r id address tls_hostname enabled remark; do
250 # Skip disabled servers
251 [ "${enabled}" != "enabled" ] && continue
252
253 # Set DNS server
254 if [ "${PROTO}" = "TLS" ]; then
255 if [ -n "${tls_hostname}" ]; then
256 echo " forward-addr: ${address}@853#${tls_hostname}"
257 fi
258 else
259 echo " forward-addr: ${address}"
260 fi
261 done < /var/ipfire/dns/servers
262 ) > /etc/unbound/forward.conf
263 }
264
265 write_tuning_conf() {
266 # https://www.unbound.net/documentation/howto_optimise.html
267
268 # Determine number of online processors
269 local processors=$(getconf _NPROCESSORS_ONLN)
270
271 # Determine number of slabs
272 local slabs=1
273 while [ ${slabs} -lt ${processors} ]; do
274 slabs=$(( ${slabs} * 2 ))
275 done
276
277 # Determine amount of system memory
278 local mem=$(get_memory_amount)
279
280 # In the worst case scenario, unbound can use double the
281 # amount of memory allocated to a cache due to malloc overhead
282
283 # Even larger systems with more than 8GB of RAM
284 if [ ${mem} -ge 8192 ]; then
285 mem=1024
286
287 # Extra large systems with more than 4GB of RAM
288 elif [ ${mem} -ge 4096 ]; then
289 mem=512
290
291 # Large systems with more than 2GB of RAM
292 elif [ ${mem} -ge 2048 ]; then
293 mem=256
294
295 # Medium systems with more than 1GB of RAM
296 elif [ ${mem} -ge 1024 ]; then
297 mem=128
298
299 # Small systems with less than 256MB of RAM
300 elif [ ${mem} -le 256 ]; then
301 mem=16
302
303 # Everything else
304 else
305 mem=64
306 fi
307
308 (
309 config_header
310
311 # We run one thread per processor
312 echo "num-threads: ${processors}"
313 echo "so-reuseport: yes"
314
315 # Adjust number of slabs
316 echo "infra-cache-slabs: ${slabs}"
317 echo "key-cache-slabs: ${slabs}"
318 echo "msg-cache-slabs: ${slabs}"
319 echo "rrset-cache-slabs: ${slabs}"
320
321 # Slice up the cache
322 echo "rrset-cache-size: $(( ${mem} / 2 ))m"
323 echo "msg-cache-size: $(( ${mem} / 4 ))m"
324 echo "key-cache-size: $(( ${mem} / 4 ))m"
325
326 # Increase parallel queries
327 echo "outgoing-range: 8192"
328 echo "num-queries-per-thread: 4096"
329
330 # Use larger send/receive buffers
331 echo "so-sndbuf: 4m"
332 echo "so-rcvbuf: 4m"
333 ) > /etc/unbound/tuning.conf
334 }
335
336 get_memory_amount() {
337 local key val unit
338
339 while read -r key val unit; do
340 case "${key}" in
341 MemTotal:*)
342 # Convert to MB
343 echo "$(( ${val} / 1024 ))"
344 break
345 ;;
346 esac
347 done < /proc/meminfo
348 }
349
350 test_name_server() {
351 local ns=${1}
352 local args
353
354 # Return codes:
355 # 0 DNSSEC validating
356 # 1 Error: unreachable, etc.
357 # 2 DNSSEC aware
358 # 3 NOT DNSSEC-aware
359
360 # Exit when the server is not reachable
361 ns_is_online ${ns} || return 1
362
363 local errors
364 for rr in DNSKEY DS RRSIG; do
365 if ! ns_forwards_${rr} ${ns} ${args}; then
366 errors="${errors} ${rr}"
367 fi
368 done
369
370 if [ -n "${errors}" ]; then
371 echo >&2 "Unable to retrieve the following resource records from ${ns}: ${errors:1}"
372 return 3
373 fi
374
375 if ns_is_validating ${ns} ${args}; then
376 # Return 0 if validating
377 return 0
378 else
379 # Is DNSSEC-aware
380 return 2
381 fi
382 }
383
384 # Sends an A query to the nameserver w/o DNSSEC
385 ns_is_online() {
386 local ns=${1}
387 shift
388
389 dig "${DIG_ARGS[@]}" @${ns} +nodnssec A ${TEST_DOMAIN} $@ >/dev/null
390 }
391
392 # Resolving ${TEST_DOMAIN_FAIL} will fail if the nameserver is validating
393 ns_is_validating() {
394 local ns=${1}
395 shift
396
397 if ! dig "${DIG_ARGS[@]}" @${ns} A ${TEST_DOMAIN_FAIL} $@ | grep -q SERVFAIL; then
398 return 1
399 else
400 # Determine if NS replies with "ad" data flag if DNSSEC enabled
401 dig "${DIG_ARGS[@]}" @${ns} +dnssec SOA ${TEST_DOMAIN} $@ | awk -F: '/\;\;\ flags\:/ { s=1; if (/\ ad/) s=0; exit s }'
402 fi
403 }
404
405 # Checks if we can retrieve the DNSKEY for this domain.
406 # dig will print the SOA if nothing was found
407 ns_forwards_DNSKEY() {
408 local ns=${1}
409 shift
410
411 dig "${DIG_ARGS[@]}" @${ns} DNSKEY ${TEST_DOMAIN} $@ | grep -qv SOA
412 }
413
414 ns_forwards_DS() {
415 local ns=${1}
416 shift
417
418 dig "${DIG_ARGS[@]}" @${ns} DS ${TEST_DOMAIN} $@ | grep -qv SOA
419 }
420
421 ns_forwards_RRSIG() {
422 local ns=${1}
423 shift
424
425 dig "${DIG_ARGS[@]}" @${ns} +dnssec A ${TEST_DOMAIN} $@ | grep -q RRSIG
426 }
427
428 ns_supports_tcp() {
429 local ns=${1}
430 shift
431
432 # If TCP is forced we know by now if the server responds to it
433 if [ "${PROTO}" = "TCP" ]; then
434 return 0
435 fi
436
437 dig "${DIG_ARGS[@]}" @${ns} +tcp A ${TEST_DOMAIN} $@ >/dev/null || return 1
438 }
439
440 get_root_nameservers() {
441 while read -r hostname ttl record address; do
442 # Searching for A records
443 [ "${record}" = "A" ] || continue
444
445 echo "${address}"
446 done < /etc/unbound/root.hints
447 }
448
449 can_resolve_root() {
450 local ns
451 for ns in $(get_root_nameservers); do
452 if dig "${DIG_ARGS[@]}" @${ns} +dnssec SOA . $@ >/dev/null; then
453 return 0
454 fi
455 done
456
457 # none of the servers was reachable
458 return 1
459 }
460
461 enable_dnssec() {
462 local status=$(unbound-control get_option val-permissive-mode)
463
464 # Log DNSSEC status
465 echo "on" > /var/ipfire/red/dnssec-status
466
467 # Don't do anything if DNSSEC is already activated
468 [ "${status}" = "no" ] && return 0
469
470 # Activate DNSSEC and flush cache with any stale and unvalidated data
471 unbound-control -q set_option val-permissive-mode: no
472 unbound-control -q flush_zone .
473 }
474
475 disable_dnssec() {
476 # Log DNSSEC status
477 echo "off" > /var/ipfire/red/dnssec-status
478
479 unbound-control -q set_option val-permissive-mode: yes
480 }
481
482 fix_time_if_dns_fail() {
483 # If DNS still not work try to init ntp with
484 # hardcoded ntp.ipfire.org (81.3.27.46)
485 check_red_has_carrier_and_ip
486 if [ -e "/var/ipfire/red/iface" -a "${?}" = "1" ]; then
487 host 0.ipfire.pool.ntp.org > /dev/null 2>&1
488 if [ "${?}" != "0" ]; then
489 boot_mesg "DNS still not functioning... Trying to sync time with ntp.ipfire.org (81.3.27.46)..."
490 loadproc /usr/local/bin/settime 81.3.27.46
491 fi
492 fi
493 }
494
495 resolve() {
496 local hostname="${1}"
497
498 local found=0
499 local ns
500 for ns in $(read_name_servers); do
501 local answer
502 for answer in $(dig "${DIG_ARGS[@]}" +short "@${ns}" A "${hostname}"); do
503 found=1
504
505 # Filter out non-IP addresses
506 if [[ ! "${answer}" =~ \.$ ]]; then
507 echo "${answer}"
508 fi
509 done
510
511 # End loop when we have got something
512 [ ${found} -eq 1 ] && break
513 done
514 }
515
516 # Sets up Safe Search for various search engines
517 update_safe_search() {
518 local google_tlds=(
519 google.ad
520 google.ae
521 google.al
522 google.am
523 google.as
524 google.at
525 google.az
526 google.ba
527 google.be
528 google.bf
529 google.bg
530 google.bi
531 google.bj
532 google.bs
533 google.bt
534 google.by
535 google.ca
536 google.cat
537 google.cd
538 google.cf
539 google.cg
540 google.ch
541 google.ci
542 google.cl
543 google.cm
544 google.cn
545 google.co.ao
546 google.co.bw
547 google.co.ck
548 google.co.cr
549 google.co.id
550 google.co.il
551 google.co.in
552 google.co.jp
553 google.co.ke
554 google.co.kr
555 google.co.ls
556 google.com
557 google.co.ma
558 google.com.af
559 google.com.ag
560 google.com.ai
561 google.com.ar
562 google.com.au
563 google.com.bd
564 google.com.bh
565 google.com.bn
566 google.com.bo
567 google.com.br
568 google.com.bz
569 google.com.co
570 google.com.cu
571 google.com.cy
572 google.com.do
573 google.com.ec
574 google.com.eg
575 google.com.et
576 google.com.fj
577 google.com.gh
578 google.com.gi
579 google.com.gt
580 google.com.hk
581 google.com.jm
582 google.com.kh
583 google.com.kw
584 google.com.lb
585 google.com.ly
586 google.com.mm
587 google.com.mt
588 google.com.mx
589 google.com.my
590 google.com.na
591 google.com.nf
592 google.com.ng
593 google.com.ni
594 google.com.np
595 google.com.om
596 google.com.pa
597 google.com.pe
598 google.com.pg
599 google.com.ph
600 google.com.pk
601 google.com.pr
602 google.com.py
603 google.com.qa
604 google.com.sa
605 google.com.sb
606 google.com.sg
607 google.com.sl
608 google.com.sv
609 google.com.tj
610 google.com.tr
611 google.com.tw
612 google.com.ua
613 google.com.uy
614 google.com.vc
615 google.com.vn
616 google.co.mz
617 google.co.nz
618 google.co.th
619 google.co.tz
620 google.co.ug
621 google.co.uk
622 google.co.uz
623 google.co.ve
624 google.co.vi
625 google.co.za
626 google.co.zm
627 google.co.zw
628 google.cv
629 google.cz
630 google.de
631 google.dj
632 google.dk
633 google.dm
634 google.dz
635 google.ee
636 google.es
637 google.fi
638 google.fm
639 google.fr
640 google.ga
641 google.ge
642 google.gg
643 google.gl
644 google.gm
645 google.gp
646 google.gr
647 google.gy
648 google.hn
649 google.hr
650 google.ht
651 google.hu
652 google.ie
653 google.im
654 google.iq
655 google.is
656 google.it
657 google.je
658 google.jo
659 google.kg
660 google.ki
661 google.kz
662 google.la
663 google.li
664 google.lk
665 google.lt
666 google.lu
667 google.lv
668 google.md
669 google.me
670 google.mg
671 google.mk
672 google.ml
673 google.mn
674 google.ms
675 google.mu
676 google.mv
677 google.mw
678 google.ne
679 google.nl
680 google.no
681 google.nr
682 google.nu
683 google.pl
684 google.pn
685 google.ps
686 google.pt
687 google.ro
688 google.rs
689 google.ru
690 google.rw
691 google.sc
692 google.se
693 google.sh
694 google.si
695 google.sk
696 google.sm
697 google.sn
698 google.so
699 google.sr
700 google.st
701 google.td
702 google.tg
703 google.tk
704 google.tl
705 google.tm
706 google.tn
707 google.to
708 google.tt
709 google.vg
710 google.vu
711 google.ws
712 )
713
714 # Cleanup previous settings
715 unbound-control local_zone_remove "bing.com" >/dev/null
716 unbound-control local_zone_remove "duckduckgo.com" >/dev/null
717 unbound-control local_zone_remove "yandex.com" >/dev/null
718 unbound-control local_zone_remove "yandex.ru" >/dev/null
719 unbound-control local_zone_remove "youtube.com" >/dev/null
720
721 local domain
722 for domain in ${google_tlds[@]}; do
723 unbound-control local_zone_remove "${domain}"
724 done >/dev/null
725
726 # Nothing to do if safe search is not enabled
727 if [ "${ENABLE_SAFE_SEARCH}" != "on" ]; then
728 return 0
729 fi
730
731 # Bing
732 unbound-control bing.com transparent >/dev/null
733 for address in $(resolve "strict.bing.com"); do
734 unbound-control local_data "www.bing.com ${LOCAL_TTL} IN A ${address}"
735 done >/dev/null
736
737 # DuckDuckGo
738 unbound-control local_zone duckduckgo.com typetransparent >/dev/null
739 for address in $(resolve "safe.duckduckgo.com"); do
740 unbound-control local_data "duckduckgo.com ${LOCAL_TTL} IN A ${address}"
741 done >/dev/null
742
743 # Google
744 local addresses="$(resolve "forcesafesearch.google.com")"
745 for domain in ${google_tlds[@]}; do
746 unbound-control local_zone "${domain}" transparent >/dev/null
747 for address in ${addresses}; do
748 unbound-control local_data: "www.${domain} ${LOCAL_TTL} IN A ${address}"
749 done >/dev/null
750 done
751
752 # Yandex
753 for domain in yandex.com yandex.ru; do
754 unbound-control local_zone "${domain}" typetransparent >/dev/null
755 for address in $(resolve "familysearch.${domain}"); do
756 unbound-control local_data "${domain} ${LOCAL_TTL} IN A ${address}"
757 done >/dev/null
758 done
759
760 # YouTube
761 unbound-control local_zone youtube.com transparent >/dev/null
762 for address in $(resolve "restrictmoderate.youtube.com"); do
763 unbound-control local_data "www.youtube.com ${LOCAL_TTL} IN A ${address}"
764 done >/dev/null
765
766 return 0
767 }
768
769 case "$1" in
770 start)
771 # Print a nicer messagen when unbound is already running
772 if pidofproc -s unbound; then
773 statusproc /usr/sbin/unbound
774 exit 0
775 fi
776
777 eval $(/usr/local/bin/readhash /var/ipfire/ethernet/settings)
778
779 # Update configuration files
780 write_tuning_conf
781 write_forward_conf
782
783 boot_mesg "Starting Unbound DNS Proxy..."
784 loadproc /usr/sbin/unbound || exit $?
785
786 # Make own hostname resolveable
787 own_hostname
788
789 # Update any known forwarding name servers
790 update_forwarders
791
792 # Install Safe Search rules when the system is already online
793 if [ -e "/var/ipfire/red/active" ]; then
794 update_safe_search
795 fi
796
797 # Update hosts
798 update_hosts
799
800 fix_time_if_dns_fail
801 ;;
802
803 stop)
804 boot_mesg "Stopping Unbound DNS Proxy..."
805 killproc /usr/sbin/unbound
806 ;;
807
808 restart)
809 $0 stop
810 sleep 1
811 $0 start
812 ;;
813
814 status)
815 statusproc /usr/sbin/unbound
816 ;;
817
818 update-forwarders)
819 # Do not try updating forwarders when unbound is not running
820 if ! pgrep unbound &>/dev/null; then
821 exit 0
822 fi
823
824 update_forwarders
825
826 unbound-control flush_negative > /dev/null
827 unbound-control flush_bogus > /dev/null
828
829 fix_time_if_dns_fail
830 ;;
831
832 remove-forwarders)
833 # Do not try updating forwarders when unbound is not running
834 if ! pgrep unbound &>/dev/null; then
835 exit 0
836 fi
837
838 remove_forwarders
839
840 unbound-control flush_negative > /dev/null
841 unbound-control flush_bogus > /dev/null
842 ;;
843
844
845 resolve)
846 resolve "${2}"
847 ;;
848
849 update-safe-search)
850 update_safe_search
851 ;;
852
853 *)
854 echo "Usage: $0 {start|stop|restart|status|update-forwarders|remove-forwarders|resolve|update-safe-search}"
855 exit 1
856 ;;
857 esac
858
859 # End $rc_base/init.d/unbound