# Split DNS with views Views let named answer differently depending on who asks. The usual setup for a homelab that is also authoritative on the internet has two views: * **internal**: your networks get recursion (they use the server as their resolver) and see private addresses (`nas.adbraun.lol → 10.10.0.20`) and internal-only zones such as reverse zones for private networks. * **external**: everyone else gets authoritative answers only. That means public addresses, no recursion, and no internal zones. ## How named treats views (and why Jarl asks to move your zones) Without any `view` statements, named puts every zone into an implicit default view. That's why an imported server shows no views. **As soon as one view exists, named requires every zone to be inside a view.** So the first view you create has to take over the existing zones, including the root hints zone `.` that Debian/Ubuntu configure. Jarl does this in the same changeset that creates the view: * web UI: Views → New view. The box "Move the N zone(s) that are not in a view into this view" is ticked by default. * CLI: `jarl view create NAME --adopt-zones` * API: `"adopt_zones": true` Creating the first view without it is refused before anything reaches named (`zone_outside_view`). Views are matched in order: the first view whose `match-clients` (and `match-destinations`) fit the client wins. Put the specific view first and the catch-all last. ## Step by step ### 1. Check the ACL for your networks The internal view matches your networks. Usually that's the ACL you already use for `allow-recursion`: ```sh jarl acl list jarl acl update homelab --add localhost # if missing; see the note below ``` Include **localhost** if the DNS server itself uses `127.0.0.1` as its resolver. Otherwise the server's own lookups land in the external view and get no recursion. ### 2. Create the internal view and move the existing zones into it ```sh jarl view create internal --match-clients homelab --adopt-zones --dry-run # look first jarl view create internal --match-clients homelab --adopt-zones ``` ``` create view internal move zone . into view internal move zone 0.10.10.in-addr.arpa into view internal move zone adbraun.lol into view internal applied (changeset 4) ``` Nothing changes for your clients: they match `internal` and get the same answers as before. The server-wide options (`recursion`, `allow-recursion`, `forwarders`, ...) still apply, because views inherit them. ### 3. Create the external view ```sh jarl view create external --match-clients any jarl option set recursion no --view external jarl option set allow-recursion '{ none; }' --view external ``` The inherited `allow-recursion { homelab; }` already keeps outsiders from recursing. The explicit `recursion no` also stops them reading the cache, and makes the intent obvious. Setting `allow-recursion { none; }` as well avoids named-checkconf's warning that `allow-recursion` is still active in a view with recursion off. Jarl warns (`security_open_resolver`) if a view ever offers recursion to everyone. ### 4. Give the external view its zones **Different data per view (split DNS).** Copy the zone, then change what should differ: ```sh jarl zone create adbraun.lol --view external --clone-from adbraun.lol --clone-view internal jarl record list adbraun.lol --view external jarl record update --data 203.0.113.20 # public address instead of 10.10.0.20 jarl record delete adbraun.lol nas-internal A --view external ``` From then on the two copies are separate zones. A record added to one isn't in the other, and the serials are separate. **The same data in both views.** Use an `in-view` zone. The external view then serves the internal view's zone, so there is one copy to maintain: ```sh jarl zone create example.org --view external --type in-view --in-view internal ``` Don't do that for zones with private data. Internal-only zones (reverse zones for 10.x / 192.168.x) simply don't get an external copy: named answers outsiders with NXDOMAIN from its built-in empty zones. ### 5. Check from both sides ```sh dig @ns1.adbraun.lol nas.adbraun.lol +short # from the LAN: 10.10.0.20 dig @ nas.adbraun.lol +short # from outside: 203.0.113.20 dig @ example.com # from outside: status REFUSED jarl check ``` On the server, `dig -b ` picks which view you test. ## Living with views * Commands that name a zone that exists in several views need `--view` (`jarl record add adbraun.lol www A ... --view external`). Jarl refuses to guess. * `jarl acme` writes challenge records into **every** view that has the zone, so Let's Encrypt (external) and internal clients see the same thing. Use `--view` to limit it. * `jarl view order internal external` changes the match order, and `jarl view list` shows it. * Deleting a view is refused while it still has zones, unless you pass `--cascade`. * Give each view a colour (Views → Edit → Colour, or `jarl view color internal green`) to tell the zones of each view apart in the zone list. The colour only affects the web UI, so it applies at once, with no changeset or deploy. `jarl view color internal default` goes back to the link colour. * Zone files are stored per view: `/var/lib/bind/jarl/internal/adbraun.lol.zone`, `/var/lib/bind/jarl/external/adbraun.lol.zone`.