# A Guided Tour of twisted.names.client

Twisted Names provides a layered selection of client APIs.

In this section you will learn:

- about the high level `client` API,

- about how you can use the client API interactively from the Python shell (useful for DNS debugging and diagnostics),

- about the `IResolverSimple` and the `IResolver` interfaces,

- about various implementations of those interfaces and when to use them,

- how to customise how the reactor carries out hostname resolution,

- and finally, you will also be introduced to some of the low level APIs.

## Using the Global Resolver

The easiest way to issue DNS queries from Twisted is to use the module level functions in `names.client`.

Here’s an example showing some DNS queries generated in an interactive `twisted.conch` shell.

Note

The `twisted.conch` shell starts a `reactor` so that asynchronous operations can be run interactively and it prints the current result of `deferred`s which have fired.

You’ll notice that the `deferred`s returned in the following examples do not immediately have a result – they are waiting for a response from the DNS server.

So we type `_` (the default variable) a little later, to display the value of the `deferred` after an answer has been received and the `deferred` has fired.

    $ python -m twisted.conch.stdio

    >>> from twisted.names import client
    >>> client.getHostByName('www.example.com')
    <Deferred at 0xf5c5a8 waiting on Deferred at 0xf5cb90>
    >>> _
    <Deferred at 0xf5c5a8 current result: '2606:2800:220:6d:26bf:1447:1097:aa7'>

    >>> client.lookupMailExchange('twistedmatrix.com')
    <Deferred at 0xf5cd40 waiting on Deferred at 0xf5cea8>
    >>> _
    <Deferred at 0xf5cd40 current result: ([<RR name=twistedmatrix.com type=MX class=IN ttl=1s auth=False>], [], [])>

All the `IResolverSimple` and `IResolver` methods are asynchronous and therefore return `deferred`s.

`getHostByName` (part of `IResolverSimple`) returns an IP address whereas `lookupMailExchange` returns three lists of DNS records. These three lists contain answer records, authority records, and additional records.

Note

- `getHostByName` may return an IPv6 address; unlike its stdlib equivalent (`socket.gethostbyname()`)

- `IResolver` contains separate functions for looking up each of the common DNS record types.

- `IResolver` includes a lower level `query` function for issuing arbitrary queries.

- The `names.client` module `directlyProvides` both the `IResolverSimple` and the `IResolver` interfaces.

- `createResolver` constructs a global resolver which performs queries against the same DNS sources and servers used by the underlying operating system.

  That is, it will use the DNS server IP addresses found in a local `resolv.conf` file (if the operating system provides such a file) and it will use an OS specific `hosts` file path.

### A simple example

In this section you will learn how the `IResolver` interface can be used to write a utility for performing a reverse DNS lookup for an IPv4 address. dig can do this too, so lets start by examining its output:

    $ dig -x 127.0.0.1
    ...
    ;; QUESTION SECTION:
    ;1.0.0.127.in-addr.arpa.             IN      PTR

    ;; ANSWER SECTION:
    1.0.0.127.in-addr.arpa.      86400   IN      PTR     localhost.
    ...

As you can see, `dig` has performed a DNS query with the following attributes:

- Name: `1.0.0.127.in-addr.arpa.`

- Class: `IN`

- Type: `PTR`

The name is a reverse domain name and is derived by reversing an IPv4 address and prepending it to the special in-addr.arpa parent domain name. So, lets write a function to create a reverse domain name from an IP address.

    def reverseNameFromIPAddress(address):
        return ".".join(reversed(address.split("."))) + ".in-addr.arpa"

We can test the output from a python shell:

    >>> reverseNameFromIPAddress('192.0.2.100')
    '100.2.0.192.in-addr.arpa'

We’re going to use `twisted.names.client.lookupPointer()` to perform the actual DNS lookup. So lets examine the output of `lookupPointer` so that we can design a function to format and print its results in a style similar to `dig`.

Note

`lookupPointer` is an asynchronous function, so we’ll use an interactive `twisted.conch` shell here.

    $ python -m twisted.conch.stdio

    >>> from twisted.names import client
    >>> from reverse_lookup import reverseNameFromIPAddress
    >>> d = client.lookupPointer(name=reverseNameFromIPAddress('127.0.0.1'))
    >>> d
    <Deferred at 0x286b170 current result: ([<RR name=1.0.0.127.in-addr.arpa type=PTR class=IN ttl=86400s auth=False>], [], [])>
    >>> d.result
    ([<RR name=1.0.0.127.in-addr.arpa type=PTR class=IN ttl=86400s auth=False>], [], [])

The deferred result of `lookupPointer` is a tuple containing three lists of records; answers, authority, and additional. The actual record is a `Record_PTR` instance which can be reached via the `RRHeader``.payload` attribute.

    >>> recordHeader = d.result[0][0]
    >>> recordHeader.payload
    <PTR name=localhost ttl=86400>

