Skip to content

Commit

Permalink
DNS: add the SVCB/HTTPS resource records (#4217)
Browse files Browse the repository at this point in the history
https://www.rfc-editor.org/rfc/rfc9460.html

```
>>> p = dns_resolve('_dns.one.one.one.one', 'SVCB', raw=True)
>>> p.an[0].show()
  rrname    = b'_dns.one.one.one.one.'
  type      = SVCB
  rclass    = IN
  ttl       = 300
  rdlen     = None
  svc_priority= 1
  target_name= b'one.one.one.one.'
  \svc_params\
   |###[ SvcParam ]###
   |  key       = alpn
   |  len       = 6
   |  value     = [b'h3', b'h2']
   |###[ SvcParam ]###
   |  key       = dohpath
   |  len       = 16
   |  value     = b'/dns-query{?dns}'

>>> p.an[1].show()
  rrname    = b'_dns.one.one.one.one.'
  type      = SVCB
  rclass    = IN
  ttl       = 300
  rdlen     = None
  svc_priority= 2
  target_name= b'one.one.one.one.'
  \svc_params\
   |###[ SvcParam ]###
   |  key       = alpn
   |  len       = 4
   |  value     = [b'dot']
```

The patch was also cross-checked with Wireshark:
```
>>> alpn = SvcParam(key='alpn', value=['h3', 'h2'])
>>> ipv4hint = SvcParam(key='ipv4hint', value=['104.16.132.229', '104.16.133.229'])
>>> ipv6hint = SvcParam(key='ipv6hint', value=['2606:4700::6810:84e5', '2606:4700::6810:85e5'])
>>> httpsrr = DNSRRHTTPS(rrname='cloudflare.com', svc_priority=1, ttl=62, target_name='.', svc_params=[alpn, ipv4hint, ipv6hint])
>>> tdecode(Ether()/IP()/UDP()/DNS(qd=[], an=[httpsrr]))
...
            Type: HTTPS (HTTPS Specific Service Endpoints) (65)
            Class: IN (0x0001)
            Time to live: 62 (1 minute, 2 seconds)
            Data length: 61
            SvcPriority: 1
            TargetName: <Root>
            SvcParam: alpn=h3,h2
                SvcParamKey: alpn (1)
                SvcParamValue length: 6
                ALPN length: 2
                ALPN: h3
                ALPN length: 2
                ALPN: h2
            SvcParam: ipv4hint=104.16.132.229,104.16.133.229
                SvcParamKey: ipv4hint (4)
                SvcParamValue length: 8
                IP: 104.16.132.229
                IP: 104.16.133.229
            SvcParam: ipv6hint=2606:4700::6810:84e5,2606:4700::6810:85e5
                SvcParamKey: ipv6hint (6)
                SvcParamValue length: 32
                IP: 2606:4700::6810:84e5
                IP: 2606:4700::6810:85e5

```

This patch was prompted by systemd/systemd#30661 (comment)
and was used to parse SVCB/HTTPS RRs produced by an upstream fuzz target and also build
packets sent by another fuzzer to resolved.
  • Loading branch information
evverx committed Jan 15, 2024
1 parent 084400f commit 51c0754
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 0 deletions.
76 changes: 76 additions & 0 deletions scapy/layers/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,80 @@ class DNSRRNSEC3PARAM(_DNSRRdummy):
StrLenField("salt", "", length_from=lambda pkt: pkt.saltlength) # noqa: E501
]


# RFC 9460 Service Binding and Parameter Specification via the DNS
# https://www.rfc-editor.org/rfc/rfc9460.html


# https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
svc_param_keys = {
0: "mandatory",
1: "alpn",
2: "no-default-alpn",
3: "port",
4: "ipv4hint",
5: "ech",
6: "ipv6hint",
7: "dohpath",
8: "ohttp",
}


class SvcParam(Packet):
name = "SvcParam"
fields_desc = [ShortEnumField("key", 0, svc_param_keys),
FieldLenField("len", None, length_of="value", fmt="H"),
MultipleTypeField(
[
# mandatory
(FieldListField("value", [],
ShortEnumField("", 0, svc_param_keys),
length_from=lambda pkt: pkt.len),
lambda pkt: pkt.key == 0),
# alpn, no-default-alpn
(DNSTextField("value", [],
length_from=lambda pkt: pkt.len),
lambda pkt: pkt.key in (1, 2)),
# port
(ShortField("value", 0),
lambda pkt: pkt.key == 3),
# ipv4hint
(FieldListField("value", [],
IPField("", "0.0.0.0"),
length_from=lambda pkt: pkt.len),
lambda pkt: pkt.key == 4),
# ipv6hint
(FieldListField("value", [],
IP6Field("", "::"),
length_from=lambda pkt: pkt.len),
lambda pkt: pkt.key == 6),
],
StrLenField("value", "",
length_from=lambda pkt:pkt.len))]

def extract_padding(self, p):
return "", p


class DNSRRSVCB(_DNSRRdummy):
name = "DNS SVCB Resource Record"
fields_desc = [DNSStrField("rrname", ""),
ShortEnumField("type", 64, dnstypes),
ShortEnumField("rclass", 1, dnsclasses),
IntField("ttl", 0),
ShortField("rdlen", None),
ShortField("svc_priority", 0),
DNSStrField("target_name", ""),
PacketListField("svc_params", [], SvcParam)]


