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