]> git.ipfire.org Git - thirdparty/dnspython.git/commitdiff
Canonicalize IPV4 and IPv6 address text form in rdata. (#1013)
authorBob Halley <halley@dnspython.org>
Thu, 16 Nov 2023 00:47:52 +0000 (16:47 -0800)
committerGitHub <noreply@github.com>
Thu, 16 Nov 2023 00:47:52 +0000 (16:47 -0800)
dns/inet.py
dns/ipv4.py
dns/ipv6.py
dns/rdata.py
tests/example1.good
tests/example2.good
tests/example3.good
tests/example4.good
tests/test_ntoaaton.py

index 02e925c6bf89f76ba9a8c0b10abfb97a32382c74..4a03f99622d8248e8318c174b85029169522d894 100644 (file)
@@ -178,3 +178,20 @@ def any_for_af(af):
     elif af == socket.AF_INET6:
         return "::"
     raise NotImplementedError(f"unknown address family {af}")
+
+
+def canonicalize(text: str) -> str:
+    """Verify that *address* is a valid text form IPv4 or IPv6 address and return its
+    canonical text form.  IPv6 addresses with scopes are rejected.
+
+    *text*, a ``str``, the address in textual form.
+
+    Raises ``ValueError`` if the text is not valid.
+    """
+    try:
+        return dns.ipv6.canonicalize(text)
+    except Exception:
+        try:
+            return dns.ipv4.canonicalize(text)
+        except Exception:
+            raise ValueError
index f549150a901356c4907efba97181ee385f1eebfc..65ee69c0d7a4f6ce949edc13d2d2d866889ec5ab 100644 (file)
@@ -62,3 +62,16 @@ def inet_aton(text: Union[str, bytes]) -> bytes:
         return struct.pack("BBBB", *b)
     except Exception:
         raise dns.exception.SyntaxError
+
+
+def canonicalize(text: Union[str, bytes]) -> str:
+    """Verify that *address* is a valid text form IPv4 address and return its
+    canonical text form.
+
+    *text*, a ``str`` or ``bytes``, the IPv4 address in textual form.
+
+    Raises ``dns.exception.SyntaxError`` if the text is not valid.
+    """
+    # Note that inet_aton() only accepts canonial form, but we still run through
+    # inet_ntoa() to ensure the output is a str.
+    return dns.ipv4.inet_ntoa(dns.ipv4.inet_aton(text))
index 0cc3d868f567a31f0089009f71320c9acf81fbd5..44a1063936891d417cc8e354acd32dc5b9f70eb1 100644 (file)
@@ -104,7 +104,7 @@ _colon_colon_end = re.compile(rb".*::$")
 def inet_aton(text: Union[str, bytes], ignore_scope: bool = False) -> bytes:
     """Convert an IPv6 address in text form to binary form.
 
-    *text*, a ``str``, the IPv6 address in textual form.
+    *text*, a ``str`` or ``bytes``, the IPv6 address in textual form.
 
     *ignore_scope*, a ``bool``.  If ``True``, a scope will be ignored.
     If ``False``, the default, it is an error for a scope to be present.
@@ -206,3 +206,14 @@ def is_mapped(address: bytes) -> bool:
     """
 
     return address.startswith(_mapped_prefix)
+
+
+def canonicalize(text: Union[str, bytes]) -> str:
+    """Verify that *address* is a valid text form IPv6 address and return its
+    canonical text form.  Addresses with scopes are rejected.
+
+    *text*, a ``str`` or ``bytes``, the IPv6 address in textual form.
+
+    Raises ``dns.exception.SyntaxError`` if the text is not valid.
+    """
+    return dns.ipv6.inet_ntoa(dns.ipv6.inet_aton(text))
index 0d262e8d85b362a29ee4e34416afeb5108b0da45..5482e71dc17eb0992e790cfc3a594658483f5253 100644 (file)
@@ -547,9 +547,7 @@ class Rdata:
     @classmethod
     def _as_ipv4_address(cls, value):
         if isinstance(value, str):
-            # call to check validity
-            dns.ipv4.inet_aton(value)
-            return value
+            return dns.ipv4.canonicalize(value)
         elif isinstance(value, bytes):
             return dns.ipv4.inet_ntoa(value)
         else:
@@ -558,9 +556,7 @@ class Rdata:
     @classmethod
     def _as_ipv6_address(cls, value):
         if isinstance(value, str):
-            # call to check validity
-            dns.ipv6.inet_aton(value)
-            return value
+            return dns.ipv6.canonicalize(value)
         elif isinstance(value, bytes):
             return dns.ipv6.inet_ntoa(value)
         else:
index 6d38a21d8b373a1e50f258aceaa1b8b1dd388528..045365c03b872400ec78ff832eb9ca7b58199eeb 100644 (file)
@@ -18,7 +18,7 @@ amtrelay03 3600 IN AMTRELAY 10 0 1 203.0.113.15
 amtrelay04 3600 IN AMTRELAY 10 0 2 2001:db8::15
 amtrelay05 3600 IN AMTRELAY 128 1 3 amtrelays.example.com.
 apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28
-apl02 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
+apl02 3600 IN APL 1:224.0.0.0/4 2:ff00::/8
 avc01 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes"
 b 300 IN CNAME foo.net.
 c 300 IN A 73.80.65.49
index 8548a3d81b1ac8ca99f8c98f40baa89765bb8860..5bb13768cbca39c5582c0133e34411dde12af145 100644 (file)
@@ -18,7 +18,7 @@ amtrelay03.example. 3600 IN AMTRELAY 10 0 1 203.0.113.15
 amtrelay04.example. 3600 IN AMTRELAY 10 0 2 2001:db8::15
 amtrelay05.example. 3600 IN AMTRELAY 128 1 3 amtrelays.example.com.
 apl01.example. 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28
-apl02.example. 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
+apl02.example. 3600 IN APL 1:224.0.0.0/4 2:ff00::/8
 avc01.example. 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes"
 b.example. 300 IN CNAME foo.net.
 c.example. 300 IN A 73.80.65.49
index 6d38a21d8b373a1e50f258aceaa1b8b1dd388528..045365c03b872400ec78ff832eb9ca7b58199eeb 100644 (file)
@@ -18,7 +18,7 @@ amtrelay03 3600 IN AMTRELAY 10 0 1 203.0.113.15
 amtrelay04 3600 IN AMTRELAY 10 0 2 2001:db8::15
 amtrelay05 3600 IN AMTRELAY 128 1 3 amtrelays.example.com.
 apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28
-apl02 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
+apl02 3600 IN APL 1:224.0.0.0/4 2:ff00::/8
 avc01 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes"
 b 300 IN CNAME foo.net.
 c 300 IN A 73.80.65.49
index befbcc9f6429ed37f902e73fe63de0e8b87536fb..3c154a0bb8a18b34bf06284e8baf2f10bbfd1ae9 100644 (file)
@@ -19,7 +19,7 @@ amtrelay03 3600 IN AMTRELAY 10 0 1 203.0.113.15
 amtrelay04 3600 IN AMTRELAY 10 0 2 2001:db8::15
 amtrelay05 3600 IN AMTRELAY 128 1 3 amtrelays.example.com.
 apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28
-apl02 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8
+apl02 3600 IN APL 1:224.0.0.0/4 2:ff00::/8
 avc01 3600 IN AVC "app-name:WOLFGANG|app-class:OAM|business=yes"
 b 300 IN CNAME foo.net.
 c 300 IN A 73.80.65.49
index 94386cec1bfb4c4676bbcf42c522751b6ff2743f..9ffafb0a581ed62b6513b87dd205ad358c342377 100644 (file)
 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-import unittest
 import binascii
+import itertools
 import socket
+import unittest
 
 import dns.exception
+import dns.inet
 import dns.ipv4
 import dns.ipv6
-import dns.inet
 
 # for convenience
 aton4 = dns.ipv4.inet_aton
@@ -41,6 +42,27 @@ v4_bad_addrs = [
     "1.2.3.4.",
 ]
 
+v4_canonicalize_addrs = [
+    # (input, expected)
+    ("127.0.0.1", "127.0.0.1"),
+    (b"127.0.0.1", "127.0.0.1"),
+]
+
+v6_canonicalize_addrs = [
+    # (input, expected)
+    ("2001:503:83eb:0:0:0:0:30", "2001:503:83eb::30"),
+    (b"2001:503:83eb:0:0:0:0:30", "2001:503:83eb::30"),
+    ("2001:db8::1:1:1:1:1", "2001:db8:0:1:1:1:1:1"),
+    ("2001:DB8::1:1:1:1:1", "2001:db8:0:1:1:1:1:1"),
+]
+
+bad_canonicalize_addrs = [
+    "127.00.0.1",
+    "hi there",
+    "2001::db8::1:1:1:1:1",
+    "fe80::1%lo0",
+]
+
 
 class NtoAAtoNTestCase(unittest.TestCase):
     def test_aton1(self):
@@ -350,6 +372,30 @@ class NtoAAtoNTestCase(unittest.TestCase):
             NotImplementedError, lambda: dns.inet.inet_ntop(12345, b"bogus")
         )
 
+    def test_ipv4_canonicalize(self):
+        for address, expected in v4_canonicalize_addrs:
+            self.assertEqual(dns.ipv4.canonicalize(address), expected)
+        for bad_address in bad_canonicalize_addrs:
+            self.assertRaises(
+                dns.exception.SyntaxError, lambda: dns.ipv4.canonicalize(bad_address)
+            )
+
+    def test_ipv6_canonicalize(self):
+        for address, expected in v6_canonicalize_addrs:
+            self.assertEqual(dns.ipv6.canonicalize(address), expected)
+        for bad_address in bad_canonicalize_addrs:
+            self.assertRaises(
+                dns.exception.SyntaxError, lambda: dns.ipv6.canonicalize(bad_address)
+            )
+
+    def test_inet_canonicalize(self):
+        for address, expected in itertools.chain(
+            v4_canonicalize_addrs, v6_canonicalize_addrs
+        ):
+            self.assertEqual(dns.inet.canonicalize(address), expected)
+        for bad_address in bad_canonicalize_addrs:
+            self.assertRaises(ValueError, lambda: dns.inet.canonicalize(bad_address))
+
 
 if __name__ == "__main__":
     unittest.main()