verify-disks: Explicitely state nothing has to be done
[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
42 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
43 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
44 #
45 # The GNU C Library defines gid_t and uid_t to be "unsigned int" and
46 # pid_t to "int".
47 #
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"
51 _STRUCT_UCRED = "iII"
52 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
53
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,
57                            re.IGNORECASE)
58
59 # Dict used to convert from a string representing an IP family to an IP
60 # version
61 _NAME_TO_IP_VER = {
62   "inet": constants.IP4_VERSION,
63   "inet6": constants.IP6_VERSION,
64   }
65
66
67 def _GetIpAddressesFromIpOutput(ip_output):
68   """Parses the output of the ip command and retrieves the IP addresses and
69   version.
70
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.
75
76   """
77   addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
78
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"))
83
84   return addr
85
86
87 def GetSocketCredentials(sock):
88   """Returns the credentials of the foreign process connected to a socket.
89
90   @param sock: Unix socket
91   @rtype: tuple; (number, number, number)
92   @return: The PID, UID and GID of the connected foreign process.
93
94   """
95   peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
96                              _STRUCT_UCRED_SIZE)
97   return struct.unpack(_STRUCT_UCRED, peercred)
98
99
100 def IsValidInterface(ifname):
101   """Validate an interface name.
102
103   @type ifname: string
104   @param ifname: Name of the network interface
105   @return: boolean indicating whether the interface name is valid or not.
106
107   """
108   return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
109
110
111 def GetInterfaceIpAddresses(ifname):
112   """Returns the IP addresses associated to the interface.
113
114   @type ifname: string
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
120
121   """
122   result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
123                          ifname])
124
125   if result.failed:
126     logging.error("Error running the ip command while getting the IP"
127                   " addresses of %s", ifname)
128     return None
129
130   return _GetIpAddressesFromIpOutput(result.output)
131
132
133 def GetHostname(name=None, family=None):
134   """Returns a Hostname object.
135
136   @type name: str
137   @param name: hostname or None
138   @type family: int
139   @param family: AF_INET | AF_INET6 | None
140   @rtype: L{Hostname}
141   @return: Hostname object
142   @raise errors.OpPrereqError: in case of errors in resolving
143
144   """
145   try:
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)
150
151
152 class Hostname:
153   """Class implementing resolver and hostname functionality.
154
155   """
156   _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
157
158   def __init__(self, name=None, family=None):
159     """Initialize the host name object.
160
161     If the name argument is None, it will use this system's name.
162
163     @type family: int
164     @param family: AF_INET | AF_INET6 | None
165     @type name: str
166     @param name: hostname or None
167
168     """
169     self.name = self.GetNormalizedName(self.GetFqdn(name))
170     self.ip = self.GetIP(self.name, family=family)
171
172   @classmethod
173   def GetSysName(cls):
174     """Legacy method the get the current system's name.
175
176     """
177     return cls.GetFqdn()
178
179   @staticmethod
180   def GetFqdn(hostname=None):
181     """Return fqdn.
182
183     If hostname is None the system's fqdn is returned.
184
185     @type hostname: str
186     @param hostname: name to be fqdn'ed
187     @rtype: str
188     @return: fqdn of given name, if it exists, unmodified name otherwise
189
190     """
191     if hostname is None:
192       return socket.getfqdn()
193     else:
194       return socket.getfqdn(hostname)
195
196   @staticmethod
197   def GetIP(hostname, family=None):
198     """Return IP address of given hostname.
199
200     Supports both IPv4 and IPv6.
201
202     @type hostname: str
203     @param hostname: hostname to look up
204     @type family: int
205     @param family: AF_INET | AF_INET6 | None
206     @rtype: str
207     @return: IP address
208     @raise errors.ResolverError: in case of errors in resolving
209
210     """
211     try:
212       if family in (socket.AF_INET, socket.AF_INET6):
213         result = socket.getaddrinfo(hostname, None, family)
214       else:
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])
220
221     # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
222     # canonname, sockaddr). We return the first tuple's first address in
223     # sockaddr
224     try:
225       return result[0][4][0]
226     except IndexError, err:
227       raise errors.ResolverError("Unknown error in getaddrinfo(): %s" % err)
228
229   @classmethod
230   def GetNormalizedName(cls, hostname):
231     """Validate and normalize the given hostname.
232
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
236
237     """
238     hostname = hostname.lower()
239     if (not cls._VALID_NAME_RE.match(hostname) or
240         # double-dots, meaning empty label
241         ".." in hostname or
242         # empty initial label
243         hostname.startswith(".")):
244       raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
245                                  errors.ECODE_INVAL)
246     if hostname.endswith("."):
247       hostname = hostname.rstrip(".")
248     return hostname
249
250
251 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
252   """Simple ping implementation using TCP connect(2).
253
254   Check if the given IP is reachable by doing attempting a TCP connect
255   to it.
256
257   @type target: str
258   @param target: the IP to ping
259   @type port: int
260   @param port: the port to connect to
261   @type timeout: int
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
270
271   """
272   try:
273     family = IPAddress.GetAddressFamily(target)
274   except errors.GenericError:
275     return False
276
277   sock = socket.socket(family, socket.SOCK_STREAM)
278   success = False
279
280   if source is not None:
281     try:
282       sock.bind((source, 0))
283     except socket.error, err:
284       if err[0] == errno.EADDRNOTAVAIL:
285         success = False
286
287   sock.settimeout(timeout)
288
289   try:
290     sock.connect((target, port))
291     sock.close()
292     success = True
293   except socket.timeout:
294     success = False
295   except socket.error, err:
296     success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
297
298   return success
299
300
301 def GetDaemonPort(daemon_name):
302   """Get the daemon port for this cluster.
303
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.
307
308   @type daemon_name: string
309   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
310   @rtype: int
311
312   """
313   if daemon_name not in constants.DAEMONS_PORTS:
314     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
315
316   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
317   try:
318     port = socket.getservbyname(daemon_name, proto)
319   except socket.error:
320     port = default_port
321
322   return port
323
324
325 class IPAddress(object):
326   """Class that represents an IP address.
327
328   """
329   iplen = 0
330   family = None
331   loopback_cidr = None
332
333   @staticmethod
334   def _GetIPIntFromString(address):
335     """Abstract method to please pylint.
336
337     """
338     raise NotImplementedError
339
340   @classmethod
341   def IsValid(cls, address):
342     """Validate a IP address.
343
344     @type address: str
345     @param address: IP address to be checked
346     @rtype: bool
347     @return: True if valid, False otherwise
348
349     """
350     if cls.family is None:
351       try:
352         family = cls.GetAddressFamily(address)
353       except errors.IPAddressError:
354         return False
355     else:
356       family = cls.family
357
358     try:
359       socket.inet_pton(family, address)
360       return True
361     except socket.error:
362       return False
363
364   @classmethod
365   def ValidateNetmask(cls, netmask):
366     """Validate a netmask suffix in CIDR notation.
367
368     @type netmask: int
369     @param netmask: netmask suffix to validate
370     @rtype: bool
371     @return: True if valid, False otherwise
372
373     """
374     assert (isinstance(netmask, (int, long)))
375
376     return 0 < netmask <= cls.iplen
377
378   @classmethod
379   def Own(cls, address):
380     """Check if the current host has the the given IP address.
381
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.
384
385     @type address: str
386     @param address: IP address to be checked
387     @rtype: bool
388     @return: True if we own the address, False otherwise
389
390     """
391     if cls.family is None:
392       try:
393         family = cls.GetAddressFamily(address)
394       except errors.IPAddressError:
395         return False
396     else:
397       family = cls.family
398
399     s = socket.socket(family, socket.SOCK_DGRAM)
400     success = False
401     try:
402       try:
403         s.bind((address, 0))
404         success = True
405       except socket.error:
406         success = False
407     finally:
408       s.close()
409     return success
410
411   @classmethod
412   def InNetwork(cls, cidr, address):
413     """Determine whether an address is within a network.
414
415     @type cidr: string
416     @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
417     @type address: str
418     @param address: IP address
419     @rtype: bool
420     @return: True if address is in cidr, False otherwise
421
422     """
423     address_int = cls._GetIPIntFromString(address)
424     subnet = cidr.split("/")
425     assert len(subnet) == 2
426     try:
427       prefix = int(subnet[1])
428     except ValueError:
429       return False
430
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)
435     # Calculate hostmask
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
441
442     return network_int <= address_int <= broadcast_int
443
444   @staticmethod
445   def GetAddressFamily(address):
446     """Get the address family of the given address.
447
448     @type address: str
449     @param address: ip address whose family will be returned
450     @rtype: int
451     @return: C{socket.AF_INET} or C{socket.AF_INET6}
452     @raise errors.GenericError: for invalid addresses
453
454     """
455     try:
456       return IP4Address(address).family
457     except errors.IPAddressError:
458       pass
459
460     try:
461       return IP6Address(address).family
462     except errors.IPAddressError:
463       pass
464
465     raise errors.IPAddressError("Invalid address '%s'" % address)
466
467   @staticmethod
468   def GetVersionFromAddressFamily(family):
469     """Convert an IP address family to the corresponding IP version.
470
471     @type family: int
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
476
477     """
478     if family == socket.AF_INET:
479       return constants.IP4_VERSION
480     elif family == socket.AF_INET6:
481       return constants.IP6_VERSION
482
483     raise errors.ProgrammerError("%s is not a valid IP address family" % family)
484
485   @staticmethod
486   def GetAddressFamilyFromVersion(version):
487     """Convert an IP version to the corresponding IP address family.
488
489     @type version: int
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
495
496     """
497     if version == constants.IP4_VERSION:
498       return socket.AF_INET
499     elif version == constants.IP6_VERSION:
500       return socket.AF_INET6
501
502     raise errors.ProgrammerError("%s is not a valid IP version" % version)
503
504   @staticmethod
505   def GetClassFromIpVersion(version):
506     """Return the IPAddress subclass for the given IP version.
507
508     @type version: int
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
513
514     """
515     if version == constants.IP4_VERSION:
516       return IP4Address
517     elif version == constants.IP6_VERSION:
518       return IP6Address
519
520     raise errors.ProgrammerError("%s is not a valid IP version" % version)
521
522   @staticmethod
523   def GetClassFromIpFamily(family):
524     """Return the IPAddress subclass for the given IP family.
525
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
529
530     """
531     return IPAddress.GetClassFromIpVersion(
532               IPAddress.GetVersionFromAddressFamily(family))
533
534   @classmethod
535   def IsLoopback(cls, address):
536     """Determine whether it is a loopback address.
537
538     @type address: str
539     @param address: IP address to be checked
540     @rtype: bool
541     @return: True if loopback, False otherwise
542
543     """
544     try:
545       return cls.InNetwork(cls.loopback_cidr, address)
546     except errors.IPAddressError:
547       return False
548
549
550 class IP4Address(IPAddress):
551   """IPv4 address class.
552
553   """
554   iplen = 32
555   family = socket.AF_INET
556   loopback_cidr = "127.0.0.0/8"
557
558   def __init__(self, address):
559     """Constructor for IPv4 address.
560
561     @type address: str
562     @param address: IP address
563     @raises errors.IPAddressError: if address invalid
564
565     """
566     IPAddress.__init__(self)
567     if not self.IsValid(address):
568       raise errors.IPAddressError("IPv4 Address %s invalid" % address)
569
570     self.address = address
571
572   @staticmethod
573   def _GetIPIntFromString(address):
574     """Get integer value of IPv4 address.
575
576     @type address: str
577     @param address: IPv6 address
578     @rtype: int
579     @return: integer value of given IP address
580
581     """
582     address_int = 0
583     parts = address.split(".")
584     assert len(parts) == 4
585     for part in parts:
586       address_int = (address_int << 8) | int(part)
587
588     return address_int
589
590
591 class IP6Address(IPAddress):
592   """IPv6 address class.
593
594   """
595   iplen = 128
596   family = socket.AF_INET6
597   loopback_cidr = "::1/128"
598
599   def __init__(self, address):
600     """Constructor for IPv6 address.
601
602     @type address: str
603     @param address: IP address
604     @raises errors.IPAddressError: if address invalid
605
606     """
607     IPAddress.__init__(self)
608     if not self.IsValid(address):
609       raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
610     self.address = address
611
612   @staticmethod
613   def _GetIPIntFromString(address):
614     """Get integer value of IPv6 address.
615
616     @type address: str
617     @param address: IPv6 address
618     @rtype: int
619     @return: integer value of given IP address
620
621     """
622     doublecolons = address.count("::")
623     assert not doublecolons > 1
624     if doublecolons == 1:
625       # We have a shorthand address, expand it
626       parts = []
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(":")
632     else:
633       parts = address.split(":")
634
635     address_int = 0
636     for part in parts:
637       address_int = (address_int << 16) + int(part or "0", 16)
638
639     return address_int
640
641
642 def FormatAddress(address, family=None):
643   """Format a socket address
644
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
649
650   """
651   if family is None:
652     try:
653       family = IPAddress.GetAddressFamily(address[0])
654     except errors.IPAddressError:
655       raise errors.ParameterError(address)
656
657   if family == socket.AF_UNIX and len(address) == 3:
658     return "pid=%s, uid=%s, gid=%s" % address
659
660   if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
661     host, port = address
662     if family == socket.AF_INET6:
663       res = "[%s]" % host
664     else:
665       res = host
666
667     if port is not None:
668       res += ":%s" % port
669
670     return res
671
672   raise errors.ParameterError(family, address)