class DNSRRHTTPS(_DNSRRdummy):
name = "DNS HTTPS Resource Record"
fields_desc = [DNSStrField("rrname", ""),
ShortEnumField("type", 65, dnstypes)
] + DNSRRSVCB.fields_desc[2:]


# RFC 2782 - A DNS RR for specifying the location of services (DNS SRV)


Expand Down Expand Up @@ -976,6 +1050,8 @@ class DNSRRTSIG(_DNSRRdummy):
48: DNSRRDNSKEY, # RFC 4034
50: DNSRRNSEC3, # RFC 5155
51: DNSRRNSEC3PARAM, # RFC 5155
64: DNSRRSVCB, # RFC 9460
65: DNSRRHTTPS, # RFC 9460
250: DNSRRTSIG, # RFC 2845
32769: DNSRRDLV, # RFC 4431
}
Expand Down
72 changes: 72 additions & 0 deletions test/scapy/layers/dns.uts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,78 @@ assert DNSRR(raw(rr)).rdata == []
rr = DNSRR(rrname='scapy', type='TXT', rdata=[])
assert raw(rr) == b

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

b = b'\x00\x00\x00\x04\x00\x01\x00\x06'
p = SvcParam(b)
assert p.key == 0 and p.value == [1, 6]
assert b == raw(SvcParam(key='mandatory', value=['alpn', 'ipv6hint']))

b = b'\x00\x01\x00\x06\x02h3\x02h2'
p = SvcParam(b)
assert p.key == 1 and p.value == [b'h3', b'h2']
assert b == raw(SvcParam(key='alpn', value=['h3', 'h2']))

b = b'\x00\x02\x00\x00'
p = SvcParam(b)
assert p.key == 2 and p.value == []
assert b == raw(SvcParam(key='no-default-alpn'))

b = b'\x00\x03\x00\x02\x04\xd2'
p = SvcParam(b)
assert p.key == 3 and p.value == 1234
assert b == raw(SvcParam(key='port', value=1234))

b = b'\x00\x04\x00\x08\xc0\xa8\x00\x01\xc0\xa8\x00\x02'
p = SvcParam(b)
assert p.key == 4 and p.value == ['192.168.0.1', '192.168.0.2']
assert b == raw(SvcParam(key='ipv4hint', value=['192.168.0.1', '192.168.0.2']))

b = b'\x00\x06\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
p = SvcParam(b)
assert p.key == 6 and p.value == ['2001:db8::1']
assert b == raw(SvcParam(key='ipv6hint', value=['2001:db8::1']))

b = b'\x00\x07\x00\x10/dns-query{?dns}'
p = SvcParam(b)
assert p.key == 7 and p.value == b'/dns-query{?dns}'
assert b == raw(SvcParam(key='dohpath', value=b'/dns-query{?dns}'))

p = DNSRRSVCB()
assert p.rrname == b'.' and p.type == 64 and p.svc_priority == 0 and p.svc_params == []

p = DNSRRHTTPS()
assert p.rrname == b'.' and p.type == 65 and p.svc_priority == 0 and p.svc_params == []

# Real-world SVCB RR
b = b'\x04_dns\x03one\x03one\x03one\x03one\x00\x00@\x00\x01\x00\x00\x01,\x001\x00\x01\x03one\x03one\x03one\x03one\x00\x00\x01\x00\x06\x02h3\x02h2\x00\x07\x00\x10/dns-query{?dns}'
p = DNSRRSVCB(b)
assert p.type == 64 and p.ttl == 300 and p.svc_priority == 1 and p.target_name == b'one.one.one.one.'

alpn = SvcParam(key='alpn', value=['h3', 'h2'])
dohpath = SvcParam(key='dohpath', value=b'/dns-query{?dns}')

assert raw(p.svc_params[0]) == raw(alpn)
assert raw(p.svc_params[1]) == raw(dohpath)

assert b == raw(DNSRRSVCB(rrname='_dns.one.one.one.one', ttl=300, svc_priority=1, target_name='one.one.one.one', svc_params=[alpn, dohpath]))

# Real-world HTTPS RR
b = b'\ncloudflare\x03com\x00\x00A\x00\x01\x00\x00\x00>\x00=\x00\x01\x00\x00\x01\x00\x06\x02h3\x02h2\x00\x04\x00\x08h\x10\x84\xe5h\x10\x85\xe5\x00\x06\x00 &\x06G\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x10\x84\xe5&\x06G\x00\x00\x00\x00\x00\x00\x00\x00\x00h\x10\x85\xe5'

p = DNSRRHTTPS(b)
assert p.type == 65 and p.ttl == 62 and p.svc_priority == 1 and p.target_name == b'.'

alpn = SvcParam(key='alpn', value=['h3', 'h2'])
ipv4hint = SvcParam(key='ipv4hint', value=['104.16.132.229', '104.16.133.229'])
ipv6hint = SvcParam(key='ipv6hint', value=['2606:4700::6810:84e5', '2606:4700::6810:85e5'])

assert raw(p.svc_params[0]) == raw(alpn)
assert raw(p.svc_params[1]) == raw(ipv4hint)
assert raw(p.svc_params[2]) == raw(ipv6hint)

assert b == raw(DNSRRHTTPS(rrname='cloudflare.com', ttl=62, svc_priority=1, target_name='.', svc_params=[alpn, ipv4hint, ipv6hint]))

= DNS - Malformed DNS over TCP message

_old_dbg = conf.debug_dissector
Expand Down

0 comments on commit 51c0754

Please sign in to comment.