64

I have an internal network with a DNS server running BIND, connected to the internet through a single gateway. My domain "example.com" is managed by an external DNS provider. Some of the entries in that domain, say "host1.example.com" and "host2.example.com", as well as the top-level entry "example.com", point to the public IP address of the gateway.

I would like hosts located on the internal network to resolve "host1.example.com", "host2.example.com" and "example.com" to internal IP addresses instead of that of the gateway. Other hosts like "otherhost.example.com" should still be resolved by the external DNS provider.

I have succeeded in doing that for the host1 and host2 entries, by defining two single-entry zones in BIND for "host1.example.com" and "host2.example.com". However, if I add a zone for "example.com", all queries for that domain are resolved by my local DNS server, and e.g. querying "otherhost.example.com" results in an error.

Is it possible to configure BIND to override only some entries of a domain, and to resolve the rest recursively?

4
  • 1
    Similar question: serverfault.com/questions/8694/…
    – MikeyB
    Commented Jun 3, 2009 at 15:34
  • 1
    "Is it possible to configure BIND to override only some entries of a domain ?" No, not with BIND. Use a subdomain.
    – bortzmeyer
    Commented Jun 4, 2009 at 10:42
  • 1
    Unbound seems to do exactly what I asked for, so I'm setting Alnitak's reply as the accepted answer. But in the end, I'm going to follow bortzmeyer's advice and not override the domain entry. Thanks for all the replies!
    – Remy Blank
    Commented Jun 5, 2009 at 16:31
  • 1
    Bind can now do it with the response policy zone. See my answer below. Other solutions such as Unbound cannot override CNAMEs. With policy zones in Bind, you don't have to do subdomains; you can just override individual records at will. Commented Apr 7, 2017 at 1:44

8 Answers 8

57

