Somebody Told Me

Proxying client information to your favorite resolver and more.

Who? He's from the village, you don't know him
Photo by Juri Gianfrancesco / Unsplash

By Yorgos Thessalonikefs

Proxies are a reality in today's networks. They are used to fulfill many different roles, from access control and gateway points to caches and load balancers. Their most used features is reverse-proxying client requests to upstream servers. This can help with security, scalability and redundancy by not providing direct access to clients and by allowing for more than one upstream server to be behind a single advertised point.

There are multiple ways to proxy the client information but most of them are protocol dependent (e.g., HTTP, SMTP) or require specific configuration (e.g., TPROXY). In contrast, the PROXY protocol was developed by Willy Tarreau of HAProxy Technologies as

... a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies.

It is protocol agnostic and can work with any layer 7 protocol even when encrypted. It works on both UDP and TCP based transports and in a nutshell it prepends the client information in the application's payload. This is done once at the start of a TCP stream, or in every UDP packet. The caveat is that both the proxy and the upstream server need to understand the PROXY protocol.

Starting from version 1.17.0, Unbound supports PROXYv2 (PROXY protocol version 2) for downstream connections; that is clients (read proxies) talking to Unbound. Unbound can now play nicely in environments where PROXYv2 supporting DNS reverse-proxying is in place. It recognizes the PROXYv2 client information and uses that as the "real" client address for all functions, except in the actual network communication, where a client address is used, such as Access Control, logging and DNSTAP.

Configuration

Configuring Unbound for PROXYv2 is pretty straight forward. The following minimal configuration allows Unbound to listen for incoming queries on port 53 (the default) and marks the same port as a PROXYv2 port:

server:
	interface: eth0
	proxy-protocol-port: 53
	interface-action: eth0 allow

This means that Unbound expects PROXYv2 information on that port. Failure to do so will terminate/drop the connection/packet.

The port configuration can be used alongside plain UDP and plain TCP ports (as in the example above), but also together with DNS over TLS ports. It should be noted that coexistence of PROXYv2 together with either DNSCrypt or DNS over HTTP is not supported.

[The interface-action: option allows all incoming traffic from the given interface and is another feature that landed with Unbound 1.17.0; namely ACL per interface]

Somebody was allowed to tell me

The way Access Control was working in Unbound was per client IP address. Based on that information different actions could be taken like refusing/dropping/allowing the client, tagging the request for further processing or applying a specific local data view.

Starting from version 1.17.0, Unbound also supports ACL per interface. This is exactly how it sounds and it allows Unbound to apply ACL actions per listening interface instead of client prefixes. This is useful if you don't know (or you don't care) which clients will reach Unbound, but based on the interface they arrive, different ACL actions are applied.

It is backwards compatible, so if you don't use the new configuration options your setup will continue to work the same as before.

Configuration

Configuring Unbound for ACL per interface is again pretty straight forward as all the new configuration options map directly to the existing ones:

server:
	interface: 10.0.0.1
	interface: eth0
    
	# Pairs of ACL configuration options.
	# First one is the ACL per client prefix,
	# second one is the ACL per interface counterpart.

	access-control: 10.0.0.0/8 allow
	interface-action: eth0 allow

	access-control-tag: 192.0.2.0/24 "tag2 tag3"
	interface-tag: 10.0.0.1 "tag2 tag3"

	access-control-tag-action: 192.0.2.0/24 tag3 refuse
	interface-tag-action: eth0 tag3 refuse

	access-control-tag-data: 192.0.2.0/24 tag2 "A 127.0.0.1"
	interface-tag-data: 10.0.0.1 tag2 "A 127.0.0.1"

	access-control-view: 192.0.2.0/24 viewname
	interface-view: eth0 viewname

It should be noted that the interface-* configuration options need to specify an already defined interface:.

Caveats

But beware! Since ACL per client prefix and ACL per interface need to coexist, there is a preference to apply the ACL per client prefix when both are present. This is done because applying an ACL per client prefix is now viewed as a more specific action; targeting specific clients instead of the whole interface.

Acknowledgements

Many thanks to Swedish University Computer Network (SUNET) and Fredrik Pettai. SUNET funded the development of the PROXYv2 protocol in Unbound, and Fredrik tested the feature extensively in production before we released it in Unbound version 1.17.0.