Skip to content

Commit 5a5c967

Browse files
committed
gh-74713: Lib/ipaddress: support reverse pointer generation for networks
Before this change, DNS reverse pointer generation for network objects produced incorrect results, such as: >>> ipaddress.IPv4Network('192.168.1.0/24').reverse_pointer '0/24.1.168.192.in-addr.arpa' >>> ipaddress.IPv6Network('2001:db8:1234::/48').reverse_pointer '8.4./.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa' This change introduces a more generalised reverse pointer generation algorithm, suitable for both address and network objects. The same code above, after applying this change: >>> ipaddress.IPv4Network('192.168.1.0/24').reverse_pointer '1.168.192.in-addr.arpa' >>> ipaddress.IPv6Network('2001:db8:1234::/48').reverse_pointer '4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa' Getting a reverse pointer for a network, whose prefix size can't be exactly represented in a reverse pointer record, will raise an exception: >>> ipaddress.IPv4Network('192.168.1.0/28').reverse_pointer ipaddress.NetmaskValueError: Reverse pointer cannot be generated for given prefix size >>> ipaddress.IPv6Network('2001:db8:1234::/50').reverse_pointer ipaddress.NetmaskValueError: Reverse pointer cannot be generated for given prefix size
1 parent 05e89c3 commit 5a5c967

File tree

3 files changed

+58
-6
lines changed

3 files changed

+58
-6
lines changed

Lib/ipaddress.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,13 +1255,20 @@ def _string_from_ip_int(cls, ip_int):
12551255
return '.'.join(map(str, ip_int.to_bytes(4, 'big')))
12561256

12571257
def _reverse_pointer(self):
1258-
"""Return the reverse DNS pointer name for the IPv4 address.
1258+
"""Return the reverse DNS pointer name for the IPv4 address or network.
12591259
12601260
This implements the method described in RFC1035 3.5.
12611261
12621262
"""
1263-
reverse_octets = str(self).split('.')[::-1]
1264-
return '.'.join(reverse_octets) + '.in-addr.arpa'
1263+
prefix = getattr(self, 'prefixlen', IPV4LENGTH)
1264+
octet_count, remainder = divmod(prefix, 8) # each octet is 8 bits long
1265+
if remainder:
1266+
raise NetmaskValueError(
1267+
'Reverse pointer cannot be generated for given prefix size')
1268+
address_exploded = getattr(self, 'network_address', self).exploded
1269+
reverse_octets = address_exploded.split('.')[:octet_count][::-1]
1270+
reverse_octets.append('in-addr.arpa')
1271+
return '.'.join(reverse_octets)
12651272

12661273
class IPv4Address(_BaseV4, _BaseAddress):
12671274

@@ -1870,13 +1877,20 @@ def _explode_shorthand_ip_string(self):
18701877
return ':'.join(parts)
18711878

18721879
def _reverse_pointer(self):
1873-
"""Return the reverse DNS pointer name for the IPv6 address.
1880+
"""Return the reverse DNS pointer name for the IPv6 address or network.
18741881
18751882
This implements the method described in RFC3596 2.5.
18761883
18771884
"""
1878-
reverse_chars = self.exploded[::-1].replace(':', '')
1879-
return '.'.join(reverse_chars) + '.ip6.arpa'
1885+
prefix = getattr(self, 'prefixlen', IPV6LENGTH)
1886+
char_count, remainder = divmod(prefix, 4) # each char is 4 bits long
1887+
if remainder:
1888+
raise NetmaskValueError(
1889+
'Reverse pointer cannot be generated for given prefix size')
1890+
address_exploded = getattr(self, 'network_address', self).exploded
1891+
reverse_chars = list(address_exploded.replace(':', '')[:char_count][::-1])
1892+
reverse_chars.append('ip6.arpa')
1893+
return '.'.join(reverse_chars)
18801894

18811895
@staticmethod
18821896
def _split_scope_id(ip_str):

Lib/test/test_ipaddress.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,24 @@ def test_subnet_of_mixed_types(self):
709709
ipaddress.IPv6Network('::1/128').subnet_of(
710710
ipaddress.IPv4Network('10.0.0.0/30'))
711711

712+
def test_reverse_pointer(self):
713+
self.assertEqual(self.factory('0.0.0.0/0').reverse_pointer,
714+
'in-addr.arpa')
715+
self.assertEqual(self.factory('127.0.0.0/8').reverse_pointer,
716+
'127.in-addr.arpa')
717+
self.assertEqual(self.factory('127.12.0.0/16').reverse_pointer,
718+
'12.127.in-addr.arpa')
719+
self.assertEqual(self.factory('127.12.34.0/24').reverse_pointer,
720+
'34.12.127.in-addr.arpa')
721+
self.assertEqual(self.factory('127.12.34.56/32').reverse_pointer,
722+
'56.34.12.127.in-addr.arpa')
723+
724+
with self.assertRaises(ipaddress.NetmaskValueError):
725+
self.factory('127.0.0.0/12').reverse_pointer
726+
727+
with self.assertRaises(ipaddress.NetmaskValueError):
728+
self.factory('127.23.0.0/17').reverse_pointer
729+
712730

713731
class NetmaskTestMixin_v6(CommonTestMixin_v6):
714732
"""Input validation on interfaces and networks is very similar"""
@@ -865,6 +883,24 @@ def test_supernet_of(self):
865883
self.factory('2000:aaa::/48').supernet_of(
866884
self.factory('2000:aaa::/56')))
867885

886+
def test_reverse_pointer(self):
887+
self.assertEqual(self.factory('::/0').reverse_pointer, 'ip6.arpa')
888+
self.assertEqual(self.factory('2000::/4').reverse_pointer, '2.ip6.arpa')
889+
self.assertEqual(self.factory('2000::/8').reverse_pointer,
890+
'0.2.ip6.arpa')
891+
self.assertEqual(self.factory('2001:db8::/32').reverse_pointer,
892+
'8.b.d.0.1.0.0.2.ip6.arpa')
893+
self.assertEqual(self.factory('2001:db8:1234:5678::/64').reverse_pointer,
894+
'8.7.6.5.4.3.2.1.8.b.d.0.1.0.0.2.ip6.arpa')
895+
self.assertEqual(self.factory('2001:db8::1/128').reverse_pointer,
896+
'1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0' +
897+
'.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa')
898+
899+
with self.assertRaises(ipaddress.NetmaskValueError):
900+
self.factory('2000::/7').reverse_pointer
901+
902+
with self.assertRaises(ipaddress.NetmaskValueError):
903+
self.factory('2001:db8::10/127').reverse_pointer
868904

869905
class FactoryFunctionErrors(BaseTestCase):
870906

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix reverse pointer generation for :class:`ipaddress.IPv4Network` and
2+
:class:`ipaddress.IPv6Network` objects.

0 commit comments

Comments
 (0)