dns-proxy, test-dns — Domain Name System (DNS) proxy
dns-proxy
[-hv
] [-d
] dbglev
-f
cfgfile
test-dns
[-hv
] [-d
] dbglev
-f
[cfgfile
-t
]test_expr
The dns-proxy provides proxying service for the Domain Name System (RFC 1034+1035 and some of their extensions).
The proxy handles incoming requests in four basic modes:
Request is not processed at all. Administrator can choose from several ways of request denial.
Request is responded without querying any other server. The faked response is set by administrator.
Request is resent to one from group of servers defined by the administrator. This mode is typical for
forwarding queries from host not fully connected to the internet
forwarding queries to server hidden in private network
forwarding zone transfers across the firewall.
This is fully functional mode when the proxy tries to recursively resolve the request form the scratch. It starts from the root servers and accepts only authoritative answers until it gets the final authoritative answer.
The proxy is not designed as a server, neither for external queries to our domain, nor as a local caching name server. Typical scenario is that all clients ask another server in the internal network and this server queries the proxy (if needed) and caches the answers. That's why the proxy does not cache answers, there is only a small cache of name servers for internal purpose only.
The proxy behaves like a server for the client and vice versa, with full syntax and semantics verification (see below, protection against cache-poisoning). When querying other servers, dns-proxy uses random source port and ID (protection against reply-spoofing).
The proxy completely reconstructs answers
from servers and limits the size of reply sent to client
(configurable by ptr-reply-size
and
adr-reply-size
items).
This feature protects clients against to attack based on buggy resolver
routines.
The proxy usually runs as two processes: the single child process manages all the sessions and the parent process manages the child and restarts it after a failure. You can learn more in udpserver(7) manual page, although the dns-proxy does not use the udpserver library, in fact. However, it uses the same operation logic.
Format of the proxy configuration file is described in dns-proxy.cfg(5). For the dns-proxy, any host in configuration file must be specified by its address, not by its name.
Program test-dns
tests syntax and partially semantics
of configuration; for test expression syntax, see test-expr(5).
The proxy reads its configuration file and starts listening
on specified IP sockets (address/port couples), as specified in
the listen-on
configuration section (see listen-on(5)).
Proxy listens for both UDP and TCP protocols.
If support of transparent connections (see transparency(7)) is requested by item transparent
in
section listen-on
, the corresponding NAT redirections
are established during proxy startup and removed upon exit.
However, transparent connections are in fact not supported by this
proxy, decisions about servers are made according to
the proxy configuration, not by original destination.
In the case of UDP requests redirected to the proxy by more general NAT rules, the real destination is neither being detected nor used in ACL selection.
In the resolve mode, the proxy checks each address being stored into cache whether it matches to one of addresses, the proxy listens on. If it does, the record is not stored, so that this does not lead to infinite loop. For this reason, listening addresses (for port 53) must be specified explicitly, expression [0.0.0.0]:53 is not allowed.
Current version of the dns-proxy implements following subset of protocol:
OPCODEs
QUERY
RFC 1035
NOTIFY
RFC 1996
Requests with unimplemented OPCODEs are replied with
the NotImp
response code.
Requests with unknown OPCODEs are replied with
the FormErr
response code.
CLASSes
IN
RFC 1035
*
RFC 1035
Requests with query resource record (RR) of unimplemented CLASS
are replied with the NotImp
response code,
unknown classes cause the FormErr
response code.
The '*' requests are converted to IN, resolved
and then sent to the client with authority flag set to 0.
TYPEs
A
RFC 1035
AAAA
RFC 3596
AFSDB
RFC 1183
AXFR
RFC 1035
CNAME
RFC 1035
DNSKEY
RFC 4034
DS
RFC 4034
HINFO
RFC 1035
IXFR
RFC 1995
MX
RFC 1035
NS
RFC 1035
NSEC
RFC 4034
NSEC3
RFC 5155
NSEC3PARAM
RFC 5155
OPT
RFC 6891
PTR
RFC 1035
RRSIG
RFC 4034
SOA
RFC 1035
SPF
RFC 4408
SRV
RFC 2782
SSHFP
RFC 4255
TXT
RFC 1035
*
RFC 1035
Requests with query RR of unimplemented TYPE are by default
replied with the NotImp
response code.
This behavior can be changed in the configuration (ACL settings).
Requests to unknown TYPE are replied with the
FormErr
response code.
Every request is stored into a table item containing
all necessary information.
Size of this table must be specified in configuration
(requests-table-size
item) and it is
recommended to reserve a little more items then estimated number
of parallel requests.
Some requests processed in resolve mode can generate so called
internal requests (see below)
that occupy table items, too.
Besides number of requests, number of simultaneously opened
sockets is monitored.
The maximum of sockets must be specified in the configuration
(sockets-table-size
item).
There are two kinds of internal requests:
If the proxy is to ask some server, address of which
is not in the cache,
it generates an internal requests with A
and AAAA
query for the name of the server.
This request is handled in the same way as the original query
with the exception that the result is stored in the cache.
Choosing just IPv4 or IPv6 protocol when querying servers
can be done by the server-proto
item.
The default is using both of them, or using just the IPv4
when no system interface has an IPv6 address defined.
If the answer got from a server contains
CNAME
RR and no (trusted) RR for
the canonical name, proxy generates an internal request
with the same query RR type and query name
equal to the canonical name received.
This request is handled in the same way as the original query
with the exception that the result is added to the previously
received RRs.
If the internal request fails to complete the resolution,
the original request is replied by
the ServFail
response code.
Both types of internal requests suspend processing of the original request (originator) until the internal request is completed. If another request is to generate a new internal request of the same subject as another running one, no new internal request is created and the originator is suspended waiting for the first internal request, too. After (successful or unsuccessful) completion of internal request, all originators are waked up.
Similar principle is used also for client requests. If a request with the same parameters (query name, query type, EDNS UDP payload etc.) is already being processed, new request is also suspended, waiting for the result of the previous request.
Both types of internal requests can also generate new internal requests. For instance, in following definition:
domain1 IN NS ns.domain2 domain2 IN NS ns.domain3
a request to the domain1
will generate request
to the ns.domain2
name
and solution of this will result to a new CACHE
request to ns.domain3
.
Proxy detects an infinite loop, if occurs. Proxy also limits number of
internal requests generated by one internal request in the row (item
internal-request-depth
) to prevent DoS attack
by means of non-infinite but very long loop of references.
Both types of internal requests respect the resolving policy
according to the query name and type given by the configuration.
For every new request, the request-acl
list (see below)
is searched through and the new ACL defines operation to be used.
If no free request table item is available,
incoming UDP requests are replied with
the ServFail
response code
incoming TCP requests are rejected by closing the connection
internal requests of any type fail and these failures are propagated to all originators.
The proxy uses two layers of ACL (see access-control(7)) named
session-acl
and request-acl
.
When a request arrives, configuration is consulted,
proper session-acl
is selected and according to it,
request is served or not.
Subsequently, protocol-specific parameters of query is checked
against set of request-acl
entry conditions
and proper mode of operation is selected.
Additionally to the general Kernun ACL concept,
request-acl
brings new entry condition items:
query-name
This item contains a set of regular expressions
and/or strings describing names, querying for which
is to be dealt by this request-acl
.
When using the regexp form, you have to respect the dot
placed to the end of every name before request processing.
The queried name matches a member of the set if
matches the regexp (regexp case) or
is part of the domain (string case).
Examples:
Regular expression
/^[^.]*\.tns\.cz\.$/
matches
queries to all hosts in the domain
tns.cz
.
String kernun.com
matches e.g.
queries to www.cz.kernun.com
,
or kernun.com
,
but not kernun.com.cz
.
request-type
This item can define subset of DNS operation codes
and RR types that is to be dealt by this
request-acl
.
By these two items, a detail selection of
request-acl
can be done to set special handling
for different tasks like regular queries, zone transfers,
server notifications etc.
If no matching ACL is found, request is replied by
the Refused
response code.
If ACL is found, query type and class are checked.
Requests with classes other than IN
and *
(ANY class) are rejected with response
code NotImp
.
As we stated above, there are several possible proxy operations.
The proxy decides among them by matching query RR type against a set of
special request-acl
items query
/notify
.
The first matching item is used and the operation is executed.
If no proper item is found, request is rejected with
the Refused
response code.
In case of resolve
and forward
operations, request is resent (with a new, random ID) to a new server.
The set of possible responders is defined in a global section of
ns-list
type.
Each received reply RR is then checked against a set of special ACL
items called reply
in the same manner as queried
RR is checked.
The reply
items can tune handling of particular RR
(permit
, remove
),
so as even predefine reaction to the whole request
(abort
, deny
).
If no proper reply
item is found,
request is rejected with the Refused
response code.
If permit
action is required for non-implemented RR,
record is removed.
After filtering the response from server, other proper RRs
(given by special fake
items) can be added
to the answer.
The same set of items is used for reply construction in case of
fake
operation.
Fake RRs are placed into the answer in the order of appearance in
the configuration.
After completing all answer RRs, reply is completely reconstructed
and sent to the client.
The resolved requests will have the authority (AA) flag cleared while
for the forwarded requests, the admin can choose whether to preserve
or clear the flag (see the request-acl.query.clear-aa
definition in dns-proxy(5)).
If the response has
the NXDomain
response code,
or the NoError
response code
with no answer (AN) records,
and if this status was caused by the proxy
(e.g. due to denying query or filtering response),
the proxy will add, by default, a SOA record
with proper TTL for successful negative caching in clients.
This behavior can be configured by the neg-resp-ttl
item of the particular request-acl
.
If the authoritative answer in resolve
operation
is not available, request is replied with
the ServFail
response code.
There are some situations where this approach is not applicable.
For instance,
queries to mail-abuse.org
domain end by non-authoritative answer.
We recommend using a special request-acl
for this case,
forwarding requests of this type directly to proper name servers.
Besides Security Policy application, the proxy checks both queries and replies to correctness in sense of relevant RFCs.
Names can be at most 254 bytes long,
every label can be at most 63 bytes long.
Labels can contain only alphanumerical characters
and a hyphen ('-
').
We allow also underscore ('_
')
and slash ('/
') because they are
commonly used.
Request ID of the answer is checked to be equal to the ID of query. Query (QD) section of the answer is checked to be equal to the query. Every answer (AN) RR must be relevant to the query or to the previous RR. Every authority (NS) RR must be relevant to the query or to the canonical name of some AN RR. Every additional (AR) RR must be relevant to some AN or NS RR.
Every server is introduced into the cache as an authoritative name server for some domain. So, all answer RRs received from this server are trusted only if they belong to this domain or some subdomain. This criteria may cause an infinite loop when two or more domains refer each other without having any regular glue record (i.e. nameserver in own domain or a subdomain). If you need to accept such a domain, you must make an ACL for this domain forwarding requests directly to proper nameservers.
Name server cache is used for increased efficiency,
namely for repeated queries to the same domain
(TLDs, resending query via TCP, resolution of CNAMEs etc.).
“Root servers” for different network zones are defined
in ns-list
configuration sections,
each zone has a separated cache “zone” named
by the name of ns-list section.
All other name servers and their addresses are
introduced to the cache as a result of authoritative answers.
“Authoritative” server (in sense of dns-proxy)
is either a root server
or a server delegated by some “authoritative” server
for a parent domain
(already being in the cache).
All new RRs are used with respect of their time to live (TTL) value.
When the minimal of TTLs of name servers
(both their NS and address records)
for a domain expires, the domain item
is unusable and it is removed from the cache at nearest cleanup.
Similarly, when the minimal of TTLs of addresses for a host expires,
the host item is unusable and it is removed from the cache
at nearest cleanup.
Some properties of the cache are configurable in a special
cache
section:
cleanup-period
sets the period (in seconds) of cache cleaning up. After the period, all items that were not used within the period and all expired items are removed.
max-domains
sets the maximum number
of domains
(not individual NS RRs) stored in the cache.
This value should be at least as large as
requests-table-size
.
max-hosts
sets the maximum number
of hosts
(not individual address RRs) stored in the cache.
This value should be at least approximately
five times larger than
requests-table-size
.
If any maximum is reached, a non-periodical cleanup is started. This cleanup removes all items currently not used.
When a domain name is to be resolved, the longest match search in the cache is done. After it, the new best server for the domain found is chosen. Server comparison criteria:
Resolved, but never contacted servers.
Unresolved servers, never tried to be resolved.
Responding servers (sorted by response time rounded to entire seconds).
Non-resolved or non-responding servers (sorted by time of the last attempt).
This algorithm guarantees a primitive "load balancing" and error recovery of multiple servers.
When querying for an EDNS request, the EDNS servers have priority. If a server responds FormErr to an EDNS query, the non-EDNS query is repeated immediately.
First of all, the selected server is queried with a very short (1 sec.) timeout. When this timeout fires, the query is simply repeated to avoid errors caused by loosing UDP packets.
Then the queried server has a longer timeout for the response.
Each server has its own timeout stored in the cache.
Starting value of this timeout is set by query-timeout
configuration directive.
Each time the server does not respond within the timeout, the timeout
doubles, up to server-dead
value.
When the timeout reaches this value, server is marked as
“dead” and a new attempt to contact it cannot be done until
server-retry
seconds period.
If the server responds within the timeout, his timeout
for the next attempt
is set to his response time plus query-timeout
.
The number of attempts per one query is hardcoded to eight
(regardless of which servers were queried).
However, typically this number is not reached before firing
the request-timeout
(see below).
The same mechanism is used for selection among forwarders. That's why forwarders lists are also stored in the cache (every list in its separated zone).
The timeout for the whole request processing (including resolving
of generated internal requests etc.) is also set in the configuration
(request-timeout
) and if reached, request is replied
with the ServFail
response code.
Zone transfers need some more special handling.
First of all, the requests are typically addressed by originators
directly to a conrete server.
That's why either the transparent mode (if public addresses are used),
or non-transparent mode to a dedicated address/port on firewall
(in the case of server on a private address) should be used.
Also, besides QUERY
operation,
the NOTIFY
operation should be permitted.
Moreover, the own transfers (responses to
the AXFR
/IXFR
queries)
should be sent by servers either separated
(i.e. more DNS messages with one RR in each one)
or aggregated (i.e. all RRs in one DNS message).
The proxy can force one of these methods, or keep the incoming format
according to the xfr-format
configuration directive.
Configuration example:
ns-list MASTER { server ns.x.y.z [10.1.1.1]; } ns-list SLAVE { server sns.x.y.z [20.2.2.2]; } ... dns-proxy ZONE-TRANSFERS { ... request-acl TO-MASTER { to non-transparent [20.2.2.1]; # special external fw adr query { axfr, ixfr } forward MASTER; ... } request-acl TO-SLAVE { to transparent [20.2.2.2]; notify forward SLAVE; ... }
The proxy uses common Kernun mechanism for listening on its sockets, optionally changing root directory and running with alternative user privileges. For more detailed information, see application(5) and listen-on(5).
The proxy uses common Kernun mechanism for network
input/output operations. Configuration allows for specifying
several parameters like buffer sizes and timeouts, both for
client and server connections. They can be included in
client-conn
and server-conn
configuration sections, respectively.
For more detailed information, see netio(7).
The proxy uses common Kernun mechanism for logging. For more detailed information, see logging(7). For every request, one REQUEST (DNSP-860-I) message is logged (besides one or two ACL messages - DNSP-810-I and DNSP-820-I). Every log message has process ID suffix equal to the index of request being currently processed.
The proxy, in fact, does not use common Kernun mechanism
for name resolving (see resolving(7) manual page),
because it does not use DNS names at all. In spite of that, the item
use-resolver
remains
in the dns-proxy configuration
for compatibility with other proxies
(and thus e.g. ability to use common cml(8) variables).
The dns-proxy handles following signals:
SIGUSR1
Log level increasing.
SIGUSR2
Log level decreasing.
SIGINFO
Operation status logging; parent process logs info about all children, child process dumps cache content and requests table content.
SIGHUP
,
SIGINT
,
SIGQUIT
,
SIGTERM
Immediate termination; proxy immediately closes all connections and terminates.
The program options are as follows:
-h
Print usage information and exit.
-v
Display version information and exit.
-d dbglev
Set debuging level to a specific number. Permitted values are 3 through to 9, 3 being the least and 9 the most verbose. See logging(7) for details. This setting is relevant only till configuration reading is finished.
-f cfgfile
Read cfgfile
for configuration information.
-t test-expr
Test configuration according to given expression.
Format of the test-expr
is described in test-expr(5).
Currently, the dns-proxy doesn't implement following features:
more queries (QD RRs) in one request
wildcard ('*
') queries.
dns-proxy.cfg(5), listen-on(5), application(5), test-expr(5), DNSP-810(6), DNSP-820(6), DNSP-860(6), access-control(7), configuration(7), logging(7), netio(7), resolving(7), transparency(7), udpserver(7)
named(8)