Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 66e884e1

History | View | Annotate | Download (13.6 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 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)