Skip to content

Commit 6fa84bd

Browse files
authored
bpo-27860: ipaddress: fix Interface missed some attributes (GH-12836)
IPv4Interface and IPv6Interface did not has netmask and hostmask attributes when its argument is bytes or int. This commit extracts method for constructors of Network and Interface, and ensure Interface class always provides them.
1 parent 74125a6 commit 6fa84bd

File tree

2 files changed

+56
-71
lines changed

2 files changed

+56
-71
lines changed

Lib/ipaddress.py

Lines changed: 40 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,30 @@ def _prefix_from_ip_string(cls, ip_str):
532532
except ValueError:
533533
cls._report_invalid_netmask(ip_str)
534534

535+
@classmethod
536+
def _split_addr_prefix(cls, address):
537+
"""Helper function to parse address of Network/Interface.
538+
539+
Arg:
540+
address: Argument of Network/Interface.
541+
542+
Returns:
543+
(addr, prefix) tuple.
544+
"""
545+
# a packed address or integer
546+
if isinstance(address, (bytes, int)):
547+
return address, cls._max_prefixlen
548+
549+
if not isinstance(address, tuple):
550+
# Assume input argument to be string or any object representation
551+
# which converts into a formatted IP prefix string.
552+
address = _split_optional_netmask(address)
553+
554+
# Constructing from a tuple (addr, [mask])
555+
if len(address) > 1:
556+
return address
557+
return address[0], cls._max_prefixlen
558+
535559
def __reduce__(self):
536560
return self.__class__, (str(self),)
537561

@@ -1305,32 +1329,16 @@ def is_link_local(self):
13051329
class IPv4Interface(IPv4Address):
13061330

13071331
def __init__(self, address):
1308-
if isinstance(address, (bytes, int)):
1309-
IPv4Address.__init__(self, address)
1310-
self.network = IPv4Network(self._ip)
1311-
self._prefixlen = self._max_prefixlen
1312-
return
1313-
1314-
if isinstance(address, tuple):
1315-
IPv4Address.__init__(self, address[0])
1316-
if len(address) > 1:
1317-
self._prefixlen = int(address[1])
1318-
else:
1319-
self._prefixlen = self._max_prefixlen
1320-
1321-
self.network = IPv4Network(address, strict=False)
1322-
self.netmask = self.network.netmask
1323-
self.hostmask = self.network.hostmask
1324-
return
1325-
1326-
addr = _split_optional_netmask(address)
1327-
IPv4Address.__init__(self, addr[0])
1332+
addr, mask = self._split_addr_prefix(address)
13281333

1329-
self.network = IPv4Network(address, strict=False)
1334+
IPv4Address.__init__(self, addr)
1335+
self.network = IPv4Network((addr, mask), strict=False)
1336+
self.netmask = self.network.netmask
13301337
self._prefixlen = self.network._prefixlen
13311338

1332-
self.netmask = self.network.netmask
1333-
self.hostmask = self.network.hostmask
1339+
@functools.cached_property
1340+
def hostmask(self):
1341+
return self.network.hostmask
13341342

