1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
53
54
55
56
57
58
59
60
61 _STRUCT_UCRED = "iII"
62 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
63
64
65 try:
66
67 _SO_PEERCRED = IN.SO_PEERCRED
68 except AttributeError:
69 _SO_PEERCRED = 17
70
71
72 _IP_RE_TEXT = r"[.:a-z0-9]+"
73 _IP_FAMILY_RE = re.compile(r"(?P<family>inet6?)\s+(?P<ip>%s)/" % _IP_RE_TEXT,
74 re.IGNORECASE)
75
76
77
78 _NAME_TO_IP_VER = {
79 "inet": constants.IP4_VERSION,
80 "inet6": constants.IP6_VERSION,
81 }
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
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
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
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
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
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
191 """Legacy method the get the current system's name.
192
193 """
194 return cls.GetFqdn()
195
196 @classmethod
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
241
242 raise errors.ResolverError(hostname, err.args[0], err.args[1])
243
244
245
246
247 try:
248 return result[0][4][0]
249 except IndexError, err:
250
251
252
253
254 raise errors.ResolverError(hostname, 0,
255 "Unknown error in getaddrinfo(): %s" % err)
256
257 @classmethod
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
269 ".." in hostname or
270
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
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
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
383 """Class that represents an IP address.
384
385 """
386 iplen = 0
387 family = None
388 loopback_cidr = None
389
390 @staticmethod
392 """Abstract method to please pylint.
393
394 """
395 raise NotImplementedError
396
397 @classmethod
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
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
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
491 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
492
493 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
494
495 network_int = target_int & netmask_int
496
497 broadcast_int = target_int | hostmask_int
498
499 return network_int <= address_int <= broadcast_int
500
501 @staticmethod
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
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
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
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
590
591 @classmethod
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
608 """IPv4 address class.
609
610 """
611 iplen = 32
612 family = socket.AF_INET
613 loopback_cidr = "127.0.0.0/8"
614
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
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
649 """IPv6 address class.
650
651 """
652 iplen = 128
653 family = socket.AF_INET6
654 loopback_cidr = "::1/128"
655
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
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
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
730