Skip to content

Commit

Permalink
DNS: support DNAME RRs in DNSRRs (#4249)
Browse files Browse the repository at this point in the history
According to https://www.rfc-editor.org/rfc/rfc6672.html#section-2.1
```
The DNAME RR has mnemonic DNAME and type code 39 (decimal).
Its RDATA is comprised of a single field, <target>, which contains a
fully qualified domain name that MUST be sent in uncompressed form
```
Even though the RFC says it MUST NOT be compressed `dns_compress`
compresses it intentionally to make it easier to test DNS-related
software that should be able to handle compressed and uncompressed
DNAMEs regradless of what the RFC says.

This patch makes it possible to work with FQDNs instead of the wire
format. It was prompted by
systemd/systemd#30392 where recursive DNAMEs
were initially built using FQDNs (by analogy with CNAMEs) and were
rejected because they weren't valid.

The patch was also cross-checked with Wireshark:
```
>>> tdecode(Ether()/IP()/UDP()/DNS(qd=[], an=[DNSRR(rrname='local', type='DNAME', rdata='local')]))
...
    Answers
        local: type DNAME, class IN, dname local
            Name: local
            Type: DNAME (39)
            Class: IN (0x0001)
            Time to live: 0 (0 seconds)
            Data length: 7
            Dname: local
```
  • Loading branch information
evverx committed Feb 16, 2024
1 parent cb7134c commit 3056f2e
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 3 deletions.
6 changes: 3 additions & 3 deletions scapy/layers/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def field_gen(dns_pkt):
for field in current.fields_desc:
if isinstance(field, DNSStrField) or \
(isinstance(field, MultipleTypeField) and
current.type in [2, 3, 4, 5, 12, 15]):
current.type in [2, 3, 4, 5, 12, 15, 39]):
# Get the associated data and store it accordingly # noqa: E501
dat = current.getfieldval(field.name)
yield current, field.name, dat
Expand Down Expand Up @@ -1085,10 +1085,10 @@ class DNSRR(Packet):
# AAAA
(IP6Field("rdata", "::"),
lambda pkt: pkt.type == 28),
# NS, MD, MF, CNAME, PTR
# NS, MD, MF, CNAME, PTR, DNAME
(DNSStrField("rdata", "",
length_from=lambda pkt: pkt.rdlen),
lambda pkt: pkt.type in [2, 3, 4, 5, 12]),
lambda pkt: pkt.type in [2, 3, 4, 5, 12, 39]),
# TEXT
(DNSTextField("rdata", [""],
length_from=lambda pkt: pkt.rdlen),
Expand Down
32 changes: 32 additions & 0 deletions test/scapy/layers/dns.uts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,38 @@ assert DNSRR(raw(rr)).rdata == []
rr = DNSRR(rrname='scapy', type='TXT', rdata=[])
assert raw(rr) == b

= DNS record type 39 (DNAME)

b = b'\x05local\x00\x00\x27\x00\x01\x00\x00\x00\x00\x00\x07\x05local\x00'

p = DNSRR(b)
assert p.rrname == b'local.' and p.type == 39 and p.rdata == b'local.'

p = DNSRR(rrname=b'local', type='DNAME', rdata='local')
assert raw(p) == b

# Even though according to https://datatracker.ietf.org/doc/html/rfc6672#section-2.5
# The DNAME RDATA target name MUST NOT be sent out in compressed form
# dns_compress compresses it intentionally to make it easier to test
# DNS-related software that should be able to handle compressed and
# uncompressed DNAMEs anyway regardless of what the RFC says.

# Make sure it isn't compressed by default
p = DNS(qd=[], an=[DNSRR(rrname='local', type='DNAME', rdata='local')])
assert raw(p).endswith(b'\x07\x05local\x00')

# Make sure it can parse uncompressed DNAMEs
rr = DNS(raw(p)).an[0]
assert rr.rrname == b'local.' and rr.type == 39 and rr.rdata == b'local.'

# Make sure dns_compress compresses DNAME RDATA
cp = dns_compress(p)
assert raw(cp).endswith(b'\x02\xc0\x0c')

# Make sure it can parse compressed DNAMEs
rr = DNS(raw(cp)).an[0]
assert rr.rrname == b'local.' and rr.type == 39 and rr.rdata == b'local.'

= DNS record type 64, 65 (SVCB, HTTPS)

b = b'\x00\x00\x00\x04\x00\x01\x00\x06'
Expand Down

0 comments on commit 3056f2e

Please sign in to comment.