Merge branch 'stable-2.7' into stable-2.8
[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   # pylint: disable=E1101
58   _SO_PEERCRED = IN.SO_PEERCRED
59 except AttributeError:
60   _SO_PEERCRED = 17
61
62 # Regexes used to find IP addresses in the output of ip.
63 _IP_RE_TEXT = r"[.:a-z0-9]+"      # separate for testing purposes
64 _IP_FAMILY_RE = re.compile(r"(?P<family>inet6?)\s+(?P<ip>%s)/" % _IP_RE_TEXT,
65                            re.IGNORECASE)
66
67 # Dict used to convert from a string representing an IP family to an IP
68 # version
69 _NAME_TO_IP_VER = {
70   "inet": constants.IP4_VERSION,
71   "inet6": constants.IP6_VERSION,
72   }
73
74
75 def _GetIpAddressesFromIpOutput(ip_output):
76   """Parses the output of the ip command and retrieves the IP addresses and
77   version.
78
79   @param ip_output: string containing the output of the ip command;
80   @rtype: dict; (int, list)
81   @return: a dict having as keys the IP versions and as values the
82            corresponding list of addresses found in the IP output.
83
84   """
85   addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
86
87   for row in ip_output.splitlines():
88     match = _IP_FAMILY_RE.search(row)
89     if match and IPAddress.IsValid(match.group("ip")):
90       addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip"))
91
92   return addr
93
94
95 def GetSocketCredentials(sock):
96   """Returns the credentials of the foreign process connected to a socket.
97
98   @param sock: Unix socket
99   @rtype: tuple; (number, number, number)
100   @return: The PID, UID and GID of the connected foreign process.
101
102   """
103   peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED,
104                              _STRUCT_UCRED_SIZE)
105   return struct.unpack(_STRUCT_UCRED, peercred)
106
107
108 def IsValidInterface(ifname):
109   """Validate an interface name.
110
111   @type ifname: string
112   @param ifname: Name of the network interface
113   @return: boolean indicating whether the interface name is valid or not.
114
115   """
116   return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
117
118
119 def GetInterfaceIpAddresses(ifname):
120   """Returns the IP addresses associated to the interface.
121
122   @type ifname: string
123   @param ifname: Name of the network interface
124   @return: A dict having for keys the IP version (either
125            L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for
126            values the lists of IP addresses of the respective version
127            associated to the interface
128
129   """
130   result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
131                          ifname])
132
133   if result.failed:
134     logging.error("Error running the ip command while getting the IP"
135                   " addresses of %s", ifname)
136     return None
137
138   return _GetIpAddressesFromIpOutput(result.output)
139
140
141 def GetHostname(name=None, family=None):
142   """Returns a Hostname object.
143
144   @type name: str
145   @param name: hostname or None
146   @type family: int
147   @param family: AF_INET | AF_INET6 | None
148   @rtype: L{Hostname}
149   @return: Hostname object
150   @raise errors.OpPrereqError: in case of errors in resolving
151
152   """
153   try:
154     return Hostname(name=name, family=family)
155   except errors.ResolverError, err:
156     raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
157                                (err[0], err[2]), errors.ECODE_RESOLVER)
158
159
160 class Hostname:
161   """Class implementing resolver and hostname functionality.
162
163   """
164   _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
165
166   def __init__(self, name=None, family=None):
167     """Initialize the host name object.
168
169     If the name argument is None, it will use this system's name.
170
171     @type family: int
172     @param family: AF_INET | AF_INET6 | None
173     @type name: str
174     @param name: hostname or None
175
176     """
177     self.name = self.GetFqdn(name)
178     self.ip = self.GetIP(self.name, family=family)
179
180   @classmethod
181   def GetSysName(cls):
182     """Legacy method the get the current system's name.
183
184     """
185     return cls.GetFqdn()
186
187   @classmethod
188   def GetFqdn(cls, hostname=None):
189     """Return fqdn.
190
191     If hostname is None the system's fqdn is returned.
192
193     @type hostname: str
194     @param hostname: name to be fqdn'ed
195     @rtype: str
196     @return: fqdn of given name, if it exists, unmodified name otherwise
197
198     """
199     if hostname is None:
200       virtfqdn = vcluster.GetVirtualHostname()
201       if virtfqdn:
202         result = virtfqdn
203       else:
204         result = socket.getfqdn()
205     else:
206       result = socket.getfqdn(hostname)
207
208     return cls.GetNormalizedName(result)
209
210   @staticmethod
211   def GetIP(hostname, family=None):
212     """Return IP address of given hostname.
213
214     Supports both IPv4 and IPv6.
215
216     @type hostname: str
217     @param hostname: hostname to look up
218     @type family: int
219     @param family: AF_INET | AF_INET6 | None
220     @rtype: str
221     @return: IP address
222     @raise errors.ResolverError: in case of errors in resolving
223
224     """
225     try:
226       if family in (socket.AF_INET, socket.AF_INET6):
227         result = socket.getaddrinfo(hostname, None, family)
228       else:
229         result = socket.getaddrinfo(hostname, None)
230     except (socket.gaierror, socket.herror, socket.error), err:
231       # hostname not found in DNS, or other socket exception in the
232       # (code, description format)
233       raise errors.ResolverError(hostname, err.args[0], err.args[1])
234
235     # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
236     # canonname, sockaddr). We return the first tuple's first address in
237     # sockaddr
238     try:
239       return result[0][4][0]
240     except IndexError, err:
241       # we don't have here an actual error code, it's just that the
242       # data type returned by getaddrinfo is not what we expected;
243       # let's keep the same format in the exception arguments with a
244       # dummy error code
245       raise errors.ResolverError(hostname, 0,
246                                  "Unknown error in getaddrinfo(): %s" % err)
247
248   @classmethod
249   def GetNormalizedName(cls, hostname):
250     """Validate and normalize the given hostname.
251
252     @attention: the validation is a bit more relaxed than the standards
253         require; most importantly, we allow underscores in names
254     @raise errors.OpPrereqError: when the name is not valid
255
256     """
257     hostname = hostname.lower()
258     if (not cls._VALID_NAME_RE.match(hostname) or
259         # double-dots, meaning empty label
260         ".." in hostname or
261         # empty initial label
262         hostname.startswith(".")):
263       raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
264                                  errors.ECODE_INVAL)
265     if hostname.endswith("."):
266       hostname = hostname.rstrip(".")
267     return hostname
268
269
270 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
271   """Simple ping implementation using TCP connect(2).
272
273   Check if the given IP is reachable by doing attempting a TCP connect
274   to it.
275
276   @type target: str
277   @param target: the IP to ping
278   @type port: int
279   @param port: the port to connect to
280   @type timeout: int
281   @param timeout: the timeout on the connection attempt
282   @type live_port_needed: boolean
283   @param live_port_needed: whether a closed port will cause the
284       function to return failure, as if there was a timeout
285   @type source: str or None
286   @param source: if specified, will cause the connect to be made
287       from this specific source address; failures to bind other
288       than C{EADDRNOTAVAIL} will be ignored
289
290   """
291   logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
292                 " of %s seconds", port, target, timeout)
293
294   try:
295     family = IPAddress.GetAddressFamily(target)
296   except errors.IPAddressError, err:
297     raise errors.ProgrammerError("Family of IP address given in parameter"
298                                  " 'target' can't be determined: %s" % err)
299
300   sock = socket.socket(family, socket.SOCK_STREAM)
301   success = False
302
303   if source is not None:
304     try:
305       sock.bind((source, 0))
306     except socket.error, err:
307       if err[0] == errno.EADDRNOTAVAIL:
308         success = False
309
310   sock.settimeout(timeout)
311
312   try:
313     sock.connect((target, port))
314     sock.close()
315     success = True
316   except socket.timeout:
317     success = False
318   except socket.error, err:
319     success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
320
321   return success
322
323
324 def GetDaemonPort(daemon_name):
325   """Get the daemon port for this cluster.
326
327   Note that this routine does not read a ganeti-specific file, but
328   instead uses C{socket.getservbyname} to allow pre-customization of
329   this parameter outside of Ganeti.
330
331   @type daemon_name: string
332   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
333   @rtype: int
334
335   """
336   if daemon_name not in constants.DAEMONS_PORTS:
337     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
338
339   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
340   try:
341     port = socket.getservbyname(daemon_name, proto)
342   except socket.error:
343     port = default_port
344
345   return port
346
347
348 class IPAddress(object):
349   """Class that represents an IP address.
350
351   """
352   iplen = 0
353   family = None
354   loopback_cidr = None
355
356   @staticmethod
357   def _GetIPIntFromString(address):
358     """Abstract method to please pylint.
359
360     """
361     raise NotImplementedError
362
363   @classmethod
364   def IsValid(cls, address):
365     """Validate a IP address.
366
367     @type address: str
368     @param address: IP address to be checked
369     @rtype: bool
370     @return: True if valid, False otherwise
371
372     """
373     if cls.family is None:
374       try:
375         family = cls.GetAddressFamily(address)
376       except errors.IPAddressError:
377         return False
378     else:
379       family = cls.family
380
381     try:
382       socket.inet_pton(family, address)
383       return True
384     except socket.error:
385       return False
386
387   @classmethod
388   def ValidateNetmask(cls, netmask):
389     """Validate a netmask suffix in CIDR notation.
390
391     @type netmask: int
392     @param netmask: netmask suffix to validate
393     @rtype: bool
394     @return: True if valid, False otherwise
395
396     """
397     assert (isinstance(netmask, (int, long)))
398
399     return 0 < netmask <= cls.iplen
400
401   @classmethod
402   def Own(cls, address):
403     """Check if the current host has the the given IP address.
404
405     This is done by trying to bind the given address. We return True if we
406     succeed or false if a socket.error is raised.
407
408     @type address: str
409     @param address: IP address to be checked
410     @rtype: bool
411     @return: True if we own the address, False otherwise
412
413     """
414     if cls.family is None:
415       try:
416         family = cls.GetAddressFamily(address)
417       except errors.IPAddressError:
418         return False
419     else:
420       family = cls.family
421
422     s = socket.socket(family, socket.SOCK_DGRAM)
423     success = False
424     try:
425       try:
426         s.bind((address, 0))
427         success = True
428       except socket.error:
429         success = False
430     finally:
431       s.close()
432     return success
433
434   @classmethod
435   def InNetwork(cls, cidr, address):
436     """Determine whether an address is within a network.
437
438     @type cidr: string
439     @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
440     @type address: str
441     @param address: IP address
442     @rtype: bool
443     @return: True if address is in cidr, False otherwise
444
445     """
446     address_int = cls._GetIPIntFromString(address)
447     subnet = cidr.split("/")
448     assert len(subnet) == 2
449     try:
450       prefix = int(subnet[1])
451     except ValueError:
452       return False
453
454     assert 0 <= prefix <= cls.iplen
455     target_int = cls._GetIPIntFromString(subnet[0])
456     # Convert prefix netmask to integer value of netmask
457     netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
458     # Calculate hostmask
459     hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
460     # Calculate network address by and'ing netmask
461     network_int = target_int & netmask_int
462     # Calculate broadcast address by or'ing hostmask
463     broadcast_int = target_int | hostmask_int
464
465     return network_int <= address_int <= broadcast_int
466
467   @staticmethod
468   def GetAddressFamily(address):
469     """Get the address family of the given address.
470
471     @type address: str
472     @param address: ip address whose family will be returned
473     @rtype: int
474     @return: C{socket.AF_INET} or C{socket.AF_INET6}
475     @raise errors.GenericError: for invalid addresses
476
477     """
478     try:
479       return IP4Address(address).family
480     except errors.IPAddressError:
481       pass
482
483     try:
484       return IP6Address(address).family
485     except errors.IPAddressError:
486       pass
487
488     raise errors.IPAddressError("Invalid address '%s'" % address)
489
490   @staticmethod
491   def GetVersionFromAddressFamily(family):
492     """Convert an IP address family to the corresponding IP version.
493
494     @type family: int
495     @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
496     @return: an int containing the IP version, one of L{constants.IP4_VERSION}
497              or L{constants.IP6_VERSION}
498     @raise errors.ProgrammerError: for unknown families
499
500     """
501     if family == socket.AF_INET:
502       return constants.IP4_VERSION
503     elif family == socket.AF_INET6:
504       return constants.IP6_VERSION
505
506     raise errors.ProgrammerError("%s is not a valid IP address family" % family)
507
508   @staticmethod
509   def GetAddressFamilyFromVersion(version):
510     """Convert an IP version to the corresponding IP address family.
511
512     @type version: int
513     @param version: IP version, one of L{constants.IP4_VERSION} or
514                     L{constants.IP6_VERSION}
515     @return: an int containing the IP address family, one of C{socket.AF_INET}
516              or C{socket.AF_INET6}
517     @raise errors.ProgrammerError: for unknown IP versions
518
519     """
520     if version == constants.IP4_VERSION:
521       return socket.AF_INET
522     elif version == constants.IP6_VERSION:
523       return socket.AF_INET6
524
525     raise errors.ProgrammerError("%s is not a valid IP version" % version)
526
527   @staticmethod
528   def GetClassFromIpVersion(version):
529     """Return the IPAddress subclass for the given IP version.
530
531     @type version: int
532     @param version: IP version, one of L{constants.IP4_VERSION} or
533                     L{constants.IP6_VERSION}
534     @return: a subclass of L{netutils.IPAddress}
535     @raise errors.ProgrammerError: for unknowo IP versions
536
537     """
538     if version == constants.IP4_VERSION:
539       return IP4Address
540     elif version == constants.IP6_VERSION:
541       return IP6Address
542
543     raise errors.ProgrammerError("%s is not a valid IP version" % version)
544
545   @staticmethod
546   def GetClassFromIpFamily(family):
547     """Return the IPAddress subclass for the given IP family.
548
549     @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
550     @return: a subclass of L{netutils.IPAddress}
551     @raise errors.ProgrammerError: for unknowo IP versions
552
553     """
554     return IPAddress.GetClassFromIpVersion(
555               IPAddress.GetVersionFromAddressFamily(family))
556
557   @classmethod
558   def IsLoopback(cls, address):
559     """Determine whether it is a loopback address.
560
561     @type address: str
562     @param address: IP address to be checked
563     @rtype: bool
564     @return: True if loopback, False otherwise
565
566     """
567     try:
568       return cls.InNetwork(cls.loopback_cidr, address)
569     except errors.IPAddressError:
570       return False
571
572
573 class IP4Address(IPAddress):
574   """IPv4 address class.
575
576   """
577   iplen = 32
578   family = socket.AF_INET
579   loopback_cidr = "127.0.0.0/8"
580
581   def __init__(self, address):
582     """Constructor for IPv4 address.
583
584     @type address: str
585     @param address: IP address
586     @raises errors.IPAddressError: if address invalid
587
588     """
589     IPAddress.__init__(self)
590     if not self.IsValid(address):
591       raise errors.IPAddressError("IPv4 Address %s invalid" % address)
592
593     self.address = address
594
595   @staticmethod
596   def _GetIPIntFromString(address):
597     """Get integer value of IPv4 address.
598
599     @type address: str
600     @param address: IPv6 address
601     @rtype: int
602     @return: integer value of given IP address
603
604     """
605     address_int = 0
606     parts = address.split(".")
607     assert len(parts) == 4
608     for part in parts:
609       address_int = (address_int << 8) | int(part)
610
611     return address_int
612
613
614 class IP6Address(IPAddress):
615   """IPv6 address class.
616
617   """
618   iplen = 128
619   family = socket.AF_INET6
620   loopback_cidr = "::1/128"
621
622   def __init__(self, address):
623     """Constructor for IPv6 address.
624
625     @type address: str
626     @param address: IP address
627     @raises errors.IPAddressError: if address invalid
628
629     """
630     IPAddress.__init__(self)
631     if not self.IsValid(address):
632       raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
633     self.address = address
634
635   @staticmethod
636   def _GetIPIntFromString(address):
637     """Get integer value of IPv6 address.
638
639     @type address: str
640     @param address: IPv6 address
641     @rtype: int
642     @return: integer value of given IP address
643
644     """
645     doublecolons = address.count("::")
646     assert not doublecolons > 1
647     if doublecolons == 1:
648       # We have a shorthand address, expand it
649       parts = []
650       twoparts = address.split("::")
651       sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
652       parts = twoparts[0].split(":")
653       parts.extend(["0"] * (8 - sep))
654       parts += twoparts[1].split(":")
655     else:
656       parts = address.split(":")
657
658     address_int = 0
659     for part in parts:
660       address_int = (address_int << 16) + int(part or "0", 16)
661
662     return address_int
663
664
665 def FormatAddress(address, family=None):
666   """Format a socket address
667
668   @type address: family specific (usually tuple)
669   @param address: address, as reported by this class
670   @type family: integer
671   @param family: socket family (one of socket.AF_*) or None
672
673   """
674   if family is None:
675     try:
676       family = IPAddress.GetAddressFamily(address[0])
677     except errors.IPAddressError:
678       raise errors.ParameterError(address)
679
680   if family == socket.AF_UNIX and len(address) == 3:
681     return "pid=%s, uid=%s, gid=%s" % address
682
683   if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
684     host, port = address
685     if family == socket.AF_INET6:
686       res = "[%s]" % host
687     else:
688       res = host
689
690     if port is not None:
691       res += ":%s" % port
692
693     return res
694
695   raise errors.ParameterError(family, address)