浏览代码

chore(api),feat(api): dnspython upgrade, support L32/L64/LP/NID

Peter Thomassen 3 年之前
父节点
当前提交
f57e5db9ab

+ 10 - 38
api/desecapi/dns.py

@@ -6,7 +6,7 @@ from ipaddress import IPv6Address
 import dns
 import dns.name
 import dns.rdtypes.txtbase, dns.rdtypes.svcbbase
-import dns.rdtypes.ANY.CDS, dns.rdtypes.ANY.DLV, dns.rdtypes.ANY.DS, dns.rdtypes.ANY.MX, dns.rdtypes.ANY.NS
+import dns.rdtypes.ANY.CERT, dns.rdtypes.ANY.MX, dns.rdtypes.ANY.NS
 import dns.rdtypes.IN.AAAA, dns.rdtypes.IN.SRV
 
 
@@ -25,6 +25,15 @@ dns.rdtypes.svcbbase.MandatoryParam.to_text = _strip_quotes_decorator(dns.rdtype
 dns.rdtypes.svcbbase.PortParam.to_text = _strip_quotes_decorator(dns.rdtypes.svcbbase.PortParam.to_text)
 
 
+@dns.immutable.immutable
+class CERT(dns.rdtypes.ANY.CERT.CERT):
+    def to_text(self, origin=None, relativize=True, **kw):
+        certificate_type = str(self.certificate_type)  # upstream implementation calls _ctype_to_text
+        return "%s %d %s %s" % (certificate_type, self.key_tag,
+                                dns.dnssec.algorithm_to_text(self.algorithm),
+                                dns.rdata._base64ify(self.certificate, **kw))
+
+
 @dns.immutable.immutable
 class AAAA(dns.rdtypes.IN.AAAA.AAAA):
     def to_text(self, origin=None, relativize=True, **kw):
@@ -69,43 +78,6 @@ class LongQuotedTXT(dns.rdtypes.txtbase.TXTBase):
                 file.write(s)
 
 
-# TODO remove when https://github.com/rthalley/dnspython/pull/625 is in the main codebase
-class _DigestLengthMixin:
-    _digest_length_by_type = {  # octets (not hex)
-        0: 1,  # reserved in RFC 3658 Sec. 2.4, but used in RFC 8078 Sec. 4 (DS delete via CDS)
-        1: 20,  # SHA-1, RFC 3658 Sec. 2.4
-        2: 32,  # SHA-256, RFC 4509 Sec. 2.2
-        3: 32,  # GOST R 34.11-94, RFC 5933 Sec. 4 in conjunction with RFC 4490 Sec. 2.1
-        4: 48,  # SHA-384, RFC 6605 Sec. 2
-    }
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        try:
-            expected_len = _DigestLengthMixin._digest_length_by_type[self.digest_type]
-        except KeyError:
-            raise ValueError('unknown digest type')
-        actual_len = len(self.digest)
-        if actual_len != expected_len:
-            raise ValueError(f'invalid digest length {actual_len*2} (expected for this digest type: {expected_len*2}')
-
-
-@dns.immutable.immutable
-class CDS(_DigestLengthMixin, dns.rdtypes.ANY.CDS.CDS):
-    pass
-
-
-@dns.immutable.immutable
-class DLV(_DigestLengthMixin, dns.rdtypes.ANY.DLV.DLV):
-    pass
-
-
-@dns.immutable.immutable
-class DS(_DigestLengthMixin, dns.rdtypes.ANY.DS.DS):
-    pass
-
-
 def _HostnameMixin(name_field, *, allow_root):
     # Taken from https://github.com/PowerDNS/pdns/blob/4646277d05f293777a3d2423a3b188ccdf42c6bc/pdns/dnsname.cc#L419
     hostname_re = re.compile(r'^(([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)\.)+$')

+ 5 - 7
api/desecapi/models.py

@@ -41,7 +41,7 @@ from rest_framework.exceptions import APIException
 
 from desecapi import metrics
 from desecapi import pdns
-from desecapi.dns import AAAA, CDS, DLV, DS, LongQuotedTXT, MX, NS, SRV
+from desecapi.dns import AAAA, CERT, LongQuotedTXT, MX, NS, SRV
 
 logger = logging.getLogger(__name__)
 psl = psl_dns.PSL(resolver=settings.PSL_RESOLVER, timeout=.5)
@@ -587,10 +587,10 @@ class Donation(ExportModelOperationsMixin('Donation'), models.Model):
 # known, but unsupported types
 RR_SET_TYPES_UNSUPPORTED = {
     'ALIAS',  # Requires signing at the frontend, hence unsupported in desec-stack
-    'IPSECKEY',  # broken in pdns, https://github.com/PowerDNS/pdns/issues/10589 TODO enable with pdns auth > 4.5.0
+    'IPSECKEY',  # broken in pdns, https://github.com/PowerDNS/pdns/issues/10589 TODO enable with pdns auth >= 4.6.0
     'KEY',  # Application use restricted by RFC 3445, DNSSEC use replaced by DNSKEY and handled automatically
     'WKS',  # General usage not recommended, "SHOULD NOT" be used in SMTP (RFC 1123)
-} | {'NID', 'L32', 'L64', 'LP'}  # https://github.com/rthalley/dnspython/issues/674
+}
 # restricted types are managed in use by the API, and cannot directly be modified by the API client
 RR_SET_TYPES_AUTOMATIC = {
     # corresponding functionality is automatically managed:
@@ -602,7 +602,7 @@ RR_SET_TYPES_AUTOMATIC = {
 RR_SET_TYPES_BACKEND = pdns.SUPPORTED_RRSET_TYPES
 # validation types are types supported by the validation backend, currently: dnspython
 RR_SET_TYPES_VALIDATION = set(ANY.__all__) | set(IN.__all__) \
-                          | {'HTTPS', 'SVCB'}  # https://github.com/rthalley/dnspython/pull/624
+                          | {'L32', 'L64', 'LP', 'NID'}  # https://github.com/rthalley/dnspython/pull/751
 # manageable types are directly managed by the API client
 RR_SET_TYPES_MANAGEABLE = \
         (RR_SET_TYPES_BACKEND & RR_SET_TYPES_VALIDATION) - RR_SET_TYPES_UNSUPPORTED - RR_SET_TYPES_AUTOMATIC
@@ -774,9 +774,7 @@ class RR(ExportModelOperationsMixin('RR'), models.Model):
 
     _type_map = {
         dns.rdatatype.AAAA: AAAA,  # TODO remove when https://github.com/PowerDNS/pdns/issues/8182 is fixed
-        dns.rdatatype.CDS: CDS,  # TODO remove when https://github.com/rthalley/dnspython/pull/625 is in main codebase
-        dns.rdatatype.DLV: DLV,  # TODO remove when https://github.com/rthalley/dnspython/pull/625 is in main codebase
-        dns.rdatatype.DS: DS,  # TODO remove when https://github.com/rthalley/dnspython/pull/625 is in main codebase
+        dns.rdatatype.CERT: CERT,  # do DNS name validation the same way as pdns
         dns.rdatatype.MX: MX,  # do DNS name validation the same way as pdns
         dns.rdatatype.NS: NS,  # do DNS name validation the same way as pdns
         dns.rdatatype.SRV: SRV,  # do DNS name validation the same way as pdns

+ 2 - 2
api/desecapi/tests/base.py

@@ -660,8 +660,8 @@ class DesecTestCase(MockPDNSTestCase):
                        'kota.aichi.jp', 's3.amazonaws.com', 'wildcard.ck'}
     SUPPORTED_RR_SET_TYPES = {
         'A', 'AAAA', 'AFSDB', 'APL', 'CAA', 'CDNSKEY', 'CDS', 'CERT', 'CNAME', 'CSYNC', 'DHCID', 'DNAME', 'DNSKEY',
-        'DLV', 'DS', 'EUI48', 'EUI64', 'HINFO', 'HTTPS', 'KX', 'LOC', 'MX', 'NAPTR', 'NS', 'OPENPGPKEY', 'PTR', 'RP',
-        'SMIMEA', 'SPF', 'SRV', 'SSHFP', 'SVCB', 'TLSA', 'TXT', 'URI',
+        'DLV', 'DS', 'EUI48', 'EUI64', 'HINFO', 'HTTPS', 'KX', 'L32', 'L64', 'LOC', 'LP', 'MX', 'NAPTR',
+        'NID', 'NS', 'OPENPGPKEY', 'PTR', 'RP', 'SMIMEA', 'SPF', 'SRV', 'SSHFP', 'SVCB', 'TLSA', 'TXT', 'URI',
     }
 
     admin = None

+ 27 - 15
api/desecapi/tests/test_rrsets.py

@@ -397,18 +397,22 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
             ('HINFO', ('"cpu" "os"', '"cpu" "os"')),
             ('HTTPS', ('01 h3POOL.exaMPLe. aLPn=h2,h3',
                        '1 h3POOL.exaMPLe. alpn=h2,h3')),
-            # ('HTTPS', ('01 h3POOL.exaMPLe. aLPn=h2,h3 ECH=MTIzLi4uCg==',  # TODO dnspython > 2.1.0
-            #            '1 h3POOL.exaMPLe. alpn=h2,h3 ech="MTIzLi4uCg=="')),
+            ('HTTPS', ('01 h3POOL.exaMPLe. aLPn=h2,h3 ECH=MTIzLi4uCg==',
+                       '1 h3POOL.exaMPLe. alpn=h2,h3 ech="MTIzLi4uCg=="')),
             # ('IPSECKEY', ('01 00 02 . ASDFAF==', '1 0 2 . ASDFAA==')),
             # ('IPSECKEY', ('01 00 02 . 000000==', '1 0 2 . 00000w==')),
             ('KX', ('010 example.com.', '10 example.com.')),
+            ('L32', ('010  10.1.2.0', '10 10.1.2.0')),
+            ('L64', ('010   2001:0Db8:2140:2000', '10 2001:0db8:2140:2000')),
             ('LOC', ('023 012 59 N 042 022 48.500 W 65.00m 20.00m 10.00m 10.00m',
                      '23 12 59.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m')),
+            ('LP', ('010   l64-subnet1.example.com.', '10 l64-subnet1.example.com.')),
             ('MX', ('10 010.1.1.1.', '10 010.1.1.1.')),
             ('MX', ('010 010.1.1.2.', '10 010.1.1.2.')),
             ('MX', ('0 .', '0 .')),
             ('NAPTR', ('100  50  "s"  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu.',
                        '100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu.')),
+            ('NID', ('010 0014:4fff:ff20:Ee64', '10 0014:4fff:ff20:ee64')),
             ('NS', ('EXaMPLE.COM.', 'example.com.')),
             ('OPENPGPKEY', ('mG8EXtVIsRMFK4EEACIDAwQSZPNqE4tS xLFJYhX+uabSgMrhOqUizJhkLx82',
                             'mG8EXtVIsRMFK4EEACIDAwQSZPNqE4tSxLFJYhX+uabSgMrhOqUizJhkLx82')),
@@ -425,8 +429,8 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
             ('SSHFP', ('2 2 aabbccEEddff', '2 2 aabbcceeddff')),
             ('SVCB', ('2 sVc2.example.NET. IPV6hint=2001:db8:00:0::2 port=01234',
                       '2 sVc2.example.NET. port=1234 ipv6hint=2001:db8::2')),
-            # ('SVCB', ('2 sVc2.example.NET. ECH=MjIyLi4uCg== IPV6hint=2001:db8:00:0::2 port=01234',  # TODO dnspython > 2.1.0
-            #           '2 sVc2.example.NET. port=1234 ech="MjIyLi4uCg==" ipv6hint=2001:db8::2')),
+            ('SVCB', ('2 sVc2.example.NET. ECH=MjIyLi4uCg== IPV6hint=2001:db8:00:0::2 port=01234',
+                      '2 sVc2.example.NET. port=1234 ech="MjIyLi4uCg==" ipv6hint=2001:db8::2')),
             ('TLSA', ('3 0001 1 000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', '3 1 1 000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')),
             ('TLSA', ('003 00 002 696B8F6B92A913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd',
                       '3 0 2 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd')),
@@ -473,7 +477,7 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 '2:FF00::/8 !1:192.168.38.0/28',
             ],
             'CAA': ['128 issue "letsencrypt.org"', '128 iodef "mailto:desec@example.com"', '1 issue "letsencrypt.org"'],
-            'CERT': ['6 0 0 sadfdd=='],
+            'CERT': ['06 0 0 sadfdd==', 'IPGP 0 0 sadfdd=='],
             'CDNSKEY': [
                 '256 3 8 AwEAAday3UX323uVzQqtOMQ7EHQYfD5Ofv4akjQGN2zY5AgB/2jmdR/+ 1PvXFqzKCAGJv4wjABEBNWLLFm7ew1hHMDZEKVL17aml0EBKI6Dsz6Mx t6n7ScvLtHaFRKaxT4i2JxiuVhKdQR9XGMiWAPQKrRM5SLG0P+2F+TLK l3D0L/cD',
                 '257 3 8 AwEAAcw5QLr0IjC0wKbGoBPQv4qmeqHy9mvL5qGQTuaG5TSrNqEAR6b/ qvxDx6my4JmEmjUPA1JeEI9YfTUieMr2UZflu7aIbZFLw0vqiYrywCGr CHXLalOrEOmrvAxLvq4vHtuTlH7JIszzYBSes8g1vle6KG7xXiP3U5Ll 96Qiu6bZ31rlMQSPB20xbqJJh6psNSrQs41QvdcXAej+K2Hl1Wd8kPri ec4AgiBEh8sk5Pp8W9ROLQ7PcbqqttFaW2m7N/Wy4qcFU13roWKDEAst bxH5CHPoBfZSbIwK4KM6BK/uDHpSPIbiOvOCW+lvu9TAiZPc0oysY6as lO7jXv16Gws=',
@@ -484,6 +488,7 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 '6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520',
                 '62703 13 2 085BF1EE0ADBBC99D4D9328229EBDCAEC5FAB20E38610072AD055474 4C7AF4A0',
                 '61655 13 4 C838A5C66FCBF83B8B6B50C3CEEC3524777FE4AF8A9FE0172ECAD242 48B0CA1A216DD0D538F20C130DD3059538204B04',
+                '6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E',
             ],
             'CNAME': ['example.com.'],
             'CSYNC': ['0 0', '66 1 A', '66 2 AAAA', '66 3 A NS AAAA', '66 15 NSEC'],
@@ -493,6 +498,7 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 '6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520',
                 '62703 13 2 085BF1EE0ADBBC99D4D9328229EBDCAEC5FAB20E38610072AD055474 4C7AF4A0',
                 '61655 13 4 C838A5C66FCBF83B8B6B50C3CEEC3524777FE4AF8A9FE0172ECAD242 48B0CA1A216DD0D538F20C130DD3059538204B04',
+                '6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E',
             ],
             'DNAME': ['example.com.'],
             'DNSKEY': [
@@ -505,6 +511,7 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 '6454 8 2 5CBA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520',
                 '62703 13 2 085BF1EE0ADBBC99D4D9328229EBDCAEC5FAB20E38610072AD055474 4C7AF4A0',
                 '61655 13 4 C838A5C66FCBF83B8B6B50C3CEEC3524777FE4AF8A9FE0172ECAD242 48B0CA1A216DD0D538F20C130DD3059538204B04',
+                '6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E',
             ],
             'EUI48': ['aa-bb-cc-dd-ee-ff', 'AA-BB-CC-DD-EE-FF'],
             'EUI64': ['aa-bb-cc-dd-ee-ff-00-11', 'AA-BB-CC-DD-EE-FF-00-11'],
