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