Skip to content

Commit f532fe5

Browse files
authored
bpo-27860: ipaddress: fix Interface constructor (GH-14200)
Interface constructor is documented as accepts address same to Network, but it didn't accept some form of the address. This commit is backport of GH-12836 (commit 6fa84bd)
1 parent b296743 commit f532fe5

File tree

3 files changed

+52
-75
lines changed

3 files changed

+52
-75
lines changed

Lib/ipaddress.py

Lines changed: 34 additions & 74 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

@@ -1381,32 +1405,13 @@ def is_link_local(self):
13811405
class IPv4Interface(IPv4Address):
13821406

13831407
def __init__(self, address):
1384-
if isinstance(address, (bytes, int)):
1385-
IPv4Address.__init__(self, address)
1386-
self.network = IPv4Network(self._ip)
1387-
self._prefixlen = self._max_prefixlen
1388-
return
1389-
1390-
if isinstance(address, tuple):
1391-
IPv4Address.__init__(self, address[0])
1392-
if len(address) > 1:
1393-
self._prefixlen = int(address[1])
1394-
else:
1395-
self._prefixlen = self._max_prefixlen
1396-
1397-
self.network = IPv4Network(address, strict=False)
1398-
self.netmask = self.network.netmask
1399-
self.hostmask = self.network.hostmask
1400-
return
1401-
1402-
addr = _split_optional_netmask(address)
1403-
IPv4Address.__init__(self, addr[0])
1404-
1405-
self.network = IPv4Network(address, strict=False)
1406-
self._prefixlen = self.network._prefixlen
1408+
addr, mask = self._split_addr_prefix(address)
14071409

1410+
IPv4Address.__init__(self, addr)
1411+
self.network = IPv4Network((addr, mask), strict=False)
14081412
self.netmask = self.network.netmask
14091413
self.hostmask = self.network.hostmask
1414+
self._prefixlen = self.network._prefixlen
14101415

14111416
def __str__(self):
14121417
return '%s/%d' % (self._string_from_ip_int(self._ip),
@@ -1511,24 +1516,9 @@ def __init__(self, address, strict=True):
15111516
an IPv4 address.
15121517
ValueError: If strict is True and a network address is not
15131518
supplied.
1514-
15151519
"""
15161520
_BaseNetwork.__init__(self, address)
1517-
1518-
# Constructing from a packed address or integer
1519-
if isinstance(address, (int, bytes)):
1520-
addr = address
1521-
mask = self._max_prefixlen
1522-
# Constructing from a tuple (addr, [mask])
1523-
elif isinstance(address, tuple):
1524-
addr = address[0]
1525-
mask = address[1] if len(address) > 1 else self._max_prefixlen
1526-
# Assume input argument to be string or any object representation
1527-
# which converts into a formatted IP prefix string.
1528-
else:
1529-
args = _split_optional_netmask(address)
1530-
addr = self._ip_int_from_string(args[0])
1531-
mask = args[1] if len(args) == 2 else self._max_prefixlen
1521+
addr, mask = self._split_addr_prefix(address)
15321522

15331523
self.network_address = IPv4Address(addr)
15341524
self.netmask, self._prefixlen = self._make_netmask(mask)
@@ -2061,28 +2051,13 @@ def sixtofour(self):
20612051
class IPv6Interface(IPv6Address):
20622052

20632053
def __init__(self, address):
2064-
if isinstance(address, (bytes, int)):
2065-
IPv6Address.__init__(self, address)
2066-
self.network = IPv6Network(self._ip)
2067-
self._prefixlen = self._max_prefixlen
2068-
return
2069-
if isinstance(address, tuple):
2070-
IPv6Address.__init__(self, address[0])
2071-
if len(address) > 1:
2072-
self._prefixlen = int(address[1])
2073-
else:
2074-
self._prefixlen = self._max_prefixlen
2075-
self.network = IPv6Network(address, strict=False)
2076-
self.netmask = self.network.netmask
2077-
self.hostmask = self.network.hostmask
2078-
return
2054+
addr, mask = self._split_addr_prefix(address)
20792055

2080-
addr = _split_optional_netmask(address)
2081-
IPv6Address.__init__(self, addr[0])
2082-
self.network = IPv6Network(address, strict=False)
2056+
IPv6Address.__init__(self, addr)
2057+
self.network = IPv6Network((addr, mask), strict=False)
20832058
self.netmask = self.network.netmask
2084-
self._prefixlen = self.network._prefixlen
20852059
self.hostmask = self.network.hostmask
2060+
self._prefixlen = self.network._prefixlen
20862061

20872062
def __str__(self):
20882063
return '%s/%d' % (self._string_from_ip_int(self._ip),
@@ -2191,24 +2166,9 @@ def __init__(self, address, strict=True):
21912166
an IPv6 address.
21922167
ValueError: If strict was True and a network address was not
21932168
supplied.
2194-
21952169
"""
21962170
_BaseNetwork.__init__(self, address)
2197-
2198-
# Constructing from a packed address or integer
2199-
if isinstance(address, (int, bytes)):
2200-
addr = address
2201-
mask = self._max_prefixlen
2202-
# Constructing from a tuple (addr, [mask])
2203-
elif isinstance(address, tuple):
2204-
addr = address[0]
2205-
mask = address[1] if len(address) > 1 else self._max_prefixlen
2206-
# Assume input argument to be string or any object representation
2207-
# which converts into a formatted IP prefix string.
2208-
else:
2209-
args = _split_optional_netmask(address)
2210-
addr = self._ip_int_from_string(args[0])
2211-
mask = args[1] if len(args) == 2 else self._max_prefixlen
2171+
addr, mask = self._split_addr_prefix(address)
22122172

22132173
self.network_address = IPv6Address(addr)
22142174
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
@@ -405,7 +405,13 @@ class NetmaskTestMixin_v4(CommonTestMixin_v4):
405405
"""Input validation on interfaces and networks is very similar"""
406406

407407
def test_no_mask(self):
408-
self.assertEqual(str(self.factory('1.2.3.4')), '1.2.3.4/32')
408+
for address in ('1.2.3.4', 0x01020304, b'\x01\x02\x03\x04'):
409+
net = self.factory(address)
410+
self.assertEqual(str(net), '1.2.3.4/32')
411+
self.assertEqual(str(net.netmask), '255.255.255.255')
412+
self.assertEqual(str(net.hostmask), '0.0.0.0')
413+
# IPv4Network has prefixlen, but IPv4Interface doesn't.
414+
# Should we add it to IPv4Interface too? (bpo-36392)
409415

410416
def test_split_netmask(self):
411417
addr = "1.2.3.4/32/24"
@@ -541,6 +547,15 @@ def test_subnet_of_mixed_types(self):
541547
class NetmaskTestMixin_v6(CommonTestMixin_v6):
542548
"""Input validation on interfaces and networks is very similar"""
543549

550+
def test_no_mask(self):
551+
for address in ('::1', 1, b'\x00'*15 + b'\x01'):
552+
net = self.factory(address)
553+
self.assertEqual(str(net), '::1/128')
554+
self.assertEqual(str(net.netmask), 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')
555+
self.assertEqual(str(net.hostmask), '::')
556+
# IPv6Network has prefixlen, but IPv6Interface doesn't.
557+
# Should we add it to IPv4Interface too? (bpo-36392)
558+
544559
def test_split_netmask(self):
545560
addr = "cafe:cafe::/128/190"
546561
with self.assertAddressError("Only one '/' permitted in %r" % addr):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix ``IPv4Interface`` and ``IPv6Interface`` didn't accept string mask when
2+
the argument is tuple.

0 commit comments

Comments
 (0)