Comment 8 for bug 1981794

Revision history for this message
Reuben Lifshay (computator) wrote (last edit ):

I have tested version 2.86-1.1ubuntu0.2 and that seems to have fixed it.

The bug only appears when dnsmasq is forwarding responses in --strict-order mode and there's a duplicate request packet received before dnsmasq has replied to the original request packet. This is a procedure I came up with to reproduce and test that specific circumstance:

I am using a network namespace to simplify networking, avoid port conflicts, and maintain tcpdump's protocol decoding. This test process requires running and interacting with multiple processes at once, and so it's easiest to just open multiple terminal windows. I create the net namespace with the first terminal, and then join the created namespace with additional terminals.

To create the NS in the first terminal:

$ sudo unshare --net
# echo $$
> 12123
# ip addr add 127.0.0.1/8 dev lo
# ip link set up dev lo
#

The `echo $$` gives the shell PID (12123 in this example), which is also the namespace PID. This can optionally be verified with lsns:

# lsns -t net
> NS TYPE NPROCS PID USER NETNSID NSFS COMMAND
> 1234567890 net 2 12123 root unassigned -bash

To join the NS from subsequent terminals:

$ sudo nsenter -n -t 12123
#

Test process:

The first process to run is a fake upstream server for dnsmasq to forward responses from. We will run this on an alternate port (5353) and have it respond to requests for a test domain.

# dnsmasq -d -p 5353 --no-resolv -A /example.com/192.0.2.1

The second process is the main dnsmasq process we want to actually test. It uses --strict-order and uses our fake upstream to answer all requests. We will also use systemd-run to limit dnsmasq's cpu resources to slow it down enough to more easily queue up duplicate requests.

# systemd-run --scope -p CPUQuota=1% -- dnsmasq -d --strict-order --no-resolv -S 127.0.0.1#5353

The third process is just tcpdump to monitor requests to and responses from dnsmasq. I have piped the output through grep to make it easier to analyze the results for refused packets, but you can also leave that off and search the output manually.

# tcpdump -ln udp port 53 | grep --line-buffered -iC 5 refused

The final process is using `scapy` to generate dns packets with a duplicated request id. Scapy can be installed from the default repositories as `python3-scapy`. Scapy has to be switched to raw sockets mode to be able to work over the loopback interface before we can start sending dns packets.

# scapy
>>> conf.L3socket = L3RawSocket

Now we can send some dns packets with scapy. All the packets should have the same request id in them, so we set it to a static number (we picked 47 in this example). We have also set it to send 50 packets, which is usually enough to get a few packets queued before dnsmasq can process the previous ones.

>>> send(IP(dst='127.0.0.1')/UDP()/DNS(id=47,qd=DNSQR(qname='example.com')),count=50)

You can also choose to send packets continuously until cancelled with Ctrl-C.

>>> send(IP(dst='127.0.0.1')/UDP()/DNS(id=47,qd=DNSQR(qname='example.com')),loop=True)

We can now check tcpdump for refused packets, which should show up anytime after there are two or more requests in a row without a response in between them. If you don't see any send another batch or two of packets. Here's some example output:

22:10:39.518439 IP 127.0.0.1.53 > 127.0.0.1.53: 47+ A? example.com. (29)
22:10:39.518896 IP 127.0.0.1.53 > 127.0.0.1.53: 47+ A? example.com. (29)
22:10:39.519366 IP 127.0.0.1.53 > 127.0.0.1.53: 47+ A? example.com. (29)
22:10:39.519824 IP 127.0.0.1.53 > 127.0.0.1.53: 47+ A? example.com. (29)
22:10:39.520290 IP 127.0.0.1.53 > 127.0.0.1.53: 47+ A? example.com. (29)
22:10:39.524495 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524504 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524512 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524534 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524547 IP 127.0.0.1.53 > 127.0.0.1.53: 47* 1/0/0 A 192.0.2.1 (45)
22:10:39.524575 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524583 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524590 IP 127.0.0.1.53 > 127.0.0.1.53: 47* 1/0/0 A 192.0.2.1 (45)
22:10:39.524612 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524620 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524628 IP 127.0.0.1.53 > 127.0.0.1.53: 47* 1/0/0 A 192.0.2.1 (45)
22:10:39.524648 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524657 IP 127.0.0.1.53 > 127.0.0.1.53: 47 Refused 0/0/0 (29)
22:10:39.524664 IP 127.0.0.1.53 > 127.0.0.1.53: 47* 1/0/0 A 192.0.2.1 (45)
22:10:40.537105 IP 127.0.0.1.53 > 127.0.0.1.53: 47+ A? example.com. (29)
22:10:40.537236 IP 127.0.0.1.53 > 127.0.0.1.53: 47* 1/0/0 A 192.0.2.1 (45)
22:10:40.537591 IP 127.0.0.1.53 > 127.0.0.1.53: 47+ A? example.com. (29)
22:10:40.537636 IP 127.0.0.1.53 > 127.0.0.1.53: 47* 1/0/0 A 192.0.2.1 (45)

If you then install the patched package and restart the dnsmasq process doing the forwarding you should no longer be seeing any refused packets.