Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] Implement Bootstrap DNS for Unbound #1114

Open
CallMeR opened this issue Jul 31, 2024 · 10 comments
Open

[FR] Implement Bootstrap DNS for Unbound #1114

CallMeR opened this issue Jul 31, 2024 · 10 comments

Comments

@CallMeR
Copy link

CallMeR commented Jul 31, 2024

Current behavior

First of all, thank you very much for your contributions to the Unbound project. I am an enthusiastic user and have identified a potential feature that could improve user experience.

Currently, when adding DoT upstream servers in Unbound service, the forward-addr: parameter only accept IP:PORT .

forward-zone:
    name: "."
    forward-tls-upstream: yes
    # Try Google DoT server with IP@PORT
    forward-addr: 8.8.8.8@853
unbound-checkconf: no errors in local-test.conf
forward-zone:
    name: "."
    forward-tls-upstream: yes
    # Try Google DoT server with DOMAIN
    forward-addr: dns.google
[1722404185] unbound-checkconf[9868:0] error: cannot parse forward . ip address: 'dns.google'
[1722404185] unbound-checkconf[9868:0] fatal error: Could not set forward zones
forward-zone:
    name: "."
    forward-tls-upstream: yes
    # Try Google DoT server with DOMAIN@PORT
    forward-addr: dns.google@853
[1722404308] unbound-checkconf[9894:0] error: cannot parse forward . ip address: 'dns.google@853'
[1722404308] unbound-checkconf[9894:0] fatal error: Could not set forward zones

However, I have noticed that many DoT service providers, especially large internet companies, usually have multiple IP addresses, and sometimes use Anycast technology, resulting in a single domain name possibly corresponding to multiple IPv4 and IPv6 addresses.

For example, Cloudflare's DoT servers have multiple IP addresses. In such cases, manually adding all IP addresses can become tedious and error-prone.

Cloudflare supports DNS over TLS (DoT) on 1.1.1.1, 1.0.0.1, and the corresponding IPv6 addresses (2606:4700:4700::1111 and 2606:4700:4700::1001) on port 853.
If your DoT client does not support IP addresses, Cloudflare’s DoT endpoint can also be reached by hostname on one.one.one.one.

I discovered that some DNS forwarders or server software similar to Unbound support a feature called "Bootstrap DNS".

This feature allows users to only fill in the DoT server's domain name (port optional), and the system will resolve the domain name to the correct IP address through Bootstrap DNS during initialization.

Implementing this feature could offer the following benefits:

  • Simplify the configuration process, reducing repetitive operations for users.
  • Enhance system flexibility, automatically adapting to changes in DoT server IPs.
  • Improve reliability, automatically switching to other available addresses when a particular IP is not usable.

Describe the desired feature

Introduce Bootstrap DNS functionality in Unbound, whereby the forward-addr: field can use DOMAIN@PORT when adding DoT servers.

If a user provides a IPv4 / IPv6 address, Unbound should use this specified IP exclusively.

If no IP is provided, Unbound would automatically resolve the server's domain name to its current IP addresses at startup, through the Bootstrap DNS mechanism.

Potential use-case

Utilize existing DNS infrastructure to provide "Bootstrap DNS" - like functionality:

  • Use the regular upstream DNS servers (IP:PORT) set in Unbound (or the DNS servers in system) to resolve DoT server domain names.
  • This approach would leverage existing configurations to provide similar functionality without introducing new components.
@Aura67
Copy link

Aura67 commented Jul 31, 2024

you also spelled it wrong, the way you wrote it, unbound can't do anything with it, it should look like this e.g with AdGuard DNS:
forward-zone:
name: "."
forward-tls-upstream: yes
forward-addr: 94.140.14.14@853#dns.adguard-dns.com
forward-addr: 94.140.15.15@853#dns.adguard-dns.com

You just have to use the Google data if you want DNS over TLS with Google.

