PROXYv2 Support in BIND 9

Overview

PROXYv2 protocol support has been added in the BIND 9.19 development branch. The PROXYv2 protocol is designed with one thing in mind: passing transport connection information (including, but not limited to, source and destination addresses and ports) to a backend system across multiple layers of NAT, TCP, or UDP proxies and load balancers. The protocol achieves its goal by prepending each connection or datagram with a header reporting the other side’s connection characteristics. Effectively, from the point of view of the backend (in our case, BIND), it is a controllable way to spoof peer and incoming interface connection information.

With the addition of this feature, BIND can act as a backend to the front-end proxies implementing the PROXYv2 protocol. The list of such proxy implementations includes but is not limited to dnsdist and HAProxy. Many cloud infrastructure providers also implement the PROXYv2 protocol in their in-house front-end software.

The PROXYv2 protocol is supported for all DNS transports currently implemented in BIND, including DNS over UDP and TCP (Do53), DNS over TLS (DoT), and DNS over HTTP(S) (DoH). The same applies to dig as well, as we wanted to ensure that DNS operators who want to use the PROXYv2 protocol have a reliable tool for diagnosing their deployments. Moreover, dig might be one of the few such tools, if not the only one, that implements PROXYv2 for so many DNS transports.

There are currently two versions of the PROXY protocol - text-based PROXYv1 and binary-based PROXYv2. Also, there are protocols with similar purposes, like Simple Proxy Protocol (SPP) for UDP from Cloudflare. BIND is capable of accepting PROXYv2 only, so if we mention the PROXY protocol without a version, PROXYv2 is implied if not stated otherwise.

PROXYv2 in BIND uses the source and destination addresses and ports extracted from PROXYv2 headers instead of the real source and destination addresses and ports, as seen by the operating system. With very few exceptions (which we will discuss later), from the point of view of BIND, these are real - as a result, you will see them in the logs, and the ACL functionality of BIND will use them during matching and so on. In short, almost all aspects of BIND functionality that need source and destination addresses and ports will use the ones provided via the PROXYv2 protocol. Of course, the source and destination addresses of the real endpoints are preserved internally and are used for the actual data exchanges.

The above is done to fulfill the PROXY protocol’s goal of filling the backend server’s internal structures with the information collected by the front-end proxy, which the server would have been able to get by itself from the operating system if the client were connecting directly to the server instead of via a front-end. That provides a level of transparency that has many architectural benefits, some of which are discussed in detail in the PROXY protocol specification. Let’s discuss them briefly.

Applications for PROXYv2

Firstly, it becomes possible to chain multiple layers of front-ends (like proxies and firewalls) and always present the original connection information (like source and destination IP addresses and ports). With PROXY, the complexity of the forwarding infrastructure in front of BIND does not matter, as it makes it possible to preserve and pass the original information about endpoints through it to the backend. It might consist of just one front-end instance running on the same machine or local network, or be a complex, multi-layer infrastructure with many forwarders.

Secondly, this feature makes it easier to deploy elaborate infrastructures with large front-end farms in front of big backend farms, possibly shared between multiple sites; when using the PROXY protocol, the servers do not need to know routes to the client, only to the closest proxy that forwarded the connection. That provides benefits over the so-called transparent proxies, because using them usually implies that there is only one return path for the data; in cases when both front-end proxies and backend servers support the PROXYv2 protocol, it is easier to provide multiple return paths while preserving the ability to pass the endpoints data to the backends. That might be particularly useful for large DNS resolver operators.

Thirdly, using PROXY eases IPv4 and IPv6 integration; in particular, it is absolutely fine to receive a request over IPv4 and forward it over the chain of intermediates that are connected over IPv6 only (or vice versa). In that case, with proper configuration, the backend server will receive the original endpoint information.

