Package ganeti :: Module netutils
[hide private]
[frames] | no frames]

Source Code for Module ganeti.netutils

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2010, 2011, 2012 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30   
 31  """Ganeti network utility module. 
 32   
 33  This module holds functions that can be used in both daemons (all) and 
 34  the command line scripts. 
 35   
 36  """ 
 37   
 38   
 39  import errno 
 40  import os 
 41  import re 
 42  import socket 
 43  import struct 
 44  import IN 
 45  import logging 
 46   
 47  from ganeti import constants 
 48  from ganeti import errors 
 49  from ganeti import utils 
 50  from ganeti import vcluster 
 51   
 52  # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...): 
 53  # struct ucred { pid_t pid; uid_t uid; gid_t gid; }; 
 54  # 
 55  # The GNU C Library defines gid_t and uid_t to be "unsigned int" and 
 56  # pid_t to "int". 
 57  # 
 58  # IEEE Std 1003.1-2008: 
 59  # "nlink_t, uid_t, gid_t, and id_t shall be integer types" 
 60  # "blksize_t, pid_t, and ssize_t shall be signed integer types" 
 61  _STRUCT_UCRED = "iII" 
 62  _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED) 
 63   
 64  # Workaround a bug in some linux distributions that don't define SO_PEERCRED 
 65  try: 
 66    # pylint: disable=E1101 
 67    _SO_PEERCRED = IN.SO_PEERCRED 
 68  except AttributeError: 
 69    _SO_PEERCRED = 17 
 70   
 71  # Regexes used to find IP addresses in the output of ip. 
 72  _IP_RE_TEXT = r"[.:a-z0-9]+"      # separate for testing purposes 
 73  _IP_FAMILY_RE = re.compile(r"(?P<family>inet6?)\s+(?P<ip>%s)/" % _IP_RE_TEXT, 
 74                             re.IGNORECASE) 
 75   
 76  # Dict used to convert from a string representing an IP family to an IP 
 77  # version 
 78  _NAME_TO_IP_VER = { 
 79    "inet": constants.IP4_VERSION, 
 80    "inet6": constants.IP6_VERSION, 
 81    } 