@@ -513,11 +520,11 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 # from https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-06.html#name-examples, with ech base64'd
                 '1 . alpn=h3',
                 '0 pool.svc.example.',
-                # '1 h3pool.example. alpn=h2,h3 ech="MTIzLi4uCg=="',  # TODO dnspython > 2.1.0
-                # '2 .      alpn=h2 ech="YWJjLi4uCg=="',  # TODO dnspython > 2.1.0
+                '1 h3pool.example. alpn=h2,h3 ech="MTIzLi4uCg=="',
+                '2 .      alpn=h2 ech="YWJjLi4uCg=="',
                 # made-up (not from RFC)
-                '1 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1',
-                # '2 . ech=... key65333=ex1 key65444=ex2 mandatory=key65444,ech',  # see #section-7  # TODO dnspython > 2.1.0
+                '1 pool.svc.example. no-default-alpn alpn=h2 port=1234 ipv4hint=192.168.123.1',
+                '2 . ech=... key65333=ex1 key65444=ex2 mandatory=key65444,ech',  # see #section-7
             ],
             # 'IPSECKEY': [
             #     '12 0 2 . asdfdf==',
@@ -526,9 +533,13 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
             #     '12 3 01 example.com. asdfdf==',
             # ],
             'KX': ['4 example.com.', '28 io.'],
