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