Search code examples
openbsdunbound

How can I raise Unbound's limit on local-zones in a view?


After upgrading from unbound 1.18 to 1.21, unbound has about a 10k-line limit on the number of local-zone directives in a view. How can I raise this limit to match the older version?

I upgraded a system from OpenBSD 7.5 to 7.6, which upgraded the bundled unbound from 1.18.1 to 1.21. The new version failed to start with the old configuration, which was about 76k lines long. (Mostly a long list of local-zone: "name" refuse zone directives in a view.)

~$ tail -4 unbound.conf
view: # censored DNS for all LAN clients
        name: "filter"
        view-first: yes
        include: "full-refuse_zones"
~$ unbound-checkconf unbound.conf
full-refuse_zones:9994: error: yacc stack overflow
read unbound.conf failed: 1 errors in configuration file
~$ wc unbound.conf full-refuse_zones
      82     168    1715 unbound.conf
   75987  227961 3196835 full-refuse_zones
   76069  228129 3198550 total

I double checked that the old version was loading the full configuration, and confirmed that 1.18 was not silently truncating, but correctly handling the entire config. On 1.21, while handling a view, it chokes just under 10k lines. If I move the long list of local-zones to the top level, just by adding a server: directive above the include, the file loads OK.

~$ tail -5 unbound-global.conf
view: # censored DNS for all LAN clients
        name: "filter"
        view-first: yes
server:
        include: "full-refuse_zones"
~$ unbound-checkconf unbound-global.conf
unbound-checkconf: no errors in unbound-global.conf
~$ wc unbound-global.conf full-refuse_zones
      83     169    1723 unbound-global.conf
   75987  227961 3196835 full-refuse_zones
   76070  228130 3198558 total

I haven't yet managed to find where yacc is throwing the error in either the NLnetLabs or OpenBSD versions of unbound, so I can't tell where it was changed. I haven't found a hint yet in man pages or release notes. If the change turns out to be a hard coded limit, I can build from source.


Solution

  • Workaround: Use "view-first: no" with a dummy local-data record

    Background: I use unbound for DNS-based ad filtering, but I want my recursive resolver to retain unfiltered DNS for its own use. With the older version of unbound, I would load many local-zone declarations in a view and use access-control-view to direct client lookups there first. I never found where the new version limits the size of a view declaration, so this approach wasn't working.

    Solution: Move local zones to the global level and add an unfiltered view. This is the obvious answer, but it didn't work at first because a view with no local-data does not ignore the global local-data[1]. I solved this with a TXT record that additionally tells clients about the view being used; this record is enough to override all top-level local-data when view-first: no is in effect.

    I configured my resolver machine with an additional loopback address 127.0.0.2 and the following unbound.conf snippet. The "include" file consists of ~70k local zones and is unchanged from the version I used to include in a view; now it is included at top level and parses without error.

    access-control: 127.0.0.2/32 allow
    access-control-view: 127.0.0.2/32 "unfiltered"
    local-data: 'which-view.home. IN TXT "global local-data"'
    include: "/var/unbound/etc/filter"
    view:
            name: "unfiltered"
            view-first: no
            local-data: 'which-view.home. IN TXT "uncensored view"'
    

    Demonstration of default behavior filtering out queries for ad servers:

    ~$ dig -b 127.0.0.1 which-view.home TXT +short
    "global local-data"
    ~$ dig -b 127.0.0.1 doubleclick.net +comments +noedns
    
    ; <<>> dig 9.10.8-P1 <<>> -b 127.0.0.1 doubleclick.net +comments +noedns
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 569
    ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;doubleclick.net.               IN      A
    
    ;; Query time: 0 msec
    ;; SERVER: 127.0.0.1#53(127.0.0.1)
    ;; WHEN: Tue Jan 07 11:02:26 EST 2025
    ;; MSG SIZE  rcvd: 33
    

    Demonstration of unfiltered behavior when querying from the other source address:

    ~$ dig -b 127.0.0.2 which-view.home TXT +short
    "uncensored view"
    ~$ dig -b 127.0.0.2 doubleclick.net +comments +noedns
    
    ; <<>> dig 9.10.8-P1 <<>> -b 127.0.0.2 doubleclick.net +comments +noedns
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 56550
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;doubleclick.net.               IN      A
    
    ;; ANSWER SECTION:
    doubleclick.net.        600     IN      A       142.251.40.110
    
    ;; Query time: 52 msec
    ;; SERVER: 127.0.0.1#53(127.0.0.1)
    ;; WHEN: Tue Jan 07 11:02:59 EST 2025
    ;; MSG SIZE  rcvd: 49
    

    [1] https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html#view-options