+            'L32': ['010   10.1.2.0', '65535 1.2.3.4'],
+            'L64': ['010   2001:0DB8:1140:1000', '10 2001:0DB8:1140:1000'],
             'LOC': ['23 12 59.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m'],
+            'LP': ['10 l64-subnet1.example.com.', '65535 .'],
             'MX': ['10 example.com.', '20 1.1.1.1.'],
             'NAPTR': ['100  50  "s"  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu.'],
+            'NID': ['65535   0014:4fff:ff20:ee64'],
             'NS': ['ns1.example.com.'],
             'OPENPGPKEY': [
                 'mG8EXtVIsRMFK4EEACIDAwQSZPNqE4tSxLFJYhX+uabSgMrhOqUizJhkLx82',  # key incomplete
@@ -545,7 +556,7 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
             'SVCB': [
                 '0 svc4-baz.example.net.',
                 '1 . key65333=...',
-                # '2 svc2.example.net. ech="MjIyLi4uCg==" ipv6hint=2001:db8::2 port=1234',  # TODO dnspython > 2.1.0
+                '2 svc2.example.net. ech="MjIyLi4uCg==" ipv6hint=2001:db8::2 port=1234',
             ],
             'TLSA': ['3 1 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
                      '3 0 2 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd',
@@ -597,7 +608,6 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 '6454 8 c 24396E17E36D031F71C354B06A979A67A01F503E',
                 '6454 8 1 d',
                 '6454 8 0 24396E17E36D031F71C354B06A979A67A01F503E',
-                '6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E',
                 '6454 8 1 aabbccddeeff',
                 '0 0 0 0',
             ],
@@ -611,7 +621,6 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 '6454 8 c 24396E17E36D031F71C354B06A979A67A01F503E',
                 '6454 8 1 d',
                 '6454 8 0 24396E17E36D031F71C354B06A979A67A01F503E',
-                '6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E',
                 '6454 8 1 aabbccddeeff',
             ],
             'DNAME': ['example.com', '10 example.com.'],
