Revisiting Samba RODC + Bind

Ok, so here’s another step in the evolution of my Samba4-RODC-based DNS setup. First steps were setting up a Samba4 Read-Only DC in my remote locations, so that DNS would be replicated to that location so that DNS doesn’t fail in case the VPN connection dies. Then we discovered that the SAMBA_INTERNAL DNS backend does not support caching, which unsurprisingly lead to performance problems, so we switched to Samba AD DC with Bind as DNS backend. This setup is quite a bit more complex though, and it seems a bit unstable in the sense that Samba lost its ability to update records in Bind for some reason and we have to “fix” that manually by re-joining the RODC to the domain. Rumor has it that the SAMBA_INTERNAL backend is a lot more stable. So, here’s step three in our evolution: Let’s allow Samba to use SAMBA_INTERNAL, but only run on 127.0.0.1, while communication with the outside world is handled by a bind instance that handles caching and forwards queries for the company domain records to Samba.

Configuration

The config is actually pretty easy (way simpler than getting the “official” way to work, I might add). Just define a forward-only zone in /etc/bind/named.conf.local that forwards to 127.0.0.1:

zone "local.lan" {
    type forward;
    forward only;
    forwarders { 127.0.0.1; };
};

Then tell bind to only listen on the external interface (so bind and samba don’t fight over who gets to listen where):

options {
    listen-on    { 192.168.0.5; };
    listen-on-v6 { none; };
};

(I disabled IPv6 because I don’t use it. Feel free to put your IPv6 address here if you have one.)

Next, set up Samba to listen only on the lo interface:

[global]
        bind interfaces only = yes
        interfaces = lo

Re-join samba using the SAMBA_INTERNAL dns backend, start it up, and that’s it!

Verifying that it works

Let’s verify that we’re actually achieving what we want to. We’ll start by checking that Samba, when used as a recursor to resolve external Domains, really doesn’t cache the replies. We’ll do that by running tcpdump -n -i <iface> udp and port 53 both for lo and eth0, and seeing what requests are being made.

“Public” domains (aka, non-AD)

When I repeatedly ask Samba for google.com, here’s what tcpdump says:

08:28:54.975100 IP 192.168.0.5.48469 > 8.8.8.8.53: 6435+ [1au] A? google.com. (39)
08:28:54.984524 IP 8.8.8.8.53 > 192.168.0.5.48469: 6435 1/0/1 A 216.58.210.14 (55)
08:28:55.231383 IP 192.168.0.5.33705 > 8.8.8.8.53: 58051+ [1au] A? google.com. (39)
08:28:55.233150 IP 8.8.8.8.53 > 192.168.0.5.33705: 58051 1/0/1 A 216.58.210.14 (55)
08:28:55.453384 IP 192.168.0.5.58312 > 8.8.8.8.53: 35012+ [1au] A? google.com. (39)
08:28:55.455038 IP 8.8.8.8.53 > 192.168.0.5.58312: 35012 1/0/1 A 216.58.210.14 (55)
08:28:55.870107 IP 192.168.0.5.37276 > 8.8.8.8.53: 48778+ [1au] A? google.com. (39)
08:28:55.871942 IP 8.8.8.8.53 > 192.168.0.5.37276: 48778 1/0/1 A 216.58.210.14 (55)
08:28:56.278881 IP 192.168.0.5.51132 > 8.8.8.8.53: 25423+ [1au] A? google.com. (39)
08:28:56.280554 IP 8.8.8.8.53 > 192.168.0.5.51132: 25423 1/0/1 A 216.58.210.14 (55)

For every time that I try, I see exactly one request going to 8.8.8.8 (which I configured as a forwarder in smb.conf for this experiment).

Sending numerous requests to bind instead, I only get this once:

08:29:27.625128 IP 192.168.0.5.36198 > 192.54.112.30.53: 58507 [1au] A? google.com. (51)
08:29:27.627388 IP 192.54.112.30.53 > 192.168.0.5.36198: 58507-| 0/7/3 (509)
08:29:27.632826 IP 192.168.0.5.43989 > 216.239.34.10.53: 37612 [1au] A? google.com. (51)
08:29:27.654861 IP 216.239.34.10.53 > 192.168.0.5.43989: 37612*- 1/0/1 A 216.58.208.46 (55)
08:29:27.655430 IP 192.168.0.5.53348 > 192.52.178.30.53: 28382 [1au] DS? google.com. (51)
08:29:27.685309 IP 192.52.178.30.53 > 192.168.0.5.53348: 28382*-| 0/4/1 (466)

Bind has to ask multiple servers because I haven’t configured a forwarder, which is why we see multiple requests to different servers here. These serve to answer my first query. When I repeat the query however, I don’t see bind making any more requests: It just uses the response from its cache. So this works. :)

Company domains (resolved through AD)

Now let’s see what happens when we ask bind to resolve a company-internal DNS record for puppet. I have not asked bind to resolve this record before, so it isn’t cached and Bind needs to ask Samba. Here’s what happens:

08:22:32.180238 IP 192.168.0.5.39036 > 192.168.0.5.53: 58534+ [1au] A? puppet.local.lan. (64)
08:22:32.181239 IP 127.0.0.1.46718 > 127.0.0.1.53: 57623+ A? puppet.local.lan. (41)
08:22:32.182392 IP 127.0.0.1.53 > 127.0.0.1.46718: 57623*- 1/1/0 A 192.168.212.100 (115)
08:22:32.183056 IP 192.168.0.5.53 > 192.168.0.5.39036: 58534 1/13/1 A 192.168.212.100 (320)

We see four packets here:

  1. My request to bind
  2. bind’s request to samba
  3. samba’s answer for bind
  4. bind’s answer for me.

Now if I just repeat exactly the same query, Bind should have cached the answer and it should not ask Samba again:

08:23:28.037650 IP 192.168.0.5.49563 > 192.168.0.5.53: 40889+ [1au] A? puppet.local.lan. (64)
08:23:28.038185 IP 192.168.0.5.53 > 192.168.0.5.49563: 40889 1/13/1 A 192.168.212.100 (320)

We now only see two packets:

  1. My request to bind
  2. bind’s answer for me.

We do not see a request being made to samba first, so it works as intended.

I’m not sure if caching of internal names is actually desirable. Maybe it would be better to ask Samba every time for these, because Samba actually has a copy of the authoritative database locally, so performance-wise, querying Samba every time should be fine. By having bind cache these entries, updates will be delayed by the TTL. I don’t think it’ll cause problems, but it’s probably something to be aware of.