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