Minimal DNS spoofing daemon

When running tests in a controlled environment, it should be possible to spoof the domain names. For instance foo.com could be mapped into slow.novalocal, an OpenStack instance responding very slowly to simulate timeouts. A twisted based spoofing DNS reverse proxy is implemented to transparently resolve domain names with other domain names IP addresses, using a python hash table such as:

fqdn2fqdn = {
    'foo.com': 'foo.me',
    'bar.com': 'bar.me',
}

It will map foo.com to foo.me as follows:

$ sudo python dns_spoof.py 8.8.8.8 &
$ ping -c 1 foo.me
PING foo.me (91.185.200.115) 56(84) bytes of data.
64 bytes from 91.185.200.115: icmp_req=1 ttl=47 time=42.2 ms
--- foo.me ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 42.268/42.268/42.268/0.000 ms
$ ping -c 1 foo.com
PING foo.com (91.185.200.115) 56(84) bytes of data.
64 bytes from 91.185.200.115: icmp_req=1 ttl=47 time=42.2 ms
--- foo.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 42.290/42.290/42.290/0.000 ms

Update May 10, 2013: an easier solution is to configure your BIND resolvers to lie using Response Policy Zones (RPZ). Thanks to S. Bortzmeyer for pointing in the right direction.

OpenStack based test environment

In an integration environment where tests scripts boot instances using

nova boot foo

the associated domain name will be foo.novalocal by default but the associated private IP is not known in advance. When testing the integration of a puppet manifests designed to deploy this machine to serve foo.com, the manifest will typically contain:

node 'foo.com', 'foo.novalocal' {
...
}

so that it deploys on the production machine ( foo.com ) and the test machine ( foo.novalocal ). If the integration test checks foo.com availability, the corresponding nagios command will try to access foo.com and not foo.novalocal.
Although it would be possible to avoid using FQDN such as foo.com, there are a number of circumstances where the software will make it inconvenient or the operator will just forget about this rule.

spoofing DNS requests

To transparently ensure that all FQDN used in puppet manifests resolve to private IPs matching a *.novalocal name, the manifests are parsed to create a table mapping each node name to the .novalocal FQDN found in the same node stanza. This dictionary can then be used by a daemon to spoof each DNS request to foo.com into a request to foo.me.

spoofing DNS server

The DNS server and client library provided with twisted is used to implement a spoofing DNS reverse proxy.
It reads a python file mapping FQDN to their spoofed equivalent.
When an incoming DNS request is received, the FQDN is substituted if it is found in the spoof map.
The spoofed name is saved in the spoofed variable and included in the closure added to the deferred created to forward the query to the authoritative DNS.
When the answer is received, the spoofed name is restored so that the original requester does not notice the difference. The name is also restored in case of an error otherwise the client is not notified of the error because it contains a reference to a FQDN that is not know to the original client.

testing the DNS server

The standalone python script is isolated to allow for inclusion by the test script. It is run as follows:

$ trial test.py
test
  DnsSpoofTestCase
    testAddressRecord_one ...                                    [OK]
    testAddressRecord_two ...                                    [OK]
    testSpoofedRecord_fail ...                                   [OK]
    testSpoofedRecord_one ...                                    [OK]
    testSpoofedRecord_two ...                                    [OK]
---------------------------------------------------------------------
Ran 5 tests in 0.017s
PASSED (successes=5)

Before each test is run, a DNS server is run to provide information for a pre-defined set of FQDN. Another DNS server is run based on the DNSSpoofFactory class, which is configured to forward to the first DNS.
The first DNS is tested to check that it resolves one.my-domain.com and two.my-domain.com. The spoofing DNS is tested to check that is resolves one.my-domain.com to the same IP as two.my-domain.com because the spoof map asks for it:

fqdn2fqdn = { 'two.my-domain.com': 'one.my-domain.com' }

The error handler is checked to raise a DNSNameError if trying to resolve a hostname that is unknown to the authoritative DNS.

other DNS servers

bind9 could be used to create zones in which some hostnames (foo.com for instance) are CNAME to the corresponding private hostname (foo.novalocal). The puppet module creating these CNAME could even be merged into the puppet module bind9 view. But the bind9 configuration to achieve is either difficult or does not exist.
The unbound, dnsmasq and ettercap DNS servers do not support CNAME : they all require that the IP of a hostname is known in advance, which is not the case when launching an instance in OpenStack.