1# Copyright 2012 OpenStack Foundation.
2# All Rights Reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5#    not use this file except in compliance with the License. You may obtain
6#    a copy of the License at
7#
8#         http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13#    License for the specific language governing permissions and limitations
14#    under the License.
15
16"""
17Network-related utilities and helper functions.
18"""
19
20import logging
21import os
22import re
23import socket
24from urllib import parse
25
26import netaddr
27from netaddr.core import INET_PTON
28import netifaces
29
30from oslo_utils._i18n import _
31
32
33LOG = logging.getLogger(__name__)
34_IS_IPV6_ENABLED = None
35
36
37def parse_host_port(address, default_port=None):
38    """Interpret a string as a host:port pair.
39
40    An IPv6 address MUST be escaped if accompanied by a port,
41    because otherwise ambiguity ensues: 2001:db8:85a3::8a2e:370:7334
42    means both [2001:db8:85a3::8a2e:370:7334] and
43    [2001:db8:85a3::8a2e:370]:7334.
44
45    >>> parse_host_port('server01:80')
46    ('server01', 80)
47    >>> parse_host_port('server01')
48    ('server01', None)
49    >>> parse_host_port('server01', default_port=1234)
50    ('server01', 1234)
51    >>> parse_host_port('[::1]:80')
52    ('::1', 80)
53    >>> parse_host_port('[::1]')
54    ('::1', None)
55    >>> parse_host_port('[::1]', default_port=1234)
56    ('::1', 1234)
57    >>> parse_host_port('2001:db8:85a3::8a2e:370:7334', default_port=1234)
58    ('2001:db8:85a3::8a2e:370:7334', 1234)
59    >>> parse_host_port(None)
60    (None, None)
61    """
62    if not address:
63        return (None, None)
64
65    if address[0] == '[':
66        # Escaped ipv6
67        _host, _port = address[1:].split(']')
68        host = _host
69        if ':' in _port:
70            port = _port.split(':')[1]
71        else:
72            port = default_port
73    else:
74        if address.count(':') == 1:
75            host, port = address.split(':')
76        else:
77            # 0 means ipv4, >1 means ipv6.
78            # We prohibit unescaped ipv6 addresses with port.
79            host = address
80            port = default_port
81
82    return (host, None if port is None else int(port))
83
84
85def is_valid_ipv4(address, strict=None):
86    """Verify that address represents a valid IPv4 address.
87
88    :param address: Value to verify
89    :type address: string
90    :param strict: flag allowing users to restrict validation
91        to IP addresses in presentation format (``a.b.c.d``) as opposed to
92        address format (``a.b.c.d``, ``a.b.c``, ``a.b``, ``a``).
93    :type flags: bool
94    :returns: bool
95
96    .. versionadded:: 1.1
97    .. versionchanged:: 4.8.0
98       Allow to restrict validation to IP addresses in presentation format
99       (``a.b.c.d``) as opposed to address format
100       (``a.b.c.d``, ``a.b.c``, ``a.b``, ``a``).
101    """
102    if strict is not None:
103        flag = INET_PTON if strict else 0
104        try:
105            return netaddr.valid_ipv4(address, flags=flag)
106        except netaddr.AddrFormatError:
107            return False
108
109    # non strict mode
110    try:
111        if netaddr.valid_ipv4(address, flags=INET_PTON):
112            return True
113        else:
114            if netaddr.valid_ipv4(address):
115                LOG.warn(
116                    'Converting in non strict mode is deprecated. '
117                    'You should pass strict=False if you want to '
118                    'preserve legacy behavior')
119                return True
120            else:
121                return False
122    except netaddr.AddrFormatError:
123        return False
124
125
126def is_valid_ipv6(address):
127    """Verify that address represents a valid IPv6 address.
128
129    :param address: Value to verify
130    :type address: string
131    :returns: bool
132
133    .. versionadded:: 1.1
134    """
135    if not address:
136        return False
137
138    parts = address.rsplit("%", 1)
139    address = parts[0]
140    scope = parts[1] if len(parts) > 1 else None
141    if scope is not None and (len(scope) < 1 or len(scope) > 15):
142        return False
143
144    try:
145        return netaddr.valid_ipv6(address, netaddr.core.INET_PTON)
146    except netaddr.AddrFormatError:
147        return False
148
149
150def is_valid_cidr(address):
151    """Verify that address represents a valid CIDR address.
152
153    :param address: Value to verify
154    :type address: string
155    :returns: bool
156
157    .. versionadded:: 3.8
158    """
159    try:
160        # Validate the correct CIDR Address
161        netaddr.IPNetwork(address)
162    except (TypeError, netaddr.AddrFormatError):
163        return False
164
165    # Prior validation partially verify /xx part
166    # Verify it here
167    ip_segment = address.split('/')
168
169    if (len(ip_segment) <= 1 or
170            ip_segment[1] == ''):
171        return False
172
173    return True
174
175
176def is_valid_ipv6_cidr(address):
177    """Verify that address represents a valid IPv6 CIDR address.
178
179    :param address: address to verify
180    :type address: string
181    :returns: true if address is valid, false otherwise
182
183    .. versionadded:: 3.17
184    """
185    try:
186        netaddr.IPNetwork(address, version=6).cidr
187        return True
188    except (TypeError, netaddr.AddrFormatError):
189        return False
190
191
192def get_ipv6_addr_by_EUI64(prefix, mac):
193    """Calculate IPv6 address using EUI-64 specification.
194
195    This method calculates the IPv6 address using the EUI-64
196    addressing scheme as explained in rfc2373.
197
198    :param prefix: IPv6 prefix.
199    :param mac: IEEE 802 48-bit MAC address.
200    :returns: IPv6 address on success.
201    :raises ValueError, TypeError: For any invalid input.
202
203    .. versionadded:: 1.4
204    """
205    # Check if the prefix is an IPv4 address
206    if is_valid_ipv4(prefix):
207        msg = _("Unable to generate IP address by EUI64 for IPv4 prefix")
208        raise ValueError(msg)
209    try:
210        eui64 = int(netaddr.EUI(mac).eui64())
211        prefix = netaddr.IPNetwork(prefix)
212        return netaddr.IPAddress(prefix.first + eui64 ^ (1 << 57))
213    except (ValueError, netaddr.AddrFormatError):
214        raise ValueError(_('Bad prefix or mac format for generating IPv6 '
215                           'address by EUI-64: %(prefix)s, %(mac)s:')
216                         % {'prefix': prefix, 'mac': mac})
217    except TypeError:
218        raise TypeError(_('Bad prefix type for generating IPv6 address by '
219                          'EUI-64: %s') % prefix)
220
221
222def get_mac_addr_by_ipv6(ipv6, dialect=netaddr.mac_unix_expanded):
223    """Extract MAC address from interface identifier based IPv6 address.
224
225    For example from link-local addresses (fe80::/10) generated from MAC.
226
227    :param ipv6: An interface identifier (i.e. mostly MAC) based IPv6
228                 address as a netaddr.IPAddress() object.
229    :param dialect: The netaddr dialect of the the object returned.
230                    Defaults to netaddr.mac_unix_expanded.
231    :returns: A MAC address as a netaddr.EUI() object.
232
233    See also:
234    * https://tools.ietf.org/html/rfc4291#appendix-A
235    * https://tools.ietf.org/html/rfc4291#section-2.5.6
236
237    .. versionadded:: 4.3.0
238    """
239    return netaddr.EUI(int(
240        # out of the lowest 8 bytes (byte positions 8-1)
241        # delete the middle 2 bytes (5-4, 0xff_fe)
242        # by shifting the highest 3 bytes to the right by 2 bytes (8-6 -> 6-4)
243        (((ipv6 & 0xff_ff_ff_00_00_00_00_00) >> 16) +
244         # adding the lowest 3 bytes as they are (3-1)
245         (ipv6 & 0xff_ff_ff)) ^
246        # then invert the universal/local bit
247        0x02_00_00_00_00_00),
248        dialect=dialect)
249
250
251def is_ipv6_enabled():
252    """Check if IPv6 support is enabled on the platform.
253
254    This api will look into the proc entries of the platform to figure
255    out the status of IPv6 support on the platform.
256
257    :returns: True if the platform has IPv6 support, False otherwise.
258
259    .. versionadded:: 1.4
260    """
261
262    global _IS_IPV6_ENABLED
263
264    if _IS_IPV6_ENABLED is None:
265        disabled_ipv6_path = "/proc/sys/net/ipv6/conf/default/disable_ipv6"
266        if os.path.exists(disabled_ipv6_path):
267            with open(disabled_ipv6_path, 'r') as f:
268                disabled = f.read().strip()
269            _IS_IPV6_ENABLED = disabled == "0"
270        else:
271            _IS_IPV6_ENABLED = False
272    return _IS_IPV6_ENABLED
273
274
275def escape_ipv6(address):
276    """Escape an IP address in square brackets if IPv6
277
278    :param address: address to optionaly escape
279    :type address: string
280    :returns: string
281
282    .. versionadded:: 3.29.0
283    """
284    if is_valid_ipv6(address):
285        return "[%s]" % address
286    return address
287
288
289def is_valid_ip(address):
290    """Verify that address represents a valid IP address.
291
292    :param address: Value to verify
293    :type address: string
294    :returns: bool
295
296    .. versionadded:: 1.1
297    """
298    return is_valid_ipv4(address) or is_valid_ipv6(address)
299
300
301def is_valid_mac(address):
302    """Verify the format of a MAC address.
303
304    Check if a MAC address is valid and contains six octets. Accepts
305    colon-separated format only.
306
307    :param address: MAC address to be validated.
308    :returns: True if valid. False if not.
309
310    .. versionadded:: 3.17
311    """
312    m = "[0-9a-f]{2}(:[0-9a-f]{2}){5}$"
313    return isinstance(address, str) and re.match(m, address.lower())
314
315
316def _is_int_in_range(value, start, end):
317    """Try to convert value to int and check if it lies within
318    range 'start' to 'end'.
319
320    :param value: value to verify
321    :param start: start number of range
322    :param end: end number of range
323    :returns: bool
324    """
325    try:
326        val = int(value)
327    except (ValueError, TypeError):
328        return False
329    return (start <= val <= end)
330
331
332def is_valid_port(port):
333    """Verify that port represents a valid port number.
334
335    Port can be valid integer having a value of 0 up to and
336    including 65535.
337
338    .. versionadded:: 1.1.1
339    """
340    return _is_int_in_range(port, 0, 65535)
341
342
343def is_valid_icmp_type(type):
344    """Verify if ICMP type is valid.
345
346    :param type: ICMP *type* field can only be a valid integer
347    :returns: bool
348
349    ICMP *type* field can be valid integer having a value of 0
350    up to and including 255.
351    """
352    return _is_int_in_range(type, 0, 255)
353
354
355def is_valid_icmp_code(code):
356    """Verify if ICMP code is valid.
357
358    :param code: ICMP *code* field can be valid integer or None
359    :returns: bool
360
361    ICMP *code* field can be either None or valid integer having
362    a value of 0 up to and including 255.
363    """
364    if code is None:
365        return True
366    return _is_int_in_range(code, 0, 255)
367
368
369def get_my_ipv4():
370    """Returns the actual ipv4 of the local machine.
371
372    This code figures out what source address would be used if some traffic
373    were to be sent out to some well known address on the Internet. In this
374    case, IP from RFC5737 is used, but the specific address does not
375    matter much. No traffic is actually sent.
376
377    .. versionadded:: 1.1
378
379    .. versionchanged:: 1.2.1
380       Return ``'127.0.0.1'`` if there is no default interface.
381    """
382    try:
383        csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
384        csock.connect(('192.0.2.0', 80))
385        (addr, port) = csock.getsockname()
386        csock.close()
387        return addr
388    except socket.error:
389        return _get_my_ipv4_address()
390
391
392def _get_my_ipv4_address():
393    """Figure out the best ipv4
394    """
395    LOCALHOST = '127.0.0.1'
396    gtw = netifaces.gateways()
397    try:
398        interface = gtw['default'][netifaces.AF_INET][1]
399    except (KeyError, IndexError):
400        LOG.info('Could not determine default network interface, '
401                 'using 127.0.0.1 for IPv4 address')
402        return LOCALHOST
403
404    try:
405        return netifaces.ifaddresses(interface)[netifaces.AF_INET][0]['addr']
406    except (KeyError, IndexError):
407        LOG.info('Could not determine IPv4 address for interface %s, '
408                 'using 127.0.0.1',
409                 interface)
410    except Exception as e:
411        LOG.info('Could not determine IPv4 address for '
412                 'interface %(interface)s: %(error)s',
413                 {'interface': interface, 'error': e})
414    return LOCALHOST
415
416
417class _ModifiedSplitResult(parse.SplitResult):
418    """Split results class for urlsplit."""
419
420    def params(self, collapse=True):
421        """Extracts the query parameters from the split urls components.
422
423        This method will provide back as a dictionary the query parameter
424        names and values that were provided in the url.
425
426        :param collapse: Boolean, turn on or off collapsing of query values
427        with the same name. Since a url can contain the same query parameter
428        name with different values it may or may not be useful for users to
429        care that this has happened. This parameter when True uses the
430        last value that was given for a given name, while if False it will
431        retain all values provided by associating the query parameter name with
432        a list of values instead of a single (non-list) value.
433        """
434        if self.query:
435            if collapse:
436                return dict(parse.parse_qsl(self.query))
437            else:
438                params = {}
439                for (key, value) in parse.parse_qsl(self.query):
440                    if key in params:
441                        if isinstance(params[key], list):
442                            params[key].append(value)
443                        else:
444                            params[key] = [params[key], value]
445                    else:
446                        params[key] = value
447                return params
448        else:
449            return {}
450
451
452def urlsplit(url, scheme='', allow_fragments=True):
453    """Parse a URL using urlparse.urlsplit(), splitting query and fragments.
454    This function papers over Python issue9374_ when needed.
455
456    .. _issue9374: http://bugs.python.org/issue9374
457
458    The parameters are the same as urlparse.urlsplit.
459    """
460    scheme, netloc, path, query, fragment = parse.urlsplit(
461        url, scheme, allow_fragments)
462    if allow_fragments and '#' in path:
463        path, fragment = path.split('#', 1)
464    if '?' in path:
465        path, query = path.split('?', 1)
466    return _ModifiedSplitResult(scheme, netloc,
467                                path, query, fragment)
468
469
470def set_tcp_keepalive(sock, tcp_keepalive=True,
471                      tcp_keepidle=None,
472                      tcp_keepalive_interval=None,
473                      tcp_keepalive_count=None):
474    """Set values for tcp keepalive parameters
475
476    This function configures tcp keepalive parameters if users wish to do
477    so.
478
479    :param tcp_keepalive: Boolean, turn on or off tcp_keepalive. If users are
480      not sure, this should be True, and default values will be used.
481
482    :param tcp_keepidle: time to wait before starting to send keepalive probes
483    :param tcp_keepalive_interval: time between successive probes, once the
484      initial wait time is over
485    :param tcp_keepalive_count: number of probes to send before the connection
486      is killed
487    """
488
489    # NOTE(praneshp): Despite keepalive being a tcp concept, the level is
490    # still SOL_SOCKET. This is a quirk.
491    if isinstance(tcp_keepalive, bool):
492        sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, tcp_keepalive)
493    else:
494        raise TypeError("tcp_keepalive must be a boolean")
495
496    if not tcp_keepalive:
497        return
498
499    # These options aren't available in the OS X version of eventlet,
500    # Idle + Count * Interval effectively gives you the total timeout.
501    if tcp_keepidle is not None:
502        if hasattr(socket, 'TCP_KEEPIDLE'):
503            sock.setsockopt(socket.IPPROTO_TCP,
504                            socket.TCP_KEEPIDLE,
505                            tcp_keepidle)
506        else:
507            LOG.warning('tcp_keepidle not available on your system')
508    if tcp_keepalive_interval is not None:
509        if hasattr(socket, 'TCP_KEEPINTVL'):
510            sock.setsockopt(socket.IPPROTO_TCP,
511                            socket.TCP_KEEPINTVL,
512                            tcp_keepalive_interval)
513        else:
514            LOG.warning('tcp_keepintvl not available on your system')
515    if tcp_keepalive_count is not None:
516        if hasattr(socket, 'TCP_KEEPCNT'):
517            sock.setsockopt(socket.IPPROTO_TCP,
518                            socket.TCP_KEEPCNT,
519                            tcp_keepalive_count)
520        else:
521            LOG.warning('tcp_keepcnt not available on your system')
522