This commit is contained in:
Foster Snowhill 2025-12-23 01:05:44 -05:00 committed by GitHub
commit 5833c3c0b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 58 additions and 6 deletions

View file

@ -1252,13 +1252,20 @@ class _BaseV4:
return '.'.join(map(str, ip_int.to_bytes(4, 'big')))
def _reverse_pointer(self):
"""Return the reverse DNS pointer name for the IPv4 address.
"""Return the reverse DNS pointer name for the IPv4 address or network.
This implements the method described in RFC1035 3.5.
"""
reverse_octets = str(self).split('.')[::-1]
return '.'.join(reverse_octets) + '.in-addr.arpa'
prefix = getattr(self, 'prefixlen', IPV4LENGTH)
octet_count, remainder = divmod(prefix, 8) # each octet is 8 bits long
if remainder:
raise NetmaskValueError(
'Reverse pointer cannot be generated for given prefix size')
address_exploded = getattr(self, 'network_address', self).exploded
reverse_octets = address_exploded.split('.')[:octet_count][::-1]
reverse_octets.append('in-addr.arpa')
return '.'.join(reverse_octets)
class IPv4Address(_BaseV4, _BaseAddress):
@ -1880,13 +1887,20 @@ class _BaseV6:
return ':'.join(parts)
def _reverse_pointer(self):
"""Return the reverse DNS pointer name for the IPv6 address.
"""Return the reverse DNS pointer name for the IPv6 address or network.
This implements the method described in RFC3596 2.5.
"""
reverse_chars = self.exploded[::-1].replace(':', '')
return '.'.join(reverse_chars) + '.ip6.arpa'
prefix = getattr(self, 'prefixlen', IPV6LENGTH)
char_count, remainder = divmod(prefix, 4) # each char is 4 bits long
if remainder:
raise NetmaskValueError(
'Reverse pointer cannot be generated for given prefix size')
address_exploded = getattr(self, 'network_address', self).exploded
reverse_chars = list(address_exploded.replace(':', '')[:char_count][::-1])
reverse_chars.append('ip6.arpa')
return '.'.join(reverse_chars)
@staticmethod
def _split_scope_id(ip_str):

View file

@ -723,6 +723,24 @@ class NetworkTestCase_v4(BaseTestCase, NetmaskTestMixin_v4):
ipaddress.IPv6Network('::1/128').subnet_of(
ipaddress.IPv4Network('10.0.0.0/30'))
def test_reverse_pointer(self):
self.assertEqual(self.factory('0.0.0.0/0').reverse_pointer,
'in-addr.arpa')
self.assertEqual(self.factory('127.0.0.0/8').reverse_pointer,
'127.in-addr.arpa')
self.assertEqual(self.factory('127.12.0.0/16').reverse_pointer,
'12.127.in-addr.arpa')
self.assertEqual(self.factory('127.12.34.0/24').reverse_pointer,
'34.12.127.in-addr.arpa')
self.assertEqual(self.factory('127.12.34.56/32').reverse_pointer,
'56.34.12.127.in-addr.arpa')
with self.assertRaises(ipaddress.NetmaskValueError):
self.factory('127.0.0.0/12').reverse_pointer
with self.assertRaises(ipaddress.NetmaskValueError):
self.factory('127.23.0.0/17').reverse_pointer
class NetmaskTestMixin_v6(CommonTestMixin_v6):
"""Input validation on interfaces and networks is very similar"""
@ -879,6 +897,24 @@ class NetworkTestCase_v6(BaseTestCase, NetmaskTestMixin_v6):
self.factory('2000:aaa::/48').supernet_of(
self.factory('2000:aaa::/56')))
def test_reverse_pointer(self):
self.assertEqual(self.factory('::/0').reverse_pointer, 'ip6.arpa')
self.assertEqual(self.factory('2000::/4').reverse_pointer, '2.ip6.arpa')
self.assertEqual(self.factory('2000::/8').reverse_pointer,
'0.2.ip6.arpa')
self.assertEqual(self.factory('2001:db8::/32').reverse_pointer,
'8.b.d.0.1.0.0.2.ip6.arpa')
self.assertEqual(self.factory('2001:db8:1234:5678::/64').reverse_pointer,
'8.7.6.5.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa')
self.assertEqual(self.factory('2001:db8::1/128').reverse_pointer,
'1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0' +
'.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa')
with self.assertRaises(ipaddress.NetmaskValueError):
self.factory('2000::/7').reverse_pointer
with self.assertRaises(ipaddress.NetmaskValueError):
self.factory('2001:db8::10/127').reverse_pointer
class FactoryFunctionErrors(BaseTestCase):

View file

@ -0,0 +1,2 @@
Fix reverse pointer generation for :class:`ipaddress.IPv4Network` and
:class:`ipaddress.IPv6Network` objects.