So, now we’ve found the information we need, lets create a function that extracts the first answer and prints the domain name and the record payload.

    def printResult(result):
        answers, authority, additional = result
        if answers:
            a = answers[0]
            print(f"{a.name.name} IN {a.payload}")

And lets test the output:

    >>> from twisted.names import dns
    >>> printResult(([dns.RRHeader(name='1.0.0.127.in-addr.arpa', type=dns.PTR, payload=dns.Record_PTR('localhost'))], [], []))
    1.0.0.127.in-addr.arpa IN <PTR name=localhost ttl=None>

Fine! Now we can assemble the pieces in a `main` function, which we’ll call using `twisted.internet.task.react()`. Here’s the complete script.

`listings/names/reverse_lookup.py`

    import sys

    from twisted.internet import task
    from twisted.names import client

    def reverseNameFromIPAddress(address):
        return ".".join(reversed(address.split("."))) + ".in-addr.arpa"

    def printResult(result):
        answers, authority, additional = result
        if answers:
            a = answers[0]
            print(f"{a.name.name} IN {a.payload}")

    def main(reactor, address):
        d = client.lookupPointer(name=reverseNameFromIPAddress(address=address))
        d.addCallback(printResult)
        return d

    task.react(main, sys.argv[1:])

The output looks like this:

    $ python reverse_lookup.py 127.0.0.1
    1.0.0.127.in-addr.arpa IN <PTR name=localhost ttl=86400>

Note

- You can read more about reverse domain names in RFC 1034 Section 5.2.1.

- We’ve ignored IPv6 addresses in this example, but you can read more about reverse IPv6 domain names in RFC 3596 Section 2.5 and the example could easily be extended to support these.

- You might also consider using netaddr, which can generate reverse domain names and which also includes sophisticated IP network and IP address handling.

- This script only prints the first answer, but sometimes you’ll get multiple answers due to CNAME indirection, for example in the case of classless reverse zones.

- All lookups and responses are handled asynchronously, so the script could be extended to perform thousands of reverse DNS lookups in parallel.

Next you should study `../examples/multi_reverse_lookup.py` which extends this example to perform both IPv4 and IPv6 addresses and which can perform multiple reverse DNS lookups in parallel.

## Creating a New Resolver

Now suppose we want to create a DNS client which sends its queries to a specific server (or servers).

In this case, we use `client.Resolver` directly and pass it a list of preferred server IP addresses and ports.

For example, suppose we want to lookup names using the free Google DNS servers:

    $ python -m twisted.conch.stdio

    >>> from twisted.names import client
    >>> resolver = client.createResolver(servers=[('8.8.8.8', 53), ('8.8.4.4', 53)])
    >>> resolver.getHostByName('example.com')
    <Deferred at 0x9dcfbac current result: '93.184.216.119'>

Here we are using the Google DNS server IP addresses and the standard DNS port (53).

## Installing a Resolver in the Reactor

You can also install a custom resolver into the reactor using the `IReactorPluggableNameResolver` interface.

The reactor uses its installed resolver whenever it needs to resolve hostnames; for example, when you supply a hostname to `connectTCP`.

Here’s a short example that shows how to install an alternative resolver for the global reactor:

    from twisted.internet import reactor
    from twisted.names import client
    reactor.installResolver(client.createResolver(servers=[('8.8.8.8', 53), ('8.8.4.4', 53)]))

After this, all hostname lookups requested by the reactor will be sent to the Google DNS servers; instead of to the local operating system.

Note

- By default the reactor uses the POSIX `gethostbyname` function provided by the operating system,

- but `gethostbyname` is a blocking function, so it has to be called in a thread pool.

- Check out `ThreadedResolver` if you’re interested in learning more about how the default threaded resolver works.

## Lower Level APIs

Here’s an example of how to use the `DNSDatagramProtocol` directly.

    from twisted.internet import task
    from twisted.names import dns

    def main(reactor):
        proto = dns.DNSDatagramProtocol(controller=None)
        reactor.listenUDP(0, proto)

        d = proto.query(('8.8.8.8', 53), [dns.Query('www.example.com', dns.AAAA)])
        d.addCallback(printResult)
        return d

    def printResult(res):
        print('ANSWERS: ', [a.payload for a in res.answers])

    task.react(main)

The disadvantage of working at this low level is that you will need to handle query failures yourself, by manually re-issuing queries or by issuing followup TCP queries using the stream based `dns.DNSProtocol`.

These things are handled automatically by the higher level APIs in `client`.

Also notice that in this case, the deferred result of `dns.DNSDatagramProtocol.query` is a `dns.Message` object, rather than a list of DNS records.

## Further Reading

Check out the Twisted Names Examples which demonstrate how the client APIs can be used to create useful DNS diagnostic tools.