Rename OpRecreateInstanceDisks and LURecreateInstanceDisks
[ganeti-local] / lib / netutils.py
1 #
2 #
3
4 # Copyright (C) 2010 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
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 # Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
40 # struct ucred { pid_t pid; uid_t uid; gid_t gid; };
41 #
42 # The GNU C Library defines gid_t and uid_t to be "unsigned int" and
43 # pid_t to "int".
44 #
45 # IEEE Std 1003.1-2008:
46 # "nlink_t, uid_t, gid_t, and id_t shall be integer types"
47 # "blksize_t, pid_t, and ssize_t shall be signed integer types"
48 _STRUCT_UCRED = "iII"
49 _STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
50
51
52 def GetSocketCredentials(sock):
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
64
65 def GetHostname(name=None, family=None):
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
83
84 class Hostname:
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
105   def GetSysName(cls):
106     """Legacy method the get the current system's name.
107
108     """
109     return cls.GetFqdn()
110
111   @staticmethod
112   def GetFqdn(hostname=None):
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       # hostname not found in DNS, or other socket exception in the
150       # (code, description format)
151       raise errors.ResolverError(hostname, err.args[0], err.args[1])
152
153     # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
154     # canonname, sockaddr). We return the first tuple's first address in
155     # sockaddr
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
162   def GetNormalizedName(cls, hostname):
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         # double-dots, meaning empty label
173         ".." in hostname or
174         # empty initial label
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
232
233 def GetDaemonPort(daemon_name):
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
256
257 class IPAddress(object):
258   """Class that represents an IP address.
259
260   """
261   iplen = 0
262   family = None
263   loopback_cidr = None
264
265   @staticmethod
266   def _GetIPIntFromString(address):
267     """Abstract method to please pylint.
268
269     """
270     raise NotImplementedError
271
272   @classmethod
273   def IsValid(cls, address):
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
330   def InNetwork(cls, cidr, address):
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     # Convert prefix netmask to integer value of netmask
352     netmask_int = (2**cls.iplen)-1 ^ ((2**cls.iplen)-1 >> prefix)
353     # Calculate hostmask
354     hostmask_int = netmask_int ^ (2**cls.iplen)-1
355     # Calculate network address by and'ing netmask
356     network_int = target_int & netmask_int
357     # Calculate broadcast address by or'ing hostmask
358     broadcast_int = target_int | hostmask_int
359
360     return network_int <= address_int <= broadcast_int
361
362   @staticmethod
363   def GetAddressFamily(address):
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
386   def IsLoopback(cls, address):
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
400
401 class IP4Address(IPAddress):
402   """IPv4 address class.
403
404   """
405   iplen = 32
406   family = socket.AF_INET
407   loopback_cidr = "127.0.0.0/8"
408
409   def __init__(self, address):
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
424   def _GetIPIntFromString(address):
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
441
442 class IP6Address(IPAddress):
443   """IPv6 address class.
444
445   """
446   iplen = 128
447   family = socket.AF_INET6
448   loopback_cidr = "::1/128"
449
450   def __init__(self, address):
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
464   def _GetIPIntFromString(address):
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       # We have a shorthand address, expand it
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
492
493 def FormatAddress(address, family=None):
494   """Format a socket address
495
496   @type address: family specific (usually tuple)
497   @param address: address, as reported by this class
498   @type family: integer
499   @param family: socket family (one of socket.AF_*) or None
500
501   """
502   if family is None:
503     try:
504       family = IPAddress.GetAddressFamily(address[0])
505     except errors.IPAddressError:
506       raise errors.ParameterError(address)
507
508   if family == socket.AF_UNIX and len(address) == 3:
509     return "pid=%s, uid=%s, gid=%s" % address
510
511   if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
512     host, port = address
513     if family == socket.AF_INET6:
514       res = "[%s]" % host
515     else:
516       res = host
517
518     if port is not None:
519       res += ":%s" % port
520
521     return res
522
523   raise errors.ParameterError(family, address)