9d9818bf48
For a network using IPv6 SLAAC, Neutron would calculate the port addresses based on EUI-64 specification which is generated via IPv6 prefix and the interface MAC address. EUI-64 is a standard algorithm and is explained at the following url http://packetlife.net/blog/2008/aug/4/eui-64-ipv6 This utility api is present in both Neutron [1] and Tempest [2] to validate certain IPv6 scenarios. Hence, having it in oslo.utils would be ideal. [1] - https://github.com/openstack/neutron/blob/master/neutron/common/ipv6_utils.py#L32 [2] - https://github.com/openstack/tempest/blob/master/tempest/common/utils/data_utils.py#L85 Change-Id: I3d1c6b22ecddf7faad83cc12c674a9c5c96b1759
343 lines
11 KiB
Python
343 lines
11 KiB
Python
# Copyright 2012 OpenStack Foundation.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
Network-related utilities and helper functions.
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import socket
|
|
|
|
import netaddr
|
|
import netifaces
|
|
from six.moves.urllib import parse
|
|
|
|
from oslo_utils._i18n import _
|
|
from oslo_utils._i18n import _LI
|
|
from oslo_utils._i18n import _LW
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
_IS_IPV6_ENABLED = None
|
|
|
|
|
|
def parse_host_port(address, default_port=None):
|
|
"""Interpret a string as a host:port pair.
|
|
|
|
An IPv6 address MUST be escaped if accompanied by a port,
|
|
because otherwise ambiguity ensues: 2001:db8:85a3::8a2e:370:7334
|
|
means both [2001:db8:85a3::8a2e:370:7334] and
|
|
[2001:db8:85a3::8a2e:370]:7334.
|
|
|
|
>>> parse_host_port('server01:80')
|
|
('server01', 80)
|
|
>>> parse_host_port('server01')
|
|
('server01', None)
|
|
>>> parse_host_port('server01', default_port=1234)
|
|
('server01', 1234)
|
|
>>> parse_host_port('[::1]:80')
|
|
('::1', 80)
|
|
>>> parse_host_port('[::1]')
|
|
('::1', None)
|
|
>>> parse_host_port('[::1]', default_port=1234)
|
|
('::1', 1234)
|
|
>>> parse_host_port('2001:db8:85a3::8a2e:370:7334', default_port=1234)
|
|
('2001:db8:85a3::8a2e:370:7334', 1234)
|
|
>>> parse_host_port(None)
|
|
(None, None)
|
|
"""
|
|
if not address:
|
|
return (None, None)
|
|
|
|
if address[0] == '[':
|
|
# Escaped ipv6
|
|
_host, _port = address[1:].split(']')
|
|
host = _host
|
|
if ':' in _port:
|
|
port = _port.split(':')[1]
|
|
else:
|
|
port = default_port
|
|
else:
|
|
if address.count(':') == 1:
|
|
host, port = address.split(':')
|
|
else:
|
|
# 0 means ipv4, >1 means ipv6.
|
|
# We prohibit unescaped ipv6 addresses with port.
|
|
host = address
|
|
port = default_port
|
|
|
|
return (host, None if port is None else int(port))
|
|
|
|
|
|
def is_valid_ipv4(address):
|
|
"""Verify that address represents a valid IPv4 address.
|
|
|
|
:param address: Value to verify
|
|
:type address: string
|
|
:returns: bool
|
|
"""
|
|
try:
|
|
return netaddr.valid_ipv4(address)
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def is_valid_ipv6(address):
|
|
"""Verify that address represents a valid IPv6 address.
|
|
|
|
:param address: Value to verify
|
|
:type address: string
|
|
:returns: bool
|
|
"""
|
|
try:
|
|
return netaddr.valid_ipv6(address)
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def get_ipv6_addr_by_EUI64(prefix, mac):
|
|
"""Calculate IPv6 address using EUI-64 specification.
|
|
|
|
This method calculates the IPv6 address using the EUI-64
|
|
addressing scheme as explained in rfc2373.
|
|
|
|
:param prefix: IPv6 prefix.
|
|
:param mac: IEEE 802 48-bit MAC address.
|
|
:returns: IPv6 address on success.
|
|
:raises ValueError, TypeError: For any invalid input.
|
|
"""
|
|
# Check if the prefix is an IPv4 address
|
|
if netaddr.valid_ipv4(prefix):
|
|
msg = _("Unable to generate IP address by EUI64 for IPv4 prefix")
|
|
raise ValueError(msg)
|
|
try:
|
|
eui64 = int(netaddr.EUI(mac).eui64())
|
|
prefix = netaddr.IPNetwork(prefix)
|
|
return netaddr.IPAddress(prefix.first + eui64 ^ (1 << 57))
|
|
except (ValueError, netaddr.AddrFormatError):
|
|
raise ValueError(_('Bad prefix or mac format for generating IPv6 '
|
|
'address by EUI-64: %(prefix)s, %(mac)s:')
|
|
% {'prefix': prefix, 'mac': mac})
|
|
except TypeError:
|
|
raise TypeError(_('Bad prefix type for generating IPv6 address by '
|
|
'EUI-64: %s') % prefix)
|
|
|
|
|
|
def is_ipv6_enabled():
|
|
"""Check if IPv6 support is enabled on the platform.
|
|
|
|
This api will look into the proc entries of the platform to figure
|
|
out the status of IPv6 support on the platform.
|
|
|
|
:returns: True if the platform has IPv6 support, False otherwise.
|
|
|
|
"""
|
|
|
|
global _IS_IPV6_ENABLED
|
|
|
|
if _IS_IPV6_ENABLED is None:
|
|
disabled_ipv6_path = "/proc/sys/net/ipv6/conf/default/disable_ipv6"
|
|
if os.path.exists(disabled_ipv6_path):
|
|
with open(disabled_ipv6_path, 'r') as f:
|
|
disabled = f.read().strip()
|
|
_IS_IPV6_ENABLED = disabled == "0"
|
|
else:
|
|
_IS_IPV6_ENABLED = False
|
|
return _IS_IPV6_ENABLED
|
|
|
|
|
|
def is_valid_ip(address):
|
|
"""Verify that address represents a valid IP address.
|
|
|
|
:param address: Value to verify
|
|
:type address: string
|
|
:returns: bool
|
|
"""
|
|
return is_valid_ipv4(address) or is_valid_ipv6(address)
|
|
|
|
|
|
def is_valid_port(port):
|
|
"""Verify that port represents a valid port number."""
|
|
try:
|
|
val = int(port)
|
|
except (ValueError, TypeError):
|
|
return False
|
|
|
|
return (val > 0 and val <= 65535)
|
|
|
|
|
|
def get_my_ipv4():
|
|
"""Returns the actual ipv4 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, IP from RFC5737 is used, but the specific address does not
|
|
matter much. No traffic is actually sent.
|
|
"""
|
|
try:
|
|
csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
csock.connect(('192.0.2.0', 80))
|
|
(addr, port) = csock.getsockname()
|
|
csock.close()
|
|
return addr
|
|
except socket.error:
|
|
return _get_my_ipv4_address()
|
|
|
|
|
|
def _get_my_ipv4_address():
|
|
"""Figure out the best ipv4
|
|
"""
|
|
LOCALHOST = '127.0.0.1'
|
|
gtw = netifaces.gateways()
|
|
try:
|
|
interface = gtw['default'][netifaces.AF_INET][1]
|
|
except (KeyError, IndexError):
|
|
LOG.info(_LI('Could not determine default network interface, '
|
|
'using 127.0.0.1 for IPv4 address'))
|
|
return LOCALHOST
|
|
|
|
try:
|
|
return netifaces.ifaddresses(interface)[netifaces.AF_INET][0]['addr']
|
|
except (KeyError, IndexError):
|
|
LOG.info(_LI('Could not determine IPv4 address for interface %s, '
|
|
'using 127.0.0.1'),
|
|
interface)
|
|
except Exception as e:
|
|
LOG.info(_LI('Could not determine IPv4 address for '
|
|
'interface %(interface)s: %(error)s'),
|
|
{'interface': interface, 'error': e})
|
|
return LOCALHOST
|
|
|
|
|
|
class _ModifiedSplitResult(parse.SplitResult):
|
|
"""Split results class for urlsplit."""
|
|
|
|
# NOTE(dims): The functions below are needed for Python 2.6.x.
|
|
# We can remove these when we drop support for 2.6.x.
|
|
@property
|
|
def hostname(self):
|
|
netloc = self.netloc.split('@', 1)[-1]
|
|
host, port = parse_host_port(netloc)
|
|
return host
|
|
|
|
@property
|
|
def port(self):
|
|
netloc = self.netloc.split('@', 1)[-1]
|
|
host, port = parse_host_port(netloc)
|
|
return port
|
|
|
|
def params(self, collapse=True):
|
|
"""Extracts the query parameters from the split urls components.
|
|
|
|
This method will provide back as a dictionary the query parameter
|
|
names and values that were provided in the url.
|
|
|
|
:param collapse: Boolean, turn on or off collapsing of query values
|
|
with the same name. Since a url can contain the same query parameter
|
|
name with different values it may or may not be useful for users to
|
|
care that this has happened. This parameter when True uses the
|
|
last value that was given for a given name, while if False it will
|
|
retain all values provided by associating the query parameter name with
|
|
a list of values instead of a single (non-list) value.
|
|
"""
|
|
if self.query:
|
|
if collapse:
|
|
return dict(parse.parse_qsl(self.query))
|
|
else:
|
|
params = {}
|
|
for (key, value) in parse.parse_qsl(self.query):
|
|
if key in params:
|
|
if isinstance(params[key], list):
|
|
params[key].append(value)
|
|
else:
|
|
params[key] = [params[key], value]
|
|
else:
|
|
params[key] = value
|
|
return params
|
|
else:
|
|
return {}
|
|
|
|
|
|
def urlsplit(url, scheme='', allow_fragments=True):
|
|
"""Parse a URL using urlparse.urlsplit(), splitting query and fragments.
|
|
This function papers over Python issue9374_ when needed.
|
|
|
|
.. _issue9374: http://bugs.python.org/issue9374
|
|
|
|
The parameters are the same as urlparse.urlsplit.
|
|
"""
|
|
scheme, netloc, path, query, fragment = parse.urlsplit(
|
|
url, scheme, allow_fragments)
|
|
if allow_fragments and '#' in path:
|
|
path, fragment = path.split('#', 1)
|
|
if '?' in path:
|
|
path, query = path.split('?', 1)
|
|
return _ModifiedSplitResult(scheme, netloc,
|
|
path, query, fragment)
|
|
|
|
|
|
def set_tcp_keepalive(sock, tcp_keepalive=True,
|
|
tcp_keepidle=None,
|
|
tcp_keepalive_interval=None,
|
|
tcp_keepalive_count=None):
|
|
"""Set values for tcp keepalive parameters
|
|
|
|
This function configures tcp keepalive parameters if users wish to do
|
|
so.
|
|
|
|
:param tcp_keepalive: Boolean, turn on or off tcp_keepalive. If users are
|
|
not sure, this should be True, and default values will be used.
|
|
|
|
:param tcp_keepidle: time to wait before starting to send keepalive probes
|
|
:param tcp_keepalive_interval: time between successive probes, once the
|
|
initial wait time is over
|
|
:param tcp_keepalive_count: number of probes to send before the connection
|
|
is killed
|
|
"""
|
|
|
|
# NOTE(praneshp): Despite keepalive being a tcp concept, the level is
|
|
# still SOL_SOCKET. This is a quirk.
|
|
if isinstance(tcp_keepalive, bool):
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, tcp_keepalive)
|
|
else:
|
|
raise TypeError("tcp_keepalive must be a boolean")
|
|
|
|
if not tcp_keepalive:
|
|
return
|
|
|
|
# These options aren't available in the OS X version of eventlet,
|
|
# Idle + Count * Interval effectively gives you the total timeout.
|
|
if tcp_keepidle is not None:
|
|
if hasattr(socket, 'TCP_KEEPIDLE'):
|
|
sock.setsockopt(socket.IPPROTO_TCP,
|
|
socket.TCP_KEEPIDLE,
|
|
tcp_keepidle)
|
|
else:
|
|
LOG.warning(_LW('tcp_keepidle not available on your system'))
|
|
if tcp_keepalive_interval is not None:
|
|
if hasattr(socket, 'TCP_KEEPINTVL'):
|
|
sock.setsockopt(socket.IPPROTO_TCP,
|
|
socket.TCP_KEEPINTVL,
|
|
tcp_keepalive_interval)
|
|
else:
|
|
LOG.warning(_LW('tcp_keepintvl not available on your system'))
|
|
if tcp_keepalive_count is not None:
|
|
if hasattr(socket, 'TCP_KEEPCNT'):
|
|
sock.setsockopt(socket.IPPROTO_TCP,
|
|
socket.TCP_KEEPCNT,
|
|
tcp_keepalive_count)
|
|
else:
|
|
LOG.warning(_LW('tcp_keepcnt not available on your system'))
|