Make ipolicy violations a warning
[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.GetNormalizedName(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   @staticmethod
181   def GetFqdn(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         return virtfqdn
196       else:
197         return socket.getfqdn()
198     else:
199       return socket.getfqdn(hostname)
200
201   @staticmethod
202   def GetIP(hostname, family=None):
203     """Return IP address of given hostname.
204
205     Supports both IPv4 and IPv6.
206
207     @type hostname: str
208     @param hostname: hostname to look up
209     @type family: int
210     @param family: AF_INET | AF_INET6 | None
211     @rtype: str
212     @return: IP address
213     @raise errors.ResolverError: in case of errors in resolving
214
215     """
216     try:
217       if family in (socket.AF_INET, socket.AF_INET6):
218         result = socket.getaddrinfo(hostname, None, family)
219       else:
220         result = socket.getaddrinfo(hostname, None)
221     except (socket.gaierror, socket.herror, socket.error), err:
222       # hostname not found in DNS, or other socket exception in the
223       # (code, description format)
224       raise errors.ResolverError(hostname, err.args[0], err.args[1])
225
226     # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
227     # canonname, sockaddr). We return the first tuple's first address in
228     # sockaddr
229     try:
230       return result[0][4][0]
231     except IndexError, err:
232       # we don't have here an actual error code, it's just that the
233       # data type returned by getaddrinfo is not what we expected;
234       # let's keep the same format in the exception arguments with a
235       # dummy error code
236       raise errors.ResolverError(hostname, 0,
237                                  "Unknown error in getaddrinfo(): %s" % err)
238
239   @classmethod
240   def GetNormalizedName(cls, hostname):
241     """Validate and normalize the given hostname.
242
243     @attention: the validation is a bit more relaxed than the standards
244         require; most importantly, we allow underscores in names
245     @raise errors.OpPrereqError: when the name is not valid
246
247     """
248     hostname = hostname.lower()
249     if (not cls._VALID_NAME_RE.match(hostname) or
250         # double-dots, meaning empty label
251         ".." in hostname or
252         # empty initial label
253         hostname.startswith(".")):
254       raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
255                                  errors.ECODE_INVAL)
256     if hostname.endswith("."):
257       hostname = hostname.rstrip(".")
258     return hostname
259
260
261 def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
262   """Simple ping implementation using TCP connect(2).
263
264   Check if the given IP is reachable by doing attempting a TCP connect
265   to it.
266
267   @type target: str
268   @param target: the IP to ping
269   @type port: int
270   @param port: the port to connect to
271   @type timeout: int
272   @param timeout: the timeout on the connection attempt
273   @type live_port_needed: boolean
274   @param live_port_needed: whether a closed port will cause the
275       function to return failure, as if there was a timeout
276   @type source: str or None
277   @param source: if specified, will cause the connect to be made
278       from this specific source address; failures to bind other
279       than C{EADDRNOTAVAIL} will be ignored
280
281   """
282   try:
283     family = IPAddress.GetAddressFamily(target)
284   except errors.GenericError:
285     return False
286
287   sock = socket.socket(family, socket.SOCK_STREAM)
288   success = False
289
290   if source is not None:
291     try:
292       sock.bind((source, 0))
293     except socket.error, err:
294       if err[0] == errno.EADDRNOTAVAIL:
295         success = False
296
297   sock.settimeout(timeout)
298
299   try:
300     sock.connect((target, port))
301     sock.close()
302     success = True
303   except socket.timeout:
304     success = False
305   except socket.error, err:
306     success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
307
308   return success
309
310
311 def GetDaemonPort(daemon_name):
312   """Get the daemon port for this cluster.
313
314   Note that this routine does not read a ganeti-specific file, but
315   instead uses C{socket.getservbyname} to allow pre-customization of
316   this parameter outside of Ganeti.
317
318   @type daemon_name: string
319   @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
320   @rtype: int
321
322   """
323   if daemon_name not in constants.DAEMONS_PORTS:
324     raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
325
326   (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
327   try:
328     port = socket.getservbyname(daemon_name, proto)
329   except socket.error:
330     port = default_port
331
332   return port
333
334
335 class IPAddress(object):
336   """Class that represents an IP address.
337
338   """
339   iplen = 0
340   family = None
341   loopback_cidr = None
342
343   @staticmethod
344   def _GetIPIntFromString(address):
345     """Abstract method to please pylint.
346
347     """
348     raise NotImplementedError
349
350   @classmethod
351   def IsValid(cls, address):
352     """Validate a IP address.
353
354     @type address: str
355     @param address: IP address to be checked
356     @rtype: bool
357     @return: True if valid, False otherwise
358
359     """
360     if cls.family is None:
361       try:
362         family = cls.GetAddressFamily(address)
363       except errors.IPAddressError:
364         return False
365     else:
366       family = cls.family
367
368     try:
369       socket.inet_pton(family, address)
370       return True
371     except socket.error:
372       return False
373
374   @classmethod
375   def ValidateNetmask(cls, netmask):
376     """Validate a netmask suffix in CIDR notation.
377
378     @type netmask: int
379     @param netmask: netmask suffix to validate
380     @rtype: bool
381     @return: True if valid, False otherwise
382
383     """
384     assert (isinstance(netmask, (int, long)))
385
386     return 0 < netmask <= cls.iplen
387
388   @classmethod
389   def Own(cls, address):
390     """Check if the current host has the the given IP address.
391
392     This is done by trying to bind the given address. We return True if we
393     succeed or false if a socket.error is raised.
394
395     @type address: str
396     @param address: IP address to be checked
397     @rtype: bool
398     @return: True if we own the address, False otherwise
399
400     """
401     if cls.family is None:
402       try:
403         family = cls.GetAddressFamily(address)
404       except errors.IPAddressError:
405         return False
406     else:
407       family = cls.family
408
409     s = socket.socket(family, socket.SOCK_DGRAM)
410     success = False
411     try:
412       try:
413         s.bind((address, 0))
414         success = True
415       except socket.error:
416         success = False
417     finally:
418       s.close()
419     return success
420
421   @classmethod
422   def InNetwork(cls, cidr, address):
423     """Determine whether an address is within a network.
424
425     @type cidr: string
426     @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
427     @type address: str
428     @param address: IP address
429     @rtype: bool
430     @return: True if address is in cidr, False otherwise
431
432     """
433     address_int = cls._GetIPIntFromString(address)
434     subnet = cidr.split("/")
435     assert len(subnet) == 2
436     try:
437       prefix = int(subnet[1])
438     except ValueError:
439       return False
440
441     assert 0 <= prefix <= cls.iplen
442     target_int = cls._GetIPIntFromString(subnet[0])
443     # Convert prefix netmask to integer value of netmask
444     netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
445     # Calculate hostmask
446     hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
447     # Calculate network address by and'ing netmask
448     network_int = target_int & netmask_int
449     # Calculate broadcast address by or'ing hostmask
450     broadcast_int = target_int | hostmask_int
451
452     return network_int <= address_int <= broadcast_int
453
454   @staticmethod
455   def GetAddressFamily(address):
456     """Get the address family of the given address.
457
458     @type address: str
459     @param address: ip address whose family will be returned
460     @rtype: int
461     @return: C{socket.AF_INET} or C{socket.AF_INET6}
462     @raise errors.GenericError: for invalid addresses
463
464     """
465     try:
466       return IP4Address(address).family
467     except errors.IPAddressError:
468       pass
469
470     try:
471       return IP6Address(address).family
472     except errors.IPAddressError:
473       pass
474
475     raise errors.IPAddressError("Invalid address '%s'" % address)
476
477   @staticmethod
478   def GetVersionFromAddressFamily(family):
479     """Convert an IP address family to the corresponding IP version.
480
481     @type family: int
482     @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
483     @return: an int containing the IP version, one of L{constants.IP4_VERSION}
484              or L{constants.IP6_VERSION}
485     @raise errors.ProgrammerError: for unknown families
486
487     """
488     if family == socket.AF_INET:
489       return constants.IP4_VERSION
490     elif family == socket.AF_INET6:
491       return constants.IP6_VERSION
492
493     raise errors.ProgrammerError("%s is not a valid IP address family" % family)
494
495   @staticmethod
496   def GetAddressFamilyFromVersion(version):
497     """Convert an IP version to the corresponding IP address family.
498
499     @type version: int
500     @param version: IP version, one of L{constants.IP4_VERSION} or
501                     L{constants.IP6_VERSION}
502     @return: an int containing the IP address family, one of C{socket.AF_INET}
503              or C{socket.AF_INET6}
504     @raise errors.ProgrammerError: for unknown IP versions
505
506     """
507     if version == constants.IP4_VERSION:
508       return socket.AF_INET
509     elif version == constants.IP6_VERSION:
510       return socket.AF_INET6
511
512     raise errors.ProgrammerError("%s is not a valid IP version" % version)
513
514   @staticmethod
515   def GetClassFromIpVersion(version):
516     """Return the IPAddress subclass for the given IP version.
517
518     @type version: int
519     @param version: IP version, one of L{constants.IP4_VERSION} or
520                     L{constants.IP6_VERSION}
521     @return: a subclass of L{netutils.IPAddress}
522     @raise errors.ProgrammerError: for unknowo IP versions
523
524     """
525     if version == constants.IP4_VERSION:
526       return IP4Address
527     elif version == constants.IP6_VERSION:
528       return IP6Address
529
530     raise errors.ProgrammerError("%s is not a valid IP version" % version)
531
532   @staticmethod
533   def GetClassFromIpFamily(family):
534     """Return the IPAddress subclass for the given IP family.
535
536     @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
537     @return: a subclass of L{netutils.IPAddress}
538     @raise errors.ProgrammerError: for unknowo IP versions
539
540     """
541     return IPAddress.GetClassFromIpVersion(
542               IPAddress.GetVersionFromAddressFamily(family))
543
544   @classmethod
545   def IsLoopback(cls, address):
546     """Determine whether it is a loopback address.
547
548     @type address: str
549     @param address: IP address to be checked
550     @rtype: bool
551     @return: True if loopback, False otherwise
552
553     """
554     try:
555       return cls.InNetwork(cls.loopback_cidr, address)
556     except errors.IPAddressError:
557       return False
558
559
560 class IP4Address(IPAddress):
561   """IPv4 address class.
562
563   """
564   iplen = 32
565   family = socket.AF_INET
566   loopback_cidr = "127.0.0.0/8"
567
568   def __init__(self, address):
569     """Constructor for IPv4 address.
570
571     @type address: str
572     @param address: IP address
573     @raises errors.IPAddressError: if address invalid
574
575     """
576     IPAddress.__init__(self)
577     if not self.IsValid(address):
578       raise errors.IPAddressError("IPv4 Address %s invalid" % address)
579
580     self.address = address
581
582   @staticmethod
583   def _GetIPIntFromString(address):
584     """Get integer value of IPv4 address.
585
586     @type address: str
587     @param address: IPv6 address
588     @rtype: int
589     @return: integer value of given IP address
590
591     """
592     address_int = 0
593     parts = address.split(".")
594     assert len(parts) == 4
595     for part in parts:
596       address_int = (address_int << 8) | int(part)
597
598     return address_int
599
600
601 class IP6Address(IPAddress):
602   """IPv6 address class.
603
604   """
605   iplen = 128
606   family = socket.AF_INET6
607   loopback_cidr = "::1/128"
608
609   def __init__(self, address):
610     """Constructor for IPv6 address.
611
612     @type address: str
613     @param address: IP address
614     @raises errors.IPAddressError: if address invalid
615
616     """
617     IPAddress.__init__(self)
618     if not self.IsValid(address):
619       raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
620     self.address = address
621
622   @staticmethod
623   def _GetIPIntFromString(address):
624     """Get integer value of IPv6 address.
625
626     @type address: str
627     @param address: IPv6 address
628     @rtype: int
629     @return: integer value of given IP address
630
631     """
632     doublecolons = address.count("::")
633     assert not doublecolons > 1
634     if doublecolons == 1:
635       # We have a shorthand address, expand it
636       parts = []
637       twoparts = address.split("::")
638       sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
639       parts = twoparts[0].split(":")
640       parts.extend(["0"] * (8 - sep))
641       parts += twoparts[1].split(":")
642     else:
643       parts = address.split(":")
644
645     address_int = 0
646     for part in parts:
647       address_int = (address_int << 16) + int(part or "0", 16)
648
649     return address_int
650
651
652 def FormatAddress(address, family=None):
653   """Format a socket address
654
655   @type address: family specific (usually tuple)
656   @param address: address, as reported by this class
657   @type family: integer
658   @param family: socket family (one of socket.AF_*) or None
659
660   """
661   if family is None:
662     try:
663       family = IPAddress.GetAddressFamily(address[0])
664     except errors.IPAddressError:
665       raise errors.ParameterError(address)
666
667   if family == socket.AF_UNIX and len(address) == 3:
668     return "pid=%s, uid=%s, gid=%s" % address
669
670   if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
671     host, port = address
672     if family == socket.AF_INET6:
673       res = "[%s]" % host
674     else:
675       res = host
676
677     if port is not None:
678       res += ":%s" % port
679
680     return res
681
682   raise errors.ParameterError(family, address)