X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/f30445162de2947d87358cd779afed63163e7cea..33a6464ed0448939c25da6225fa61f063133cfc0:/lib/netutils.py diff --git a/lib/netutils.py b/lib/netutils.py index 509329e..f39ef77 100644 --- a/lib/netutils.py +++ b/lib/netutils.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2010 Google Inc. +# Copyright (C) 2010, 2011, 2012 Google Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,13 +28,17 @@ the command line scripts. import errno +import os import re import socket import struct import IN +import logging from ganeti import constants from ganeti import errors +from ganeti import utils +from ganeti import vcluster # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...): # struct ucred { pid_t pid; uid_t uid; gid_t gid; }; @@ -48,6 +52,45 @@ from ganeti import errors _STRUCT_UCRED = "iII" _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED) +# Workaround a bug in some linux distributions that don't define SO_PEERCRED +try: + # pylint: disable=E1101 + _SO_PEERCRED = IN.SO_PEERCRED +except AttributeError: + _SO_PEERCRED = 17 + +# Regexes used to find IP addresses in the output of ip. +_IP_RE_TEXT = r"[.:a-z0-9]+" # separate for testing purposes +_IP_FAMILY_RE = re.compile(r"(?Pinet6?)\s+(?P%s)/" % _IP_RE_TEXT, + re.IGNORECASE) + +# Dict used to convert from a string representing an IP family to an IP +# version +_NAME_TO_IP_VER = { + "inet": constants.IP4_VERSION, + "inet6": constants.IP6_VERSION, + } + + +def _GetIpAddressesFromIpOutput(ip_output): + """Parses the output of the ip command and retrieves the IP addresses and + version. + + @param ip_output: string containing the output of the ip command; + @rtype: dict; (int, list) + @return: a dict having as keys the IP versions and as values the + corresponding list of addresses found in the IP output. + + """ + addr = dict((i, []) for i in _NAME_TO_IP_VER.values()) + + for row in ip_output.splitlines(): + match = _IP_FAMILY_RE.search(row) + if match and IPAddress.IsValid(match.group("ip")): + addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip")) + + return addr + def GetSocketCredentials(sock): """Returns the credentials of the foreign process connected to a socket. @@ -57,11 +100,44 @@ def GetSocketCredentials(sock): @return: The PID, UID and GID of the connected foreign process. """ - peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED, + peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED, _STRUCT_UCRED_SIZE) return struct.unpack(_STRUCT_UCRED, peercred) +def IsValidInterface(ifname): + """Validate an interface name. + + @type ifname: string + @param ifname: Name of the network interface + @return: boolean indicating whether the interface name is valid or not. + + """ + return os.path.exists(utils.PathJoin("/sys/class/net", ifname)) + + +def GetInterfaceIpAddresses(ifname): + """Returns the IP addresses associated to the interface. + + @type ifname: string + @param ifname: Name of the network interface + @return: A dict having for keys the IP version (either + L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for + values the lists of IP addresses of the respective version + associated to the interface + + """ + result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show", + ifname]) + + if result.failed: + logging.error("Error running the ip command while getting the IP" + " addresses of %s", ifname) + return None + + return _GetIpAddressesFromIpOutput(result.output) + + def GetHostname(name=None, family=None): """Returns a Hostname object. @@ -71,7 +147,7 @@ def GetHostname(name=None, family=None): @param family: AF_INET | AF_INET6 | None @rtype: L{Hostname} @return: Hostname object - @raise: errors.OpPrereqError + @raise errors.OpPrereqError: in case of errors in resolving """ try: @@ -98,7 +174,7 @@ class Hostname: @param name: hostname or None """ - self.name = self.GetNormalizedName(self.GetFqdn(name)) + self.name = self.GetFqdn(name) self.ip = self.GetIP(self.name, family=family) @classmethod @@ -108,8 +184,8 @@ class Hostname: """ return cls.GetFqdn() - @staticmethod - def GetFqdn(hostname=None): + @classmethod + def GetFqdn(cls, hostname=None): """Return fqdn. If hostname is None the system's fqdn is returned. @@ -121,9 +197,15 @@ class Hostname: """ if hostname is None: - return socket.getfqdn() + virtfqdn = vcluster.GetVirtualHostname() + if virtfqdn: + result = virtfqdn + else: + result = socket.getfqdn() else: - return socket.getfqdn(hostname) + result = socket.getfqdn(hostname) + + return cls.GetNormalizedName(result) @staticmethod def GetIP(hostname, family=None): @@ -156,7 +238,12 @@ class Hostname: try: return result[0][4][0] except IndexError, err: - raise errors.ResolverError("Unknown error in getaddrinfo(): %s" % err) + # we don't have here an actual error code, it's just that the + # data type returned by getaddrinfo is not what we expected; + # let's keep the same format in the exception arguments with a + # dummy error code + raise errors.ResolverError(hostname, 0, + "Unknown error in getaddrinfo(): %s" % err) @classmethod def GetNormalizedName(cls, hostname): @@ -187,7 +274,7 @@ def TcpPing(target, port, timeout=10, live_port_needed=False, source=None): to it. @type target: str - @param target: the IP or hostname to ping + @param target: the IP to ping @type port: int @param port: the port to connect to @type timeout: int @@ -201,10 +288,14 @@ def TcpPing(target, port, timeout=10, live_port_needed=False, source=None): than C{EADDRNOTAVAIL} will be ignored """ + logging.debug("Attempting to reach TCP port %s on target %s with a timeout" + " of %s seconds", port, target, timeout) + try: family = IPAddress.GetAddressFamily(target) - except errors.GenericError: - return False + except errors.IPAddressError, err: + raise errors.ProgrammerError("Family of IP address given in parameter" + " 'target' can't be determined: %s" % err) sock = socket.socket(family, socket.SOCK_STREAM) success = False @@ -212,8 +303,8 @@ def TcpPing(target, port, timeout=10, live_port_needed=False, source=None): if source is not None: try: sock.bind((source, 0)) - except socket.error, (errcode, _): - if errcode == errno.EADDRNOTAVAIL: + except socket.error, err: + if err[0] == errno.EADDRNOTAVAIL: success = False sock.settimeout(timeout) @@ -224,8 +315,8 @@ def TcpPing(target, port, timeout=10, live_port_needed=False, source=None): success = True except socket.timeout: success = False - except socket.error, (errcode, _): - success = (not live_port_needed) and (errcode == errno.ECONNREFUSED) + except socket.error, err: + success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED) return success @@ -294,6 +385,20 @@ class IPAddress(object): return False @classmethod + def ValidateNetmask(cls, netmask): + """Validate a netmask suffix in CIDR notation. + + @type netmask: int + @param netmask: netmask suffix to validate + @rtype: bool + @return: True if valid, False otherwise + + """ + assert (isinstance(netmask, (int, long))) + + return 0 < netmask <= cls.iplen + + @classmethod def Own(cls, address): """Check if the current host has the the given IP address. @@ -349,9 +454,9 @@ class IPAddress(object): assert 0 <= prefix <= cls.iplen target_int = cls._GetIPIntFromString(subnet[0]) # Convert prefix netmask to integer value of netmask - netmask_int = (2**cls.iplen)-1 ^ ((2**cls.iplen)-1 >> prefix) + netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix) # Calculate hostmask - hostmask_int = netmask_int ^ (2**cls.iplen)-1 + hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1 # Calculate network address by and'ing netmask network_int = target_int & netmask_int # Calculate broadcast address by or'ing hostmask @@ -366,7 +471,7 @@ class IPAddress(object): @type address: str @param address: ip address whose family will be returned @rtype: int - @return: socket.AF_INET or socket.AF_INET6 + @return: C{socket.AF_INET} or C{socket.AF_INET6} @raise errors.GenericError: for invalid addresses """ @@ -382,6 +487,73 @@ class IPAddress(object): raise errors.IPAddressError("Invalid address '%s'" % address) + @staticmethod + def GetVersionFromAddressFamily(family): + """Convert an IP address family to the corresponding IP version. + + @type family: int + @param family: IP address family, one of socket.AF_INET or socket.AF_INET6 + @return: an int containing the IP version, one of L{constants.IP4_VERSION} + or L{constants.IP6_VERSION} + @raise errors.ProgrammerError: for unknown families + + """ + if family == socket.AF_INET: + return constants.IP4_VERSION + elif family == socket.AF_INET6: + return constants.IP6_VERSION + + raise errors.ProgrammerError("%s is not a valid IP address family" % family) + + @staticmethod + def GetAddressFamilyFromVersion(version): + """Convert an IP version to the corresponding IP address family. + + @type version: int + @param version: IP version, one of L{constants.IP4_VERSION} or + L{constants.IP6_VERSION} + @return: an int containing the IP address family, one of C{socket.AF_INET} + or C{socket.AF_INET6} + @raise errors.ProgrammerError: for unknown IP versions + + """ + if version == constants.IP4_VERSION: + return socket.AF_INET + elif version == constants.IP6_VERSION: + return socket.AF_INET6 + + raise errors.ProgrammerError("%s is not a valid IP version" % version) + + @staticmethod + def GetClassFromIpVersion(version): + """Return the IPAddress subclass for the given IP version. + + @type version: int + @param version: IP version, one of L{constants.IP4_VERSION} or + L{constants.IP6_VERSION} + @return: a subclass of L{netutils.IPAddress} + @raise errors.ProgrammerError: for unknowo IP versions + + """ + if version == constants.IP4_VERSION: + return IP4Address + elif version == constants.IP6_VERSION: + return IP6Address + + raise errors.ProgrammerError("%s is not a valid IP version" % version) + + @staticmethod + def GetClassFromIpFamily(family): + """Return the IPAddress subclass for the given IP family. + + @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6} + @return: a subclass of L{netutils.IPAddress} + @raise errors.ProgrammerError: for unknowo IP versions + + """ + return IPAddress.GetClassFromIpVersion( + IPAddress.GetVersionFromAddressFamily(family)) + @classmethod def IsLoopback(cls, address): """Determine whether it is a loopback address. @@ -425,7 +597,7 @@ class IP4Address(IPAddress): """Get integer value of IPv4 address. @type address: str - @param: IPv6 address + @param address: IPv6 address @rtype: int @return: integer value of given IP address @@ -465,7 +637,7 @@ class IP6Address(IPAddress): """Get integer value of IPv6 address. @type address: str - @param: IPv6 address + @param address: IPv6 address @rtype: int @return: integer value of given IP address @@ -476,29 +648,35 @@ class IP6Address(IPAddress): # We have a shorthand address, expand it parts = [] twoparts = address.split("::") - sep = len(twoparts[0].split(':')) + len(twoparts[1].split(':')) - parts = twoparts[0].split(':') - [parts.append("0") for _ in range(8 - sep)] - parts += twoparts[1].split(':') + sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":")) + parts = twoparts[0].split(":") + parts.extend(["0"] * (8 - sep)) + parts += twoparts[1].split(":") else: parts = address.split(":") address_int = 0 for part in parts: - address_int = (address_int << 16) + int(part or '0', 16) + address_int = (address_int << 16) + int(part or "0", 16) return address_int -def FormatAddress(family, address): +def FormatAddress(address, family=None): """Format a socket address - @type family: integer - @param family: socket family (one of socket.AF_*) @type address: family specific (usually tuple) @param address: address, as reported by this class + @type family: integer + @param family: socket family (one of socket.AF_*) or None """ + if family is None: + try: + family = IPAddress.GetAddressFamily(address[0]) + except errors.IPAddressError: + raise errors.ParameterError(address) + if family == socket.AF_UNIX and len(address) == 3: return "pid=%s, uid=%s, gid=%s" % address