wcawijngaards added a commit that referenced this issue Jul 31, 2024
  performed, so that with nonzero target-fetch-policy it fetches
  forwarder addresses and uses them from cache. Also updated that
  delegation point cache fill routines use CDflag for AAAA message
  lookups, so that its negative lookup stops a recursion since the
  cache uses the bit for disambiguation for dns64 but the recursion
  uses CDflag for the AAAA target lookups, so the check correctly
  stops a useless recursion by its cache lookup.
@wcawijngaards
Copy link
Member

Unbound actually has syntax to specify this, with forward-host: ns.example.com and stub-host: ns.example.com. Unbound then, recursively, resolves the names to look up the nameserver addresses. This is useful for lower stubs or forwards, that then use the global lookup to fetch the information, eg. for example.com but the lookup can work by looking outside example.com. For the use case here, it would get stuck in a loop; the lookup for the name needs the name resolved.

The fix commit adds a new check to fill the name from cache if a lookup has looked up the extra addresses. Otherwise it would perform a recursion, that would also look in the cache, but this is better. It also checks with CD flag since the lookups seems to have faultily not stopped recursion attempts, those are performed with CD flag and for type AAAA the cache disambiguates because of dns64 processing. If the negative message exists, then it can not be attempted again, so that fix results in less spurious recursion attempts for target AAAA lookups that do not succeed.

I think it is important to not become dependent on other infrastructure that is not needed, for starting the DNS service. So that bootstrap mechanism is not as good from that point of view. But for unbound, perhaps this config can be easier to type, apart from conveniently copy and pasting text that is premade, make a forward-zone with one address and then a forward-host with the name to look up for the other addresses. Unbound can then use the address to recursively look up the name and it is simpler configuration, like

forward-zone:
    name: "."
    forward-addr: 192.0.2.1
    forward-host: ns.example.com

The suggested config given earlier is also a nicely working configuration, and sets the name to check in the TLS certificate.

A test with '8.8.8.8' and 'dns.google' works, and uses the additional addresses from the name lookup. That is performed from the address. The target-fetch-policy: "3 2 1 0 0" has to be set like that, or the default, not to all zeroes, for unbound to perform lookups for the forward-host name. With all zeroes, the additional addresses are not fetched, but with the suggested value the extra addresses are fetched and used.

@Aura67
Copy link

Aura67 commented Aug 1, 2024

I just added this part here: forward-host: ns.example.com to my config to see if I get an error but I don't get any errors my internet works normally I just specified quad9 over port 853 you can add what is in the wiki # @ and everything and it works.

@CallMeR
Copy link
Author

CallMeR commented Aug 1, 2024

I apologize for providing the incorrect configuration. I may need more time to familiarize myself with and thoroughly understand the functions and interactions of Unbound's various configuration parameters.

In my test environment, for reasons unknown, I had to disable the harden-dnssec-stripped parameter to continue with the tests. Therefore, all subsequent activities were conducted with this setting turned off.

harden-dnssec-stripped: no

In this FR, I'm aiming to achieve the following objectives:

  • For Unbound's custom upstream DNS, use only DoT (and/or DoH) servers.
  • Allow the DoT (and/or DoH) servers to be specified using domain names.
  • Provide a separate, independent setting for the server used to initially resolve these domain names. This server would be used solely for the initial resolution of the DoT servers and would not participate in regular DNS resolution.

Test Case 1: Using 8.8.8.8 Exclusively

forward-zone:
    name: "."
    #forward-tls-upstream: yes
    #forward-host: dns.quad9.net
    forward-addr: 8.8.8.8

log 1 :