82 83 84 -def _GetIpAddressesFromIpOutput(ip_output):
85 """Parses the output of the ip command and retrieves the IP addresses and 86 version. 87 88 @param ip_output: string containing the output of the ip command; 89 @rtype: dict; (int, list) 90 @return: a dict having as keys the IP versions and as values the 91 corresponding list of addresses found in the IP output. 92 93 """ 94 addr = dict((i, []) for i in _NAME_TO_IP_VER.values()) 95 96 for row in ip_output.splitlines(): 97 match = _IP_FAMILY_RE.search(row) 98 if match and IPAddress.IsValid(match.group("ip")): 99 addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip")) 100 101 return addr
102
103 104 -def GetSocketCredentials(sock):
105 """Returns the credentials of the foreign process connected to a socket. 106 107 @param sock: Unix socket 108 @rtype: tuple; (number, number, number) 109 @return: The PID, UID and GID of the connected foreign process. 110 111 """ 112 peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED, 113 _STRUCT_UCRED_SIZE) 114 return struct.unpack(_STRUCT_UCRED, peercred)
115
116 117 -def IsValidInterface(ifname):
118 """Validate an interface name. 119 120 @type ifname: string 121 @param ifname: Name of the network interface 122 @return: boolean indicating whether the interface name is valid or not. 123 124 """ 125 return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
126
127 128 -def GetInterfaceIpAddresses(ifname):
129 """Returns the IP addresses associated to the interface. 130 131 @type ifname: string 132 @param ifname: Name of the network interface 133 @return: A dict having for keys the IP version (either 134 L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for 135 values the lists of IP addresses of the respective version 136 associated to the interface 137 138 """ 139 result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show", 140 ifname]) 141 142 if result.failed: 143 logging.error("Error running the ip command while getting the IP" 144 " addresses of %s", ifname) 145 return None 146 147 return _GetIpAddressesFromIpOutput(result.output)
148
149 150 -def GetHostname(name=None, family=None):
151 """Returns a Hostname object. 152 153 @type name: str 154 @param name: hostname or None 155 @type family: int 156 @param family: AF_INET | AF_INET6 | None 157 @rtype: L{Hostname} 158 @return: Hostname object 159 @raise errors.OpPrereqError: in case of errors in resolving 160 161 """ 162 try: 163 return Hostname(name=name, family=family) 164 except errors.ResolverError, err: 165 raise errors.OpPrereqError("The given name (%s) does not resolve: %s" % 166 (err[0], err[2]), errors.ECODE_RESOLVER)
167
168 169 -class Hostname(object):
170 """Class implementing resolver and hostname functionality. 171 172 """ 173 _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$") 174
175 - def __init__(self, name=None, family=None):
176 """Initialize the host name object. 177 178 If the name argument is None, it will use this system's name. 179 180 @type family: int 181 @param family: AF_INET | AF_INET6 | None 182 @type name: str 183 @param name: hostname or None 184 185 """ 186 self.name = self.GetFqdn(name) 187 self.ip = self.GetIP(self.name, family=family)
188 189 @classmethod
190 - def GetSysName(cls):
191 """Legacy method the get the current system's name. 192 193 """ 194 return cls.GetFqdn()
195 196 @classmethod
197 - def GetFqdn(cls, hostname=None):
198 """Return fqdn. 199 200 If hostname is None the system's fqdn is returned. 201 202 @type hostname: str 203 @param hostname: name to be fqdn'ed 204 @rtype: str 205 @return: fqdn of given name, if it exists, unmodified name otherwise 206 207 """ 208 if hostname is None: 209 virtfqdn = vcluster.GetVirtualHostname() 210 if virtfqdn: 211 result = virtfqdn 212 else: 213 result = socket.getfqdn() 214 else: 215 result = socket.getfqdn(hostname) 216 217 return cls.GetNormalizedName(result)
218 219 @staticmethod
220 - def GetIP(hostname, family=None):
221 """Return IP address of given hostname. 222 223 Supports both IPv4 and IPv6. 224 225 @type hostname: str 226 @param hostname: hostname to look up 227 @type family: int 228 @param family: AF_INET | AF_INET6 | None 229 @rtype: str 230 @return: IP address 231 @raise errors.ResolverError: in case of errors in resolving 232 233 """ 234 try: 235 if family in (socket.AF_INET, socket.AF_INET6): 236 result = socket.getaddrinfo(hostname, None, family) 237 else: 238 result = socket.getaddrinfo(hostname, None) 239 except (socket.gaierror, socket.herror, socket.error), err: 240 # hostname not found in DNS, or other socket exception in the 241 # (code, description format) 242 raise errors.ResolverError(hostname, err.args[0], err.args[1]) 243 244 # getaddrinfo() returns a list of 5-tupes (family, socktype, proto, 245 # canonname, sockaddr). We return the first tuple's first address in 246 # sockaddr 247 try: 248 return result[0][4][0] 249 except IndexError, err: 250 # we don't have here an actual error code, it's just that the 251 # data type returned by getaddrinfo is not what we expected; 252 # let's keep the same format in the exception arguments with a 253 # dummy error code 254 raise errors.ResolverError(hostname, 0, 255 "Unknown error in getaddrinfo(): %s" % err)
256 257 @classmethod
258 - def GetNormalizedName(cls, hostname):
259 """Validate and normalize the given hostname. 260 261 @attention: the validation is a bit more relaxed than the standards 262 require; most importantly, we allow underscores in names 263 @raise errors.OpPrereqError: when the name is not valid 264 265 """ 266 hostname = hostname.lower() 267 if (not cls._VALID_NAME_RE.match(hostname) or 268 # double-dots, meaning empty label 269 ".." in hostname or 270 # empty initial label 271 hostname.startswith(".")): 272 raise errors.OpPrereqError("Invalid hostname '%s'" % hostname, 273 errors.ECODE_INVAL) 274 if hostname.endswith("."): 275 hostname = hostname.rstrip(".") 276 return hostname
277
278 279 -def ValidatePortNumber(port):
280 """Returns the validated integer port number if it is valid. 281 282 @param port: the port number to be validated 283 284 @raise ValueError: if the port is not valid 285 @rtype: int 286 @return: the validated value. 287 288 """ 289 290 try: 291 port = int(port) 292 except TypeError: 293 raise errors.ProgrammerError("ValidatePortNumber called with non-numeric" 294 " type %s." % port.__class__.__name__) 295 except ValueError: 296 raise ValueError("Invalid port value: '%s'" % port) 297 298 if not 0 < port < 2 ** 16: 299 raise ValueError("Invalid port value: '%d'" % port) 300 301 return port
302
303 304 -def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
305 """Simple ping implementation using TCP connect(2). 306 307 Check if the given IP is reachable by doing attempting a TCP connect 308 to it. 309 310 @type target: str 311 @param target: the IP to ping 312 @type port: int 313 @param port: the port to connect to 314 @type timeout: int 315 @param timeout: the timeout on the connection attempt 316 @type live_port_needed: boolean 317 @param live_port_needed: whether a closed port will cause the 318 function to return failure, as if there was a timeout 319 @type source: str or None 320 @param source: if specified, will cause the connect to be made 321 from this specific source address; failures to bind other 322 than C{EADDRNOTAVAIL} will be ignored 323 324 """ 325 logging.debug("Attempting to reach TCP port %s on target %s with a timeout" 326 " of %s seconds", port, target, timeout) 327 328 try: 329 family = IPAddress.GetAddressFamily(target) 330 except errors.IPAddressError, err: 331 raise errors.ProgrammerError("Family of IP address given in parameter" 332 " 'target' can't be determined: %s" % err) 333 334 sock = socket.socket(family, socket.SOCK_STREAM) 335 success = False 336 337 if source is not None: 338 try: 339 sock.bind((source, 0)) 340 except socket.error, err: 341 if err[0] == errno.EADDRNOTAVAIL: 342 success = False 343 344 sock.settimeout(timeout) 345 346 try: 347 sock.connect((target, port)) 348 sock.close() 349 success = True 350 except socket.timeout: 351 success = False 352 except socket.error, err: 353 success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED) 354 355 return success
356
357 358 -def GetDaemonPort(daemon_name):
359 """Get the daemon port for this cluster. 360 361 Note that this routine does not read a ganeti-specific file, but 362 instead uses C{socket.getservbyname} to allow pre-customization of 363 this parameter outside of Ganeti. 364 365 @type daemon_name: string 366 @param daemon_name: daemon name (in constants.DAEMONS_PORTS) 367 @rtype: int 368 369 """ 370 if daemon_name not in constants.DAEMONS_PORTS: 371 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name) 372 373 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name] 374 try: 375 port = socket.getservbyname(daemon_name, proto) 376 except socket.error: 377 port = default_port 378 379 return port
380
381 382 -class IPAddress(object):
383 """Class that represents an IP address. 384 385 """ 386 iplen = 0 387 family = None 388 loopback_cidr = None 389 390 @staticmethod
391 - def _GetIPIntFromString(address):
392 """Abstract method to please pylint. 393 394 """ 395 raise NotImplementedError
396 397 @classmethod
398 - def IsValid(cls, address):
399 """Validate a IP address. 400 401 @type address: str 402 @param address: IP address to be checked 403 @rtype: bool 404 @return: True if valid, False otherwise 405 406 """ 407 if cls.family is None: 408 try: 409 family = cls.GetAddressFamily(address) 410 except errors.IPAddressError: 411 return False 412 else: 413 family = cls.family 414 415 try: 416 socket.inet_pton(family, address) 417 return True 418 except socket.error: 419 return False
420 421 @classmethod
422 - def ValidateNetmask(cls, netmask):
423 """Validate a netmask suffix in CIDR notation. 424 425 @type netmask: int 426 @param netmask: netmask suffix to validate 427 @rtype: bool 428 @return: True if valid, False otherwise 429 430 """ 431 assert (isinstance(netmask, (int, long))) 432 433 return 0 < netmask <= cls.iplen
434 435 @classmethod
436 - def Own(cls, address):
437 """Check if the current host has the the given IP address. 438 439 This is done by trying to bind the given address. We return True if we 440 succeed or false if a socket.error is raised. 441 442 @type address: str 443 @param address: IP address to be checked 444 @rtype: bool 445 @return: True if we own the address, False otherwise 446 447 """ 448 if cls.family is None: 449 try: 450 family = cls.GetAddressFamily(address) 451 except errors.IPAddressError: 452 return False 453 else: 454 family = cls.family 455 456 s = socket.socket(family, socket.SOCK_DGRAM) 457 success = False 458 try: 459 try: 460 s.bind((address, 0)) 461 success = True 462 except socket.error: 463 success = False 464 finally: 465 s.close() 466 return success
467 468 @classmethod
469 - def InNetwork(cls, cidr, address):
470 """Determine whether an address is within a network. 471 472 @type cidr: string 473 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64' 474 @type address: str 475 @param address: IP address 476 @rtype: bool 477 @return: True if address is in cidr, False otherwise 478 479 """ 480 address_int = cls._GetIPIntFromString(address) 481 subnet = cidr.split("/") 482 assert len(subnet) == 2 483 try: 484 prefix = int(subnet[1]) 485 except ValueError: 486 return False 487 488 assert 0 <= prefix <= cls.iplen 489 target_int = cls._GetIPIntFromString(subnet[0]) 490 # Convert prefix netmask to integer value of netmask 491 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix) 492 # Calculate hostmask 493 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1 494 # Calculate network address by and'ing netmask 495 network_int = target_int & netmask_int 496 # Calculate broadcast address by or'ing hostmask 497 broadcast_int = target_int | hostmask_int 498 499 return network_int <= address_int <= broadcast_int
500 501 @staticmethod
502 - def GetAddressFamily(address):
503 """Get the address family of the given address. 504 505 @type address: str 506 @param address: ip address whose family will be returned 507 @rtype: int 508 @return: C{socket.AF_INET} or C{socket.AF_INET6} 509 @raise errors.GenericError: for invalid addresses 510 511 """ 512 try: 513 return IP4Address(address).family 514 except errors.IPAddressError: 515 pass 516 517 try: 518 return IP6Address(address).family 519 except errors.IPAddressError: 520 pass 521 522 raise errors.IPAddressError("Invalid address '%s'" % address)
523 524 @staticmethod
525 - def GetVersionFromAddressFamily(family):
526 """Convert an IP address family to the corresponding IP version. 527 528 @type family: int 529 @param family: IP address family, one of socket.AF_INET or socket.AF_INET6 530 @return: an int containing the IP version, one of L{constants.IP4_VERSION} 531 or L{constants.IP6_VERSION} 532 @raise errors.ProgrammerError: for unknown families 533 534 """ 535 if family == socket.AF_INET: 536 return constants.IP4_VERSION 537 elif family == socket.AF_INET6: 538 return constants.IP6_VERSION 539 540 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
541 542 @staticmethod
543 - def GetAddressFamilyFromVersion(version):
544 """Convert an IP version to the corresponding IP address family. 545 546 @type version: int 547 @param version: IP version, one of L{constants.IP4_VERSION} or 548 L{constants.IP6_VERSION} 549 @return: an int containing the IP address family, one of C{socket.AF_INET} 550 or C{socket.AF_INET6} 551 @raise errors.ProgrammerError: for unknown IP versions 552 553 """ 554 if version == constants.IP4_VERSION: 555 return socket.AF_INET 556 elif version == constants.IP6_VERSION: 557 return socket.AF_INET6 558 559 raise errors.ProgrammerError("%s is not a valid IP version" % version)
560 561 @staticmethod
562 - def GetClassFromIpVersion(version):
563 """Return the IPAddress subclass for the given IP version. 564 565 @type version: int 566 @param version: IP version, one of L{constants.IP4_VERSION} or 567 L{constants.IP6_VERSION} 568 @return: a subclass of L{netutils.IPAddress} 569 @raise errors.ProgrammerError: for unknowo IP versions 570 571 """ 572 if version == constants.IP4_VERSION: 573 return IP4Address 574 elif version == constants.IP6_VERSION: 575 return IP6Address 576 577 raise errors.ProgrammerError("%s is not a valid IP version" % version)
578 579 @staticmethod
580 - def GetClassFromIpFamily(family):
581 """Return the IPAddress subclass for the given IP family. 582 583 @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6} 584 @return: a subclass of L{netutils.IPAddress} 585 @raise errors.ProgrammerError: for unknowo IP versions 586 587 """ 588 return IPAddress.GetClassFromIpVersion( 589 IPAddress.GetVersionFromAddressFamily(family))
590 591 @classmethod
592 - def IsLoopback(cls, address):
593 """Determine whether it is a loopback address. 594 595 @type address: str 596 @param address: IP address to be checked 597 @rtype: bool 598 @return: True if loopback, False otherwise 599 600 """ 601 try: 602 return cls.InNetwork(cls.loopback_cidr, address) 603 except errors.IPAddressError: 604 return False
605
606 607 -class IP4Address(IPAddress):
608 """IPv4 address class. 609 610 """ 611 iplen = 32 612 family = socket.AF_INET 613 loopback_cidr = "127.0.0.0/8" 614
615 - def __init__(self, address):
616 """Constructor for IPv4 address. 617 618 @type address: str 619 @param address: IP address 620 @raises errors.IPAddressError: if address invalid 621 622 """ 623 IPAddress.__init__(self) 624 if not self.IsValid(address): 625 raise errors.IPAddressError("IPv4 Address %s invalid" % address) 626 627 self.address = address
628 629 @staticmethod
630 - def _GetIPIntFromString(address):
631 """Get integer value of IPv4 address. 632 633 @type address: str 634 @param address: IPv6 address 635 @rtype: int 636 @return: integer value of given IP address 637 638 """ 639 address_int = 0 640 parts = address.split(".") 641 assert len(parts) == 4 642 for part in parts: 643 address_int = (address_int << 8) | int(part) 644 645 return address_int
646
647 648 -class IP6Address(IPAddress):
649 """IPv6 address class. 650 651 """ 652 iplen = 128 653 family = socket.AF_INET6 654 loopback_cidr = "::1/128" 655
656 - def __init__(self, address):
657 """Constructor for IPv6 address. 658 659 @type address: str 660 @param address: IP address 661 @raises errors.IPAddressError: if address invalid 662 663 """ 664 IPAddress.__init__(self) 665 if not self.IsValid(address): 666 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address) 667 self.address = address
668 669 @staticmethod
670 - def _GetIPIntFromString(address):
671 """Get integer value of IPv6 address. 672 673 @type address: str 674 @param address: IPv6 address 675 @rtype: int 676 @return: integer value of given IP address 677 678 """ 679 doublecolons = address.count("::") 680 assert not doublecolons > 1 681 if doublecolons == 1: 682 # We have a shorthand address, expand it 683 parts = [] 684 twoparts = address.split("::") 685 sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":")) 686 parts = twoparts[0].split(":") 687 parts.extend(["0"] * (8 - sep)) 688 parts += twoparts[1].split(":") 689 else: 690 parts = address.split(":") 691 692 address_int = 0 693 for part in parts: 694 address_int = (address_int << 16) + int(part or "0", 16) 695 696 return address_int
697
698 699 -def FormatAddress(address, family=None):
700 """Format a socket address 701 702 @type address: family specific (usually tuple) 703 @param address: address, as reported by this class 704 @type family: integer 705 @param family: socket family (one of socket.AF_*) or None 706 707 """ 708 if family is None: 709 try: 710 family = IPAddress.GetAddressFamily(address[0]) 711 except errors.IPAddressError: 712 raise errors.ParameterError(address) 713 714 if family == socket.AF_UNIX and len(address) == 3: 715 return "pid=%s, uid=%s, gid=%s" % address 716 717 if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2: 718 host, port = address 719 if family == socket.AF_INET6: 720 res = "[%s]" % host 721 else: 722 res = host 723 724 if port is not None: 725 res += ":%s" % port 726 727 return res 728 729 raise errors.ParameterError(family, address)
730