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