4 # Copyright (C) 2010, 2011, 2012 Google Inc.
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.
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.
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
22 """Ganeti network utility module.
24 This module holds functions that can be used in both daemons (all) and
25 the command line scripts.
38 from ganeti import constants
39 from ganeti import errors
40 from ganeti import utils
41 from ganeti import vcluster
43 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
44 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
46 # The GNU C Library defines gid_t and uid_t to be "unsigned int" and
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"
53 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
55 # Workaround a bug in some linux distributions that don't define SO_PEERCRED
57 _SO_PEERCRED = IN.SO_PEERCRED
58 except AttributeError:
61 # Regexes used to find IP addresses in the output of ip.
62 _IP_RE_TEXT = r"[.:a-z0-9]+" # separate for testing purposes
63 _IP_FAMILY_RE = re.compile(r"(?P<family>inet6?)\s+(?P<ip>%s)/" % _IP_RE_TEXT,
66 # Dict used to convert from a string representing an IP family to an IP
69 "inet": constants.IP4_VERSION,
70 "inet6": constants.IP6_VERSION,
74 def _GetIpAddressesFromIpOutput(ip_output):
75 """Parses the output of the ip command and retrieves the IP addresses and
78 @param ip_output: string containing the output of the ip command;
79 @rtype: dict; (int, list)
80 @return: a dict having as keys the IP versions and as values the
81 corresponding list of addresses found in the IP output.
84 addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
86 for row in ip_output.splitlines():
87 match = _IP_FAMILY_RE.search(row)
88 if match and IPAddress.IsValid(match.group("ip")):
89 addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip"))
94 def GetSocketCredentials(sock):
95 """Returns the credentials of the foreign process connected to a socket.
97 @param sock: Unix socket
98 @rtype: tuple; (number, number, number)
99 @return: The PID, UID and GID of the connected foreign process.
102 peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED,
104 return struct.unpack(_STRUCT_UCRED, peercred)
107 def IsValidInterface(ifname):
108 """Validate an interface name.
111 @param ifname: Name of the network interface
112 @return: boolean indicating whether the interface name is valid or not.
115 return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
118 def GetInterfaceIpAddresses(ifname):
119 """Returns the IP addresses associated to the interface.
122 @param ifname: Name of the network interface
123 @return: A dict having for keys the IP version (either
124 L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for
125 values the lists of IP addresses of the respective version
126 associated to the interface
129 result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
133 logging.error("Error running the ip command while getting the IP"
134 " addresses of %s", ifname)
137 return _GetIpAddressesFromIpOutput(result.output)
140 def GetHostname(name=None, family=None):
141 """Returns a Hostname object.
144 @param name: hostname or None
146 @param family: AF_INET | AF_INET6 | None
148 @return: Hostname object
149 @raise errors.OpPrereqError: in case of errors in resolving
153 return Hostname(name=name, family=family)
154 except errors.ResolverError, err:
155 raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
156 (err[0], err[2]), errors.ECODE_RESOLVER)
160 """Class implementing resolver and hostname functionality.
163 _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
165 def __init__(self, name=None, family=None):
166 """Initialize the host name object.
168 If the name argument is None, it will use this system's name.
171 @param family: AF_INET | AF_INET6 | None
173 @param name: hostname or None
176 self.name = self.GetFqdn(name)
177 self.ip = self.GetIP(self.name, family=family)
181 """Legacy method the get the current system's name.
187 def GetFqdn(cls, hostname=None):
190 If hostname is None the system's fqdn is returned.
193 @param hostname: name to be fqdn'ed
195 @return: fqdn of given name, if it exists, unmodified name otherwise
199 virtfqdn = vcluster.GetVirtualHostname()
203 result = socket.getfqdn()
205 result = socket.getfqdn(hostname)
207 return cls.GetNormalizedName(result)
210 def GetIP(hostname, family=None):
211 """Return IP address of given hostname.
213 Supports both IPv4 and IPv6.
216 @param hostname: hostname to look up
218 @param family: AF_INET | AF_INET6 | None
221 @raise errors.ResolverError: in case of errors in resolving
225 if family in (socket.AF_INET, socket.AF_INET6):
226 result = socket.getaddrinfo(hostname, None, family)
228 result = socket.getaddrinfo(hostname, None)
229 except (socket.gaierror, socket.herror, socket.error), err:
230 # hostname not found in DNS, or other socket exception in the
231 # (code, description format)
232 raise errors.ResolverError(hostname, err.args[0], err.args[1])
234 # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
235 # canonname, sockaddr). We return the first tuple's first address in
238 return result[0][4][0]
239 except IndexError, err:
240 # we don't have here an actual error code, it's just that the
241 # data type returned by getaddrinfo is not what we expected;
242 # let's keep the same format in the exception arguments with a
244 raise errors.ResolverError(hostname, 0,
245 "Unknown error in getaddrinfo(): %s" % err)
248 def GetNormalizedName(cls, hostname):
249 """Validate and normalize the given hostname.
251 @attention: the validation is a bit more relaxed than the standards
252 require; most importantly, we allow underscores in names
253 @raise errors.OpPrereqError: when the name is not valid
256 hostname = hostname.lower()
257 if (not cls._VALID_NAME_RE.match(hostname) or
258 # double-dots, meaning empty label
260 # empty initial label
261 hostname.startswith(".")):
262 raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
264 if hostname.endswith("."):
265 hostname = hostname.rstrip(".")
269 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
270 """Simple ping implementation using TCP connect(2).
272 Check if the given IP is reachable by doing attempting a TCP connect
276 @param target: the IP to ping
278 @param port: the port to connect to
280 @param timeout: the timeout on the connection attempt
281 @type live_port_needed: boolean
282 @param live_port_needed: whether a closed port will cause the
283 function to return failure, as if there was a timeout
284 @type source: str or None
285 @param source: if specified, will cause the connect to be made
286 from this specific source address; failures to bind other
287 than C{EADDRNOTAVAIL} will be ignored
290 logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
291 " of %s seconds", port, target, timeout)
294 family = IPAddress.GetAddressFamily(target)
295 except errors.IPAddressError, err:
296 raise errors.ProgrammerError("Family of IP address given in parameter"
297 " 'target' can't be determined: %s" % err)
299 sock = socket.socket(family, socket.SOCK_STREAM)
302 if source is not None:
304 sock.bind((source, 0))
305 except socket.error, err:
306 if err[0] == errno.EADDRNOTAVAIL:
309 sock.settimeout(timeout)
312 sock.connect((target, port))
315 except socket.timeout:
317 except socket.error, err:
318 success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
323 def GetDaemonPort(daemon_name):
324 """Get the daemon port for this cluster.
326 Note that this routine does not read a ganeti-specific file, but
327 instead uses C{socket.getservbyname} to allow pre-customization of
328 this parameter outside of Ganeti.
330 @type daemon_name: string
331 @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
335 if daemon_name not in constants.DAEMONS_PORTS:
336 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
338 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
340 port = socket.getservbyname(daemon_name, proto)
347 class IPAddress(object):
348 """Class that represents an IP address.
356 def _GetIPIntFromString(address):
357 """Abstract method to please pylint.
360 raise NotImplementedError
363 def IsValid(cls, address):
364 """Validate a IP address.
367 @param address: IP address to be checked
369 @return: True if valid, False otherwise
372 if cls.family is None:
374 family = cls.GetAddressFamily(address)
375 except errors.IPAddressError:
381 socket.inet_pton(family, address)
387 def ValidateNetmask(cls, netmask):
388 """Validate a netmask suffix in CIDR notation.
391 @param netmask: netmask suffix to validate
393 @return: True if valid, False otherwise
396 assert (isinstance(netmask, (int, long)))
398 return 0 < netmask <= cls.iplen
401 def Own(cls, address):
402 """Check if the current host has the the given IP address.
404 This is done by trying to bind the given address. We return True if we
405 succeed or false if a socket.error is raised.
408 @param address: IP address to be checked
410 @return: True if we own the address, False otherwise
413 if cls.family is None:
415 family = cls.GetAddressFamily(address)
416 except errors.IPAddressError:
421 s = socket.socket(family, socket.SOCK_DGRAM)
434 def InNetwork(cls, cidr, address):
435 """Determine whether an address is within a network.
438 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
440 @param address: IP address
442 @return: True if address is in cidr, False otherwise
445 address_int = cls._GetIPIntFromString(address)
446 subnet = cidr.split("/")
447 assert len(subnet) == 2
449 prefix = int(subnet[1])
453 assert 0 <= prefix <= cls.iplen
454 target_int = cls._GetIPIntFromString(subnet[0])
455 # Convert prefix netmask to integer value of netmask
456 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
458 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
459 # Calculate network address by and'ing netmask
460 network_int = target_int & netmask_int
461 # Calculate broadcast address by or'ing hostmask
462 broadcast_int = target_int | hostmask_int
464 return network_int <= address_int <= broadcast_int
467 def GetAddressFamily(address):
468 """Get the address family of the given address.
471 @param address: ip address whose family will be returned
473 @return: C{socket.AF_INET} or C{socket.AF_INET6}
474 @raise errors.GenericError: for invalid addresses
478 return IP4Address(address).family
479 except errors.IPAddressError:
483 return IP6Address(address).family
484 except errors.IPAddressError:
487 raise errors.IPAddressError("Invalid address '%s'" % address)
490 def GetVersionFromAddressFamily(family):
491 """Convert an IP address family to the corresponding IP version.
494 @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
495 @return: an int containing the IP version, one of L{constants.IP4_VERSION}
496 or L{constants.IP6_VERSION}
497 @raise errors.ProgrammerError: for unknown families
500 if family == socket.AF_INET:
501 return constants.IP4_VERSION
502 elif family == socket.AF_INET6:
503 return constants.IP6_VERSION
505 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
508 def GetAddressFamilyFromVersion(version):
509 """Convert an IP version to the corresponding IP address family.
512 @param version: IP version, one of L{constants.IP4_VERSION} or
513 L{constants.IP6_VERSION}
514 @return: an int containing the IP address family, one of C{socket.AF_INET}
515 or C{socket.AF_INET6}
516 @raise errors.ProgrammerError: for unknown IP versions
519 if version == constants.IP4_VERSION:
520 return socket.AF_INET
521 elif version == constants.IP6_VERSION:
522 return socket.AF_INET6
524 raise errors.ProgrammerError("%s is not a valid IP version" % version)
527 def GetClassFromIpVersion(version):
528 """Return the IPAddress subclass for the given IP version.
531 @param version: IP version, one of L{constants.IP4_VERSION} or
532 L{constants.IP6_VERSION}
533 @return: a subclass of L{netutils.IPAddress}
534 @raise errors.ProgrammerError: for unknowo IP versions
537 if version == constants.IP4_VERSION:
539 elif version == constants.IP6_VERSION:
542 raise errors.ProgrammerError("%s is not a valid IP version" % version)
545 def GetClassFromIpFamily(family):
546 """Return the IPAddress subclass for the given IP family.
548 @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
549 @return: a subclass of L{netutils.IPAddress}
550 @raise errors.ProgrammerError: for unknowo IP versions
553 return IPAddress.GetClassFromIpVersion(
554 IPAddress.GetVersionFromAddressFamily(family))
557 def IsLoopback(cls, address):
558 """Determine whether it is a loopback address.
561 @param address: IP address to be checked
563 @return: True if loopback, False otherwise
567 return cls.InNetwork(cls.loopback_cidr, address)
568 except errors.IPAddressError:
572 class IP4Address(IPAddress):
573 """IPv4 address class.
577 family = socket.AF_INET
578 loopback_cidr = "127.0.0.0/8"
580 def __init__(self, address):
581 """Constructor for IPv4 address.
584 @param address: IP address
585 @raises errors.IPAddressError: if address invalid
588 IPAddress.__init__(self)
589 if not self.IsValid(address):
590 raise errors.IPAddressError("IPv4 Address %s invalid" % address)
592 self.address = address
595 def _GetIPIntFromString(address):
596 """Get integer value of IPv4 address.
599 @param address: IPv6 address
601 @return: integer value of given IP address
605 parts = address.split(".")
606 assert len(parts) == 4
608 address_int = (address_int << 8) | int(part)
613 class IP6Address(IPAddress):
614 """IPv6 address class.
618 family = socket.AF_INET6
619 loopback_cidr = "::1/128"
621 def __init__(self, address):
622 """Constructor for IPv6 address.
625 @param address: IP address
626 @raises errors.IPAddressError: if address invalid
629 IPAddress.__init__(self)
630 if not self.IsValid(address):
631 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
632 self.address = address
635 def _GetIPIntFromString(address):
636 """Get integer value of IPv6 address.
639 @param address: IPv6 address
641 @return: integer value of given IP address
644 doublecolons = address.count("::")
645 assert not doublecolons > 1
646 if doublecolons == 1:
647 # We have a shorthand address, expand it
649 twoparts = address.split("::")
650 sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
651 parts = twoparts[0].split(":")
652 parts.extend(["0"] * (8 - sep))
653 parts += twoparts[1].split(":")
655 parts = address.split(":")
659 address_int = (address_int << 16) + int(part or "0", 16)
664 def FormatAddress(address, family=None):
665 """Format a socket address
667 @type address: family specific (usually tuple)
668 @param address: address, as reported by this class
669 @type family: integer
670 @param family: socket family (one of socket.AF_*) or None
675 family = IPAddress.GetAddressFamily(address[0])
676 except errors.IPAddressError:
677 raise errors.ParameterError(address)
679 if family == socket.AF_UNIX and len(address) == 3:
680 return "pid=%s, uid=%s, gid=%s" % address
682 if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
684 if family == socket.AF_INET6:
694 raise errors.ParameterError(family, address)