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
278
279 -def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
280 """Simple ping implementation using TCP connect(2).
281
282 Check if the given IP is reachable by doing attempting a TCP connect
283 to it.
284
285 @type target: str
286 @param target: the IP to ping
287 @type port: int
288 @param port: the port to connect to
289 @type timeout: int
290 @param timeout: the timeout on the connection attempt
291 @type live_port_needed: boolean
292 @param live_port_needed: whether a closed port will cause the
293 function to return failure, as if there was a timeout
294 @type source: str or None
295 @param source: if specified, will cause the connect to be made
296 from this specific source address; failures to bind other
297 than C{EADDRNOTAVAIL} will be ignored
298
299 """
300 logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
301 " of %s seconds", port, target, timeout)
302
303 try:
304 family = IPAddress.GetAddressFamily(target)
305 except errors.IPAddressError, err:
306 raise errors.ProgrammerError("Family of IP address given in parameter"
307 " 'target' can't be determined: %s" % err)
308
309 sock = socket.socket(family, socket.SOCK_STREAM)
310 success = False
311
312 if source is not None:
313 try:
314 sock.bind((source, 0))
315 except socket.error, err:
316 if err[0] == errno.EADDRNOTAVAIL:
317 success = False
318
319 sock.settimeout(timeout)
320
321 try:
322 sock.connect((target, port))
323 sock.close()
324 success = True
325 except socket.timeout:
326 success = False
327 except socket.error, err:
328 success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
329
330 return success
331
334 """Get the daemon port for this cluster.
335
336 Note that this routine does not read a ganeti-specific file, but
337 instead uses C{socket.getservbyname} to allow pre-customization of
338 this parameter outside of Ganeti.
339
340 @type daemon_name: string
341 @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
342 @rtype: int
343
344 """
345 if daemon_name not in constants.DAEMONS_PORTS:
346 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
347
348 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
349 try:
350 port = socket.getservbyname(daemon_name, proto)
351 except socket.error:
352 port = default_port
353
354 return port
355
358 """Class that represents an IP address.
359
360 """
361 iplen = 0
362 family = None
363 loopback_cidr = None
364
365 @staticmethod
367 """Abstract method to please pylint.
368
369 """
370 raise NotImplementedError
371
372 @classmethod
374 """Validate a IP address.
375
376 @type address: str
377 @param address: IP address to be checked
378 @rtype: bool
379 @return: True if valid, False otherwise
380
381 """
382 if cls.family is None:
383 try:
384 family = cls.GetAddressFamily(address)
385 except errors.IPAddressError:
386 return False
387 else:
388 family = cls.family
389
390 try:
391 socket.inet_pton(family, address)
392 return True
393 except socket.error:
394 return False
395
396 @classmethod
398 """Validate a netmask suffix in CIDR notation.
399
400 @type netmask: int
401 @param netmask: netmask suffix to validate
402 @rtype: bool
403 @return: True if valid, False otherwise
404
405 """
406 assert (isinstance(netmask, (int, long)))
407
408 return 0 < netmask <= cls.iplen
409
410 @classmethod
411 - def Own(cls, address):
412 """Check if the current host has the the given IP address.
413
414 This is done by trying to bind the given address. We return True if we
415 succeed or false if a socket.error is raised.
416
417 @type address: str
418 @param address: IP address to be checked
419 @rtype: bool
420 @return: True if we own the address, False otherwise
421
422 """
423 if cls.family is None:
424 try:
425 family = cls.GetAddressFamily(address)
426 except errors.IPAddressError:
427 return False
428 else:
429 family = cls.family
430
431 s = socket.socket(family, socket.SOCK_DGRAM)
432 success = False
433 try:
434 try:
435 s.bind((address, 0))
436 success = True
437 except socket.error:
438 success = False
439 finally:
440 s.close()
441 return success
442
443 @classmethod
445 """Determine whether an address is within a network.
446
447 @type cidr: string
448 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
449 @type address: str
450 @param address: IP address
451 @rtype: bool
452 @return: True if address is in cidr, False otherwise
453
454 """
455 address_int = cls._GetIPIntFromString(address)
456 subnet = cidr.split("/")
457 assert len(subnet) == 2
458 try:
459 prefix = int(subnet[1])
460 except ValueError:
461 return False
462
463 assert 0 <= prefix <= cls.iplen
464 target_int = cls._GetIPIntFromString(subnet[0])
465
466 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
467
468 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
469
470 network_int = target_int & netmask_int
471
472 broadcast_int = target_int | hostmask_int
473
474 return network_int <= address_int <= broadcast_int
475
476 @staticmethod
478 """Get the address family of the given address.
479
480 @type address: str
481 @param address: ip address whose family will be returned
482 @rtype: int
483 @return: C{socket.AF_INET} or C{socket.AF_INET6}
484 @raise errors.GenericError: for invalid addresses
485
486 """
487 try:
488 return IP4Address(address).family
489 except errors.IPAddressError:
490 pass
491
492 try:
493 return IP6Address(address).family
494 except errors.IPAddressError:
495 pass
496
497 raise errors.IPAddressError("Invalid address '%s'" % address)
498
499 @staticmethod
501 """Convert an IP address family to the corresponding IP version.
502
503 @type family: int
504 @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
505 @return: an int containing the IP version, one of L{constants.IP4_VERSION}
506 or L{constants.IP6_VERSION}
507 @raise errors.ProgrammerError: for unknown families
508
509 """
510 if family == socket.AF_INET:
511 return constants.IP4_VERSION
512 elif family == socket.AF_INET6:
513 return constants.IP6_VERSION
514
515 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
516
517 @staticmethod
519 """Convert an IP version to the corresponding IP address family.
520
521 @type version: int
522 @param version: IP version, one of L{constants.IP4_VERSION} or
523 L{constants.IP6_VERSION}
524 @return: an int containing the IP address family, one of C{socket.AF_INET}
525 or C{socket.AF_INET6}
526 @raise errors.ProgrammerError: for unknown IP versions
527
528 """
529 if version == constants.IP4_VERSION:
530 return socket.AF_INET
531 elif version == constants.IP6_VERSION:
532 return socket.AF_INET6
533
534 raise errors.ProgrammerError("%s is not a valid IP version" % version)
535
536 @staticmethod
538 """Return the IPAddress subclass for the given IP version.
539
540 @type version: int
541 @param version: IP version, one of L{constants.IP4_VERSION} or
542 L{constants.IP6_VERSION}
543 @return: a subclass of L{netutils.IPAddress}
544 @raise errors.ProgrammerError: for unknowo IP versions
545
546 """
547 if version == constants.IP4_VERSION:
548 return IP4Address
549 elif version == constants.IP6_VERSION:
550 return IP6Address
551
552 raise errors.ProgrammerError("%s is not a valid IP version" % version)
553
554 @staticmethod
565
566 @classmethod
568 """Determine whether it is a loopback address.
569
570 @type address: str
571 @param address: IP address to be checked
572 @rtype: bool
573 @return: True if loopback, False otherwise
574
575 """
576 try:
577 return cls.InNetwork(cls.loopback_cidr, address)
578 except errors.IPAddressError:
579 return False
580
583 """IPv4 address class.
584
585 """
586 iplen = 32
587 family = socket.AF_INET
588 loopback_cidr = "127.0.0.0/8"
589
591 """Constructor for IPv4 address.
592
593 @type address: str
594 @param address: IP address
595 @raises errors.IPAddressError: if address invalid
596
597 """
598 IPAddress.__init__(self)
599 if not self.IsValid(address):
600 raise errors.IPAddressError("IPv4 Address %s invalid" % address)
601
602 self.address = address
603
604 @staticmethod
606 """Get integer value of IPv4 address.
607
608 @type address: str
609 @param address: IPv6 address
610 @rtype: int
611 @return: integer value of given IP address
612
613 """
614 address_int = 0
615 parts = address.split(".")
616 assert len(parts) == 4
617 for part in parts:
618 address_int = (address_int << 8) | int(part)
619
620 return address_int
621
624 """IPv6 address class.
625
626 """
627 iplen = 128
628 family = socket.AF_INET6
629 loopback_cidr = "::1/128"
630
632 """Constructor for IPv6 address.
633
634 @type address: str
635 @param address: IP address
636 @raises errors.IPAddressError: if address invalid
637
638 """
639 IPAddress.__init__(self)
640 if not self.IsValid(address):
641 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
642 self.address = address
643
644 @staticmethod
646 """Get integer value of IPv6 address.
647
648 @type address: str
649 @param address: IPv6 address
650 @rtype: int
651 @return: integer value of given IP address
652
653 """
654 doublecolons = address.count("::")
655 assert not doublecolons > 1
656 if doublecolons == 1:
657
658 parts = []
659 twoparts = address.split("::")
660 sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
661 parts = twoparts[0].split(":")
662 parts.extend(["0"] * (8 - sep))
663 parts += twoparts[1].split(":")
664 else:
665 parts = address.split(":")
666
667 address_int = 0
668 for part in parts:
669 address_int = (address_int << 16) + int(part or "0", 16)
670
671 return address_int
672
705