Aug 01 10:45:51 unbound[2405:0] info: resolving time1.google.com. A IN
Aug 01 10:45:51 unbound[2405:0] info: response for time1.google.com. A IN
Aug 01 10:45:51 unbound[2405:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:45:51 unbound[2405:0] info: query response was ANSWER
Aug 01 10:45:51 unbound[2405:0] info: prime trust anchor
Aug 01 10:45:51 unbound[2405:0] info: generate keytag query _ta-4f66. NULL IN
Aug 01 10:45:51 unbound[2405:0] info: resolving . DNSKEY IN
Aug 01 10:45:51 unbound[2405:0] info: validate keys with anchor(DS): sec_status_bogus
Aug 01 10:45:51 unbound[2405:0] info: failed to prime trust anchor -- DNSKEY rrset is not secure . DNSKEY IN
Aug 01 10:45:51 unbound[2405:0] info: resolving _ta-4f66. NULL IN
Aug 01 10:45:51 unbound[2405:0] info: Verified that unsigned response is INSECURE

Test Case 2: Using 8.8.8.8 and dns.quad9.net

forward-zone:
    name: "."
    #forward-tls-upstream: yes
    forward-host: dns.quad9.net
    forward-addr: 8.8.8.8

log 2 :

Aug 01 10:49:55 unbound[2712:0] info: resolving time2.google.com. A IN
Aug 01 10:49:55 unbound[2712:0] info: resolving dns.quad9.net. AAAA IN
Aug 01 10:49:55 unbound[2712:0] info: resolving dns.quad9.net. A IN
Aug 01 10:49:55 unbound[2712:0] info: response for time2.google.com. A IN
Aug 01 10:49:55 unbound[2712:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:49:55 unbound[2712:0] info: query response was ANSWER
Aug 01 10:49:55 unbound[2712:0] info: prime trust anchor
Aug 01 10:49:55 unbound[2712:0] info: generate keytag query _ta-4f66. NULL IN
Aug 01 10:49:55 unbound[2712:0] info: resolving . DNSKEY IN
Aug 01 10:49:55 unbound[2712:0] info: resolving _ta-4f66. NULL IN
Aug 01 10:49:55 unbound[2712:0] info: response for dns.quad9.net. A IN
Aug 01 10:49:55 unbound[2712:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:49:55 unbound[2712:0] info: query response was ANSWER
Aug 01 10:49:55 unbound[2712:0] info: response for dns.quad9.net. AAAA IN
Aug 01 10:49:55 unbound[2712:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:49:55 unbound[2712:0] info: query response was nodata ANSWER
Aug 01 10:49:55 unbound[2712:0] info: response for . DNSKEY IN
Aug 01 10:49:55 unbound[2712:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:49:55 unbound[2712:0] info: query response was ANSWER
Aug 01 10:49:55 unbound[2712:0] info: validate keys with anchor(DS): sec_status_bogus
Aug 01 10:49:55 unbound[2712:0] info: failed to prime trust anchor -- DNSKEY rrset is not secure . DNSKEY IN
Aug 01 10:49:55 unbound[2712:0] info: Verified that unsigned response is INSECURE
Aug 01 10:49:55 unbound[2712:0] info: response for _ta-4f66. NULL IN
Aug 01 10:49:55 unbound[2712:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:49:55 unbound[2712:0] info: query response was NXDOMAIN ANSWER

Aug 01 10:49:59 unbound[2712:0] info: resolving time3.google.com. A IN
Aug 01 10:49:59 unbound[2712:0] info: resolving dns.quad9.net. AAAA IN
Aug 01 10:49:59 unbound[2712:0] info: resolving dns.quad9.net. A IN
Aug 01 10:49:59 unbound[2712:0] info: response for dns.quad9.net. A IN
Aug 01 10:49:59 unbound[2712:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:49:59 unbound[2712:0] info: query response was ANSWER
Aug 01 10:49:59 unbound[2712:0] info: response for time3.google.com. A IN
Aug 01 10:49:59 unbound[2712:0] info: reply from <.> 8.8.8.8#53
Aug 01 10:49:59 unbound[2712:0] info: query response was ANSWER

Test Case 3: Initially Attempting to Use DoT, Using 8.8.8.8 and dns.quad9.net

forward-zone:
    name: "."
    forward-tls-upstream: yes
    forward-host: dns.quad9.net
    forward-addr: 8.8.8.8

log 3 : No query results were obtained

$ kdig time4.google.com
;; WARNING: response timeout for 127.0.0.1@53(UDP)
;; WARNING: response timeout for 127.0.0.1@53(UDP)
;; WARNING: response timeout for 127.0.0.1@53(UDP)
;; ERROR: failed to query server 127.0.0.1@53(UDP)
;; WARNING: response timeout for ::1@53(UDP)
;; ->>HEADER<<- opcode: QUERY; status: SERVFAIL; id: 26436
;; Flags: qr rd ra; QUERY: 1; ANSWER: 0; AUTHORITY: 0; ADDITIONAL: 0

;; QUESTION SECTION:
;; time4.google.com.            IN      A

;; Received 34 B
;; Time 2024-08-01 10:59:01 CST
;; From ::1@53(UDP) in 0.8 ms


Aug 01 10:57:37 unbound[2798:0] info: start of service (unbound 1.19.2).
Aug 01 10:58:41 unbound[2798:0] info: resolving time4.google.com. A IN
Aug 01 10:58:41 unbound[2798:0] info: resolving dns.quad9.net. AAAA IN
Aug 01 10:58:41 unbound[2798:0] info: resolving dns.quad9.net. A IN
Aug 01 10:58:46 unbound[2798:0] info: resolving time4.google.com. A IN
Aug 01 10:58:51 unbound[2798:0] info: resolving time4.google.com. A IN
Aug 01 10:58:56 unbound[2798:0] info: resolving time4.google.com. A IN
Aug 01 10:59:09 unbound[2798:0] error: str: channel closed
Aug 01 10:59:09 unbound[2798:0] notice: ssl handshake failed 8.8.8.8 port 53
Aug 01 10:59:19 unbound[2798:0] error: str: channel closed
Aug 01 10:59:19 unbound[2798:0] notice: ssl handshake failed 8.8.8.8 port 53
Aug 01 10:59:29 unbound[2798:0] error: str: channel closed
Aug 01 10:59:29 unbound[2798:0] notice: ssl handshake failed 8.8.8.8 port 53
Aug 01 10:59:40 unbound[2798:0] error: str: channel closed
Aug 01 10:59:40 unbound[2798:0] notice: ssl handshake failed 8.8.8.8 port 53

However, when querying this server using another device, results can be obtained, but Unbound doesn't show any logs. I'm not sure about the specific reason for this.

PS D:\> nslookup time4.google.com 192.168.186.130
130.186.168.192.in-addr.arpa
        primary name server = a.gtld-servers.net
        responsible mail addr = nstld.verisign-grs.com
        serial  = 1800
        refresh = 1800 (30 mins)
        retry   = 900 (15 mins)
        expire  = 604800 (7 days)
        default TTL = 86400 (1 day)
Server:  UnKnown
Address:  192.168.186.130

Non-authoritative answer:
Name:    time4.google.com
Address:  216.239.35.12

Test Case 3: Use the standard configuration mentioned earlier

forward-zone:
    name: "."
    forward-tls-upstream: yes
    forward-addr: 94.140.14.14@853#dns.adguard-dns.com
    #forward-host: dns.quad9.net
    #forward-addr: 8.8.8.8

Due to reasons that have not yet been understood, testing could not continue temporarily because this error was observed.

error: ssl handshake failed crypto error:0A000086:SSL routines::certificate verify failed


Actually, I would like to request considering the following configuration for Unbound.

forward-zone:
    name: "."
    forward-tls-upstream: yes                             # This parameter can remain enabled.

    forward(bootstrap)-addr: 8.8.8.8                      # This upstream DNS server is only used for resolving the IPs of DoT (and/or DoH) domain servers and does not participate in routine resolutions.

    forward-addr: 94.140.14.14@853#dns.adguard-dns.com    # Similarly, if the upstream DoT server is already in IP form, then the preceding 8.8.8.8 is not needed as an initializing resolution server.
    forward-addr: 94.140.14.14@443#dns.adguard-dns.com    # DoH upstream server.

    forward-host: dns.quad9.net@853
    forward-host: one.one.one.one@853

    forward-host: dns.google@853
    forward-host: dns.google@443

Thank you again for your patient explanation :)

@Aura67
Copy link

Aura67 commented Aug 1, 2024

Unbound uses port 53 by default, but if you tell it in your config that you have a forward-tls-upstream, then Unbound sends everything to this upstream server and everything should then run via port 853. I'll adjust the settings for you now, you basically just have to copy it into your config and restart your Unbound service once

Adguard dns
name: "."
forward-tls-upstream: yes
forward-addr: 94.140.14.14@853#dns.adguard-dns.com
forward-addr: 94.140.15.15@853#dns.adguard-dns.com

Quad9
name: "."
forward-tls-upstream: yes
forward-addr: 9.9.9.9@853#dns.quad9.net
forward-addr: 149.112.112.112@853#dns.quad9.net

google
name: "."
forward-tls-upstream: yes
forward-addr: 8.8.8.8@853#dns.google
forward-addr: 8.8.4.4@853#dns.google

you can activate dnssec in unbound, it doesn't do anything, google quad9 and adguard also validate it but you send your dns requests to the upstream server (encrypted) once you have added the settings in your unbound config, save the file and restart your unbound and enter service unbound status and see what unbound tells you, whether there is an error.

jedisct1 added a commit to jedisct1/unbound that referenced this issue Aug 17, 2024
* nlnet/master: (66 commits)
  - Tag for release 1.21.0, the repository continues with 1.21.1   in development.
  - Fix spelling for the cache-min-negative-ttl entry in the   example.conf.
  - Fix that for windows the module startup is called and sets up   the module-config.
  - Set version number to 1.21.0 for release.
  - Fix CacheFlush issues with limit on NS RRs. Thanks to Yehuda Afek,   Anat Bremler-Barr, Shoham Danino and Yuval Shavitt (Tel-Aviv   University and Reichman University).
  - Fix CAMP issues with global quota. Thanks to Huayi Duan, Marco   Bearzi, Jodok Vieli, and Cagin Tanir from NetSec group, ETH Zurich.
  - Fix that alloc stats for forwards and hints are printed, and when   alloc stats is enabled, the unit test for unbound control waits for   reloads to complete.
  Changelog note for NLnetLabs#1090 - Merge NLnetLabs#1090: Cookie secret file. Adds   `cookie-secret-file: "unbound_cookiesecrets.txt"` option to store   cookie secrets for EDNS COOKIE secret rollover. The remote control   add_cookie_secret, activate_cookie_secret and drop_cookie_secret   commands can be used for rollover, the command print_cookie_secrets   shows the values in use.
  Cookie secret file (NLnetLabs#1090)
  Update changelog. - Fix testbound for alloc stats strdup in util/alloc.c.
  - Fix testbound for alloc stats strdup in util/alloc.c.
  - Fix that alloc stats has strdup checks, it stops debuggers from   complaining about mismatch at free time.
  - Fix that the worker mem report with alloc stats does not attempt   to print memory use of forwards and hints if they have been   deleted already.
  - Fix dnstap test program, cleans up to have clean memory on exit,   for tap_data_free, does not delete NULL items. Also it does not try   to free the tail, specifically in the free of the list since that   picked up the next item in the list for its loop causing invalid   free. Added internal unit test to unbound-dnstap-socket for that.
  - Fix for NLnetLabs#1114: Fix that cache fill for forward-host names is   performed, so that with nonzero target-fetch-policy it fetches   forwarder addresses and uses them from cache. Also updated that   delegation point cache fill routines use CDflag for AAAA message   lookups, so that its negative lookup stops a recursion since the   cache uses the bit for disambiguation for dns64 but the recursion   uses CDflag for the AAAA target lookups, so the check correctly   stops a useless recursion by its cache lookup.
  - Fix to document parameters of auth_zone_verify_zonemd_with_key.
  - Add root key 38696 from 2024 for DNSSEC validation. It is added   to the default root keys in unbound-anchor. The content can be   inspected with `unbound-anchor -l`.
  - For NLnetLabs#935 and NLnetLabs#1104, clarify RPZ order and semantics.
  - Cleanup ede.tdir test.
  - Fix link of unbound-dnstap-socket without openssl.
  ...
@pemensik
Copy link
Contributor

Do I guess it correctly, that this is request to resolve global forwarder using whatever is in /etc/resolv.conf or system resolver, then switch to something like this:

server:
  tls-system-cert: yes

forward-zone:
  name: "."
  forward-host: "dns.google@853"
  forward-tls-upstream: yes

#forward-zone:
#  name: "dns.google"
#  forward-addr: 127.0.0.1

If you use unbound-host -dvC /tmp/fwd.conf example.net with above config, it fails. Because it does not know how to resolve the forward-host itself. If you uncomment "dns.google" forward zone pointing to localhost resolver, it will work from unbound-host.

Sure, it would be a nice feature, if forward-host could be resolved using system getaddrinfo(3) call, avoiding a need to specify resolver address itself. Would some boolean allowing to resolve forwarder using system calls be necessary? Because we check the certificate, resolving it with local unencrypted and unauthenticated resolver should still work. While keeping the same level of security and working also for IPv6 network (hopefully).

@Aura67
Copy link

Aura67 commented Oct 16, 2024

the forward-host belongs to the forward-zone and then you have to add it under forward-addr that's how I did it.

forward-zone:
forward-tls-upstream: yes
name: "."
forward-host: 8.8.8.8@853#dns.google
forward-host: 8.8.4.4@853#dns.google

just copy it out and paste it into your forward zone under forward-addr and restart your unbound service: service unbound restart and then test with service unbound status whether there are any errors.

@pemensik
Copy link
Contributor

the forward-host belongs to the forward-zone and then you have to add it under forward-addr that's how I did it.

forward-zone: forward-tls-upstream: yes name: "." forward-host: 8.8.8.8@853#dns.google forward-host: 8.8.4.4@853#dns.google

just copy it out and paste it into your forward zone under forward-addr and restart your unbound service: service unbound restart and then test with service unbound status whether there are any errors.

Yes, the problem with this is, it would not work on IPv6-only network. Because you have specified fixed IP addresses, but only IPv4. You would have to specify 4 to make it fully specified for both address families.

You have to specify forward-addr this way. That can be avoided when initial forward-host resolution is done by the whatever API is configured. Of course that can succeed only when /etc/resolv.conf does not yet point to localhost address only, meaning the unbound is not the only working resolution system on the host.

What I would like is fallback to system provided resolver(s), but only for resolving initial forward-host names, which are otherwise unknown. Because forward-first is no, it cannot obtain that address itself. This allows avoiding hardcoded addresses at the cost of relying on network resolvers. Just for one name and its A+AAAA record, but without them it won't start working.

@Aura67
Copy link

Aura67 commented Oct 16, 2024

then you try it by simply adding the ipv6 addresses to the forward zone, i.e. the one from google dns of course.

forward-zone:
forward-tls-upstream: yes
name: "."
forward-host: 8.8.8.8@853#dns.google
forward-host: 8.8.4.4@853#dns.google
forward-host: 2001:4860:4860::8888@853#dns.google
forward-host: 2001:4860:4860::8844@853#dns.google

I don't use IPv6 myself, I only use IPv4 and it works without any problems. I have also added these settings to my unbound config.

@pemensik
Copy link
Contributor

Sure, I can do that.

But my point is, these addresses can be fetched from whatever local resolver when starting for bootstrap. It is better if I provide them, but if I specify at least forward-host, unbound could get its addresses itself. I am quite sure forward-addr needs to be specified instead of forward-host in example you have provided. It might use just system getaddrinfo(3) call when starting. I think this issue were about that exactly. I provide just the part it needs to fetch more information itself, a resolver hostname.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants