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 re
32 import socket
33 import struct
34 import IN
35
36 from ganeti import constants
37 from ganeti import errors
38
39
40
41
42
43
44
45
46
47
48 _STRUCT_UCRED = "iII"
49 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
53 """Returns the credentials of the foreign process connected to a socket.
54
55 @param sock: Unix socket
56 @rtype: tuple; (number, number, number)
57 @return: The PID, UID and GID of the connected foreign process.
58
59 """
60 peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
61 _STRUCT_UCRED_SIZE)
62 return struct.unpack(_STRUCT_UCRED, peercred)
63
66 """Returns a Hostname object.
67
68 @type name: str
69 @param name: hostname or None
70 @type family: int
71 @param family: AF_INET | AF_INET6 | None
72 @rtype: L{Hostname}
73 @return: Hostname object
74 @raise errors.OpPrereqError: in case of errors in resolving
75
76 """
77 try:
78 return Hostname(name=name, family=family)
79 except errors.ResolverError, err:
80 raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
81 (err[0], err[2]), errors.ECODE_RESOLVER)
82
85 """Class implementing resolver and hostname functionality.
86
87 """
88 _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
89
90 - def __init__(self, name=None, family=None):
91 """Initialize the host name object.
92
93 If the name argument is None, it will use this system's name.
94
95 @type family: int
96 @param family: AF_INET | AF_INET6 | None
97 @type name: str
98 @param name: hostname or None
99
100 """
101 self.name = self.GetNormalizedName(self.GetFqdn(name))
102 self.ip = self.GetIP(self.name, family=family)
103
104 @classmethod
106 """Legacy method the get the current system's name.
107
108 """
109 return cls.GetFqdn()
110
111 @staticmethod
113 """Return fqdn.
114
115 If hostname is None the system's fqdn is returned.
116
117 @type hostname: str
118 @param hostname: name to be fqdn'ed
119 @rtype: str
120 @return: fqdn of given name, if it exists, unmodified name otherwise
121
122 """
123 if hostname is None:
124 return socket.getfqdn()
125 else:
126 return socket.getfqdn(hostname)
127
128 @staticmethod
129 - def GetIP(hostname, family=None):
130 """Return IP address of given hostname.
131
132 Supports both IPv4 and IPv6.
133
134 @type hostname: str
135 @param hostname: hostname to look up
136 @type family: int
137 @param family: AF_INET | AF_INET6 | None
138 @rtype: str
139 @return: IP address
140 @raise errors.ResolverError: in case of errors in resolving
141
142 """
143 try:
144 if family in (socket.AF_INET, socket.AF_INET6):
145 result = socket.getaddrinfo(hostname, None, family)
146 else:
147 result = socket.getaddrinfo(hostname, None)
148 except (socket.gaierror, socket.herror, socket.error), err:
149
150
151 raise errors.ResolverError(hostname, err.args[0], err.args[1])
152
153
154
155
156 try:
157 return result[0][4][0]
158 except IndexError, err:
159 raise errors.ResolverError("Unknown error in getaddrinfo(): %s" % err)
160
161 @classmethod
163 """Validate and normalize the given hostname.
164
165 @attention: the validation is a bit more relaxed than the standards
166 require; most importantly, we allow underscores in names
167 @raise errors.OpPrereqError: when the name is not valid
168
169 """
170 hostname = hostname.lower()
171 if (not cls._VALID_NAME_RE.match(hostname) or
172
173 ".." in hostname or
174
175 hostname.startswith(".")):
176 raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
177 errors.ECODE_INVAL)
178 if hostname.endswith("."):
179 hostname = hostname.rstrip(".")
180 return hostname
181
182
183 -def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
184 """Simple ping implementation using TCP connect(2).
185
186 Check if the given IP is reachable by doing attempting a TCP connect
187 to it.
188
189 @type target: str
190 @param target: the IP or hostname to ping
191 @type port: int
192 @param port: the port to connect to
193 @type timeout: int
194 @param timeout: the timeout on the connection attempt
195 @type live_port_needed: boolean
196 @param live_port_needed: whether a closed port will cause the
197 function to return failure, as if there was a timeout
198 @type source: str or None
199 @param source: if specified, will cause the connect to be made
200 from this specific source address; failures to bind other
201 than C{EADDRNOTAVAIL} will be ignored
202
203 """
204 try:
205 family = IPAddress.GetAddressFamily(target)
206 except errors.GenericError:
207 return False
208
209 sock = socket.socket(family, socket.SOCK_STREAM)
210 success = False
211
212 if source is not None:
213 try:
214 sock.bind((source, 0))
215 except socket.error, (errcode, _):
216 if errcode == errno.EADDRNOTAVAIL:
217 success = False
218
219 sock.settimeout(timeout)
220
221 try:
222 sock.connect((target, port))
223 sock.close()
224 success = True
225 except socket.timeout:
226 success = False
227 except socket.error, (errcode, _):
228 success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
229
230 return success
231
234 """Get the daemon port for this cluster.
235
236 Note that this routine does not read a ganeti-specific file, but
237 instead uses C{socket.getservbyname} to allow pre-customization of
238 this parameter outside of Ganeti.
239
240 @type daemon_name: string
241 @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
242 @rtype: int
243
244 """
245 if daemon_name not in constants.DAEMONS_PORTS:
246 raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
247
248 (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
249 try:
250 port = socket.getservbyname(daemon_name, proto)
251 except socket.error:
252 port = default_port
253
254 return port
255
258 """Class that represents an IP address.
259
260 """
261 iplen = 0
262 family = None
263 loopback_cidr = None
264
265 @staticmethod
267 """Abstract method to please pylint.
268
269 """
270 raise NotImplementedError
271
272 @classmethod
274 """Validate a IP address.
275
276 @type address: str
277 @param address: IP address to be checked
278 @rtype: bool
279 @return: True if valid, False otherwise
280
281 """
282 if cls.family is None:
283 try:
284 family = cls.GetAddressFamily(address)
285 except errors.IPAddressError:
286 return False
287 else:
288 family = cls.family
289
290 try:
291 socket.inet_pton(family, address)
292 return True
293 except socket.error:
294 return False
295
296 @classmethod
297 - def Own(cls, address):
298 """Check if the current host has the the given IP address.
299
300 This is done by trying to bind the given address. We return True if we
301 succeed or false if a socket.error is raised.
302
303 @type address: str
304 @param address: IP address to be checked
305 @rtype: bool
306 @return: True if we own the address, False otherwise
307
308 """
309 if cls.family is None:
310 try:
311 family = cls.GetAddressFamily(address)
312 except errors.IPAddressError:
313 return False
314 else:
315 family = cls.family
316
317 s = socket.socket(family, socket.SOCK_DGRAM)
318 success = False
319 try:
320 try:
321 s.bind((address, 0))
322 success = True
323 except socket.error:
324 success = False
325 finally:
326 s.close()
327 return success
328
329 @classmethod
331 """Determine whether an address is within a network.
332
333 @type cidr: string
334 @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
335 @type address: str
336 @param address: IP address
337 @rtype: bool
338 @return: True if address is in cidr, False otherwise
339
340 """
341 address_int = cls._GetIPIntFromString(address)
342 subnet = cidr.split("/")
343 assert len(subnet) == 2
344 try:
345 prefix = int(subnet[1])
346 except ValueError:
347 return False
348
349 assert 0 <= prefix <= cls.iplen
350 target_int = cls._GetIPIntFromString(subnet[0])
351
352 netmask_int = (2**cls.iplen)-1 ^ ((2**cls.iplen)-1 >> prefix)
353
354 hostmask_int = netmask_int ^ (2**cls.iplen)-1
355
356 network_int = target_int & netmask_int
357
358 broadcast_int = target_int | hostmask_int
359
360 return network_int <= address_int <= broadcast_int
361
362 @staticmethod
364 """Get the address family of the given address.
365
366 @type address: str
367 @param address: ip address whose family will be returned
368 @rtype: int
369 @return: socket.AF_INET or socket.AF_INET6
370 @raise errors.GenericError: for invalid addresses
371
372 """
373 try:
374 return IP4Address(address).family
375 except errors.IPAddressError:
376 pass
377
378 try:
379 return IP6Address(address).family
380 except errors.IPAddressError:
381 pass
382
383 raise errors.IPAddressError("Invalid address '%s'" % address)
384
385 @classmethod
387 """Determine whether it is a loopback address.
388
389 @type address: str
390 @param address: IP address to be checked
391 @rtype: bool
392 @return: True if loopback, False otherwise
393
394 """
395 try:
396 return cls.InNetwork(cls.loopback_cidr, address)
397 except errors.IPAddressError:
398 return False
399
402 """IPv4 address class.
403
404 """
405 iplen = 32
406 family = socket.AF_INET
407 loopback_cidr = "127.0.0.0/8"
408
410 """Constructor for IPv4 address.
411
412 @type address: str
413 @param address: IP address
414 @raises errors.IPAddressError: if address invalid
415
416 """
417 IPAddress.__init__(self)
418 if not self.IsValid(address):
419 raise errors.IPAddressError("IPv4 Address %s invalid" % address)
420
421 self.address = address
422
423 @staticmethod
425 """Get integer value of IPv4 address.
426
427 @type address: str
428 @param address: IPv6 address
429 @rtype: int
430 @return: integer value of given IP address
431
432 """
433 address_int = 0
434 parts = address.split(".")
435 assert len(parts) == 4
436 for part in parts:
437 address_int = (address_int << 8) | int(part)
438
439 return address_int
440
443 """IPv6 address class.
444
445 """
446 iplen = 128
447 family = socket.AF_INET6
448 loopback_cidr = "::1/128"
449
451 """Constructor for IPv6 address.
452
453 @type address: str
454 @param address: IP address
455 @raises errors.IPAddressError: if address invalid
456
457 """
458 IPAddress.__init__(self)
459 if not self.IsValid(address):
460 raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
461 self.address = address
462
463 @staticmethod
465 """Get integer value of IPv6 address.
466
467 @type address: str
468 @param address: IPv6 address
469 @rtype: int
470 @return: integer value of given IP address
471
472 """
473 doublecolons = address.count("::")
474 assert not doublecolons > 1
475 if doublecolons == 1:
476
477 parts = []
478 twoparts = address.split("::")
479 sep = len(twoparts[0].split(':')) + len(twoparts[1].split(':'))
480 parts = twoparts[0].split(':')
481 [parts.append("0") for _ in range(8 - sep)]
482 parts += twoparts[1].split(':')
483 else:
484 parts = address.split(":")
485
486 address_int = 0
487 for part in parts:
488 address_int = (address_int << 16) + int(part or '0', 16)
489
490 return address_int
491
524