From a7fd69233d59ce38ac73598bb44d20cc05b04d15 Mon Sep 17 00:00:00 2001 From: Eric Luehrsen Date: Sun, 10 Jun 2018 15:20:24 -0400 Subject: [PATCH] unbound: improve local zone evaluation in UCI When UCI local zone is private and static, Unbound covered private addresses with defaults. Optional delegated global IP6 prefix protection lacked a static zone, but it was prevented from appearing in global DNS responses. Domain names router-as-TLD, "lan." and "local." were static, but they lacked default SOA or NS such as Unbound had assinged to private addresses. Clean up these local zones UCI evaluation and block global DNS inclusion. Signed-off-by: Eric Luehrsen --- net/unbound/Makefile | 2 +- net/unbound/files/README.md | 15 ++- net/unbound/files/iptools.sh | 22 ++++ net/unbound/files/odhcpd.sh | 4 +- net/unbound/files/unbound.sh | 223 ++++++++++++++++++++++++----------- 5 files changed, 189 insertions(+), 77 deletions(-) diff --git a/net/unbound/Makefile b/net/unbound/Makefile index a6de8db33e..12144aeb96 100644 --- a/net/unbound/Makefile +++ b/net/unbound/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=unbound PKG_VERSION:=1.7.2 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_LICENSE:=BSD-3-Clause PKG_LICENSE_FILES:=LICENSE diff --git a/net/unbound/files/README.md b/net/unbound/files/README.md index 4e81162f8a..d897044380 100644 --- a/net/unbound/files/README.md +++ b/net/unbound/files/README.md @@ -139,7 +139,7 @@ config unbound ### Hybrid Manual/UCI You like the UCI. Yet, you need to add some difficult to standardize options, or just are not ready to make a UCI request yet. The files `/etc/unbound/unbound_srv.conf` and `/etc/unbound/unbound_ext.conf` will be copied to Unbounds chroot directory and included during auto generation. -The former will be added to the end of the `server:` clause. The later will be added to the end of the file for extended `forward:` and `view:` clauses. You can also disable unbound-control in the UCI which only allows "localhost" connections unencrypted, and then add an encrypted remote `control:` clause. +The file `unbound_srv.conf` will be added into the `server:` clause. The file `unbound_ext.conf` will be added to the end of all configuration. It is for extended `forward-zone:`, `stub-zone:`, `auth-zone:`, and `view:` clauses. You can also disable unbound-control in the UCI which only allows "localhost" connections unencrypted, and then add an encrypted remote `control:` clause. ## Complete List of UCI Options **/etc/config/unbound**: @@ -196,8 +196,11 @@ config unbound option domain_type 'static' Unbound local-zone: . This allows you to lock - down or allow forwarding of your domain, your router host name - without suffix, and leakage of RFC6762 "local." + down or allow forwarding of the local zone. Notable types: + static - typical single router setup much like OpenWrt dnsmasq default + refuse - to answer overtly with DNS code REFUSED + deny - to drop queries for the local zone + transparent - to use your manually added forward-zone: or stub-zone: clause option edns_size '1280' Bytes. Extended DNS is necessary for DNSSEC. However, it can run @@ -226,9 +229,9 @@ config unbound configuration. Make changes to /etc/unbound/unbound.conf. option prefetch_root '0' - Boolean. Enable Unbound authority zone clauses for "." (root), "arpa," - "in-addr.arpa," and "ip6.arpa" and obtain complete zone files from public - servers using http or AXFR (gTLD are unfortunately not as public). + Boolean. Cache the entire root. Enable Unbound `auth-zone:` clauses for + "." (root), "arpa," "in-addr.arpa," and "ip6.arpa." Obtain complete zone + files from public servers using http or AXFR. (see RFC7706) option protocol 'mixed' Unbound can limit its protocol used for recursive queries. diff --git a/net/unbound/files/iptools.sh b/net/unbound/files/iptools.sh index 1725242ec4..9985f76d05 100644 --- a/net/unbound/files/iptools.sh +++ b/net/unbound/files/iptools.sh @@ -138,3 +138,25 @@ private_subnet() { ############################################################################## +domain_ptr_any() { + local subnet=$1 + local arpa validip4 validip6 + + validip4=$( valid_subnet4 $subnet ) + validip6=$( valid_subnet6 $subnet ) + + + if [ "$validip4" = "ok" ] ; then + arpa=$( domain_ptr_ip4 "$subnet" ) + elif [ "$validip6" = "ok" ] ; then + arpa=$( domain_ptr_ip6 "$subnet" ) + fi + + + if [ -n "$arpa" ] ; then + echo $arpa + fi +} + +############################################################################## + diff --git a/net/unbound/files/odhcpd.sh b/net/unbound/files/odhcpd.sh index 9c01dc6f6e..93efa73ad1 100644 --- a/net/unbound/files/odhcpd.sh +++ b/net/unbound/files/odhcpd.sh @@ -43,7 +43,9 @@ odhcpd_zonedata() { local dhcp_origin=$( uci_get dhcp.@odhcpd[0].leasefile ) - if [ "$dhcp_link" = "odhcpd" -a -f "$dhcp_origin" ] ; then + if [ "$dhcp_link" = "odhcpd" \ + -a -f "$dhcp_origin" \ + -a -n "$dhcp_domain" ] ; then # Capture the lease file which could be changing often sort $dhcp_origin > $dhcp_ls_new diff --git a/net/unbound/files/unbound.sh b/net/unbound/files/unbound.sh index 2fda84e869..a221177515 100644 --- a/net/unbound/files/unbound.sh +++ b/net/unbound/files/unbound.sh @@ -63,12 +63,18 @@ UNBOUND_TXT_HOSTNAME=thisrouter UNBOUND_LIST_FORWARD="" UNBOUND_LIST_INSECURE="" -UNBOUND_LIST_PRV_SUBNET="" ############################################################################## -# keep track of local-domain: assignments during inserted resource records +# keep track of assignments during inserted resource records UNBOUND_LIST_DOMAINS="" +UNBOUND_LIST_IFACE="" +UNBOUND_LIST_PRV_IP6GLA="" +UNBOUND_LIST_LAN_NET="" + +# Similar default SOA / NS RR as Unbound uses for private ARPA zones +UNBOUND_XSOA="3600 IN SOA localhost. nobody.invalid. 1 3600 1200 7200 600" +UNBOUND_XNS="3600 IN NS localhost." ############################################################################## @@ -82,34 +88,13 @@ UNBOUND_LIST_DOMAINS="" ############################################################################## -copy_dash_update() { - # TODO: remove this function and use builtins when this issues is resovled. - # Due to OpenWrt/LEDE divergence "cp -u" isn't yet universally available. - local filetime keeptime - - - if [ -f $UNBOUND_KEYFILE.keep ] ; then - # root.key.keep is reused if newest - filetime=$( date -r $UNBOUND_KEYFILE +%s ) - keeptime=$( date -r $UNBOUND_KEYFILE.keep +%s ) - - - if [ $keeptime -gt $filetime ] ; then - cp $UNBOUND_KEYFILE.keep $UNBOUND_KEYFILE - fi - - - rm -f $UNBOUND_KEYFILE.keep - fi -} - -############################################################################## - create_interface_dns() { local cfg="$1" local ipcommand logint ignore ifname ifdashname local name names address addresses - local ulaprefix if_fqdn host_fqdn mode mode_ptr + local ulaprefix if_fqdn host_fqdn + local mode_ptr="$UNBOUND_TXT_HOSTNAME" + local names="$UNBOUND_TXT_HOSTNAME" # Create local-data: references for this hosts interfaces (router). config_get logint "$cfg" interface @@ -124,45 +109,60 @@ create_interface_dns() { if_fqdn="$ifdashname.$host_fqdn" - if [ -z "${ulaprefix%%:/*}" ] ; then - # Nonsense so this option isn't globbed below - ulaprefix="fdno:such:addr::/48" - fi + if [ -z "$ifdashname" ] ; then + # race conditions at init can rarely cause a blank device return + # the record format is invalid and Unbound won't load the conf file + mode=0 + elif [ -n "$UNBOUND_LIST_IFACE" ] ; then + case "$UNBOUND_LIST_IFACE" in + *$ifdashname*) + # repeat such as dual WAN (eth0-1) and WAN6 (eth0-1) + mode=0 + ;; + + *) + mode=1 + ;; + esac - if [ "$ignore" -gt 0 ] ; then - mode="$UNBOUND_D_WAN_FQDN" else - mode="$UNBOUND_D_LAN_FQDN" + mode=1 fi - case "$mode" in - 3) - mode_ptr="$host_fqdn" - names="$host_fqdn $UNBOUND_TXT_HOSTNAME" - ;; + if [ $mode -gt 0 ] ; then + UNBOUND_LIST_IFACE="$UNBOUND_LIST_IFACE $ifdashname" + + + if [ -z "${ulaprefix%%:/*}" ] ; then + # Nonsense so this option isn't globbed below + ulaprefix="fdno:such:addr::/48" + fi + - 4) - if [ -z "$ifdashname" ] ; then - # race conditions at init can rarely cause a blank device return - # the record format is invalid and Unbound won't load the conf file + if [ "$ignore" -gt 0 ] ; then + mode="$UNBOUND_D_WAN_FQDN" + else + mode="$UNBOUND_D_LAN_FQDN" + fi + fi + + + if [ "$mode" -gt 1 ] ; then + case "$mode" in + 3) mode_ptr="$host_fqdn" names="$host_fqdn $UNBOUND_TXT_HOSTNAME" - else + ;; + + 4) mode_ptr="$if_fqdn" names="$if_fqdn $host_fqdn $UNBOUND_TXT_HOSTNAME" - fi - ;; - - *) - mode_ptr="$UNBOUND_TXT_HOSTNAME" - names="$UNBOUND_TXT_HOSTNAME" - ;; - esac + ;; + esac - if [ "$mode" -gt 1 ] ; then { for address in $addresses ; do case $address in @@ -385,21 +385,37 @@ bundle_domain_insecure() { ############################################################################## bundle_private_interface() { - local ipcommand ifsubnet ifsubnets ifname + local ipcommand ifsubnet ifsubnets ifname validip4 network_get_device ifname $1 + if [ -n "$ifname" ] ; then - ipcommand="ip -6 -o address show $ifname" - ifsubnets=$( $ipcommand | awk '/inet6/{ print $4 }' ) + ipcommand="ip -o address show $ifname" + ifsubnets=$( $ipcommand | awk '/inet/{ print $4 }' ) if [ -n "$ifsubnets" ] ; then for ifsubnet in $ifsubnets ; do case $ifsubnet in - [1-9]*:*[0-9a-f]) + [1-9][0-9a-f][0-9a-f][0-9a-f]:*[0-9a-f]) # Special GLA protection for local block; ULA protected as a catagory - UNBOUND_LIST_PRV_SUBNET="$UNBOUND_LIST_PRV_SUBNET $ifsubnet" ;; + UNBOUND_LIST_PRV_IP6GLA="$UNBOUND_LIST_PRV_IP6GLA $ifsubnet" + ;; + + f[dc][0-9a-f][0-9a-f]:*[0-9a-f]) + # Used to configure specific local-zone: data + UNBOUND_LIST_LAN_NET="$UNBOUND_LIST_LAN_NET $ifsubnet" + ;; + + *) + validip4=$( valid_subnet4 $ifsubnet ) + + + if [ "$validip4" = "ok" ] ; then + UNBOUND_LIST_LAN_NET="$UNBOUND_LIST_LAN_NET $ifsubnet" + fi + ;; esac done fi @@ -411,6 +427,7 @@ bundle_private_interface() { unbound_mkdir() { local filestuff + if [ "$UNBOUND_D_DHCP_LINK" = "odhcpd" ] ; then local dhcp_origin=$( uci_get dhcp.@odhcpd[0].leasefile ) local dhcp_dir=$( dirname $dhcp_origin ) @@ -422,6 +439,7 @@ unbound_mkdir() { fi fi + if [ -f $UNBOUND_KEYFILE ] ; then filestuff=$( cat $UNBOUND_KEYFILE ) @@ -469,7 +487,11 @@ unbound_mkdir() { fi - copy_dash_update + if [ -f $UNBOUND_KEYFILE.keep ] ; then + # root.key.keep is reused if newest + cp -u $UNBOUND_KEYFILE.keep $UNBOUND_KEYFILE + rm -f $UNBOUND_KEYFILE.keep + fi # Ensure access and prepare to jail @@ -809,6 +831,7 @@ unbound_conf() { logger -t unbound -s "default memory configuration" fi + # Assembly of module-config: options is tricky; order matters modulestring="iterator" @@ -941,8 +964,8 @@ unbound_conf() { fi - if [ -n "$UNBOUND_LIST_PRV_SUBNET" -a "$UNBOUND_D_PRIV_BLCK" -gt 1 ] ; then - for ifsubnet in $UNBOUND_LIST_PRV_SUBNET ; do + if [ -n "$UNBOUND_LIST_PRV_IP6GLA" -a "$UNBOUND_D_PRIV_BLCK" -gt 1 ] ; then + for ifsubnet in $UNBOUND_LIST_PRV_IP6GLA ; do # Remove global DNS responses with your local network IP6 GLA echo " private-address: $ifsubnet" >> $UNBOUND_CONFFILE done @@ -1019,6 +1042,7 @@ unbound_adblock() { # TODO: Unbound 1.6.0 added "tags" and "views"; lets work with adblock team local adb_enabled adb_file + if [ ! -x /usr/bin/adblock.sh -o ! -x /etc/init.d/adblock ] ; then adb_enabled=0 else @@ -1040,31 +1064,90 @@ unbound_adblock() { ############################################################################## unbound_hostname() { + local ifsubnet ifarpa + + if [ -n "$UNBOUND_TXT_DOMAIN" ] ; then { - # TODO: Unbound 1.6.0 added "tags" and "views" and we could make - # domains by interface to prevent DNS from "guest" to "home" - echo " local-zone: $UNBOUND_TXT_DOMAIN. $UNBOUND_D_DOMAIN_TYPE" - echo " domain-insecure: $UNBOUND_TXT_DOMAIN" - echo " private-domain: $UNBOUND_TXT_DOMAIN" - echo - echo " local-zone: $UNBOUND_TXT_HOSTNAME. $UNBOUND_D_DOMAIN_TYPE" + # Hostname as TLD works, but not transparent through recursion echo " domain-insecure: $UNBOUND_TXT_HOSTNAME" echo " private-domain: $UNBOUND_TXT_HOSTNAME" + echo " local-zone: $UNBOUND_TXT_HOSTNAME. static" + echo " local-data: \"$UNBOUND_TXT_HOSTNAME. $UNBOUND_XSOA\"" + echo " local-data: \"$UNBOUND_TXT_HOSTNAME. $UNBOUND_XNS\"" echo } >> $UNBOUND_CONFFILE case "$UNBOUND_D_DOMAIN_TYPE" in deny|inform_deny|refuse|static) + if [ -n "$UNBOUND_LIST_PRV_IP6GLA" \ + -a "$UNBOUND_D_PRIV_BLCK" -gt 1 ] ; then + for ifsubnet in $UNBOUND_LIST_PRV_IP6GLA ; do + ifarpa=$( domain_ptr_any "$ifsubnet" ) + + + if [ -n "$ifarpa" ] ; then + { + # Do NOT forward queries with your GLA ip6.arpa + echo " domain-insecure: $ifarpa" + echo " local-zone: $ifarpa. $UNBOUND_D_DOMAIN_TYPE" + echo " local-data: \"$ifarpa. $UNBOUND_XSOA\"" + echo " local-data: \"$ifarpa. $UNBOUND_XNS\"" + echo + } >> $UNBOUND_CONFFILE + fi + done + fi + + + if [ -n "$UNBOUND_LIST_LAN_NET" \ + -a "$UNBOUND_D_PRIV_BLCK" -gt 0 ] ; then + for ifsubnet in $UNBOUND_LIST_LAN_NET ; do + ifarpa=$( domain_ptr_any "$ifsubnet" ) + + + if [ -n "$ifarpa" ] ; then + { + # Do NOT forward queries with your ULA ip6.arpa or in-addr.arpa + echo " domain-insecure: $ifarpa" + echo " local-zone: $ifarpa. $UNBOUND_D_DOMAIN_TYPE" + echo " local-data: \"$ifarpa. $UNBOUND_XSOA\"" + echo " local-data: \"$ifarpa. $UNBOUND_XNS\"" + echo + } >> $UNBOUND_CONFFILE + fi + done + fi + + { - # avoid upstream involvement in RFC6762 like responses (link only) - echo " local-zone: local. $UNBOUND_D_DOMAIN_TYPE" + # avoid upstream involvement in RFC6762 echo " domain-insecure: local" echo " private-domain: local" + echo " local-zone: local. $UNBOUND_D_DOMAIN_TYPE" + echo " local-data: \"local. $UNBOUND_XSOA\"" + echo " local-data: \"local. $UNBOUND_XNS\"" + echo " local-data: \"local. 3600 IN TXT RFC6762\"" + echo + # type static means only this router has your domain + # type transparent will permit forward-zone: or stub-zone: clauses + echo " domain-insecure: $UNBOUND_TXT_DOMAIN" + echo " private-domain: $UNBOUND_TXT_DOMAIN" + echo " local-zone: $UNBOUND_TXT_DOMAIN. $UNBOUND_D_DOMAIN_TYPE" + echo " local-data: \"$UNBOUND_TXT_DOMAIN. $UNBOUND_XSOA\"" + echo " local-data: \"$UNBOUND_TXT_DOMAIN. $UNBOUND_XNS\"" echo } >> $UNBOUND_CONFFILE ;; + + *) + # likely transparent domain with fordward-zone: clause to next router + echo " domain-insecure: $UNBOUND_TXT_DOMAIN" + echo " private-domain: $UNBOUND_TXT_DOMAIN" + echo " local-zone: $UNBOUND_TXT_DOMAIN. $UNBOUND_D_DOMAIN_TYPE" + echo + ;; esac @@ -1227,6 +1310,7 @@ unbound_resolv_setup() { return fi + if [ -x /etc/init.d/dnsmasq ] && /etc/init.d/dnsmasq enabled \ && nslookup localhost 127.0.0.1#53 >/dev/null 2>&1 ; then # unbound is configured for port 53, but dnsmasq is enabled and a resolver @@ -1237,6 +1321,7 @@ unbound_resolv_setup() { return fi + # unbound is designated to listen on 127.0.0.1#53, # set resolver file to local. rm -f /tmp/resolv.conf -- 2.30.2