GitHub - c3l3si4n/pugdns: An experimental high-performance DNS query bruteforce tool built with AF_XDP for extremely fast and accurate bulk DNS lookups.

GitHub

5 min read Original article ↗

pugDNS logo

A bulk DNS resolver for Linux.

pugDNS sends and receives DNS packets with AF_XDP and eBPF/XDP. The program is written in the Rust language.

Warning

Use pugDNS only on the networks that you have permission to test. A bulk DNS scan sends a large quantity of traffic. This traffic can cause damage to a network.

What pugDNS does

pugDNS makes the Ethernet, IPv4, UDP, and DNS packets in user space. It then puts the packets into the AF_XDP rings. Thus the packets do not go through the usual Linux UDP socket path. An eBPF/XDP program collects the replies. A query engine then examines each reply.

pugDNS resolves millions of names in seconds. A test at 1 Gbit/s gave these results:

Measurement Result
Speed 115,593 names each second
Replies received 100 %
AF_XDP transmission drops 0

Before you start

You must have these items:

  • A Linux host with a network interface that operates with AF_XDP.
  • Root permission on that host.
  • A file of DNS names. Put one name on each line.
  • A file of resolver IP addresses. Put one address on each line.

The resolver file is necessary only for a recursive scan.

Input files

# domains.txt
www.example.com
api.example.com

# resolvers.txt
1.1.1.1
8.8.8.8

Procedure 1. Check the host

Do this check before you attach the XDP program. The check finds the problems in the configuration of the host.

sudo ./pugdns --doctor \
  --interface eth0 \
  --domains domains.txt \
  --nameservers resolvers.txt \
  --output results.jsonl \
  --metrics-file metrics.json

Procedure 2. Do a recursive scan

A recursive scan sends the queries to the resolvers in your resolver file.

sudo ./pugdns \
  --interface eth0 \
  --domains domains.txt \
  --nameservers resolvers.txt \
  --query-type AAAA \
  --output results.jsonl \
  --metrics-file metrics.json

Procedure 3. Do an iterative scan

An iterative scan starts at the DNS root. You do not need a resolver file.

sudo ./pugdns \
  --resolve-mode iterative \
  --interface eth0 \
  --domains domains.txt \
  --output results.jsonl \
  --metrics-file metrics.json

Query types

A recursive scan uses the A record type as the default. To get the IPv6 addresses, set --query-type AAAA. An iterative scan operates only with the A record type.

How pugDNS works

flowchart LR
    A[Domain input] --> B[Normalize and schedule]
    B --> C[DNS wire encoder]
    C --> D[Ethernet/IP/UDP template]
    D --> E[AF_XDP TX rings]
    E --> F[NIC]
    F --> G[eBPF/XDP DNS gate]
    G -->|DNS traffic| H[XSK map and AF_XDP RX rings]
    G -->|other traffic| I[Linux network stack]
    H --> J[Port and DNS ID match]
    J --> K[Question and source check]
    K --> L[DNS parser]
    L --> M[JSONL or names writer]
    J --> N[Retry deadlines]
    N --> D
Loading

1. It encodes one time and changes only the necessary bytes

pugDNS does not build a new packet for each query. A packet template holds the Ethernet, IPv4, and UDP header bytes. For each query, pugDNS changes only these values:

  • the destination IP address
  • the source port
  • the IP identifier
  • the lengths
  • the checksums

Many scans use the names below one apex, such as word.target.com. For this work, pugDNS keeps target.com in DNS wire format. It then writes only the first label and the header. Other names go to the general encoder.

2. It sends and receives in batches

pugDNS puts the frames into the AF_XDP transmission rings. It uses each available queue in turn and collects the queue kicks together. It empties the receive rings in batches. pugDNS does not make a socket or a thread for each name.

Note: AF_XDP does not give zero-copy operation on all drivers. The increase in speed comes from the ring data path, the re-used buffers, and the smaller quantity of kernel work.

3. It filters early but does not take the traffic of the host

The eBPF/XDP program examines the Ethernet, IPv4, UDP, and DNS lengths. It accepts the UDP replies from source port 53 that go to the port window of pugDNS. It sends these packets to the XSK map of the queue. All other packets continue through the usual Linux network stack.

The Rust code then examines the source resolver, the destination port, the DNS identifier, the question name, and the query type. Thus a late or incorrect packet cannot complete the wrong query.

4. It keeps the scheduler small

The recursive mode uses a fixed set of pending slots. It finds each slot by the DNS identifier. It makes the query identity from the DNS identifier and a source port. The main loop does these steps in a cycle:

  1. It fills the in-flight window.
  2. It sends the packets from the queues that changed.
  3. It collects the replies.
  4. It sends again only the queries whose deadline is complete.

5. It parses only the necessary data

A writer thread makes the output file. The thread uses a 64 KiB buffer. You can select a full JSONL record or a list of names only.

Output

Mode Content
JSONL One structured record on each line
Names One name on each line

The metrics file holds the counters for the domains, the packets, the bytes, the retries, the drops, the CPU, the memory, the cache, and the XDP layer.

Terms

Term Meaning
AF_XDP A Linux socket type that moves the packets through the rings
eBPF/XDP A program that examines the packets in the network driver
Apex The registrable domain, such as example.com
In-flight window The maximum quantity of queries that have no reply
JSONL A file that holds one JSON record on each line