Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ d367b66c

History | View | Annotate | Download (11.8 kB)

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 GetHostInfo(name=None):
66
  """Lookup host name and raise an OpPrereqError for failures"""
67

    
68
  try:
69
    return HostInfo(name)
70
  except errors.ResolverError, err:
71
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
72
                               (err[0], err[2]), errors.ECODE_RESOLVER)
73

    
74

    
75
class HostInfo:
76
  """Class implementing resolver and hostname functionality
77

78
  """
79
  _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
80

    
81
  def __init__(self, name=None):
82
    """Initialize the host name object.
83

84
    If the name argument is not passed, it will use this system's
85
    name.
86

87
    """
88
    if name is None:
89
      name = self.SysName()
90

    
91
    self.query = name
92
    self.name, self.aliases, self.ipaddrs = self.LookupHostname(name)
93
    self.ip = self.ipaddrs[0]
94

    
95
  def ShortName(self):
96
    """Returns the hostname without domain.
97

98
    """
99
    return self.name.split('.')[0]
100

    
101
  @staticmethod
102
  def SysName():
103
    """Return the current system's name.
104

105
    This is simply a wrapper over C{socket.gethostname()}.
106

107
    """
108
    return socket.gethostname()
109

    
110
  @staticmethod
111
  def LookupHostname(hostname):
112
    """Look up hostname
113

114
    @type hostname: str
115
    @param hostname: hostname to look up
116

117
    @rtype: tuple
118
    @return: a tuple (name, aliases, ipaddrs) as returned by
119
        C{socket.gethostbyname_ex}
120
    @raise errors.ResolverError: in case of errors in resolving
121

122
    """
123
    try:
124
      result = socket.gethostbyname_ex(hostname)
125
    except (socket.gaierror, socket.herror, socket.error), err:
126
      # hostname not found in DNS, or other socket exception in the
127
      # (code, description format)
128
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
129

    
130
    return result
131

    
132
  @classmethod
133
  def NormalizeName(cls, hostname):
134
    """Validate and normalize the given hostname.
135

136
    @attention: the validation is a bit more relaxed than the standards
137
        require; most importantly, we allow underscores in names
138
    @raise errors.OpPrereqError: when the name is not valid
139

140
    """
141
    hostname = hostname.lower()
142
    if (not cls._VALID_NAME_RE.match(hostname) or
143
        # double-dots, meaning empty label
144
        ".." in hostname or
145
        # empty initial label
146
        hostname.startswith(".")):
147
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
148
                                 errors.ECODE_INVAL)
149
    if hostname.endswith("."):
150
      hostname = hostname.rstrip(".")
151
    return hostname
152

    
153

    
154
def TcpPing(target, port, timeout=10, live_port_needed=False, source=None):
155
  """Simple ping implementation using TCP connect(2).
156

157
  Check if the given IP is reachable by doing attempting a TCP connect
158
  to it.
159

160
  @type target: str
161
  @param target: the IP or hostname to ping
162
  @type port: int
163
  @param port: the port to connect to
164
  @type timeout: int
165
  @param timeout: the timeout on the connection attempt
166
  @type live_port_needed: boolean
167
  @param live_port_needed: whether a closed port will cause the
168
      function to return failure, as if there was a timeout
169
  @type source: str or None
170
  @param source: if specified, will cause the connect to be made
171
      from this specific source address; failures to bind other
172
      than C{EADDRNOTAVAIL} will be ignored
173

174
  """
175
  try:
176
    family = IPAddress.GetAddressFamily(target)
177
  except errors.GenericError:
178
    return False
179

    
180
  sock = socket.socket(family, socket.SOCK_STREAM)
181
  success = False
182

    
183
  if source is not None:
184
    try:
185
      sock.bind((source, 0))
186
    except socket.error, (errcode, _):
187
      if errcode == errno.EADDRNOTAVAIL:
188
        success = False
189

    
190
  sock.settimeout(timeout)
191

    
192
  try:
193
    sock.connect((target, port))
194
    sock.close()
195
    success = True
196
  except socket.timeout:
197
    success = False
198
  except socket.error, (errcode, _):
199
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
200

    
201
  return success
202

    
203

    
204
def GetDaemonPort(daemon_name):
205
  """Get the daemon port for this cluster.
206

207
  Note that this routine does not read a ganeti-specific file, but
208
  instead uses C{socket.getservbyname} to allow pre-customization of
209
  this parameter outside of Ganeti.
210

211
  @type daemon_name: string
212
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
213
  @rtype: int
214

215
  """
216
  if daemon_name not in constants.DAEMONS_PORTS:
217
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
218

    
219
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
220
  try:
221
    port = socket.getservbyname(daemon_name, proto)
222
  except socket.error:
223
    port = default_port
224

    
225
  return port
226

    
227

    
228
class IPAddress(object):
229
  """Class that represents an IP address.
230

231
  """
232
  iplen = 0
233
  family = None
234
  loopback_cidr = None
235

    
236
  @staticmethod
237
  def _GetIPIntFromString(address):
238
    """Abstract method to please pylint.
239

240
    """
241
    raise NotImplementedError
242

    
243
  @classmethod
244
  def IsValid(cls, address):
245
    """Validate a IP address.
246

247
    @type address: str
248
    @param address: IP address to be checked
249
    @rtype: bool
250
    @return: True if valid, False otherwise
251

252
    """
253
    if cls.family is None:
254
      try:
255
        family = cls.GetAddressFamily(address)
256
      except errors.IPAddressError:
257
        return False
258
    else:
259
      family = cls.family
260

    
261
    try:
262
      socket.inet_pton(family, address)
263
      return True
264
    except socket.error:
