1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
44
45
46
47
48
49
50
51
52 _STRUCT_UCRED = "iII"
53 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
54
55
56 try:
57
58 _SO_PEERCRED = IN.SO_PEERCRED
59 except AttributeError:
60 _SO_PEERCRED = 17
61
62
63 _IP_RE_TEXT = r"[.:a-z0-9]+"
64 _IP_FAMILY_RE = re.compile(r"(?P<family>inet6?)\s+(?P<ip>%s)/" % _IP_RE_TEXT,
65 re.IGNORECASE)
66
67
68
69 _NAME_TO_IP_VER = {
70 "inet": constants.IP4_VERSION,
71 "inet6": constants.IP6_VERSION,
72 }
76 """Parses the output of the ip command and retrieves the IP addresses and
77 version.
78
79 @param ip_output: string containing the output of the ip command;
80 @rtype: dict; (int, list)
81 @return: a dict having as keys the IP versions and as values the
82 corresponding list of addresses found in the IP output.
83
84 """
85 addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
86
87 for row in ip_output.splitlines():
88 match = _IP_FAMILY_RE.search(row)
89 if match and IPAddress.IsValid(match.group("ip")):
90 addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip"))
91
92 return addr
93
96 """Returns the credentials of the foreign process connected to a socket.
97
98 @param sock: Unix socket
99 @rtype: tuple; (number, number, number)
100 @return: The PID, UID and GID of the connected foreign process.
101
102 """
103 peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED,
104 _STRUCT_UCRED_SIZE)
105 return struct.unpack(_STRUCT_UCRED, peercred)
106
109 """Validate an interface name.
110
111 @type ifname: string
112 @param ifname: Name of the network interface
113 @return: boolean indicating whether the interface name is valid or not.
114
115 """
116 return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
117
120 """Returns the IP addresses associated to the interface.
121
122 @type ifname: string
123 @param ifname: Name of the network interface
124 @return: A dict having for keys the IP version (either
125 L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for
126 values the lists of IP addresses of the respective version
127 associated to the interface
128
129 """
130 result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
131 ifname])
132
133 if result.failed:
134 logging.error("Error running the ip command while getting the IP"
135 " addresses of %s", ifname)
136 return None
137
138 return _GetIpAddressesFromIpOutput(result.output)
139
142 """Returns a Hostname object.
143
144 @type name: str
145 @param name: hostname or None
146 @type family: int
147 @param family: AF_INET | AF_INET6 | None
148 @rtype: L{Hostname}
149 @return: Hostname object
150 @raise errors.OpPrereqError: in case of errors in resolving
151
152 """
153 try:
154 return Hostname(name=name, family=family)
155 except errors.ResolverError, err:
156 raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
157 (err[0], err[2]), errors.ECODE_RESOLVER)
158
161 """Class implementing resolver and hostname functionality.
162
163 """
164 _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
165
166 - def __init__(self, name=None, family=None):
167 """Initialize the host name object.
168
169 If the name argument is None, it will use this system's name.
170
171 @type family: int
172 @param family: AF_INET | AF_INET6 | None
173 @type name: str
174 @param name: hostname or None
175
176 """
177 self.name = self.GetFqdn(name)
178 self.ip = self.GetIP(self.name, family=family)
179
180 @classmethod
182 """Legacy method the get the current system's name.
183
184 """
185 return cls.GetFqdn()
186
187 @classmethod
189 """Return fqdn.
190
191 If hostname is None the system's fqdn is returned.
192
193 @type hostname: str
194 @param hostname: name to be fqdn'ed
195 @rtype: str
196 @return: fqdn of given name, if it exists, unmodified name otherwise
197
198 """
199 if hostname is None:
200 virtfqdn = vcluster.GetVirtualHostname()
201 if virtfqdn:
202 result = virtfqdn
203 else:
204 result = socket.getfqdn()
205 else:
206 result = socket.getfqdn(hostname)
207
208 return cls.GetNormalizedName(result)
209
210 @staticmethod
211 - def GetIP(hostname, family=None):
212 """Return IP address of given hostname.
213
214 Supports both IPv4 and IPv6.
215
216 @type hostname: str
217 @param hostname: hostname to look up
218 @type family: int
219 @param family: AF_INET | AF_INET6 | None
220 @rtype: str
221 @return: IP address
222 @raise errors.ResolverError: in case of errors in resolving
223
224 """
225 try:
226 if family in (socket.AF_INET, socket.AF_INET6):
227 result = socket.getaddrinfo(hostname, None, family)
228 else:
229 result = socket.getaddrinfo(hostname, None)
230 except (socket.gaierror, socket.herror, socket.error), err:
231
232
233 raise errors.ResolverError(hostname, err.args[0], err.args[1])
234
235
236
237
238 try:
239 return result[0][4][0]
240 except IndexError, err:
241
242
243
244
245 raise errors.ResolverError(hostname, 0,
246 "Unknown error in getaddrinfo(): %s" % err)
247
248 @classmethod
250 """Validate and normalize the given hostname.
251
252 @attention: the validation is a bit more relaxed than the standards
253 require; most importantly, we allow underscores in names
254 @raise errors.OpPrereqError: when the name is not valid
255
256 """
257 hostname = hostname.lower()
258 if (not cls._VALID_NAME_RE.match(hostname) or
259
260 ".." in hostname or
261
262 hostname.startswith(".")):
263 raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
264 errors.ECODE_INVAL)
265 if hostname.endswith("."):
266 hostname = hostname.rstrip(".")
267 return hostname
268
269
270 -def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
271 """Simple ping implementation using TCP connect(2).
272
273 Check if the given IP is reachable by doing attempting a TCP connect
274 to it.
275
276 @type target: str
277 @param target: the IP to ping
278 @type port: int
279 @param port: the port to connect to
280 @type timeout: int
281 @param timeout: the timeout on the connection attempt
282 @type live_port_needed: boolean
283 @param live_port_needed: whether a closed port will cause the
284 function to return failure, as if there was a timeout
285 @type source: str or None
286 @param source: if specified, will cause the connect to be made
287 from this specific source address; failures to bind other
288 than C{EADDRNOTAVAIL} will be ignored
289
290 """
291 logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
292 " of %s seconds", port, target, timeout)
293
294 try:
295 family = IPAddress.GetAddressFamily(target)
296 except errors.IPAddressError, err:
297 raise errors.ProgrammerError("Family of IP address given in parameter"
298 " 'target' can't be determined: %s" % err)
299
300 sock = socket.socket(family, socket.SOCK_STREAM)
301 success = False
302
303 if source is not None:
304 try:
305 sock.bind((source, 0))
306 except socket.error, err:
307 if err[0] == errno.EADDRNOTAVAIL:
308 success = False
309
310 sock.settimeout(timeout)
311
312 try:
313 sock.connect((target, port))
314 sock.close()
315 success = True
316 except socket.timeout:
317 success = False
318 except socket.error, err:
319 success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
320
321 return success
322
325 """Get the daemon port for this cluster.
326
327 Note that this routine does not read a ganeti-specific file, but
328 instead uses C{socket.getservbyname} to allow pre-customization of
329 this parameter outside of Ganeti.
330
331 @type daemon_name: string
332 @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
333 @rtype: int
334
335 """
336 if daemon_name not in constants.DAEMONS_PORTS:
337 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
338
339 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
340 try:
341 port = socket.getservbyname(daemon_name, proto)
342 except socket.error:
343 port = default_port
344
345 return port
346
349 """Class that represents an IP address.
350
351 """
352 iplen = 0
353 family = None
354 loopback_cidr = None
355
356 @staticmethod
358 """Abstract method to please pylint.
359
360 """
361 raise NotImplementedError
362
363 @classmethod
365 """Validate a IP address.
366
367 @type address: str
368 @param address: IP address to be checked
369 @rtype: bool
370 @return: True if valid, False otherwise
371
372 """
373 if cls.family is None:
374 try:
375 family = cls.GetAddressFamily(address)
376 except errors.IPAddressError:
377 return False
378 else:
379 family = cls.family
380
381 try:
382 socket.inet_pton(family, address)
383 return True
384 except socket.error:
385 return False
386
387 @classmethod
389 """Validate a netmask suffix in CIDR notation.
390
391 @type netmask: int
392 @param netmask: netmask suffix to validate
393 @rtype: bool
394 @return: True if valid, False otherwise
395
396 """
397 assert (isinstance(netmask, (int, long)))
398
399 return 0 < netmask <= cls.iplen
400
401 @classmethod
402 - def Own(cls, address):
403 """Check if the current host has the the given IP address.
404
405 This is done by trying to bind the given address. We return True if we
406 succeed or false if a socket.error is raised.
407
408 @type address: str
409 @param address: IP address to be checked
410 @rtype: bool
411 @return: True if we own the address, False otherwise
412
413 """
414 if cls.family is None:
415 try:
416 family = cls.GetAddressFamily(address)
417 except errors.IPAddressError:
418 return False
419 else:
420 family = cls.family
421
422 s = socket.socket(family, socket.SOCK_DGRAM)
423 success = False
424 try:
425 try:
426 s.bind((address, 0))
427 success = True
428 except socket.error:
429 success = False
430 finally:
431 s.close()
432 return success
433
434 @classmethod
436 """Determine whether an address is within a network.
437
438 @type cidr: string
439 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
440 @type address: str
441 @param address: IP address
442 @rtype: bool
443 @return: True if address is in cidr, False otherwise
444
445 """
446 address_int = cls._GetIPIntFromString(address)
447 subnet = cidr.split("/")
448 assert len(subnet) == 2
449 try:
450 prefix = int(subnet[1])
451 except ValueError:
452 return False
453
454 assert 0 <= prefix <= cls.iplen
455 target_int = cls._GetIPIntFromString(subnet[0])
456
457 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
458
459 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
460
461 network_int = target_int & netmask_int
462
463 broadcast_int = target_int | hostmask_int
464
465 return network_int <= address_int <= broadcast_int
466
467 @staticmethod
469 """Get the address family of the given address.
470
471 @type address: str
472 @param address: ip address whose family will be returned
473 @rtype: int
474 @return: C{socket.AF_INET} or C{socket.AF_INET6}
475 @raise errors.GenericError: for invalid addresses
476
477 """
478 try:
479 return IP4Address(address).family
480 except errors.IPAddressError:
481 pass
482
483 try:
484 return IP6Address(address).family
485 except errors.IPAddressError:
486 pass
487
488 raise errors.IPAddressError("Invalid address '%s'" % address)
489
490 @staticmethod
492 """Convert an IP address family to the corresponding IP version.
493
494 @type family: int
495 @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
496 @return: an int containing the IP version, one of L{constants.IP4_VERSION}
497 or L{constants.IP6_VERSION}
498 @raise errors.ProgrammerError: for unknown families
499
500 """
501 if family == socket.AF_INET:
502 return constants.IP4_VERSION
503 elif family == socket.AF_INET6:
504 return constants.IP6_VERSION
505
506 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
507
508 @staticmethod
510 """Convert an IP version to the corresponding IP address family.
511
512 @type version: int
513 @param version: IP version, one of L{constants.IP4_VERSION} or
514 L{constants.IP6_VERSION}
515 @return: an int containing the IP address family, one of C{socket.AF_INET}
516 or C{socket.AF_INET6}
517 @raise errors.ProgrammerError: for unknown IP versions
518
519 """
520 if version == constants.IP4_VERSION:
521 return socket.AF_INET
522 elif version == constants.IP6_VERSION:
523 return socket.AF_INET6
524
525 raise errors.ProgrammerError("%s is not a valid IP version" % version)
526
527 @staticmethod
529 """Return the IPAddress subclass for the given IP version.
530
531 @type version: int
532 @param version: IP version, one of L{constants.IP4_VERSION} or
533 L{constants.IP6_VERSION}
534 @return: a subclass of L{netutils.IPAddress}
535 @raise errors.ProgrammerError: for unknowo IP versions
536
537 """
538 if version == constants.IP4_VERSION:
539 return IP4Address
540 elif version == constants.IP6_VERSION:
541 return IP6Address
542
543 raise errors.ProgrammerError("%s is not a valid IP version" % version)
544
545 @staticmethod
556
557 @classmethod
559 """Determine whether it is a loopback address.
560
561 @type address: str
562 @param address: IP address to be checked
563 @rtype: bool
564 @return: True if loopback, False otherwise
565
566 """
567 try:
568 return cls.InNetwork(cls.loopback_cidr, address)
569 except errors.IPAddressError:
570 return False
571
574 """IPv4 address class.
575
576 """
577 iplen = 32
578 family = socket.AF_INET
579 loopback_cidr = "127.0.0.0/8"
580
582 """Constructor for IPv4 address.
583
584 @type address: str
585 @param address: IP address
586 @raises errors.IPAddressError: if address invalid
587
588 """
589 IPAddress.__init__(self)
590 if not self.IsValid(address):
591 raise errors.IPAddressError("IPv4 Address %s invalid" % address)
592
593 self.address = address
594
595 @staticmethod
597 """Get integer value of IPv4 address.
598
599 @type address: str
600 @param address: IPv6 address
601 @rtype: int
602 @return: integer value of given IP address
603
604 """
605 address_int = 0
606 parts = address.split(".")
607 assert len(parts) == 4
608 for part in parts:
609 address_int = (address_int << 8) | int(part)
610
611 return address_int
612
615 """IPv6 address class.
616
617 """
618 iplen = 128
619 family = socket.AF_INET6
620 loopback_cidr = "::1/128"
621
623 """Constructor for IPv6 address.
624
625 @type address: str
626 @param address: IP address
627 @raises errors.IPAddressError: if address invalid
628
629 """
630 IPAddress.__init__(self)
631 if not self.IsValid(address):
632 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
633 self.address = address
634
635 @staticmethod
637 """Get integer value of IPv6 address.
638
639 @type address: str
640 @param address: IPv6 address
641 @rtype: int
642 @return: integer value of given IP address
643
644 """
645 doublecolons = address.count("::")
646 assert not doublecolons > 1
647 if doublecolons == 1:
648
649 parts = []
650 twoparts = address.split("::")
651 sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
652 parts = twoparts[0].split(":")
653 parts.extend(["0"] * (8 - sep))
654 parts += twoparts[1].split(":")
655 else:
656 parts = address.split(":")
657
658 address_int = 0
659 for part in parts:
660 address_int = (address_int << 16) + int(part or "0", 16)
661
662 return address_int
663
696