Index nodes by their UUID
[ganeti-local] / lib / netutils.py
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 from ganeti import vcluster
42
43 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
44 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
45 #
46 # The GNU C Library defines gid_t and uid_t to be "unsigned int" and
47 # pid_t to "int".
48 #
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"
52 _STRUCT_UCRED = "iII"
53 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
54
55 # Workaround a bug in some linux distributions that don't define SO_PEERCRED
56 try:
57   _SO_PEERCRED = IN.SO_PEERCRED
58 except AttributeError:
59   _SO_PEERCRED = 17
60
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,
64                            re.IGNORECASE)
65
66 # Dict used to convert from a string representing an IP family to an IP
67 # version
68 _NAME_TO_IP_VER = {
69   "inet": constants.IP4_VERSION,
70   "inet6": constants.IP6_VERSION,
71   }
72
73
74 def _GetIpAddressesFromIpOutput(ip_output):
75   """Parses the output of the ip command and retrieves the IP addresses and
76   version.
77
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.
82
83   """
84   addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
85
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"))
90
91   return addr
92
93
94 def GetSocketCredentials(sock):
95   """Returns the credentials of the foreign process connected to a socket.
96
97   @param sock: Unix socket
98   @rtype: tuple; (number, number, number)
99   @return: The PID, UID and GID of the connected foreign process.
100
101   """
102   peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED,
103                              _STRUCT_UCRED_SIZE)
104   return struct.unpack(_STRUCT_UCRED, peercred)
105
106
107 def IsValidInterface(ifname):
108   """Validate an interface name.
109
110   @type ifname: string
111   @param ifname: Name of the network interface
112   @return: boolean indicating whether the interface name is valid or not.
113
114   """
115   return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
116
117
118 def GetInterfaceIpAddresses(ifname):
119   """Returns the IP addresses associated to the interface.
120
121   @type ifname: string
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
127
128   """
129   result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
130                          ifname])
131
132   if result.failed:
133     logging.error("Error running the ip command while getting the IP"
134                   " addresses of %s", ifname)
135     return None
136
137   return _GetIpAddressesFromIpOutput(result.output)
138
139
140 def GetHostname(name=None, family=None):
141   """Returns a Hostname object.
142
143   @type name: str
144   @param name: hostname or None
145   @type family: int
146   @param family: AF_INET | AF_INET6 | None
147   @rtype: L{Hostname}
148   @return: Hostname object
149   @raise errors.OpPrereqError: in case of errors in resolving
150
151   """
152   try:
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)
157
158
159 class Hostname:
160   """Class implementing resolver and hostname functionality.
161
162   """
163   _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
164
165   def __init__(self, name=None, family=None):
166     """Initialize the host name object.
167
168     If the name argument is None, it will use this system's name.
169
170     @type family: int
171     @param family: AF_INET | AF_INET6 | None
172     @type name: str
173     @param name: hostname or None
174
175     """
176     self.name = self.GetFqdn(name)
177     self.ip = self.GetIP(self.name, family=family)
178
179   @classmethod
180   def GetSysName(cls):
181     """Legacy method the get the current system's name.
182
183     """
184     return cls.GetFqdn()
185
186   @classmethod
187   def GetFqdn(cls, hostname=None):
188     """Return fqdn.
189
190     If hostname is None the system's fqdn is returned.
191
192     @type hostname: str
193     @param hostname: name to be fqdn'ed
194     @rtype: str
195     @return: fqdn of given name, if it exists, unmodified name otherwise
196
197     """
198     if hostname is None:
199       virtfqdn = vcluster.GetVirtualHostname()
200       if virtfqdn:
201         result = virtfqdn
202       else:
203         result = socket.getfqdn()
204     else:
205       result = socket.getfqdn(hostname)
206
207     return cls.GetNormalizedName(result)
208
209   @staticmethod
210   def GetIP(hostname, family=None):
211     """Return IP address of given hostname.
212
213     Supports both IPv4 and IPv6.
214
215     @type hostname: str
216     @param hostname: hostname to look up
217     @type family: int
218     @param family: AF_INET | AF_INET6 | None
219     @rtype: str
220     @return: IP address
221     @raise errors.ResolverError: in case of errors in resolving
222
223     """
224     try:
225       if family in (socket.AF_INET, socket.AF_INET6):
226         result = socket.getaddrinfo(hostname, None, family)
227       else:
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])
233
234     # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
235     # canonname, sockaddr). We return the first tuple's first address in
236     # sockaddr
237     try:
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
243       # dummy error code
244       raise errors.ResolverError(hostname, 0,
245                                  "Unknown error in getaddrinfo(): %s" % err)
246
247   @classmethod
248   def GetNormalizedName(cls, hostname):
249     """Validate and normalize the given hostname.
250
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
254
255     """
256     hostname = hostname.lower()
257     if (not cls._VALID_NAME_RE.match(hostname) or
258         # double-dots, meaning empty label
259         ".." in hostname or
260         # empty initial label
261         hostname.startswith(".")):
262       raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
263                                  errors.ECODE_INVAL)
264     if hostname.endswith("."):
265       hostname = hostname.rstrip(".")
266     return hostname
267
268
269 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
270   """Simple ping implementation using TCP connect(2).
271
272   Check if the given IP is reachable by doing attempting a TCP connect
273   to it.
274
275   @type target: str
276   @param target: the IP to ping
277   @type port: int
278   @param port: the port to connect to
279   @type timeout: int
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
288
289   """
290   logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
291                 " of %s seconds", port, target, timeout)
292
293   try:
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)
298
299   sock = socket.socket(family, socket.SOCK_STREAM)
300   success = False
301
302   if source is not None:
303     try:
304       sock.bind((source, 0))
305     except socket.error, err:
306       if err[0] == errno.EADDRNOTAVAIL:
307         success = False
308
309   sock.settimeout(timeout)
310
311   try:
312     sock.connect((target, port))
313     sock.close()
314     success = True
315   except socket.timeout:
316     success = False
317   except socket.error, err:
318     success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
319
320   return success
321
322
323 def GetDaemonPort(daemon_name):
324   """Get the daemon port for this cluster.
325
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.
329
330   @type daemon_name: string
331   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
332   @rtype: int
333
334   """
335   if daemon_name not in constants.DAEMONS_PORTS:
336     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
337
338   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
339   try:
340     port = socket.getservbyname(daemon_name, proto)
341   except socket.error:
342     port = default_port
343
344   return port
345
346
347 class IPAddress(object):
348   """Class that represents an IP address.
349
350   """
351   iplen = 0
352   family = None
353   loopback_cidr = None
354
355   @staticmethod
356   def _GetIPIntFromString(address):
357     """Abstract method to please pylint.
358
359     """
360     raise NotImplementedError
361
362   @classmethod
363   def IsValid(cls, address):
364     """Validate a IP address.
365
366     @type address: str
367     @param address: IP address to be checked
368     @rtype: bool
369     @return: True if valid, False otherwise
370
371     """
372     if cls.family is None:
373       try:
374         family = cls.GetAddressFamily(address)
375       except errors.IPAddressError:
376         return False
377     else:
378       family = cls.family
379
380     try:
381       socket.inet_pton(family, address)
382       return True
383     except socket.error:
384       return False
385
386   @classmethod
387   def ValidateNetmask(cls, netmask):
388     """Validate a netmask suffix in CIDR notation.
389
390     @type netmask: int
391     @param netmask: netmask suffix to validate
392     @rtype: bool
393     @return: True if valid, False otherwise
394
395     """
396     assert (isinstance(netmask, (int, long)))
397
398     return 0 < netmask <= cls.iplen
399
400   @classmethod
401   def Own(cls, address):
402     """Check if the current host has the the given IP address.
403
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.
406
407     @type address: str
408     @param address: IP address to be checked
409     @rtype: bool
410     @return: True if we own the address, False otherwise
411
412     """
413     if cls.family is None:
414       try:
415         family = cls.GetAddressFamily(address)
416       except errors.IPAddressError:
417         return False
418     else:
419       family = cls.family
420
421     s = socket.socket(family, socket.SOCK_DGRAM)
422     success = False
423     try:
424       try:
425         s.bind((address, 0))
426         success = True
427       except socket.error:
428         success = False
429     finally:
430       s.close()
431     return success
432
433   @classmethod
434   def InNetwork(cls, cidr, address):
435     """Determine whether an address is within a network.
436
437     @type cidr: string
438     @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
439     @type address: str
440     @param address: IP address
441     @rtype: bool
442     @return: True if address is in cidr, False otherwise
443
444     """
445     address_int = cls._GetIPIntFromString(address)
446     subnet = cidr.split("/")
447     assert len(subnet) == 2
448     try:
449       prefix = int(subnet[1])
450     except ValueError:
451       return False
452
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)
457     # Calculate hostmask
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
463
464     return network_int <= address_int <= broadcast_int
465
466   @staticmethod
467   def GetAddressFamily(address):
468     """Get the address family of the given address.
469
470     @type address: str
471     @param address: ip address whose family will be returned
472     @rtype: int
473     @return: C{socket.AF_INET} or C{socket.AF_INET6}
474     @raise errors.GenericError: for invalid addresses
475
476     """
477     try:
478       return IP4Address(address).family
479     except errors.IPAddressError:
480       pass
481
482     try:
483       return IP6Address(address).family
484     except errors.IPAddressError:
485       pass
486
487     raise errors.IPAddressError("Invalid address '%s'" % address)
488
489   @staticmethod
490   def GetVersionFromAddressFamily(family):
491     """Convert an IP address family to the corresponding IP version.
492
493     @type family: int
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
498
499     """
500     if family == socket.AF_INET:
501       return constants.IP4_VERSION
502     elif family == socket.AF_INET6:
503       return constants.IP6_VERSION
504
505     raise errors.ProgrammerError("%s is not a valid IP address family" % family)
506
507   @staticmethod
508   def GetAddressFamilyFromVersion(version):
509     """Convert an IP version to the corresponding IP address family.
510
511     @type version: int
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
517
518     """
519     if version == constants.IP4_VERSION:
520       return socket.AF_INET
521     elif version == constants.IP6_VERSION:
522       return socket.AF_INET6
523
524     raise errors.ProgrammerError("%s is not a valid IP version" % version)
525
526   @staticmethod
527   def GetClassFromIpVersion(version):
528     """Return the IPAddress subclass for the given IP version.
529
530     @type version: int
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
535
536     """
537     if version == constants.IP4_VERSION:
538       return IP4Address
539     elif version == constants.IP6_VERSION:
540       return IP6Address
541
542     raise errors.ProgrammerError("%s is not a valid IP version" % version)
543
544   @staticmethod
545   def GetClassFromIpFamily(family):
546     """Return the IPAddress subclass for the given IP family.
547
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
551
552     """
553     return IPAddress.GetClassFromIpVersion(
554               IPAddress.GetVersionFromAddressFamily(family))
555
556   @classmethod
557   def IsLoopback(cls, address):
558     """Determine whether it is a loopback address.
559
560     @type address: str
561     @param address: IP address to be checked
562     @rtype: bool
563     @return: True if loopback, False otherwise
564
565     """
566     try:
567       return cls.InNetwork(cls.loopback_cidr, address)
568     except errors.IPAddressError:
569       return False
570
571
572 class IP4Address(IPAddress):
573   """IPv4 address class.
574
575   """
576   iplen = 32
577   family = socket.AF_INET
578   loopback_cidr = "127.0.0.0/8"
579
580   def __init__(self, address):
581     """Constructor for IPv4 address.
582
583     @type address: str
584     @param address: IP address
585     @raises errors.IPAddressError: if address invalid
586
587     """
588     IPAddress.__init__(self)
589     if not self.IsValid(address):
590       raise errors.IPAddressError("IPv4 Address %s invalid" % address)
591
592     self.address = address
593
594   @staticmethod
595   def _GetIPIntFromString(address):
596     """Get integer value of IPv4 address.
597
598     @type address: str
599     @param address: IPv6 address
600     @rtype: int
601     @return: integer value of given IP address
602
603     """
604     address_int = 0
605     parts = address.split(".")
606     assert len(parts) == 4
607     for part in parts:
608       address_int = (address_int << 8) | int(part)
609
610     return address_int
611
612
613 class IP6Address(IPAddress):
614   """IPv6 address class.
615
616   """
617   iplen = 128
618   family = socket.AF_INET6
619   loopback_cidr = "::1/128"
620
621   def __init__(self, address):
622     """Constructor for IPv6 address.
623
624     @type address: str
625     @param address: IP address
626     @raises errors.IPAddressError: if address invalid
627
628     """
629     IPAddress.__init__(self)
630     if not self.IsValid(address):
631       raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
632     self.address = address
633
634   @staticmethod
635   def _GetIPIntFromString(address):
636     """Get integer value of IPv6 address.
637
638     @type address: str
639     @param address: IPv6 address
640     @rtype: int
641     @return: integer value of given IP address
642
643     """
644     doublecolons = address.count("::")
645     assert not doublecolons > 1
646     if doublecolons == 1:
647       # We have a shorthand address, expand it
648       parts = []
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(":")
654     else:
655       parts = address.split(":")
656
657     address_int = 0
658     for part in parts:
659       address_int = (address_int << 16) + int(part or "0", 16)
660
661     return address_int
662
663
664 def FormatAddress(address, family=None):
665   """Format a socket address
666
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
671
672   """
673   if family is None:
674     try:
675       family = IPAddress.GetAddressFamily(address[0])
676     except errors.IPAddressError:
677       raise errors.ParameterError(address)
678
679   if family == socket.AF_UNIX and len(address) == 3:
680     return "pid=%s, uid=%s, gid=%s" % address
681
682   if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
683     host, port = address
684     if family == socket.AF_INET6:
685       res = "[%s]" % host
686     else:
687       res = host
688
689     if port is not None:
690       res += ":%s" % port
691
692     return res
693
694   raise errors.ParameterError(family, address)