Fourthly, PROXY support allows relatively transparent transport protocol conversion (from the backend server perspective), including TLS termination. There are front-end implementations that allow transport protocol conversion; for example, it is possible to configure a dnsdist instance to serve DNS over HTTP/2 or DNS over QUIC (starting from version 1.9.X), while the DNS backend server might not have these transports enabled or not have support for them. Similarly, HAProxy is notorious for allowing HTTP protocol version conversion support and is also often used for TLS termination. However, simply placing such front-ends in front of a backend (e.g. BIND) involves losing the original endpoint information. That is exactly the problem that enabling PROXYv2 both on the front-end and backend can solve. This feature is useful for both small and large installations alike. In particular, it allows serving DNS over transports currently not supported by BIND, like QUIC (DNS over QUIC/DoQ), in a very transparent way.

Let’s discuss how we can use the PROXYv2 protocol in BIND now with a few examples.

Preserving Connection Information with PROXYv2 in BIND

To demonstrate the use of PROXYv2 in BIND, we should discuss a couple of things about the front-ends we are going to use for demonstration, namely HAProxy and dnsdist, and some details about the PROXYv2 protocol.

The specification advises sending a PROXYv2 header at once after establishing a TCP connection. In fact, it only provides very few details about UDP (mostly the necessary constant definitions) and is not concerned about using PROXY over TLS.

Regarding PROXY over UDP, it seems that most software developers have agreed that a PROXY header should precede data in a datagram. It seems to be a very logical choice in this case. dnsdist works like this and is one of the few choices that use and support PROYXv2 over UDP.

Regarding PROXY over TLS, it is trickier. As noted above, the specification of the PROXY protocols is mainly concerned with TCP proxies. As TLS is used on TCP connections, the PROXY protocol can be used on TLS connections just as it is described in the specification: that is, by sending a PROXY header in front of any data related to TLS handshake. In this case, the PROXY header itself is, as one could expect, not encrypted. Most of the software implementing PROXY over TLS support works like this, including HAProxy.

One can see a possible problem with this approach, as relatively sensitive data is transmitted in clear text over what is expected to be an otherwise secure, encrypted connection. In order to resolve this problem, the dnsdist authors implemented PROXYv2 slightly differently: instead of sending plain PROXY header data before the TLS handshake over TCP, they decided to send an encrypted PROXY header as the first chunk of data after the handshake. One could argue that this is not described in the specification and, thus, deviates from it. On the other hand, all sensitive data is protected and cannot be collected or analyzed by any intermediaries. Also, it is worth noting that, as of the latest versions (since 1.8.X, 1.9.0), dnsdist started adding support for accepting non-encrypted PROXYv2 protocol messages as well.

BIND and dig, which strive to be useful in all deployment scenarios, support both plain and encrypted mode for the PROXY protocol. As expected, only plain mode is available for non-encrypted DNS transports, while the ones based on TLS support both.

Configuration

Configuring PROXYv2 support in BIND requires first establishing which protocols BIND will listen for, then adjusting ACLs to determine which clients have access.

Here, you can see a couple of examples for the first step:

options {

	# Enable PROXYv2 for Do53 (both TCP and UDP)
	listen-on port 53 proxy plain { any; };

	# Enable proxy for DoT, use encrypted PROXY
	# headers for compatibility with dnsdist
	listen-on port 853 proxy encrypted tls local-tls { any; };

	# Enable proxy for DoH, unencrypted proxy
	# headers for compatibility with HAProxy (and other tools)
	listen-on port 443 proxy plain tls local-tls http local-http-server { any; };
};

By default, this configuration is not enough to allow accepting PROXYv2. The second step is to set access control lists (ACLs) associated with PROXYv2, namely allow-proxy and allow-proxy-on.

  • allow-proxy - defines an ACL for the client addresses allowed to send PROXYv2 headers.
  • allow-proxy-on - defines an ACL for the interface addresses allowed to accept PROXYv2 headers.

