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
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:
- It fills the in-flight window.
- It sends the packets from the queues that changed.
- It collects the replies.
- 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 |
