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, err:
284 if err[0] == 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, err:
296 success = (not live_port_needed) and (err[0] == 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
366 """Validate a netmask suffix in CIDR notation.
367
368 @type netmask: int
369 @param netmask: netmask suffix to validate
370 @rtype: bool
371 @return: True if valid, False otherwise
372
373 """
374 assert (isinstance(netmask, (int, long)))
375
376 return 0 < netmask <= cls.iplen
377
378 @classmethod
379 - def Own(cls, address):
380 """Check if the current host has the the given IP address.
381
382 This is done by trying to bind the given address. We return True if we
383 succeed or false if a socket.error is raised.
384
385 @type address: str
386 @param address: IP address to be checked
387 @rtype: bool
388 @return: True if we own the address, False otherwise
389
390 """
391 if cls.family is None:
392 try:
393 family = cls.GetAddressFamily(address)
394 except errors.IPAddressError:
395 return False
396 else:
397 family = cls.family
398
399 s = socket.socket(family, socket.SOCK_DGRAM)
400 success = False
401 try:
402 try:
403 s.bind((address, 0))
404 success = True
405 except socket.error:
406 success = False
407 finally:
408 s.close()
409 return success
410
411 @classmethod
413 """Determine whether an address is within a network.
414
415 @type cidr: string
416 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
417 @type address: str
418 @param address: IP address
419 @rtype: bool
420 @return: True if address is in cidr, False otherwise
421
422 """
423 address_int = cls._GetIPIntFromString(address)
424 subnet = cidr.split("/")
425 assert len(subnet) == 2
426 try:
427 prefix = int(subnet[1])
428 except ValueError:
429 return False
430
431 assert 0 <= prefix <= cls.iplen
432 target_int = cls._GetIPIntFromString(subnet[0])
433
434 netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
435
436 hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
437
438 network_int = target_int & netmask_int
439
440 broadcast_int = target_int | hostmask_int
441
442 return network_int <= address_int <= broadcast_int
443
444 @staticmethod
446 """Get the address family of the given address.
447
448 @type address: str
449 @param address: ip address whose family will be returned
450 @rtype: int
451 @return: C{socket.AF_INET} or C{socket.AF_INET6}
452 @raise errors.GenericError: for invalid addresses
453
454 """
455 try:
456 return IP4Address(address).family
457 except errors.IPAddressError:
458 pass
459
460 try:
461 return IP6Address(address).family
462 except errors.IPAddressError:
463 pass
464
465 raise errors.IPAddressError("Invalid address '%s'" % address)
466
467 @staticmethod
469 """Convert an IP address family to the corresponding IP version.
470
471 @type family: int
472 @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
473 @return: an int containing the IP version, one of L{constants.IP4_VERSION}
474 or L{constants.IP6_VERSION}
475 @raise errors.ProgrammerError: for unknown families
476
477 """
478 if family == socket.AF_INET:
479 return constants.IP4_VERSION
480 elif family == socket.AF_INET6:
481 return constants.IP6_VERSION
482
483 raise errors.ProgrammerError("%s is not a valid IP address family" % family)
484
485 @staticmethod
487 """Convert an IP version to the corresponding IP address family.
488
489 @type version: int
490 @param version: IP version, one of L{constants.IP4_VERSION} or
491 L{constants.IP6_VERSION}
492 @return: an int containing the IP address family, one of C{socket.AF_INET}
493 or C{socket.AF_INET6}
494 @raise errors.ProgrammerError: for unknown IP versions
495
496 """
497 if version == constants.IP4_VERSION:
498 return socket.AF_INET
499 elif version == constants.IP6_VERSION:
500 return socket.AF_INET6
501
502 raise errors.ProgrammerError("%s is not a valid IP version" % version)
503
504 @staticmethod
506 """Return the IPAddress subclass for the given IP version.
507
508 @type version: int
509 @param version: IP version, one of L{constants.IP4_VERSION} or
510 L{constants.IP6_VERSION}
511 @return: a subclass of L{netutils.IPAddress}
512 @raise errors.ProgrammerError: for unknowo IP versions
513
514 """
515 if version == constants.IP4_VERSION:
516 return IP4Address
517 elif version == constants.IP6_VERSION:
518 return IP6Address
519
520 raise errors.ProgrammerError("%s is not a valid IP version" % version)
521
522 @staticmethod
533
534 @classmethod
536 """Determine whether it is a loopback address.
537
538 @type address: str
539 @param address: IP address to be checked
540 @rtype: bool
541 @return: True if loopback, False otherwise
542
543 """
544 try:
545 return cls.InNetwork(cls.loopback_cidr, address)
546 except errors.IPAddressError:
547 return False
548
551 """IPv4 address class.
552
553 """
554 iplen = 32
555 family = socket.AF_INET
556 loopback_cidr = "127.0.0.0/8"
557
559 """Constructor for IPv4 address.
560
561 @type address: str
562 @param address: IP address
563 @raises errors.IPAddressError: if address invalid
564
565 """
566 IPAddress.__init__(self)
567 if not self.IsValid(address):
568 raise errors.IPAddressError("IPv4 Address %s invalid" % address)
569
570 self.address = address
571
572 @staticmethod
574 """Get integer value of IPv4 address.
575
576 @type address: str
577 @param address: IPv6 address
578 @rtype: int
579 @return: integer value of given IP address
580
581 """
582 address_int = 0
583 parts = address.split(".")
584 assert len(parts) == 4
585 for part in parts:
586 address_int = (address_int << 8) | int(part)
587
588 return address_int
589
592 """IPv6 address class.
593
594 """
595 iplen = 128
596 family = socket.AF_INET6
597 loopback_cidr = "::1/128"
598
600 """Constructor for IPv6 address.
601
602 @type address: str
603 @param address: IP address
604 @raises errors.IPAddressError: if address invalid
605
606 """
607 IPAddress.__init__(self)
608 if not self.IsValid(address):
609 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
610 self.address = address
611
612 @staticmethod
614 """Get integer value of IPv6 address.
615
616 @type address: str
617 @param address: IPv6 address
618 @rtype: int
619 @return: integer value of given IP address
620
621 """
622 doublecolons = address.count("::")
623 assert not doublecolons > 1
624 if doublecolons == 1:
625
626 parts = []
627 twoparts = address.split("::")
628 sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
629 parts = twoparts[0].split(":")
630 parts.extend(["0"] * (8 - sep))
631 parts += twoparts[1].split(":")
632 else:
633 parts = address.split(":")
634
635 address_int = 0
636 for part in parts:
637 address_int = (address_int << 16) + int(part or "0", 16)
638
639 return address_int
640
673