@@ -628,7 +637,6 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
                 '6454 8 c 24396E17E36D031F71C354B06A979A67A01F503E',
                 '6454 8 1 d',
                 '6454 8 0 24396E17E36D031F71C354B06A979A67A01F503E',
-                '6454 8 5 24396E17E36D031F71C354B06A979A67A01F503E',
                 '6454 8 1 aabbccddeeff',
             ],
             'EUI48': ['aa-bb-ccdd-ee-ff', 'AA-BB-CC-DD-EE-GG'],
@@ -636,18 +644,22 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
             'HINFO': ['"ARMv8-A"', f'"a" "{"b"*256}"'],
             'HTTPS': [
                 # from https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-02#section-10.3, with ech base64'd
-                # '1 h3pool alpn=h2,h3 ech="MTIzLi4uCg=="',  # TODO dnspython > 2.1.0
+                '1 h3pool alpn=h2,h3 ech="MTIzLi4uCg=="',
                 # made-up (not from RFC)
                 '0 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1',  # no keys in alias mode
                 '1 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1 ipv4hint=192.168.123.2',  # dup
             ],
             # 'IPSECKEY': [],
             'KX': ['-1 example.com', '10 example.com'],
+            'L32': ['65536 10.1.2.0', '5 a.1.2.0', '10 10.1.02.0'],
+            'L64': ['65536 2001:0DB8:4140:4000', '5 01:0DB8:4140:4000'],
             'LOC': ['23 12 61.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m', 'foo', '1.1.1.1'],
