4 # Copyright (C) 2010 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
42 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
43 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
45 # The GNU C Library defines gid_t and uid_t to be "unsigned int" and
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"
52 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
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,
59 # Dict used to convert from a string representing an IP family to an IP
62 "inet": constants.IP4_VERSION,
63 "inet6": constants.IP6_VERSION,
67 def _GetIpAddressesFromIpOutput(ip_output):
68 """Parses the output of the ip command and retrieves the IP addresses and
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.
77 addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
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"))
87 def GetSocketCredentials(sock):
88 """Returns the credentials of the foreign process connected to a socket.
90 @param sock: Unix socket
91 @rtype: tuple; (number, number, number)
92 @return: The PID, UID and GID of the connected foreign process.
95 peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
97 return struct.unpack(_STRUCT_UCRED, peercred)
100 def IsValidInterface(ifname):
101 """Validate an interface name.
104 @param ifname: Name of the network interface
105 @return: boolean indicating whether the interface name is valid or not.
108 return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
111 def GetInterfaceIpAddresses(ifname):
112 """Returns the IP addresses associated to the interface.
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
122 result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
126 logging.error("Error running the ip command while getting the IP"
127 " addresses of %s", ifname)
130 return _GetIpAddressesFromIpOutput(result.output)
133 def GetHostname(name=None, family=None):
134 """Returns a Hostname object.
137 @param name: hostname or None
139 @param family: AF_INET | AF_INET6 | None
141 @return: Hostname object
142 @raise errors.OpPrereqError: in case of errors in resolving
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)
153 """Class implementing resolver and hostname functionality.
156 _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
158 def __init__(self, name=None, family=None):
159 """Initialize the host name object.
161 If the name argument is None, it will use this system's name.
164 @param family: AF_INET | AF_INET6 | None
166 @param name: hostname or None
169 self.name = self.GetNormalizedName(self.GetFqdn(name))
170 self.ip = self.GetIP(self.name, family=family)
174 """Legacy method the get the current system's name.
180 def GetFqdn(hostname=None):
183 If hostname is None the system's fqdn is returned.
186 @param hostname: name to be fqdn'ed
188 @return: fqdn of given name, if it exists, unmodified name otherwise
192 return socket.getfqdn()
194 return socket.getfqdn(hostname)
197 def GetIP(hostname, family=None):
198 """Return IP address of given hostname.
200 Supports both IPv4 and IPv6.
203 @param hostname: hostname to look up
205 @param family: AF_INET | AF_INET6 | None
208 @raise errors.ResolverError: in case of errors in resolving
212 if family in (socket.AF_INET, socket.AF_INET6):
213 result = socket.getaddrinfo(hostname, None, family)
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])
221 # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
222 # canonname, sockaddr). We return the first tuple's first address in
225 return result[0][4][0]
226 except IndexError, err:
227 raise errors.ResolverError("Unknown error in getaddrinfo(): %s" % err)
230 def GetNormalizedName(cls, hostname):
231 """Validate and normalize the given hostname.
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
238 hostname = hostname.lower()
239 if (not cls._VALID_NAME_RE.match(hostname) or
240 # double-dots, meaning empty label
242 # empty initial label
243 hostname.startswith(".")):
244 raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
246 if hostname.endswith("."):
247 hostname = hostname.rstrip(".")
251 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
252 """Simple ping implementation using TCP connect(2).
254 Check if the given IP is reachable by doing attempting a TCP connect
258 @param target: the IP to ping
260 @param port: the port to connect to
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
273 family = IPAddress.GetAddressFamily(target)
274 except errors.GenericError:
277 sock = socket.socket(family, socket.SOCK_STREAM)
280 if source is not None:
282 sock.bind((source, 0))
283 except socket.error, (errcode, _):
284 if errcode == errno.EADDRNOTAVAIL:
287 sock.settimeout(timeout)
290 sock.connect((target, port))
293 except socket.timeout:
295 except socket.error, (errcode, _):
296 success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
301 def GetDaemonPort(daemon_name):
302 """Get the daemon port for this cluster.
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.
308 @type daemon_name: string
309 @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
313 if daemon_name not in constants.DAEMONS_PORTS:
314 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
316 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
318 port = socket.getservbyname(daemon_name, proto)
325 class IPAddress(object):
326 """Class that represents an IP address.
334 def _GetIPIntFromString(address):
335 """Abstract method to please pylint.
338 raise NotImplementedError
341 def IsValid(cls, address):
342 """Validate a IP address.
345 @param address: IP address to be checked
347 @return: True if valid, False otherwise
350 if cls.family is None:
352 family = cls.GetAddressFamily(address)
353 except errors.IPAddressError:
359 socket.inet_pton(family, address)
365 def ValidateNetmask(cls, netmask):
366 """Validate a netmask suffix in CIDR notation.
369 @param netmask: netmask suffix to validate
371 @return: True if valid, False otherwise
374 assert (isinstance(netmask, (int, long)))
376 return 0 < netmask <= cls.iplen
379 def Own(cls, address):
380 """Check if the current host has the the given IP address.
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.
386 @param address: IP address to be checked
388 @return: True if we own the address, False otherwise
391 if cls.family is None:
393 family = cls.GetAddressFamily(address)
394 except errors.IPAddressError:
399 s = socket.socket(family, socket.SOCK_DGRAM)
412 def InNetwork(cls, cidr, address):
413 """Determine whether an address is within a network.
416 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
418 @param address: IP address
420 @return: True if address is in cidr, False otherwise
423 address_int = cls._GetIPIntFromString(address)
424 subnet = cidr.split("/")
425 assert len(subnet) == 2
427 prefix = int(subnet[1])
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)
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
442 return network_int <= address_int <= broadcast_int
445 def GetAddressFamily(address):
446 """Get the address family of the given address.
449 @param address: ip address whose family will be returned
451 @return: C{socket.AF_INET} or C{socket.AF_INET6}
452 @raise errors.GenericError: for invalid addresses
456 return IP4Address(address).family
457 except errors.IPAddressError:
461 return IP6Address(address).family
462 except errors.IPAddressError:
465 raise errors.IPAddressError("Invalid address '%s'" % address)
468 def GetVersionFromAddressFamily(family):
469 """Convert an IP address family to the corresponding IP version.
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
478 if family == socket.AF_INET:
479 return constants.IP4_VERSION
480 elif family == socket.AF_INET6:
481 return constants.IP6_VERSION
483 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
486 def GetAddressFamilyFromVersion(version):
487 """Convert an IP version to the corresponding IP address family.
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
497 if version == constants.IP4_VERSION:
498 return socket.AF_INET
499 elif version == constants.IP6_VERSION:
500 return socket.AF_INET6
502 raise errors.ProgrammerError("%s is not a valid IP version" % version)
505 def GetClassFromIpVersion(version):
506 """Return the IPAddress subclass for the given IP version.
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
515 if version == constants.IP4_VERSION:
517 elif version == constants.IP6_VERSION:
520 raise errors.ProgrammerError("%s is not a valid IP version" % version)
523 def GetClassFromIpFamily(family):
524 """Return the IPAddress subclass for the given IP family.
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
531 return IPAddress.GetClassFromIpVersion(
532 IPAddress.GetVersionFromAddressFamily(family))
535 def IsLoopback(cls, address):
536 """Determine whether it is a loopback address.
539 @param address: IP address to be checked
541 @return: True if loopback, False otherwise
545 return cls.InNetwork(cls.loopback_cidr, address)
546 except errors.IPAddressError:
550 class IP4Address(IPAddress):
551 """IPv4 address class.
555 family = socket.AF_INET
556 loopback_cidr = "127.0.0.0/8"
558 def __init__(self, address):
559 """Constructor for IPv4 address.
562 @param address: IP address
563 @raises errors.IPAddressError: if address invalid
566 IPAddress.__init__(self)
567 if not self.IsValid(address):
568 raise errors.IPAddressError("IPv4 Address %s invalid" % address)
570 self.address = address
573 def _GetIPIntFromString(address):
574 """Get integer value of IPv4 address.
577 @param address: IPv6 address
579 @return: integer value of given IP address
583 parts = address.split(".")
584 assert len(parts) == 4
586 address_int = (address_int << 8) | int(part)
591 class IP6Address(IPAddress):
592 """IPv6 address class.
596 family = socket.AF_INET6
597 loopback_cidr = "::1/128"
599 def __init__(self, address):
600 """Constructor for IPv6 address.
603 @param address: IP address
604 @raises errors.IPAddressError: if address invalid
607 IPAddress.__init__(self)
608 if not self.IsValid(address):
609 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
610 self.address = address
613 def _GetIPIntFromString(address):
614 """Get integer value of IPv6 address.
617 @param address: IPv6 address
619 @return: integer value of given IP address
622 doublecolons = address.count("::")
623 assert not doublecolons > 1
624 if doublecolons == 1:
625 # We have a shorthand address, expand it
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(":")
633 parts = address.split(":")
637 address_int = (address_int << 16) + int(part or "0", 16)
642 def FormatAddress(address, family=None):
643 """Format a socket address
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
653 family = IPAddress.GetAddressFamily(address[0])
654 except errors.IPAddressError:
655 raise errors.ParameterError(address)
657 if family == socket.AF_UNIX and len(address) == 3:
658 return "pid=%s, uid=%s, gid=%s" % address
660 if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
662 if family == socket.AF_INET6:
672 raise errors.ParameterError(family, address)