265
      return False
266

    
267
  @classmethod
268
  def Own(cls, address):
269
    """Check if the current host has the the given IP address.
270

271
    This is done by trying to bind the given address. We return True if we
272
    succeed or false if a socket.error is raised.
273

274
    @type address: str
275
    @param address: IP address to be checked
276
    @rtype: bool
277
    @return: True if we own the address, False otherwise
278

279
    """
280
    if cls.family is None:
281
      try:
282
        family = cls.GetAddressFamily(address)
283
      except errors.IPAddressError:
284
        return False
285
    else:
286
      family = cls.family
287

    
288
    s = socket.socket(family, socket.SOCK_DGRAM)
289
    success = False
290
    try:
291
      try:
292
        s.bind((address, 0))
293
        success = True
294
      except socket.error:
295
        success = False
296
    finally:
297
      s.close()
298
    return success
299

    
300
  @classmethod
301
  def InNetwork(cls, cidr, address):
302
    """Determine whether an address is within a network.
303

304
    @type cidr: string
305
    @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
306
    @type address: str
307
    @param address: IP address
308
    @rtype: bool
309
    @return: True if address is in cidr, False otherwise
310

311
    """
312
    address_int = cls._GetIPIntFromString(address)
313
    subnet = cidr.split("/")
314
    assert len(subnet) == 2
315
    try:
316
      prefix = int(subnet[1])
317
    except ValueError:
318
      return False
319

    
320
    assert 0 <= prefix <= cls.iplen
321
    target_int = cls._GetIPIntFromString(subnet[0])
322
    # Convert prefix netmask to integer value of netmask
323
    netmask_int = (2**cls.iplen)-1 ^ ((2**cls.iplen)-1 >> prefix)
324
    # Calculate hostmask
325
    hostmask_int = netmask_int ^ (2**cls.iplen)-1
326
    # Calculate network address by and'ing netmask
327
    network_int = target_int & netmask_int
328
    # Calculate broadcast address by or'ing hostmask
329
    broadcast_int = target_int | hostmask_int
330

    
331
    return network_int <= address_int <= broadcast_int
332

    
333
  @staticmethod
334
  def GetAddressFamily(address):
335
    """Get the address family of the given address.
336

337
    @type address: str
338
    @param address: ip address whose family will be returned
339
    @rtype: int
340
    @return: socket.AF_INET or socket.AF_INET6
341
    @raise errors.GenericError: for invalid addresses
342

343
    """
344
    try:
345
      return IP4Address(address).family
346
    except errors.IPAddressError:
347
      pass
348

    
349
    try:
350
      return IP6Address(address).family
351
    except errors.IPAddressError:
352
      pass
353

    
354
    raise errors.IPAddressError("Invalid address '%s'" % address)
355

    
356
  @classmethod
357
  def IsLoopback(cls, address):
358
    """Determine whether it is a loopback address.
359

360
    @type address: str
361
    @param address: IP address to be checked
362
    @rtype: bool
363
    @return: True if loopback, False otherwise
364

365
    """
366
    try:
367
      return cls.InNetwork(cls.loopback_cidr, address)
368
    except errors.IPAddressError:
369
      return False
370

    
371

    
372
class IP4Address(IPAddress):
373
  """IPv4 address class.
374

375
  """
376
  iplen = 32
377
  family = socket.AF_INET
378
  loopback_cidr = "127.0.0.0/8"
379

    
380
  def __init__(self, address):
381
    """Constructor for IPv4 address.
382

383
    @type address: str
384
    @param address: IP address
385
    @raises errors.IPAddressError: if address invalid
386

387
    """
388
    IPAddress.__init__(self)
389
    if not self.IsValid(address):
390
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
391

    
392
    self.address = address
393

    
394
  @staticmethod
395
  def _GetIPIntFromString(address):
396
    """Get integer value of IPv4 address.
397

398
    @type address: str
399
    @param: IPv6 address
400
    @rtype: int
401
    @return: integer value of given IP address
402

403
    """
404
    address_int = 0
405
    parts = address.split(".")
406
    assert len(parts) == 4
407
    for part in parts:
408
      address_int = (address_int << 8) | int(part)
409

    
410
    return address_int
411

    
412

    
413
class IP6Address(IPAddress):
414
  """IPv6 address class.
415

416
  """
417
  iplen = 128
418
  family = socket.AF_INET6
419
  loopback_cidr = "::1/128"
420

    
421
  def __init__(self, address):
422
    """Constructor for IPv6 address.
423

424
    @type address: str
425
    @param address: IP address
426
    @raises errors.IPAddressError: if address invalid
427

428
    """
429
    IPAddress.__init__(self)
430
    if not self.IsValid(address):
431
      raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
432
    self.address = address
433

    
434
  @staticmethod
435
  def _GetIPIntFromString(address):
436
    """Get integer value of IPv6 address.
437

438
    @type address: str
439
    @param: IPv6 address
440
    @rtype: int
441
    @return: integer value of given IP address
442

443
    """
444
    doublecolons = address.count("::")
445
    assert not doublecolons > 1
446
    if doublecolons == 1:
447
      # We have a shorthand address, expand it
448
      parts = []
449
      twoparts = address.split("::")
450
      sep = len(twoparts[0].split(':')) + len(twoparts[1].split(':'))
451
      parts = twoparts[0].split(':')
452
      [parts.append("0") for _ in range(8 - sep)]
453
      parts += twoparts[1].split(':')
454
    else:
455
      parts = address.split(":")
456

    
457
    address_int = 0
458
    for part in parts:
459
      address_int = (address_int << 16) + int(part or '0', 16)
460

    
461
    return address_int