Recently, at a BIND 10 Face to face meeting, we scheduled a short slot of time to discuss the features of a DNS forwarder. As part of the development process of the BIND 10 recursive resolver, we initially implemented a basic forwarder. As we added actual recursive resolver features, the original ‘forwarding’ mode was left in, and got some of the features that were added for the ‘resolving’ mode, mostly on an ad-hoc basis. As it turned out, we did not have a shared view of what features the forwarder was supposed to have, and we decided it was time to write down which ones it should and specifically should not get, and make sure we implemented that. We assumed this would be a short discussion ending up with a checklist of features.
Of course, this assumption was completely wrong.
As it turns out, it’s not entirely clear what a forwarder is or is not, and depending on what your definition is, some behaviour can be either completely expected to the point of being mandatory or so utterly out of place that even proposing it would get you an immediate bug report and a weekend of reading standards documents.
So what is a forwarder, exactly?
Most people have a general notion of what forwarding means in a DNS context; In short, you receive a request, and instead of ‘handling’ it yourself, you pass it on to another server. When that server sends you the response, you pass it back to your own client. That sounds simple enough, but when you get down to the details, things start getting less clear; should you change query ID’s? What about requeries on truncation? Or, for that matter, use your own EDNS0 values? Fallback logic for fragmentation problems? Should it cache data? Or a more abstract choice, do you consider the forwarder to be a ‘hop’, or should it be completely transparent? And can it be?
These are just some examples, and in most cases, the answers come down to your implicit definition a forwarder, or rather, the usage scenario you have in mind.
In principle, what we tend to refer to as a ‘resolver’ is also a forwarder; another name for it is a caching forwarder. But when talking about a ‘forwarder’, people tend to have something more specific in mind. Mostly one would think of a server that does not actually perform the recursive resolution steps, but lets another server do that. It may or may not cache data. It may or may not contain additional DNS-related intelligence. Some of the questions on what it should or should not do can be answered by refering to RFC 5625, which discusses DNS proxies, which in this context are the same as forwarders. However, this RFC has a specific subset in mind: that of proxies on SOHO gateway equipment. This already implies a specific use-case and hence a specific set of features. It’s goal is to specify what an implementation for that should support in order to be considered a ‘good citizen’ in the DNS space. I’d like to take a more general approach here.
Given two ends of the spectrum, the totally transparent one, and the full resolver, after some discussion we came up with a number of use-cases. The actual set of features go from “Do nothing but…” to “Do everything except…”. Here is the list we came up with. If you feel like we’ve missed any, or got one wrong, please let us know. I shall leave out caching considerations in this list, and get back to that later.
1. A very, VERY Simple Forwarder; Pass almost everything through without
- Port number
- ACL considerations
Useful for systems like SOHO gateways that want to provide DNS data to DHCP clients that do not have a fixed address for their DNS servers themselves. It can also be used for access control, or to work with certain firewall setups. Yet another case might be spreading the queries over multiple real resolvers, not necessarily for load-balancing, but for source obfuscation as well.
Since these systems are expected to aggregate the DNS handling for multiple clients, they should replace the source port and QID of the queries they get, to be able to tell different responses apart, and pass them back to the right client.
Simple ACLs can be implemented based on addresses or data that is asked.
2. Very Simple Forwarder; Pass everything through without interpretation, except:
- QID Port number
- ACL considerations
- Hop-to-hop services.
This is essentially the same as the first, but considers itself a ‘hop’, and hence should correctly handle hop-to-hop communications like those that may be present in EDNS0 (RFC 2671) directives like nsid (RFC 5001), and server.id queries if supported (see RFC 4892, Requirements for a Mechanism Identifying a Name Server Instance). If it changes the buffer size value from EDNS0, it *should* only lower it.
The use-cases here are essentially the same as for the first. The biggest difference is that one can actually ‘see’ the server when issuing identification queries, which may lead to less confusion.
3. Proxy Forwarder; Do everything except follow delegations.
This reaches the ‘Do everything except’ part of the list. The forwarder here can issue queries on its own, for instance to follow CNAME chains (like BIND 9 does), or for fallback scenarios, if the forwarding target does not appear to support something. It should also requery over TCP when it sees a truncated answer, and should have its own logic for truncating the responses it sends back to clients.
The use-case could be that this server may have better knowledge about the network environment, and can hence set its own buffer size values in EDNS0. If it does that, it should* also be able to handle EDNS0 fallback strategies, and requery on truncation. This means it should also be able to modify answers, and reconstruct them, in order for it to be able to handle the different EDNS0 values.
What it would not do is follow delegations, which would make it:
4. Full resolver that only sends queries to specific addresses
Imagine an implementation where you just take out the code that sends out queries for resolution to authoritative nameservers, and make it send queries to a specifically configured (set of) server(s). It would have to set RD=1 on these queries, but otherwise it would not need that many changes. In most cases, actual behaviour would be the same as case 3, since the responses you expect from the forward target would not be delegations.
5. DNSSEC Validating Forwarder; Full resolver that only goes to specific addresses
Having a validating proxy, i.e. a forwarder that does full validation, but sends all its outgoing queries to a specific (set of) ‘normal’ resolver(s), is a pretty specific use-case. It can only be used if the forward targets that are used do support DNSSEC (since they must be able to pass on DNSSEC data), but don’t do validation, or if you do not trust its validation. Given the knowledge needed to do DNSSEC, essentially you are running a full validating resolver, where the only practical difference is that instead of sending outgoing queries with RD=0 to the authoritative servers, you send all your outgoing queries with RD=1 to a specific (set of) nameserver(s).
These 5 could all be extended with a cache. In the case of the last three, it makes the most sense, especially since it may need to reconstruct answers. If it has no cache, the server should pass on truncated responses, and directly forward TCP queries. In the case of the first 2, some more knowledge about DNS needs to be added, in order to expire cache contents. Those caches would also need to make sure that it is not simply keyed on the question section of the query, since the responses may depend on specific flags.
So what’s next?
As you see, there are many things that could be seen as a forwarder, and this list of 10 (5 without cache, 5 with cache) is most likely far from complete. As for BIND 10, we currently plan to implement scenario 2 from this list for now, and probably 5 later, when we have DNSSEC validation in the first place. If you have any specific features you would like to see, please let us know.