Merge ~mpontillo/maas:workaround-netaddr-slash128-bug--2.4 into maas:2.4

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: 9738651ab460b6e6eb2d53216dc4074554fa3397
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~mpontillo/maas:workaround-netaddr-slash128-bug--2.4
Merge into: maas:2.4
Diff against target: 92 lines (+56/-8)
2 files modified
src/provisioningserver/utils/network.py (+29/-8)
src/provisioningserver/utils/tests/test_network.py (+27/-0)
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+354010@code.launchpad.net

Commit message

Backport 80dcada - LP: #1789721 - Workaround for StopIteration seen via get_source_address().

This works around a bug in netaddr that causes StopIteration to be raised when a /128 IPv6 network is passed in.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Self-approve backport.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/provisioningserver/utils/network.py b/src/provisioningserver/utils/network.py
index 6093871..06ed74e 100644
--- a/src/provisioningserver/utils/network.py
+++ b/src/provisioningserver/utils/network.py
@@ -1421,20 +1421,41 @@ def coerce_to_valid_hostname(hostname):
1421 return hostname1421 return hostname
14221422
14231423
1424def get_source_address(destination_ip: IPAddressOrNetwork):1424def get_source_address(destination: IPAddressOrNetwork):
1425 """Returns the local source address for the specified destination IP.1425 """Returns the local source address for the specified destination.
14261426
1427 :param destination_ip: Can be an IP address in string format, an IPNetwork,1427 :param destination: Can be an IP address in string format, an IPNetwork,
1428 or an IPAddress object.1428 or an IPAddress object.
1429 :return: the string representation of the local IP address that would be1429 :return: the string representation of the local IP address that would be
1430 used for communication with the specified destination.1430 used for communication with the specified destination.
1431 """1431 """
1432 if isinstance(destination_ip, IPNetwork):1432 if isinstance(destination, IPNetwork):
1433 destination_ip = IPAddress(next(destination_ip.iter_hosts()))1433 if destination.prefixlen == 128:
1434 # LP: #1789721 - netaddr raises an exception when using
1435 # iter_hosts() with a /128 address.
1436 destination = destination.ip
1437 else:
1438 # Make sure to return a host (not a network) if possible.
1439 # Using iter_hosts() here is a general way to accomplish that.
1440 destination = IPAddress(next(destination.iter_hosts()))
1434 else:1441 else:
1435 destination_ip = make_ipaddress(destination_ip)1442 destination = make_ipaddress(destination)
1436 if destination_ip.is_ipv4_mapped():1443 if destination.is_ipv4_mapped():
1437 destination_ip = destination_ip.ipv4()1444 destination = destination.ipv4()
1445 return get_source_address_for_ipaddress(destination)
1446
1447
1448def get_source_address_for_ipaddress(destination_ip: IPAddress):
1449 """Returns the local source address for the specified IPAddress.
1450
1451 Callers should generally use the `get_source_address()` utility instead;
1452 it is more flexible about the type of the input parameter.
1453
1454 :param destination_ip: Can be an IP address in string format, an IPNetwork,
1455 or an IPAddress object.
1456 :return: the string representation of the local IP address that would be
1457 used for communication with the specified destination.
1458 """
1438 af = AF_INET if destination_ip.version == 4 else AF_INET61459 af = AF_INET if destination_ip.version == 4 else AF_INET6
1439 with socket.socket(af, socket.SOCK_DGRAM) as sock:1460 with socket.socket(af, socket.SOCK_DGRAM) as sock:
1440 peername = str(destination_ip)1461 peername = str(destination_ip)
diff --git a/src/provisioningserver/utils/tests/test_network.py b/src/provisioningserver/utils/tests/test_network.py
index f23f31b..b7472ef 100644
--- a/src/provisioningserver/utils/tests/test_network.py
+++ b/src/provisioningserver/utils/tests/test_network.py
@@ -2294,6 +2294,33 @@ class TestGetSourceAddress(MAASTestCase):
2294 self.assertThat(2294 self.assertThat(
2295 get_source_address(IPNetwork("127.0.0.1/8")), Equals("127.0.0.1"))2295 get_source_address(IPNetwork("127.0.0.1/8")), Equals("127.0.0.1"))
22962296
2297 def test__ipnetwork_works_for_ipv4_slash32(self):
2298 mock = self.patch(network_module, 'get_source_address_for_ipaddress')
2299 mock.return_value = "10.0.0.1"
2300 self.assertThat(
2301 get_source_address(IPNetwork("10.0.0.1/32")), Equals("10.0.0.1"))
2302
2303 def test__ipnetwork_works_for_ipv6_slash128(self):
2304 mock = self.patch(network_module, 'get_source_address_for_ipaddress')
2305 mock.return_value = "2001:67c:1560::1"
2306 self.assertThat(
2307 get_source_address(
2308 IPNetwork("2001:67c:1560::/128")), Equals("2001:67c:1560::1"))
2309
2310 def test__ipnetwork_works_for_ipv6_slash48(self):
2311 mock = self.patch(network_module, 'get_source_address_for_ipaddress')
2312 mock.return_value = "2001:67c:1560::1"
2313 self.assertThat(
2314 get_source_address(
2315 IPNetwork("2001:67c:1560::/48")), Equals("2001:67c:1560::1"))
2316
2317 def test__ipnetwork_works_for_ipv6_slash64(self):
2318 mock = self.patch(network_module, 'get_source_address_for_ipaddress')
2319 mock.return_value = "2001:67c:1560::1"
2320 self.assertThat(
2321 get_source_address(
2322 IPNetwork("2001:67c:1560::1/64")), Equals("2001:67c:1560::1"))
2323
2297 def test__accepts_ipaddress(self):2324 def test__accepts_ipaddress(self):
2298 self.assertThat(2325 self.assertThat(
2299 get_source_address(IPAddress("127.0.0.1")), Equals("127.0.0.1"))2326 get_source_address(IPAddress("127.0.0.1")), Equals("127.0.0.1"))

Subscribers

People subscribed via source and target branches