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 try: 292 family = IPAddress.GetAddressFamily(target) 293 except errors.GenericError: 294 return False 295 296 sock = socket.socket(family, socket.SOCK_STREAM) 297 success = False 298 299 if source is not None: 300 try: 301 sock.bind((source, 0)) 302 except socket.error, err: 303 if err[0] == errno.EADDRNOTAVAIL: 304 success = False 305 306 sock.settimeout(timeout) 307 308 try: 309 sock.connect((target, port)) 310 sock.close() 311 success = True 312 except socket.timeout: 313 success = False 314 except socket.error, err: 315 success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED) 316 317 return success
318
319 320 -def GetDaemonPort(daemon_name):
321 """Get the daemon port for this cluster. 322 323 Note that this routine does not read a ganeti-specific file, but 324 instead uses C{socket.getservbyname} to allow pre-customization of 325 this parameter outside of Ganeti. 326 327 @type daemon_name: string 328 @param daemon_name: daemon name (in constants.DAEMONS_PORTS) 329 @rtype: int 330 331 """ 332 if daemon_name not in constants.DAEMONS_PORTS: 333 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name) 334 335 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name] 336 try: 337 port = socket.getservbyname(daemon_name, proto) 338 except socket.error: 339 port = default_port 340 341 return port
342
343 344 -class IPAddress(object):
345 """Class that represents an IP address. 346 347 """ 348 iplen = 0 349 family = None 350 loopback_cidr = None 351 352 @staticmethod
353 - def _GetIPIntFromString(address):
354 """Abstract method to please pylint. 355 356 """ 357 raise NotImplementedError
358 359 @classmethod
360 - def IsValid(cls, address):
361 """Validate a IP address. 362 363 @type address: str 364 @param address: IP address to be checked 365 @rtype: bool 366 @return: True if valid, False otherwise 367 368 """ 369 if cls.family is None: 370 try: 371 family = cls.GetAddressFamily(address) 372 except errors.IPAddressError: 373 return False 374 else: 375 family = cls.family 376 377 try: 378 socket.inet_pton(family, address) 379 return True 380 except socket.error: 381 return False
382 383 @classmethod
384 - def ValidateNetmask(cls, netmask):
385 """Validate a netmask suffix in CIDR notation. 386 387 @type netmask: int 388 @param netmask: netmask suffix to validate 389 @rtype: bool 390 @return: True if valid, False otherwise 391 392 """ 393 assert (isinstance(netmask, (int, long))) 394 395 return 0 < netmask <= cls.iplen
396 397 @classmethod
398 - def Own(cls, address):
399 """Check if the current host has the the given IP address. 400 401 This is done by trying to bind the given address. We return True if we 402 succeed or false if a socket.error is raised. 403 404 @type address: str 405 @param address: IP address to be checked 406 @rtype: bool 407 @return: True if we own the address, False otherwise 408 409 """ 410 if cls.family is None: 411 try: 412 family = cls.GetAddressFamily(address) 413 except errors.IPAddressError: 414 return False 415 else: 416 family = cls.family 417 418 s = socket.socket(family, socket.SOCK_DGRAM) 419 success = False 420 try: 421 try: 422 s.bind((address, 0)) 423 success = True 424 except socket.error: 425 success = False 426 finally: 427 s.close() 428 return success
429 430 @classmethod
431 - def InNetwork(cls, cidr, address):
432 """Determine whether an address is within a network. 433 434 @type cidr: string 435 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64' 436 @type address: str 437 @param address: IP address 438 @rtype: bool 439 @return: True if address is in cidr, False otherwise 440 441 """ 442 address_int = cls._GetIPIntFromString(address) 443 subnet = cidr.split("/") 444 assert len(subnet) == 2 445 try: 446 prefix = int(subnet[1]) 447 except ValueError: 448 return False 449 450 assert 0 <= prefix <= cls.iplen 451 target_int = cls._GetIPIntFromString(subnet[0]) 452 # Convert prefix netmask to integer value of netmask 453 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix) 454 # Calculate hostmask 455 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1 456 # Calculate network address by and'ing netmask 457 network_int = target_int & netmask_int 458 # Calculate broadcast address by or'ing hostmask 459 broadcast_int = target_int | hostmask_int 460 461 return network_int <= address_int <= broadcast_int
462 463 @staticmethod
464 - def GetAddressFamily(address):
465 """Get the address family of the given address. 466 467 @type address: str 468 @param address: ip address whose family will be returned 469 @rtype: int 470 @return: C{socket.AF_INET} or C{socket.AF_INET6} 471 @raise errors.GenericError: for invalid addresses 472 473 """ 474 try: 475 return IP4Address(address).family 476 except errors.IPAddressError: 477 pass 478 479 try: 480 return IP6Address(address).family 481 except errors.IPAddressError: 482 pass 483 484 raise errors.IPAddressError("Invalid address '%s'" % address)
485 486 @staticmethod
487 - def GetVersionFromAddressFamily(family):
488 """Convert an IP address family to the corresponding IP version. 489 490 @type family: int 491 @param family: IP address family, one of socket.AF_INET or socket.AF_INET6 492 @return: an int containing the IP version, one of L{constants.IP4_VERSION} 493 or L{constants.IP6_VERSION} 494 @raise errors.ProgrammerError: for unknown families 495 496 """ 497 if family == socket.AF_INET: 498 return constants.IP4_VERSION 499 elif family == socket.AF_INET6: 500 return constants.IP6_VERSION 501 502 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
503 504 @staticmethod
505 - def GetAddressFamilyFromVersion(version):
506 """Convert an IP version to the corresponding IP address family. 507 508 @type version: int 509 @param version: IP version, one of L{constants.IP4_VERSION} or 510 L{constants.IP6_VERSION} 511 @return: an int containing the IP address family, one of C{socket.AF_INET} 512 or C{socket.AF_INET6} 513 @raise errors.ProgrammerError: for unknown IP versions 514 515 """ 516 if version == constants.IP4_VERSION: 517 return socket.AF_INET 518 elif version == constants.IP6_VERSION: 519 return socket.AF_INET6 520 521 raise errors.ProgrammerError("%s is not a valid IP version" % version)
522 523 @staticmethod
524 - def GetClassFromIpVersion(version):
525 """Return the IPAddress subclass for the given IP version. 526 527 @type version: int 528 @param version: IP version, one of L{constants.IP4_VERSION} or 529 L{constants.IP6_VERSION} 530 @return: a subclass of L{netutils.IPAddress} 531 @raise errors.ProgrammerError: for unknowo IP versions 532 533 """ 534 if version == constants.IP4_VERSION: 535 return IP4Address 536 elif version == constants.IP6_VERSION: 537 return IP6Address 538 539 raise errors.ProgrammerError("%s is not a valid IP version" % version)
540 541 @staticmethod
542 - def GetClassFromIpFamily(family):
543 """Return the IPAddress subclass for the given IP family. 544 545 @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6} 546 @return: a subclass of L{netutils.IPAddress} 547 @raise errors.ProgrammerError: for unknowo IP versions 548 549 """ 550 return IPAddress.GetClassFromIpVersion( 551 IPAddress.GetVersionFromAddressFamily(family))
552 553 @classmethod
554 - def IsLoopback(cls, address):
555 """Determine whether it is a loopback address. 556 557 @type address: str 558 @param address: IP address to be checked 559 @rtype: bool 560 @return: True if loopback, False otherwise 561 562 """ 563 try: 564 return cls.InNetwork(cls.loopback_cidr, address) 565 except errors.IPAddressError: 566 return False
567
568 569 -class IP4Address(IPAddress):
570 """IPv4 address class. 571 572 """ 573 iplen = 32 574 family = socket.AF_INET 575 loopback_cidr = "127.0.0.0/8" 576
577 - def __init__(self, address):
578 """Constructor for IPv4 address. 579 580 @type address: str 581 @param address: IP address 582 @raises errors.IPAddressError: if address invalid 583 584 """ 585 IPAddress.__init__(self) 586 if not self.IsValid(address): 587 raise errors.IPAddressError("IPv4 Address %s invalid" % address) 588 589 self.address = address
590 591 @staticmethod
592 - def _GetIPIntFromString(address):
593 """Get integer value of IPv4 address. 594 595 @type address: str 596 @param address: IPv6 address 597 @rtype: int 598 @return: integer value of given IP address 599 600 """ 601 address_int = 0 602 parts = address.split(".") 603 assert len(parts) == 4 604 for part in parts: 605 address_int = (address_int << 8) | int(part) 606 607 return address_int
608
609 610 -class IP6Address(IPAddress):
611 """IPv6 address class. 612 613 """ 614 iplen = 128 615 family = socket.AF_INET6 616 loopback_cidr = "::1/128" 617
618 - def __init__(self, address):
619 """Constructor for IPv6 address. 620 621 @type address: str 622 @param address: IP address 623 @raises errors.IPAddressError: if address invalid 624 625 """ 626 IPAddress.__init__(self) 627 if not self.IsValid(address): 628 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address) 629 self.address = address
630 631 @staticmethod
632 - def _GetIPIntFromString(address):
633 """Get integer value of IPv6 address. 634 635 @type address: str 636 @param address: IPv6 address 637 @rtype: int 638 @return: integer value of given IP address 639 640 """ 641 doublecolons = address.count("::") 642 assert not doublecolons > 1 643 if doublecolons == 1: 644 # We have a shorthand address, expand it 645 parts = [] 646 twoparts = address.split("::") 647 sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":")) 648 parts = twoparts[0].split(":") 649 parts.extend(["0"] * (8 - sep)) 650 parts += twoparts[1].split(":") 651 else: 652 parts = address.split(":") 653 654 address_int = 0 655 for part in parts: 656 address_int = (address_int << 16) + int(part or "0", 16) 657 658 return address_int
659
660 661 -def FormatAddress(address, family=None):
662 """Format a socket address 663 664 @type address: family specific (usually tuple) 665 @param address: address, as reported by this class 666 @type family: integer 667 @param family: socket family (one of socket.AF_*) or None 668 669 """ 670 if family is None: 671 try: 672 family = IPAddress.GetAddressFamily(address[0]) 673 except errors.IPAddressError: 674 raise errors.ParameterError(address) 675 676 if family == socket.AF_UNIX and len(address) == 3: 677 return "pid=%s, uid=%s, gid=%s" % address 678 679 if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2: 680 host, port = address 681 if family == socket.AF_INET6: 682 res = "[%s]" % host 683 else: 684 res = host 685 686 if port is not None: 687 res += ":%s" % port 688 689 return res 690 691 raise errors.ParameterError(family, address)
692