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