These are the only ACLs that work with real endpoint addresses and ports - everything else will use the information carried by the PROXYv2 protocol.

One could ask why enabling PROXYv2 via listen-on statements is not enough. That is done for security reasons. As mentioned above, the core idea behind PROXYv2 is to provide a way for spoofing source and destination addresses and ports, which has security implications, as a client might make it seem as if a request is coming from someone else.

For example, it is customary, albeit not recommended, to configure BIND to allow recursive queries for clients on the local networks only, while serving zones on public networks. If such a BIND instance allows the PROXY protocol on a public interface, then a remote client could run recursive queries over a public interface, effectively turning the instance into an open resolver. That is only one example that comes to mind, but in general, PROXY allows bypassing other ACLs, too. That, of course, is undesirable and might be unexpected for the operator.

On the other hand, every deployment is different, so it is impossible to provide a default that would fit everyone, especially without sacrificing security. As a result, BIND does not allow PROXY for any clients by default; BIND defaults to the following:

options {
	...
	allow-proxy { none; };
	allow-proxy-on { any; };
	...
};

In other words, by default, the PROXY protocol is not allowed for any client address but is allowed on any interface. For PROXY to be accepted, a request should pass checks by both of the ACLs.

For example, it is possible to allow PROXY for clients on local networks where BIND has network interfaces by configuring it this way:

options {
	...
	allow-proxy { localnets; };
	...
};

As another example, it is possible to allow PROXY for clients on a loop-back interface only by configuring it this way:

options {
	...
	allow-proxy { 127.0.0.1; ::1; };
	...
};

And now a somewhat “inverted” example. Let’s imagine a situation where we know that it is safe for BIND to accept the PROXY protocol on a particular interface (192.168.1.10 in this example):

options {
	...
	allow-proxy { any; };
	allow-proxy-on { 192.168.1.10; };
	...
};

Of course, you can configure these two ACLs to match your infrastructure needs exactly. What we do not recommend doing, though, is allowing PROXY on publicly accessible network interfaces due to the security concerns described above.

We should note that enabling PROXYv2 on a listen-on statement will prevent the corresponding listener from accepting “regular” DNS queries that arrive without a PROXYv2 header.

Now that we have learned enough about PROXYv2 support in BIND, we can provide more examples of using BIND with both dnsdist and HAProxy. Both of these proxying front-ends have unique functionality, and although some of the capabilities overlap, they can also complement each other.

Conclusion

This document is an introduction to the new PROXYv2 protocol support in BIND. We hope that it fulfills its purpose and, even more than that, can serve as a guide on how the PROXYv2 protocol works in general when applied in a DNS setting. We have omitted some details, but if you are more interested in the PROXYv2 protocol itself, we suggest you read the relatively short specification. We have a longer version of this article, with a number of additional configuration examples, in the ISC Knowledgebase.

With the help of the front-ends that support PROXYv2, you can have a very complicated, yet transparent to BIND, network of forwarding entities. Both dnsdist and HAProxy support accepting and forwarding PROXYv2. To learn how to make these tools accept PROXYv2, please take a look at setProxyProtocolACL for dnsdist and accept-proxy (that can be added to the bind statement) in the case of HAProxy. As of this blog post, BIND is capable of receiving PROXYv2, but there is no support for sending connection details when interacting with other servers (e.g. for forwarding). We may address this in future releases if there is demand for this feature.

Implementing PROXYv2 support for all DNS transports that BIND supports was a major task and required a significant redesign of some of the DNS transports. It is particularly connected to Stream DNS, BIND’s new unified DNS transport for DNS over TCP and DNS over TLS. Having PROXYv2 support will benefit both large and small installations of BIND and, maybe, even allow you to think of your infrastructure in different ways, as transparently passing information about remote peers to backends is a very powerful mechanism. We hope that you will find it useful.

References

Recent Posts

What's New from ISC

Previous post: BIND 9 Security Audit