+            'LP': ['10 l64-subnet1.example.com', '-3 l64-subnet1.example.com.', '65536 l64-subnet1.example.com.'],
             'MX': ['10 example.com', 'example.com.', '-5 asdf.', '65537 asdf.', '10 _foo.example.com.', '10 $url.'],
             'NAPTR': ['100  50  "s"  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu',
                       '100  50  "s"     ""  _z3950._tcp.gatech.edu.',
                       '100  50  3 2  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu.'],
+            'NID': ['010 14:4fff:ff20:Ee64', 'd 0014:4fff:ff20:ee64', '20 ::14::ee64'],
             'NS': ['ns1.example.com', '127.0.0.1', '_foobar.example.dedyn.io.', '.'],
             'OPENPGPKEY': ['1 2 3'],
             'PTR': ['"example.com."', '10 *.example.com.'],
@@ -659,7 +671,7 @@ class AuthenticatedRRSetTestCase(AuthenticatedRRSetBaseTestCase):
             'SVCB': [
                 '0 svc4-baz.example.net. keys=val',
                 '1 not.fully.qualified key65333=...',
-                # '2 duplicate.key. ech="MjIyLi4uCg==" ech="MjIyLi4uCg=="',  # TODO dnspython > 2.1.0
+                '2 duplicate.key. ech="MjIyLi4uCg==" ech="MjIyLi4uCg=="',
             ],
             'TLSA': ['3 1 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
             'TXT': [

+ 1 - 1
api/requirements.txt

@@ -9,7 +9,7 @@ django-celery-email~=3.0.0
 django-netfields~=1.2.4
 django-pgtrigger~=2.4.0
 django-prometheus~=2.2.0
-dnspython~=2.1.0
+dnspython~=2.2.0
 httpretty~=1.0.5
 psycopg2~=2.9.2
 prometheus-client~=0.12.0  # added to control django-prometheus' dependency version

+ 4 - 3
docs/dns/rrsets.rst

@@ -534,9 +534,10 @@ automatically.
 At least the following record types are supported: ``A``, ``AAAA``, ``AFSDB``,
 ``APL``, ``CAA``, ``CDNSKEY``, ``CDS``, ``CERT``, ``CNAME``, ``DHCID``,
 ``DNAME``, ``DNSKEY``, ``DLV``, ``DS``, ``EUI48``, ``EUI64``, ``HINFO``,
-``HTTPS``, ``KX``, ``LOC``, ``MX``, ``NAPTR``, ``NS``, ``OPENPGPKEY``,
-``PTR``, ``RP``, ``SMIMEA``, ``SPF``, ``SRV``, ``SSHFP``, ``SVCB``, ``TLSA``,
-``TXT``, ``URI``.  (The ``SOA`` record is managed automatically.)
+``HTTPS``, ``KX``, ``L32``, ``L64``, ``LOC``, ``LP``, ``MX``,
+``NAPTR``, ``NID``, ``NS``, ``OPENPGPKEY``, ``PTR``, ``RP``, ``SMIMEA``,
+``SPF``, ``SRV``, ``SSHFP``, ``SVCB``, ``TLSA``, ``TXT``, ``URI``.
+(The ``SOA`` record is managed automatically.)
 
 Special care needs to be taken with some types of records, as explained below.
 

+ 1 - 1
test/e2e2/requirements.txt

@@ -2,4 +2,4 @@ pytest
 pytest-schema
 pytest-xdist
 requests
-dnspython~=2.1.0
+dnspython~=2.2.0

+ 27 - 23
test/e2e2/spec/test_api_rr.py

@@ -40,7 +40,7 @@ VALID_RECORDS_CANONICAL = {
         '6454 8 1 24396e17e36d031f71c354b06a979a67a01f503e',
         '0 0 0 00',
     ],
-    'CERT': ['6 0 0 sadfdQ=='],
+    'CERT': [],  # handled in non-canonical, because apply presentation format conversion on the stack (but not here)
     'CNAME': ['example.com.'],
     'CSYNC': ['0 0', '66 1 A', '66 2 AAAA', '66 3 A NS AAAA', '66 15 NSEC'],
     'DHCID': ['aaaaaaaaaaaa', 'xxxx'],
@@ -58,17 +58,17 @@ VALID_RECORDS_CANONICAL = {
     'HINFO': ['"ARMv8-A" "Linux"'],
     'HTTPS': [
         '1 h3POOL.exaMPLe. alpn=h2,h3',
-        # '1 h3POOL.exaMPLe. alpn=h2,h3 ech="MTIzLi4uCg=="',  # TODO dnspython > 2.1.0
+        '1 h3POOL.exaMPLe. alpn=h2,h3 ech="MTIzLi4uCg=="',
     ],
     # 'IPSECKEY': ['12 0 2 . asdfdQ==', '3 1 1 127.0.0.1 asdfdQ==', '12 3 1 example.com. asdfdQ==',],
     'KX': ['4 example.com.', '28 io.', '0 .'],
-    'LOC': [
-        '23 12 59.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m',
-    ],
+    'L32': ['10 10.1.2.0'],
+    'L64': ['10 2001:0db8:2140:2000'],
+    'LOC': ['23 12 59.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m'],
+    'LP': ['10 l64-subnet1.example.com.'],
     'MX': ['10 example.com.', '20 1.1.1.1.'],
-    'NAPTR': [
-        '100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu.',
-    ],
+    'NAPTR': ['100 50 "s" "z3950+I2L+I2C" "" _z3950._tcp.gatech.edu.'],
+    'NID': ['10 0014:4fff:ff20:ee64'],
     'NS': ['ns1.example.com.'],
     'OPENPGPKEY': [
         'mQINBF3yev8BEADR9GxB6OJ5AJlXBWc3nWyWZ+yNNVBiy73XjgOs0uowbxph'
@@ -135,7 +135,7 @@ VALID_RECORDS_CANONICAL = {
     'SSHFP': ['2 2 aabbcceeddff'],
     'SVCB': [
         '2 sVc2.example.NET. port=1234 ipv6hint=2001:db8::2',
-        # '2 sVc2.example.NET. port=1234 ech="MjIyLi4uCg==" ipv6hint=2001:db8::2',  # TODO dnspython > 2.1.0
+        '2 sVc2.example.NET. port=1234 ech="MjIyLi4uCg==" ipv6hint=2001:db8::2',
     ],
     'TLSA': ['3 0 2 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd 696b8f6b92a913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd',],
     'TXT': [
@@ -168,7 +168,7 @@ VALID_RECORDS_NON_CANONICAL = {
         '06454  08   01    24396e17e36d031f71c354b06a979a67a01f503e',
         '6454 8 2 5C BA665A006F6487625C6218522F09BD3673C25FA10F25CB18459AA1 0DF1F520',
     ],
-    'CERT': ['06 00 00 sadfee=='],
+    'CERT': ['6 0 0 sadfdQ==', '06 00 00 sadfee==', 'IPGP 00 00 sadfee=='],
     'CNAME': ['EXAMPLE.TEST.'],
     'CSYNC': ['066 03  NS  AAAA A'],
     'DHCID': ['aa aaa  aaaa a a a', 'xxxx'],
@@ -193,21 +193,21 @@ VALID_RECORDS_NON_CANONICAL = {
         # from https://www.ietf.org/archive/id/draft-ietf-dnsop-svcb-https-06.html#name-examples, with ech base64'd
         '1 . alpn=h3',
         '0 pool.svc.example.',
-        # '1 h3pool.example. alpn=h2,h3 ech="MTIzLi4uCg=="',  # TODO dnspython > 2.1.0
-        # '2 .      alpn=h2 ech="YWJjLi4uCg=="',  # TODO dnspython > 2.1.0
+        '1 h3pool.example. alpn=h2,h3 ech="MTIzLi4uCg=="',
+        '2 .      alpn=h2 ech="YWJjLi4uCg=="',
         # made-up (not from RFC)
-        '1 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1',
-        # '2 . ech=... key65333=ex1 key65444=ex2 mandatory=key65444,ech',  # see #section-7  # TODO dnspython > 2.1.0
+        '1 pool.svc.example. no-default-alpn alpn=h2 port=1234 ipv4hint=192.168.123.1',
+        '2 . ech=... key65333=ex1 key65444=ex2 mandatory=key65444,ech',  # see #section-7
     ],
     # 'IPSECKEY': ['12 0 2 . asdfdf==', '03 1 1 127.0.00.1 asdfdf==', '12 3 1 example.com. asdfdf==',],
     'KX': ['012 example.TEST.'],
-    'LOC': [
-        '023 012 59 N 042 022 48.500 W 65.00m 20.00m 10.00m 10.00m',
-    ],
+    'L32': ['010  10.1.2.0', '65535 1.2.3.4'],
+    'L64': ['010   2001:0Db8:2140:2000', '10 2001:0DB8:1140:1000'],
+    'LOC': ['023 012 59 N 042 022 48.500 W 65.00m 20.00m 10.00m 10.00m'],
+    'LP': ['010   l64-subnet1.example.com.', '65535 .'],
     'MX': ['10 010.1.1.1.'],
-    'NAPTR': [
-        '100  50  "s"  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu.',
-    ],
+    'NAPTR': ['100  50  "s"  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu.'],
+    'NID': ['010 0014:4fff:ff20:Ee64', '65535   0014:4fff:ff20:ee64'],
     'NS': ['EXaMPLE.COM.'],
     'OPENPGPKEY': [
         'mG8EXtVIsRMFK4EEAC==',
@@ -271,7 +271,7 @@ VALID_RECORDS_NON_CANONICAL = {
     'SVCB': [
         '0 svc4-baz.example.net.',
         '1 . key65333=...',
-        # '2 svc2.example.net. ech="MjIyLi4uCg==" ipv6hint=2001:db8::2 port=1234',  # TODO dnspython > 2.1.0
+        '2 svc2.example.net. ech="MjIyLi4uCg==" ipv6hint=2001:db8::2 port=1234',
     ],
     'TLSA': ['003 00 002 696B8F6B92A913560b23ef5720c378881faffe74432d04eb35db957c0a93987b47adf26abb5dac10ba482597ae16edb069b511bec3e26010d1927bf6392760dd',],
     'TXT': [
@@ -323,18 +323,22 @@ INVALID_RECORDS = {
     'HINFO': ['"ARMv8-A"', f'"a" "{"b" * 256}"'],
     'HTTPS': [
         # from https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-02#section-10.3, with ech base64'd
-        # '1 h3pool alpn=h2,h3 ech="MTIzLi4uCg=="',  # TODO dnspython > 2.1.0
+        '1 h3pool alpn=h2,h3 ech="MTIzLi4uCg=="',
         # made-up (not from RFC)
         '0 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1',  # no keys in alias mode
         '1 pool.svc.example. no-default-alpn port=1234 ipv4hint=192.168.123.1 ipv4hint=192.168.123.2',  # dup
     ],
     # 'IPSECKEY': [],
     'KX': ['-1 example.com', '10 example.com'],
+    'L32': ['65536 10.1.2.0', '5 a.1.2.0', '10 10.1.02.0'],
+    'L64': ['65536 2001:0DB8:4140:4000', '5 01:0DB8:4140:4000'],
     'LOC': ['23 12 61.000 N 42 22 48.500 W 65.00m 20.00m 10.00m 10.00m', 'foo', '1.1.1.1'],
+    'LP': ['10 l64-subnet1.example.com', '-3 l64-subnet1.example.com.', '65536 l64-subnet1.example.com.'],
     'MX': ['10 example.com', 'example.com.', '-5 asdf.', '65537 asdf.' '10 _foo.example.com.', '10 $url.'],
     'NAPTR': ['100  50  "s"  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu',
               '100  50  "s"     ""  _z3950._tcp.gatech.edu.',
               '100  50  3 2  "z3950+I2L+I2C"     ""  _z3950._tcp.gatech.edu.'],
+    'NID': ['010 14:4fff:ff20:Ee64', 'd 0014:4fff:ff20:ee64', '20 ::14::ee64'],
     'NS': ['ns1.example.com', '127.0.0.1'],
     'OPENPGPKEY': ['1 2 3'],
     'PTR': ['"example.com."', '10 *.example.com.'],
@@ -346,7 +350,7 @@ INVALID_RECORDS = {
     'SVCB': [
         '0 svc4-baz.example.net. keys=val',
         '1 not.fully.qualified key65333=...',
-        # '2 duplicate.key. ech="MjIyLi4uCg==" ech="MjIyLi4uCg=="',  # TODO dnspython > 2.1.0
+        '2 duplicate.key. ech="MjIyLi4uCg==" ech="MjIyLi4uCg=="',
     ],
     'TLSA': ['3 1 1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
     'TXT': [