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

Source Code for Module ganeti.netutils

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2010 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, (errcode, _): 284 if errcode == 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, (errcode, _): 296 success = (not live_port_needed) and (errcode == 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 Own(cls, address):
366 """Check if the current host has the the given IP address. 367 368 This is done by trying to bind the given address. We return True if we 369 succeed or false if a socket.error is raised. 370 371 @type address: str 372 @param address: IP address to be checked 373 @rtype: bool 374 @return: True if we own the address, False otherwise 375 376 """ 377 if cls.family is None: 378 try: 379 family = cls.GetAddressFamily(address) 380 except errors.IPAddressError: 381 return False 382 else: 383 family = cls.family 384 385 s = socket.socket(family, socket.SOCK_DGRAM) 386 success = False 387 try: 388 try: 389 s.bind((address, 0)) 390 success = True 391 except socket.error: 392 success = False 393 finally: 394 s.close() 395 return success
396 397 @classmethod
398 - def InNetwork(cls, cidr, address):
399 """Determine whether an address is within a network. 400 401 @type cidr: string 402 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64' 403 @type address: str 404 @param address: IP address 405 @rtype: bool 406 @return: True if address is in cidr, False otherwise 407 408 """ 409 address_int = cls._GetIPIntFromString(address) 410 subnet = cidr.split("/") 411 assert len(subnet) == 2 412 try: 413 prefix = int(subnet[1]) 414 except ValueError: 415 return False 416 417 assert 0 <= prefix <= cls.iplen 418 target_int = cls._GetIPIntFromString(subnet[0]) 419 # Convert prefix netmask to integer value of netmask 420 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix) 421 # Calculate hostmask 422 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1 423 # Calculate network address by and'ing netmask 424 network_int = target_int & netmask_int 425 # Calculate broadcast address by or'ing hostmask 426 broadcast_int = target_int | hostmask_int 427 428 return network_int <= address_int <= broadcast_int
429 430 @staticmethod
431 - def GetAddressFamily(address):
432 """Get the address family of the given address. 433 434 @type address: str 435 @param address: ip address whose family will be returned 436 @rtype: int 437 @return: C{socket.AF_INET} or C{socket.AF_INET6} 438 @raise errors.GenericError: for invalid addresses 439 440 """ 441 try: 442 return IP4Address(address).family 443 except errors.IPAddressError: 444 pass 445 446 try: 447 return IP6Address(address).family 448 except errors.IPAddressError: 449 pass 450 451 raise errors.IPAddressError("Invalid address '%s'" % address)
452 453 @staticmethod
454 - def GetVersionFromAddressFamily(family):
455 """Convert an IP address family to the corresponding IP version. 456 457 @type family: int 458 @param family: IP address family, one of socket.AF_INET or socket.AF_INET6 459 @return: an int containing the IP version, one of L{constants.IP4_VERSION} 460 or L{constants.IP6_VERSION} 461 @raise errors.ProgrammerError: for unknown families 462 463 """ 464 if family == socket.AF_INET: 465 return constants.IP4_VERSION 466 elif family == socket.AF_INET6: 467 return constants.IP6_VERSION 468 469 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
470 471 @staticmethod
472 - def GetAddressFamilyFromVersion(version):
473 """Convert an IP version to the corresponding IP address family. 474 475 @type version: int 476 @param version: IP version, one of L{constants.IP4_VERSION} or 477 L{constants.IP6_VERSION} 478 @return: an int containing the IP address family, one of C{socket.AF_INET} 479 or C{socket.AF_INET6} 480 @raise errors.ProgrammerError: for unknown IP versions 481 482 """ 483 if version == constants.IP4_VERSION: 484 return socket.AF_INET 485 elif version == constants.IP6_VERSION: 486 return socket.AF_INET6 487 488 raise errors.ProgrammerError("%s is not a valid IP version" % version)
489 490 @classmethod
491 - def IsLoopback(cls, address):
492 """Determine whether it is a loopback address. 493 494 @type address: str 495 @param address: IP address to be checked 496 @rtype: bool 497 @return: True if loopback, False otherwise 498 499 """ 500 try: 501 return cls.InNetwork(cls.loopback_cidr, address) 502 except errors.IPAddressError: 503 return False
504
505 506 -class IP4Address(IPAddress):
507 """IPv4 address class. 508 509 """ 510 iplen = 32 511 family = socket.AF_INET 512 loopback_cidr = "127.0.0.0/8" 513
514 - def __init__(self, address):
515 """Constructor for IPv4 address. 516 517 @type address: str 518 @param address: IP address 519 @raises errors.IPAddressError: if address invalid 520 521 """ 522 IPAddress.__init__(self) 523 if not self.IsValid(address): 524 raise errors.IPAddressError("IPv4 Address %s invalid" % address) 525 526 self.address = address
527 528 @staticmethod
529 - def _GetIPIntFromString(address):
530 """Get integer value of IPv4 address. 531 532 @type address: str 533 @param address: IPv6 address 534 @rtype: int 535 @return: integer value of given IP address 536 537 """ 538 address_int = 0 539 parts = address.split(".") 540 assert len(parts) == 4 541 for part in parts: 542 address_int = (address_int << 8) | int(part) 543 544 return address_int
545
546 547 -class IP6Address(IPAddress):
548 """IPv6 address class. 549 550 """ 551 iplen = 128 552 family = socket.AF_INET6 553 loopback_cidr = "::1/128" 554
555 - def __init__(self, address):
556 """Constructor for IPv6 address. 557 558 @type address: str 559 @param address: IP address 560 @raises errors.IPAddressError: if address invalid 561 562 """ 563 IPAddress.__init__(self) 564 if not self.IsValid(address): 565 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address) 566 self.address = address
567 568 @staticmethod
569 - def _GetIPIntFromString(address):
570 """Get integer value of IPv6 address. 571 572 @type address: str 573 @param address: IPv6 address 574 @rtype: int 575 @return: integer value of given IP address 576 577 """ 578 doublecolons = address.count("::") 579 assert not doublecolons > 1 580 if doublecolons == 1: 581 # We have a shorthand address, expand it 582 parts = [] 583 twoparts = address.split("::") 584 sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":")) 585 parts = twoparts[0].split(":") 586 parts.extend(["0"] * (8 - sep)) 587 parts += twoparts[1].split(":") 588 else: 589 parts = address.split(":") 590 591 address_int = 0 592 for part in parts: 593 address_int = (address_int << 16) + int(part or "0", 16) 594 595 return address_int
596
597 598 -def FormatAddress(address, family=None):
599 """Format a socket address 600 601 @type address: family specific (usually tuple) 602 @param address: address, as reported by this class 603 @type family: integer 604 @param family: socket family (one of socket.AF_*) or None 605 606 """ 607 if family is None: 608 try: 609 family = IPAddress.GetAddressFamily(address[0]) 610 except errors.IPAddressError: 611 raise errors.ParameterError(address) 612 613 if family == socket.AF_UNIX and len(address) == 3: 614 return "pid=%s, uid=%s, gid=%s" % address 615 616 if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2: 617 host, port = address 618 if family == socket.AF_INET6: 619 res = "[%s]" % host 620 else: 621 res = host 622 623 if port is not None: 624 res += ":%s" % port 625 626 return res 627 628 raise errors.ParameterError(family, address)
629