A few days ago, the Scapy project was brought to my attention.

Scapy is an internet packet manipulation library for Python2. It can be used to sniff and decode packets, or to generate your own custom packets.

In the most basic form, it runs on raw sockets, sniffing and decoding traffic like tcpdump. See the sniff() examples and the send(IP(dst="1.2.3.4") / ICMP()) example for sending a simple packet.

But just as easily, it works on regular datagram sockets — those that you don't need CAP_NET_RAW powers for. Like in the following example.

This example snippet demonstrates how Scapy takes the hassle out of parsing and creating DNS requests and responses. It accepts DNS requests on port 1053, in the form <NAME>.<IPV4>.example.com., returning <IPV4> as an A record.

This example provides a similar feature as the nip.io (or xip.io) service: a wildcard DNS for any IPv4 address. Useful when serving development sites on internal IP addresses.

# Use scapy2.3.1+ from pip (secdev original) or for Python3 use the
# https://github.com/phaethon/scapy Scapy3K version.
#
# Example DNS server that resolves NAME.IPV4.example.com A record
# requests to an A:IPV4 response.
#
# $ dig test.12.34.56.78.example.com -p 1053 @127.0.0.1 +short
# 12.34.56.78

from scapy.all import DNS, DNSQR, DNSRR, dnsqtypes
from socket import AF_INET, SOCK_DGRAM, socket
from traceback import print_exc

sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('0.0.0.0', 1053))

while True:
    request, addr = sock.recvfrom(4096)

    try:
        dns = DNS(request)
        assert dns.opcode == 0, dns.opcode  # QUERY
        assert dnsqtypes[dns[DNSQR].qtype] == 'A', dns[DNSQR].qtype
        query = dns[DNSQR].qname.decode('ascii')  # test.1.2.3.4.example.com.
        head, domain, tld, tail = query.rsplit('.', 3)
        assert domain == 'example' and tld == 'com' and tail == ''
        head = head.split('.', 1)[-1]  # drop leading "prefix." part

        response = DNS(
            id=dns.id, ancount=1, qr=1,
            an=DNSRR(rrname=str(query), type='A', rdata=str(head), ttl=1234))
        print(repr(response))
        sock.sendto(bytes(response), addr)

    except Exception as e:
        print('')
        print_exc()
        print('garbage from {!r}? data {!r}'.format(addr, request))

As you can see, all the protocol stuff is nicely tucked away in pre-existing structures, so you get nice and readable Python. Thanks, Scapy!

python snippet scapy dns