Skip to content

Commit 51c0754

Browse files
authored
DNS: add the SVCB/HTTPS resource records (#4217)
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.
1 parent 084400f commit 51c0754

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

scapy/layers/dns.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,80 @@ class DNSRRNSEC3PARAM(_DNSRRdummy):
887887
StrLenField("salt", "", length_from=lambda pkt: pkt.saltlength) # noqa: E501
888888
]
889889

890+
891+
# RFC 9460 Service Binding and Parameter Specification via the DNS
892+
# https://www.rfc-editor.org/rfc/rfc9460.html
893+
894+
895+
# https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
896+
svc_param_keys = {
897+
0: "mandatory",
898+
1: "alpn",
899+
2: "no-default-alpn",
900+
3: "port",
901+
4: "ipv4hint",
902+
5: "ech",
903+
6: "ipv6hint",
904+
7: "dohpath",
905+
8: "ohttp",
906+
}
907+
908+
909+
class SvcParam(Packet):
910+
name = "SvcParam"
911+
fields_desc = [ShortEnumField("key", 0, svc_param_keys),
912+
FieldLenField("len", None, length_of="value", fmt="H"),
913+
MultipleTypeField(
914+
[
915+
# mandatory
916+
(FieldListField("value", [],
917+
ShortEnumField("", 0, svc_param_keys),
918+
length_from=lambda pkt: pkt.len),
919+
lambda pkt: pkt.key == 0),
920+
# alpn, no-default-alpn
921+
(DNSTextField("value", [],
922+
length_from=lambda pkt: pkt.len),
923+
lambda pkt: pkt.key in (1, 2)),
924+
# port
925+
(ShortField("value", 0),
926+
lambda pkt: pkt.key == 3),
927+
# ipv4hint
928+
(FieldListField("value", [],
929+
IPField("", "0.0.0.0"),
930+
length_from=lambda pkt: pkt.len),
931+
lambda pkt: pkt.key == 4),
932+
# ipv6hint
933+
(FieldListField("value", [],
934+
IP6Field("", "::"),
935+
length_from=lambda pkt: pkt.len),
936+
lambda pkt: pkt.key == 6),
937+
],
938+
StrLenField("value", "",
939+
length_from=lambda pkt:pkt.len))]
940+
941+
def extract_padding(self, p):
942+
return "", p
943+
944+
945+
class DNSRRSVCB(_DNSRRdummy):
946+
name = "DNS SVCB Resource Record"
947+
fields_desc = [DNSStrField("rrname", ""),
948+
ShortEnumField("type", 64, dnstypes),
949+
ShortEnumField("rclass", 1, dnsclasses),
950+
IntField("ttl", 0),
951+
ShortField("rdlen", None),
952+
ShortField("svc_priority", 0),
953+
DNSStrField("target_name", ""),
954+
PacketListField("svc_params", [], SvcParam)]
955+
956+
957+
class DNSRRHTTPS(_DNSRRdummy):
958+
name = "DNS HTTPS Resource Record"
959+
fields_desc = [DNSStrField("rrname", ""),
960+
ShortEnumField("type", 65, dnstypes)
961+
] + DNSRRSVCB.fields_desc[2:]
962+
963+
890964
# RFC 2782 - A DNS RR for specifying the location of services (DNS SRV)
891965

892966

@@ -976,6 +1050,8 @@ class DNSRRTSIG(_DNSRRdummy):
9761050
48: DNSRRDNSKEY, # RFC 4034
9771051
50: DNSRRNSEC3, # RFC 5155
9781052
51: DNSRRNSEC3PARAM, # RFC 5155
1053+
64: DNSRRSVCB, # RFC 9460
1054+
65: DNSRRHTTPS, # RFC 9460
9791055
250: DNSRRTSIG, # RFC 2845
9801056
32769: DNSRRDLV, # RFC 4431
9811057
}

test/scapy/layers/dns.uts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,78 @@ assert DNSRR(raw(rr)).rdata == []
243243
rr = DNSRR(rrname='scapy', type='TXT', rdata=[])
244244
assert raw(rr) == b
245245

