From 650b31f8eac66b4892716377a6abbfcd23ad53fe Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Tue, 21 Mar 2023 09:55:08 -0400 Subject: [PATCH] Add netutils.get_my_ipv6() Analog to netutils.get_my_ipv4(), except will return an IPv6 address on the local host. Change-Id: I26812ca6eaaeff70796bafa9f735d15d80f1bc30 --- oslo_utils/netutils.py | 46 +++++++++++++++++ oslo_utils/tests/test_netutils.py | 49 ++++++++++++------- ...netutils-get_my_ipv6-c9f124374655326b.yaml | 5 ++ 3 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 releasenotes/notes/netutils-get_my_ipv6-c9f124374655326b.yaml diff --git a/oslo_utils/netutils.py b/oslo_utils/netutils.py index 19f54e73..ecbe7796 100644 --- a/oslo_utils/netutils.py +++ b/oslo_utils/netutils.py @@ -414,6 +414,52 @@ def _get_my_ipv4_address(): return LOCALHOST +def get_my_ipv6(): + """Returns the actual IPv6 address of the local machine. + + This code figures out what source address would be used if some traffic + were to be sent out to some well known address on the Internet. In this + case, IPv6 from RFC3849 is used, but the specific address does not + matter much. No traffic is actually sent. + + .. versionadded:: 6.1 + Return ``'::1'`` if there is no default interface. + """ + try: + csock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + csock.connect(('2001:db8::1', 80)) + (addr, _, _, _) = csock.getsockname() + csock.close() + return addr + except socket.error: + return _get_my_ipv6_address() + + +def _get_my_ipv6_address(): + """Figure out the best IPv6 address + """ + LOCALHOST = '::1' + gtw = netifaces.gateways() + try: + interface = gtw['default'][netifaces.AF_INET6][1] + except (KeyError, IndexError): + LOG.info('Could not determine default network interface, ' + 'using %s for IPv6 address', LOCALHOST) + return LOCALHOST + + try: + return netifaces.ifaddresses(interface)[netifaces.AF_INET6][0]['addr'] + except (KeyError, IndexError): + LOG.info('Could not determine IPv6 address for interface ' + '%(interface)s, using %(address)s', + {'interface': interface, 'address': LOCALHOST}) + except Exception as e: + LOG.info('Could not determine IPv6 address for ' + 'interface %(interface)s: %(error)s', + {'interface': interface, 'error': e}) + return LOCALHOST + + class _ModifiedSplitResult(parse.SplitResult): """Split results class for urlsplit.""" diff --git a/oslo_utils/tests/test_netutils.py b/oslo_utils/tests/test_netutils.py index eb22b2c1..82212991 100644 --- a/oslo_utils/tests/test_netutils.py +++ b/oslo_utils/tests/test_netutils.py @@ -276,13 +276,21 @@ class NetworkUtilsTest(test_base.BaseTestCase): for input_str in invalid_inputs: self.assertFalse(netutils.is_valid_port(input_str)) - def test_get_my_ip(self): + def test_get_my_ipv4(self): sock_attrs = { 'return_value.getsockname.return_value': ['1.2.3.4', '']} with mock.patch('socket.socket', **sock_attrs): addr = netutils.get_my_ipv4() self.assertEqual(addr, '1.2.3.4') + def test_get_my_ipv6(self): + sock_attrs = { + 'return_value.getsockname.return_value': ['2001:db8::2', '', + '', '']} + with mock.patch('socket.socket', **sock_attrs): + addr = netutils.get_my_ipv6() + self.assertEqual(addr, '2001:db8::2') + def test_is_int_in_range(self): valid_inputs = [(1, -100, 100), ('1', -100, 100), @@ -323,37 +331,44 @@ class NetworkUtilsTest(test_base.BaseTestCase): @mock.patch('socket.socket') @mock.patch('oslo_utils.netutils._get_my_ipv4_address') - def test_get_my_ip_socket_error(self, ip, mock_socket): + def test_get_my_ipv4_socket_error(self, ip, mock_socket): mock_socket.side_effect = socket.error ip.return_value = '1.2.3.4' addr = netutils.get_my_ipv4() self.assertEqual(addr, '1.2.3.4') - @mock.patch('netifaces.gateways') - @mock.patch('netifaces.ifaddresses') - def test_get_my_ipv4_address_with_default_route( - self, ifaddr, gateways): - with mock.patch.dict(netifaces.__dict__, {'AF_INET': '0'}): - ifaddr.return_value = {'0': [{'addr': '172.18.204.1'}]} - addr = netutils._get_my_ipv4_address() - self.assertEqual('172.18.204.1', addr) + @mock.patch('socket.socket') + @mock.patch('oslo_utils.netutils._get_my_ipv6_address') + def test_get_my_ipv6_socket_error(self, ip, mock_socket): + mock_socket.side_effect = socket.error + ip.return_value = '2001:db8::2' + addr = netutils.get_my_ipv6() + self.assertEqual(addr, '2001:db8::2') @mock.patch('netifaces.gateways') @mock.patch('netifaces.ifaddresses') - def test_get_my_ipv4_address_without_default_route( + def test_get_my_ip_address_with_default_route( self, ifaddr, gateways): - with mock.patch.dict(netifaces.__dict__, {'AF_INET': '0'}): - ifaddr.return_value = {} - addr = netutils._get_my_ipv4_address() - self.assertEqual('127.0.0.1', addr) + ifaddr.return_value = {netifaces.AF_INET: [{'addr': '172.18.204.1'}], + netifaces.AF_INET6: [{'addr': '2001:db8::2'}]} + self.assertEqual('172.18.204.1', netutils._get_my_ipv4_address()) + self.assertEqual('2001:db8::2', netutils._get_my_ipv6_address()) + + @mock.patch('netifaces.gateways') + @mock.patch('netifaces.ifaddresses') + def test_get_my_ip_address_without_default_route( + self, ifaddr, gateways): + ifaddr.return_value = {} + self.assertEqual('127.0.0.1', netutils._get_my_ipv4_address()) + self.assertEqual('::1', netutils._get_my_ipv6_address()) @mock.patch('netifaces.gateways') @mock.patch('netifaces.ifaddresses') def test_get_my_ipv4_address_without_default_interface( self, ifaddr, gateways): gateways.return_value = {} - addr = netutils._get_my_ipv4_address() - self.assertEqual('127.0.0.1', addr) + self.assertEqual('127.0.0.1', netutils._get_my_ipv4_address()) + self.assertEqual('::1', netutils._get_my_ipv6_address()) self.assertFalse(ifaddr.called) diff --git a/releasenotes/notes/netutils-get_my_ipv6-c9f124374655326b.yaml b/releasenotes/notes/netutils-get_my_ipv6-c9f124374655326b.yaml new file mode 100644 index 00000000..c1e3b5a5 --- /dev/null +++ b/releasenotes/notes/netutils-get_my_ipv6-c9f124374655326b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + New method ``netutils.get_my_ipv6()`` returns the IPv6 address + of the local machine.