1import ipaddress
2
3from django.core.exceptions import ValidationError
4from django.utils.translation import gettext_lazy as _
5
6MAX_IPV6_ADDRESS_LENGTH = 39
7
8
9def _ipv6_address_from_str(ip_str, max_length=MAX_IPV6_ADDRESS_LENGTH):
10 if len(ip_str) > max_length:
11 raise ValueError(
12 f"Unable to convert {ip_str} to an IPv6 address (value too long)."
13 )
14 return ipaddress.IPv6Address(int(ipaddress.IPv6Address(ip_str)))
15
16
17def clean_ipv6_address(
18 ip_str,
19 unpack_ipv4=False,
20 error_message=_("This is not a valid IPv6 address."),
21 max_length=MAX_IPV6_ADDRESS_LENGTH,
22):
23 """
24 Clean an IPv6 address string.
25
26 Raise ValidationError if the address is invalid.
27
28 Replace the longest continuous zero-sequence with "::", remove leading
29 zeroes, and make sure all hextets are lowercase.
30
31 Args:
32 ip_str: A valid IPv6 address.
33 unpack_ipv4: if an IPv4-mapped address is found,
34 return the plain IPv4 address (default=False).
35 error_message: An error message used in the ValidationError.
36
37 Return a compressed IPv6 address or the same value.
38 """
39 try:
40 addr = _ipv6_address_from_str(ip_str, max_length)
41 except ValueError:
42 raise ValidationError(
43 error_message, code="invalid", params={"protocol": _("IPv6")}
44 )
45
46 if unpack_ipv4 and addr.ipv4_mapped:
47 return str(addr.ipv4_mapped)
48 elif addr.ipv4_mapped:
49 return "::ffff:%s" % str(addr.ipv4_mapped)
50
51 return str(addr)
52
53
54def is_valid_ipv6_address(ip_addr):
55 """
56 Return whether the `ip_addr` object is a valid IPv6 address.
57 """
58 if isinstance(ip_addr, ipaddress.IPv6Address):
59 return True
60 try:
61 _ipv6_address_from_str(ip_addr)
62 except (TypeError, ValueError):
63 return False
64 return True