13351343
def __str__(self):
13361344
return '%s/%d' % (self._string_from_ip_int(self._ip),
@@ -1435,20 +1443,7 @@ def __init__(self, address, strict=True):
14351443
ValueError: If strict is True and a network address is not
14361444
supplied.
14371445
"""
1438-
# Constructing from a packed address or integer
1439-
if isinstance(address, (int, bytes)):
1440-
addr = address
1441-
mask = self._max_prefixlen
1442-
# Constructing from a tuple (addr, [mask])
1443-
elif isinstance(address, tuple):
1444-
addr = address[0]
1445-
mask = address[1] if len(address) > 1 else self._max_prefixlen
1446-
# Assume input argument to be string or any object representation
1447-
# which converts into a formatted IP prefix string.
1448-
else:
1449-
args = _split_optional_netmask(address)
1450-
addr = self._ip_int_from_string(args[0])
1451-
mask = args[1] if len(args) == 2 else self._max_prefixlen
1446+
addr, mask = self._split_addr_prefix(address)
14521447

14531448
self.network_address = IPv4Address(addr)
14541449
self.netmask, self._prefixlen = self._make_netmask(mask)
@@ -1979,28 +1974,16 @@ def sixtofour(self):
19791974
class IPv6Interface(IPv6Address):
19801975

19811976
def __init__(self, address):
1982-
if isinstance(address, (bytes, int)):
1983-
IPv6Address.__init__(self, address)
1984-
self.network = IPv6Network(self._ip)
1985-
self._prefixlen = self._max_prefixlen
1986-
return
1987-
if isinstance(address, tuple):
1988-
IPv6Address.__init__(self, address[0])
1989-
if len(address) > 1:
1990-
self._prefixlen = int(address[1])
1991-
else:
1992-
self._prefixlen = self._max_prefixlen
1993-
self.network = IPv6Network(address, strict=False)
1994-
self.netmask = self.network.netmask
1995-
self.hostmask = self.network.hostmask
1996-
return
1977+
addr, mask = self._split_addr_prefix(address)
19971978

1998-
addr = _split_optional_netmask(address)
1999-
IPv6Address.__init__(self, addr[0])
2000-
self.network = IPv6Network(address, strict=False)
1979+
IPv6Address.__init__(self, addr)
1980+
self.network = IPv6Network((addr, mask), strict=False)
20011981
self.netmask = self.network.netmask
20021982
self._prefixlen = self.network._prefixlen
2003-
self.hostmask = self.network.hostmask
1983+
1984+
@functools.cached_property
1985+
def hostmask(self):
1986+
return self.network.hostmask
20041987

20051988
def __str__(self):
20061989
return '%s/%d' % (self._string_from_ip_int(self._ip),
@@ -2110,20 +2093,7 @@ def __init__(self, address, strict=True):
21102093
ValueError: If strict was True and a network address was not
21112094
supplied.
21122095
"""
2113-
# Constructing from a packed address or integer
2114-
if isinstance(address, (int, bytes)):
2115-
addr = address
2116-
mask = self._max_prefixlen
2117-
# Constructing from a tuple (addr, [mask])
2118-
elif isinstance(address, tuple):
2119-
addr = address[0]
2120-
mask = address[1] if len(address) > 1 else self._max_prefixlen
2121-
# Assume input argument to be string or any object representation
2122-
# which converts into a formatted IP prefix string.
2123-
else:
2124-
args = _split_optional_netmask(address)
2125-
addr = self._ip_int_from_string(args[0])
2126-
mask = args[1] if len(args) == 2 else self._max_prefixlen
2096+
addr, mask = self._split_addr_prefix(address)
21272097

21282098
self.network_address = IPv6Address(addr)
21292099
self.netmask, self._prefixlen = self._make_netmask(mask)

Lib/test/test_ipaddress.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,13 @@ class NetmaskTestMixin_v4(CommonTestMixin_v4):
399399
"""Input validation on interfaces and networks is very similar"""
400400

401401
def test_no_mask(self):
402-
self.assertEqual(str(self.factory('1.2.3.4')), '1.2.3.4/32')
402+
for address in ('1.2.3.4', 0x01020304, b'\x01\x02\x03\x04'):
403+
net = self.factory(address)
404+
self.assertEqual(str(net), '1.2.3.4/32')
405+
self.assertEqual(str(net.netmask), '255.255.255.255')
406+
self.assertEqual(str(net.hostmask), '0.0.0.0')
407+
# IPv4Network has prefixlen, but IPv4Interface doesn't.
408+
# Should we add it to IPv4Interface too? (bpo-36392)
403409

404410
def test_split_netmask(self):
405411
addr = "1.2.3.4/32/24"
@@ -527,6 +533,15 @@ def test_subnet_of_mixed_types(self):
527533
class NetmaskTestMixin_v6(CommonTestMixin_v6):
528534
"""Input validation on interfaces and networks is very similar"""
529535

536+
def test_no_mask(self):
537+
for address in ('::1', 1, b'\x00'*15 + b'\x01'):
538+
net = self.factory(address)
539+
self.assertEqual(str(net), '::1/128')
540+
self.assertEqual(str(net.netmask), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')
541+
self.assertEqual(str(net.hostmask), '::')
542+
# IPv6Network has prefixlen, but IPv6Interface doesn't.
543+
# Should we add it to IPv4Interface too? (bpo-36392)
544+
530545
def test_split_netmask(self):
531546
addr = "cafe:cafe::/128/190"
532547
with self.assertAddressError("Only one '/' permitted in %r" % addr):

0 commit comments

Comments
 (0)