The best method is via the response policy zone in Bind 9.8.1 or newer. It allows you to override single records in arbitrary zones (and there's no need to create a whole subdomain for that, only the single record you want to change), it allows you to override CNAMEs, etc. Other solutions such as Unbound cannot override CNAMEs.

https://www.redpill-linpro.com/sysadvent/2015/12/08/dns-rpz.html


EDIT: Let's do this properly then. I will document what I've done based on the tutorial linked above.

My OS is Raspbian 4.4 for Raspberry Pi, but the technique should work without any changes on Debian and Ubuntu, or with minimal changes on other platforms.

Go to where your Bind config files are kept on your system - here it's in /etc/bind. Create in there a file called db.rpz with the following contents:

$TTL 60
@            IN    SOA  localhost. root.localhost.  (
                          2015112501   ; serial
                          1h           ; refresh
                          30m          ; retry
                          1w           ; expiry
                          30m)         ; minimum
                   IN     NS    localhost.

localhost       A   127.0.0.1

www.some-website.com    A        127.0.0.1

www.other-website.com   CNAME    fake-hostname.com.

What does it do?

  • it overrides the IP address for www.some-website.com with the fake address 127.0.0.1, effectively sending all traffic for that site to the loopback address
  • it sends traffic for www.other-website.com to another site called fake-hostname.com

Anything that could go in a Bind zone file you can use here.

To activate these changes there are a few more steps:

Edit named.conf.local and add this section:

zone "rpz" {
  type master;
  file "/etc/bind/db.rpz";
};

The tutorial linked above tells you to add more stuff to zone "rpz" { } but that's not necessary in simple setups - what I've shown here is the minimum to make it work on your local resolver.

Edit named.conf.options and somewhere in the options { } section add the response-policy option:

options {
  // bunch
  // of
  // stuff
  // please
  // ignore

  response-policy { zone "rpz"; };
}

Now restart Bind:

service bind9 restart

That's it. The nameserver should begin overriding those records now.

If you need to make changes, just edit db.rpz, then restart Bind again.

Bonus: if you want to log DNS queries to syslog, so you can keep an eye on the proceedings, edit named.conf.local and make sure there's a logging section that includes these statements:

logging {
    // stuff
    // already
    // there

    channel my_syslog {
        syslog daemon;
        severity info;
    };
    category queries { my_syslog; };
};

Restart Bind again and that's it.

Test it on the machine running Bind:

dig @127.0.0.1 www.other-website.com. any

If you run dig on a different machine just use @the-ip-address-of-Bind-server instead of @127.0.0.1

I've used this technique with great success to override the CNAME for a website I was working on, sending it to a new AWS load balancer that I was just testing. A Raspberry Pi was used to run Bind, and the RPi was also configured to function as a WiFi router - so by connecting devices to the SSID running on the RPi I would get the DNS overrides I needed for testing.

6
  • 4
    Please note that BIND RPZ can't actually (yet) override single records on a per QTYPE basis - it will override all of the records for a particular owner name. That means that if you want to override the A record for a domain, but not e.g. the MX record, you can't. You have to put the MX record in the RPZ zone as well, and keep it in sync with the real zone.
    – Alnitak
    Commented Apr 7, 2017 at 11:14
  • 2
    Thanks for breaking this down like you did. Very helpful.
    – sruffell
    Commented Jul 5, 2017 at 21:05
  • 1
    Are there any caveats? I'm trying to do this on a pfsense, but I cannot "fake" any results, it still reports the real address. I believe I have followed the instructions to the letter.
    – Lenne
    Commented Oct 18, 2018 at 11:06
  • @Lenne It should just work. I've edited the post and added a suggestion for testing the changes. Commented Oct 18, 2018 at 19:03
  • 1
    bless you! (worst is I can see I already upvoted this at some point in the far past without any recollection) - consider this a second upvote Commented Jul 28 at 5:34
23

The Unbound recursive DNS server has the ability to override individual resource records.

Look at the local-zone and local-data configuration settings in the manual, e.g.:

local-zone: "example.com." transparent
local-data: "foo.example.com. IN A 192.168.1.1"

The transparent setting on the local-zone tells it to do normal recursive lookups for any names not supplied with local-data.

6
  • 1
    Seems to be exactly what I want to do, thanks. I'll read up on Unbound tonight.
    – Remy Blank
    Commented Jun 3, 2009 at 16:19
  • On the other hand, the wisdom of it is quite questionable. Having an internal.example.com domain would be clearer.
    – bortzmeyer
    Commented Jun 4, 2009 at 10:41
  • 4
    @bortzmeyer, sometimes there's no choice. Example: SBS 2008 must have a single LAN IP, yet it needs to be reached from outside using an external IP that the router is forwarding. Microsoft doesn't allow two network cards on SBS, nor two IP's configured on the same card. If local servers resolve the DNS name to the external IP, then the router needs to do both DNAT and SNAT for LAN ip's, and then the logs on the SBS will show all access to be from the router's IP, and that's just wrong. I'll install unbound on my own router, I think the solution is much better. Commented Feb 9, 2012 at 9:17
  • 2
    This does not answer the question, as the question is specific to BIND.
    – bzeaman
    Commented Apr 9, 2017 at 19:08
  • 1
    @bzeaman until two days ago, this was the accepted answer. The now preferred RPZ method didn't even exist until a couple of years after this was written.
    – Alnitak
    Commented Apr 9, 2017 at 19:16
4

You may want to look into "dnsmasq", which lets you do some pretty clever things with tweaking resolution.

2
  • 1
    Thanks, good tip. Too bad dnsmasq doesn't do recursive resolution, so I'll still have to run BIND on another port for that (the DNS servers of my ISP are flakey).
    – Remy Blank
    Commented Jun 3, 2009 at 14:38
  • with hindsight, it's a tad more fragile to do these things with dnsmasq/unbound, the maintainabilty is kinda worse (telling from broken setups by people who also found it clever - just don't need a clever in middle of a critical service. just switched one over to bind and it has brought peace. Commented Jul 28 at 5:37
4

What you're looking for is split DNS, which is defined by Webopedia as:

In a split DNS infrastructure, you create two zones for the same domain, one to be used by the internal network, the other used by the external network. Split DNS directs internal hosts to an internal domain name server for name resolution and external hosts are directed to an external domain name server for name resolution.

Essentially, you will need to make a copy of your external zone file and prop it up on your internal DNS server, then change or add the records needed specifically for your internal network. This is a pretty common setup, though it can be a pain to keep the "external" records synchronized between the two DNS servers. If you create or change a record on the public server, it will also need to be created or changed on the private server as well.

This can be implemented regardless of what DNS server implementation you use. In most setups, you will have one DNS server that serves the external network, and a different one that serves the internal network. With BIND, as possibly other implementations, you can have both versions of the zone on the same server through a use of the "allow-query" statement within the zone section of the named.conf file.

Another possibility on BIND (and I've never tried this) would be to set your example.com domain on the internal DNS server with only the records you use internally. Then, set a "forward" statement with the "first" argument (in conjunction with "forwarders"). In theory, this would go ask the external DNS server (as set in "forwarders" for an answer, which wouldn't have your internal records and return a failure response. Then, the internal server would look at itself for an answer. Not sure if that would work, but it's a thought.

4
  • Keeping the two zone files in sync will be complicated, as the external one is updated through a dynamic DNS client. I'll read up on the forward statement, though. Thanks for the tip.
    – Remy Blank
    Commented Jun 3, 2009 at 16:12
  • No, the BIND forwarding will not work, it will be only for unknown domains but the internal name server will know about example.com, it will be authoritative for it.
    – bortzmeyer
    Commented Jun 4, 2009 at 10:40
  • 1
    If I'm reading the documentation correctly, the "forward first" statement within a zone section should tell BIND to go out and look for an answer at the forwarder even for an authoritative local domain, then use the local information only if it can't get an answer from the forwarder. Commented Jun 4, 2009 at 20:52
  • If you put a global "forward first" 'it will send the queries to the forwarder and if not answered will attempt to answer the query' (from the doc) but if it receives a response, it won't try to resolve itself. It would be great if you could force to resolve if the response is not authoritative or even if it's a NXDOMAIN, but bind won't try if it gets a response from the forwarder. Commented Jan 23, 2013 at 16:38
3

Use dnsmasq makes it real easy. http://www.thekelleys.org.uk/dnsmasq/doc.html Acts as the dns server but gets answers from local dns server. Nice thing is you can override single domain records without messing with zone files

3

In BIND I get to this results by defining a zone using desired host name. The approach is fine if you only want to override a few hosts.

My zone declaration looks like this:

zone "override.example.com" {
        type master;
        notify no;
        file "zone-config/override.example.com";
};

My zone definition looks like this:

$TTL 4H
@       IN      SOA     ns.override.example.com.    root.override.example.com. (
                        2009072215      ; Serial
                        3600            ; Refresh
                        600             ; Retry
                        604800          ; Expire
                        3600    )       ; Minimum
;
                NS      ns
        IN      NS      ns.override.example.com.
        IN      A       192.168.1.100
ns      IN      A       192.168.1.100

So if I query example.com on intranet DNS and ISP DNS I get same IP but if I query override.example.com I get different results if intranet DNS (primary) is accessible.

2

You're already on the right track.

On your internal DNS servers, you'll need to define a zone for every exception host immediately below the "example.com". To minimize these exceptions, it is common practice to name all internal machines "hosta.internal.example.com", with the DNS server sending most queries to external DNS servers, but authoritative for the zone "internal.example.com". (Once you get past a small operations, there is usually a couple DNS server to which clients are directed and a separate authoritative DNS to which those are servers are directed for "internal.example.com".)

Usually, it is only when a host must be reachable both externally and internally that the exceptions you describe get created. Even then, you may want to use "host1.example.com" from the outside and "host1.internal.example.com" from the inside. Internal hosts get configured to look for names within "internal.example.com". There are situations where what you're already doing is appropriate, such as if the certificate for a server identifies the server as "host1.example.com", in which case you want that to be the name to which clients connect.

1
  • Yes, for the "host1.example.com" hosts, it's already working well. The tricky part is handling the top-level "example.com" in the same way...
    – Remy Blank
    Commented Jun 3, 2009 at 16:14
2

As a matter of fact there is another, even if perhaps slightly different, way to do this. I have the same situation, I have a domain which is used externally and internally, and I have external static and dynamic hosts. The only really painful ones are the external dynamic ones. The solution is possibly not the most elegant, but implementable with a small script. Mostly I am doing my own dynamic DNS script with the API of my dynamic DNS provider, I run this script by cron, every 5 mins:

1) get my external IP. has it changed ? no, exit.

2) changed IP, call API of dyndns-provider, with the new IP-address,

3) sed the db.mydomain.com with the external IP

4) restart bind.

Works very reliably for my home network

1
  • Same process done by me. please find the details in our wiki Stealth Name Server. But unable to resolve dyn.dev.shahed.biz from World Wide! Would you please help us to resolve this issue? Commented Aug 5, 2019 at 7:58

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .