Skip to content

Commit 0dd08cd

Browse files
evverxgpotter2
authored andcommitted
DNS: add the DAU, DHU and N3U EDNS(0) options
These options are used for signaling cryptographic algorithm understanding in DNS Security Extensions (DNSSEC): https://www.rfc-editor.org/rfc/rfc6975.html The patch was cross-checked with Wireshark: ```sh >>> dau = EDNS0DAU(alg_code=['RSA/SHA-256', 'RSA/SHA-512']) >>> dhu = EDNS0DHU(alg_code=['SHA-1', 'SHA-256', 'SHA-384']) >>> n3u = EDNS0N3U(alg_code=['SHA-1']) >>> tdecode(Ether()/IP()/UDP()/DNS(ar=[DNSRROPT(rdata=[dau, dhu, n3u])])) ... Option: DAU - DNSSEC Algorithm Understood (RFC6975) Option Code: DAU - DNSSEC Algorithm Understood (RFC6975) (5) Option Length: 2 Option Data: 080a DAU: RSA/SHA-256 (8) DAU: RSA/SHA-512 (10) Option: DHU - DS Hash Understood (RFC6975) Option Code: DHU - DS Hash Understood (RFC6975) (6) Option Length: 3 Option Data: 010204 DHU: SHA-1 (1) DHU: SHA-256 (2) DHU: SHA-384 (4) Option: N3U - NSEC3 Hash Understood (RFC6975) Option Code: N3U - NSEC3 Hash Understood (RFC6975) (7) Option Length: 1 Option Data: 01 N3U: SHA-1 (1) ```
1 parent a562a06 commit 0dd08cd

File tree

2 files changed

+130
-32
lines changed

2 files changed

+130
-32
lines changed

scapy/layers/dns.py

Lines changed: 68 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
ConditionalField,
3535
Field,
3636
FieldLenField,
37+
FieldListField,
3738
FlagsField,
3839
I,
3940
IP6Field,
@@ -89,6 +90,22 @@
8990
dnsclasses = {1: 'IN', 2: 'CS', 3: 'CH', 4: 'HS', 255: 'ANY'}
9091

9192

93+
# 09/2013 from http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml # noqa: E501
94+
dnssecalgotypes = {0: "Reserved", 1: "RSA/MD5", 2: "Diffie-Hellman", 3: "DSA/SHA-1", # noqa: E501
95+
4: "Reserved", 5: "RSA/SHA-1", 6: "DSA-NSEC3-SHA1",
96+
7: "RSASHA1-NSEC3-SHA1", 8: "RSA/SHA-256", 9: "Reserved",
97+
10: "RSA/SHA-512", 11: "Reserved", 12: "GOST R 34.10-2001",
98+
13: "ECDSA Curve P-256 with SHA-256", 14: "ECDSA Curve P-384 with SHA-384", # noqa: E501
99+
252: "Reserved for Indirect Keys", 253: "Private algorithms - domain name", # noqa: E501
100+
254: "Private algorithms - OID", 255: "Reserved"}
101+
102+
# 09/2013 from http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
103+
dnssecdigesttypes = {0: "Reserved", 1: "SHA-1", 2: "SHA-256", 3: "GOST R 34.11-94", 4: "SHA-384"} # noqa: E501
104+
105+
# 12/2023 from https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml # noqa: E501
106+
dnssecnsec3algotypes = {0: "Reserved", 1: "SHA-1"}
107+
108+
92109
def dns_get_str(s, full=None, _ignore_compression=False):
93110
"""This function decompresses a string s, starting
94111
from the given pointer.
@@ -387,21 +404,25 @@ def i2m(self, pkt, s):
387404
# RFC 2671 - Extension Mechanisms for DNS (EDNS0)
388405

389406
edns0types = {0: "Reserved", 1: "LLQ", 2: "UL", 3: "NSID", 4: "Reserved",
390-
5: "PING", 8: "edns-client-subnet", 10: "COOKIE",
407+
5: "DAU", 6: "DHU", 7: "N3U", 8: "edns-client-subnet", 10: "COOKIE",
391408
15: "Extended DNS Error"}
392409

393410

394-
class EDNS0TLV(Packet):
411+
class _EDNS0Dummy(Packet):
412+
name = "Dummy class that implements extract_padding()"
413+
414+
def extract_padding(self, p):
415+
# type: (bytes) -> Tuple[bytes, Optional[bytes]]
416+
return "", p
417+
418+
419+
class EDNS0TLV(_EDNS0Dummy):
395420
name = "DNS EDNS0 TLV"
396421
fields_desc = [ShortEnumField("optcode", 0, edns0types),
397422
FieldLenField("optlen", None, "optdata", fmt="H"),
398423
StrLenField("optdata", "",
399424
length_from=lambda pkt: pkt.optlen)]
400425

401-
def extract_padding(self, p):
402-
# type: (bytes) -> Tuple[bytes, Optional[bytes]]
403-
return "", p
404-
405426
@classmethod
406427
def dispatch_hook(cls, _pkt=None, *args, **kargs):
407428
# type: (Optional[bytes], *Any, **Any) -> Type[Packet]
@@ -410,11 +431,7 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs):
410431
if len(_pkt) < 2:
411432
return Raw
412433
edns0type = struct.unpack("!H", _pkt[:2])[0]
413-
if edns0type == 8:
414-
return EDNS0ClientSubnet
415-
if edns0type == 15:
416-
return EDNS0ExtendedDNSError
417-
return EDNS0TLV
434+
return EDNS0OPT_DISPATCHER.get(edns0type, EDNS0TLV)
418435

419436

420437
class DNSRROPT(Packet):
@@ -432,6 +449,36 @@ class DNSRROPT(Packet):
432449
length_from=lambda pkt: pkt.rdlen)]
433450

434451

452+
# RFC 6975 - Signaling Cryptographic Algorithm Understanding in
453+
# DNS Security Extensions (DNSSEC)
454+
455+
class EDNS0DAU(_EDNS0Dummy):
456+
name = "DNSSEC Algorithm Understood (DAU)"
457+
fields_desc = [ShortEnumField("optcode", 5, edns0types),
458+
FieldLenField("optlen", None, count_of="alg_code", fmt="H"),
459+
FieldListField("alg_code", None,
460+
ByteEnumField("", 0, dnssecalgotypes),
461+
count_from=lambda pkt:pkt.optlen)]
462+
463+
464+
class EDNS0DHU(_EDNS0Dummy):
465+
name = "DS Hash Understood (DHU)"
466+
fields_desc = [ShortEnumField("optcode", 6, edns0types),
467+
FieldLenField("optlen", None, count_of="alg_code", fmt="H"),
468+
FieldListField("alg_code", None,
469+
ByteEnumField("", 0, dnssecdigesttypes),
470+
count_from=lambda pkt:pkt.optlen)]
471+
472+
473+
class EDNS0N3U(_EDNS0Dummy):
474+
name = "NSEC3 Hash Understood (N3U)"
475+
fields_desc = [ShortEnumField("optcode", 7, edns0types),
476+
FieldLenField("optlen", None, count_of="alg_code", fmt="H"),
477+
FieldListField("alg_code", None,
478+
ByteEnumField("", 0, dnssecnsec3algotypes),
479+
count_from=lambda pkt:pkt.optlen)]
480+
481+
435482
# RFC 7871 - Client Subnet in DNS Queries
436483

437484
class ClientSubnetv4(StrLenField):
@@ -489,7 +536,7 @@ class ClientSubnetv6(ClientSubnetv4):
489536
af_default = b"\x20" # 2000::
490537

491538

492-
class EDNS0ClientSubnet(Packet):
539+
class EDNS0ClientSubnet(_EDNS0Dummy):
493540
name = "DNS EDNS0 Client Subnet"
494541
fields_desc = [ShortEnumField("optcode", 8, edns0types),
495542
FieldLenField("optlen", None, "address", fmt="H",
@@ -510,9 +557,6 @@ class EDNS0ClientSubnet(Packet):
510557
ClientSubnetv4("address", "192.168.0.0",
511558
length_from=lambda p: p.source_plen))]
512559

513-
def extract_padding(self, p):
514-
return "", p
515-
516560

517561
# RFC 8914 - Extended DNS Errors
518562

@@ -552,7 +596,7 @@ def extract_padding(self, p):
552596

553597

554598
# https://www.rfc-editor.org/rfc/rfc8914.html
555-
class EDNS0ExtendedDNSError(Packet):
599+
class EDNS0ExtendedDNSError(_EDNS0Dummy):
556600
name = "DNS EDNS0 Extended DNS Error"
557601
fields_desc = [ShortEnumField("optcode", 15, edns0types),
558602
FieldLenField("optlen", None, length_of="extra_text", fmt="!H",
@@ -561,25 +605,17 @@ class EDNS0ExtendedDNSError(Packet):
561605
StrLenField("extra_text", "",
562606
length_from=lambda pkt: pkt.optlen - 2)]
563607

564-
def extract_padding(self, p):
565-
return "", p
566-
567-
568-
# RFC 4034 - Resource Records for the DNS Security Extensions
569-
570608

571-
# 09/2013 from http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml # noqa: E501
572-
dnssecalgotypes = {0: "Reserved", 1: "RSA/MD5", 2: "Diffie-Hellman", 3: "DSA/SHA-1", # noqa: E501
573-
4: "Reserved", 5: "RSA/SHA-1", 6: "DSA-NSEC3-SHA1",
574-
7: "RSASHA1-NSEC3-SHA1", 8: "RSA/SHA-256", 9: "Reserved",
575-
10: "RSA/SHA-512", 11: "Reserved", 12: "GOST R 34.10-2001",
576-
13: "ECDSA Curve P-256 with SHA-256", 14: "ECDSA Curve P-384 with SHA-384", # noqa: E501
577-
252: "Reserved for Indirect Keys", 253: "Private algorithms - domain name", # noqa: E501
578-
254: "Private algorithms - OID", 255: "Reserved"}
609+
EDNS0OPT_DISPATCHER = {
610+
5: EDNS0DAU,
611+
6: EDNS0DHU,
612+
7: EDNS0N3U,
613+
8: EDNS0ClientSubnet,
614+
15: EDNS0ExtendedDNSError,
615+
}
579616

580-
# 09/2013 from http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml
581-
dnssecdigesttypes = {0: "Reserved", 1: "SHA-1", 2: "SHA-256", 3: "GOST R 34.11-94", 4: "SHA-384"} # noqa: E501
582617

618+
# RFC 4034 - Resource Records for the DNS Security Extensions
583619

584620
def bitmap2RRlist(bitmap):
585621
"""

test/scapy/layers/dns_edns0.uts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,63 @@ def _test():
7878
retry_test(_test)
7979

8080

81+
+ EDNS0 - DAU
82+
83+
= Basic instantiation & dissection
84+
85+
b = b'\x00\x05\x00\x00'
86+
87+
p = EDNS0DAU()
88+
assert raw(p) == b
89+
90+
p = EDNS0DAU(b)
91+
assert p.optcode == 5 and p.optlen == 0 and p.alg_code == []
92+
93+
b = raw(EDNS0DAU(alg_code=['RSA/SHA-256', 'RSA/SHA-512']))
94+
95+
p = EDNS0DAU(b)
96+
repr(p)
97+
assert p.optcode == 5 and p.optlen == 2 and p.alg_code == [8, 10]
98+
99+
100+
+ EDNS0 - DHU
101+
102+
= Basic instantiation & dissection
103+
104+
b = b'\x00\x06\x00\x00'
105+
106+
p = EDNS0DHU()
107+
assert raw(p) == b
108+
109+
p = EDNS0DHU(b)
110+
assert p.optcode == 6 and p.optlen == 0 and p.alg_code == []
111+
112+
b = raw(EDNS0DHU(alg_code=['SHA-1', 'SHA-256', 'SHA-384']))
113+
114+
p = EDNS0DHU(b)
115+
repr(p)
116+
assert p.optcode == 6 and p.optlen == 3 and p.alg_code == [1, 2, 4]
117+
118+
119+
+ EDNS0 - N3U
120+
121+
= Basic instantiation & dissection
122+
123+
b = b'\x00\x07\x00\x00'
124+
125+
p = EDNS0N3U()
126+
assert raw(p) == b
127+
128+
p = EDNS0N3U(b)
129+
assert p.optcode == 7 and p.optlen == 0 and p.alg_code == []
130+
131+
b = raw(EDNS0N3U(alg_code=['SHA-1']))
132+
133+
p = EDNS0N3U(b)
134+
repr(p)
135+
assert p.optcode == 7 and p.optlen == 1 and p.alg_code == [1]
136+
137+
81138
+ EDNS0 - Client Subnet
82139

83140
= Basic instantiation & dissection
@@ -123,3 +180,8 @@ assert p.info_code == 6 and p.optlen == 45 and p.extra_text == b'proof of non-ex
123180
p = DNSRROPT(raw(DNSRROPT(rdata=[EDNS0ExtendedDNSError(), EDNS0ClientSubnet(), EDNS0TLV()])))
124181
assert len(p.rdata) == 3
125182
assert all(Raw not in opt for opt in p.rdata)
183+
184+
for opt_class in EDNS0OPT_DISPATCHER.values():
185+
p = DNSRROPT(raw(DNSRROPT(rdata=[EDNS0TLV(), opt_class(), opt_class()])))
186+
assert len(p.rdata) == 3
187+
assert all(Raw not in opt for opt in p.rdata)

0 commit comments

Comments
 (0)