246+
= DNS record type 64, 65 (SVCB, HTTPS)
247+
248+
b = b'\x00\x00\x00\x04\x00\x01\x00\x06'
249+
p = SvcParam(b)
250+
assert p.key == 0 and p.value == [1, 6]
251+
assert b == raw(SvcParam(key='mandatory', value=['alpn', 'ipv6hint']))
252+
253+
b = b'\x00\x01\x00\x06\x02h3\x02h2'
254+
p = SvcParam(b)
255+
assert p.key == 1 and p.value == [b'h3', b'h2']
256+
assert b == raw(SvcParam(key='alpn', value=['h3', 'h2']))
257+
258+
b = b'\x00\x02\x00\x00'
259+
p = SvcParam(b)
260+
assert p.key == 2 and p.value == []
261+
assert b == raw(SvcParam(key='no-default-alpn'))
262+
263+
b = b'\x00\x03\x00\x02\x04\xd2'
264+
p = SvcParam(b)
265+
assert p.key == 3 and p.value == 1234
266+
assert b == raw(SvcParam(key='port', value=1234))
267+
268+
b = b'\x00\x04\x00\x08\xc0\xa8\x00\x01\xc0\xa8\x00\x02'
269+
p = SvcParam(b)
270+
assert p.key == 4 and p.value == ['192.168.0.1', '192.168.0.2']
271+
assert b == raw(SvcParam(key='ipv4hint', value=['192.168.0.1', '192.168.0.2']))
272+
273+
b = b'\x00\x06\x00\x10 \x01\r\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
274+
p = SvcParam(b)
275+
assert p.key == 6 and p.value == ['2001:db8::1']
276+
assert b == raw(SvcParam(key='ipv6hint', value=['2001:db8::1']))
277+
278+
b = b'\x00\x07\x00\x10/dns-query{?dns}'
279+
p = SvcParam(b)
280+
assert p.key == 7 and p.value == b'/dns-query{?dns}'
281+
assert b == raw(SvcParam(key='dohpath', value=b'/dns-query{?dns}'))
282+
283+
p = DNSRRSVCB()
284+
assert p.rrname == b'.' and p.type == 64 and p.svc_priority == 0 and p.svc_params == []
285+
286+
p = DNSRRHTTPS()
287+
assert p.rrname == b'.' and p.type == 65 and p.svc_priority == 0 and p.svc_params == []
288+
289+
# Real-world SVCB RR
290+
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}'
291+
p = DNSRRSVCB(b)
292+
assert p.type == 64 and p.ttl == 300 and p.svc_priority == 1 and p.target_name == b'one.one.one.one.'
293+
294+
alpn = SvcParam(key='alpn', value=['h3', 'h2'])
295+
dohpath = SvcParam(key='dohpath', value=b'/dns-query{?dns}')
296+
297+
assert raw(p.svc_params[0]) == raw(alpn)
298+
assert raw(p.svc_params[1]) == raw(dohpath)
299+
300+
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]))
301+
302+
# Real-world HTTPS RR
303+
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'
304+
305+
p = DNSRRHTTPS(b)
306+
assert p.type == 65 and p.ttl == 62 and p.svc_priority == 1 and p.target_name == b'.'
307+
308+
alpn = SvcParam(key='alpn', value=['h3', 'h2'])
309+
ipv4hint = SvcParam(key='ipv4hint', value=['104.16.132.229', '104.16.133.229'])
310+
ipv6hint = SvcParam(key='ipv6hint', value=['2606:4700::6810:84e5', '2606:4700::6810:85e5'])
311+
312+
assert raw(p.svc_params[0]) == raw(alpn)
313+
assert raw(p.svc_params[1]) == raw(ipv4hint)
314+
assert raw(p.svc_params[2]) == raw(ipv6hint)
315+
316+
assert b == raw(DNSRRHTTPS(rrname='cloudflare.com', ttl=62, svc_priority=1, target_name='.', svc_params=[alpn, ipv4hint, ipv6hint]))
317+
246318
= DNS - Malformed DNS over TCP message
247319

248320
_old_dbg = conf.debug_dissector

0 commit comments

Comments
 (0)