Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 364c350f

History | View | Annotate | Download (19.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010, 2011, 2012 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 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
from ganeti import vcluster
42

    
43
# Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
44
# struct ucred { pid_t pid; uid_t uid; gid_t gid; };
45
#
46
# The GNU C Library defines gid_t and uid_t to be "unsigned int" and
47
# pid_t to "int".
48
#
49
# IEEE Std 1003.1-2008:
50
# "nlink_t, uid_t, gid_t, and id_t shall be integer types"
51
# "blksize_t, pid_t, and ssize_t shall be signed integer types"
52
_STRUCT_UCRED = "iII"
53
_STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
54

    
55
# Workaround a bug in some linux distributions that don't define SO_PEERCRED
56
try:
57
  # pylint: disable=E1101
58
  _SO_PEERCRED = IN.SO_PEERCRED
59
except AttributeError:
60
  _SO_PEERCRED = 17
61

    
62
# Regexes used to find IP addresses in the output of ip.
63
_IP_RE_TEXT = r"[.:a-z0-9]+"      # separate for testing purposes
64
_IP_FAMILY_RE = re.compile(r"(?P<family>inet6?)\s+(?P<ip>%s)/" % _IP_RE_TEXT,
65
                           re.IGNORECASE)
66

    
67
# Dict used to convert from a string representing an IP family to an IP
68
# version
69
_NAME_TO_IP_VER = {
70
  "inet": constants.IP4_VERSION,
71
  "inet6": constants.IP6_VERSION,
72
  }
73

    
74

    
75
def _GetIpAddressesFromIpOutput(ip_output):
76
  """Parses the output of the ip command and retrieves the IP addresses and
77
  version.
78

79
  @param ip_output: string containing the output of the ip command;
80
  @rtype: dict; (int, list)
81
  @return: a dict having as keys the IP versions and as values the
82
           corresponding list of addresses found in the IP output.
83

84
  """
85
  addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
86

    
87
  for row in ip_output.splitlines():
88
    match = _IP_FAMILY_RE.search(row)
89
    if match and IPAddress.IsValid(match.group("ip")):
90
      addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip"))
91

    
92
  return addr
93

    
94

    
95
def GetSocketCredentials(sock):
96
  """Returns the credentials of the foreign process connected to a socket.
97

98
  @param sock: Unix socket
99
  @rtype: tuple; (number, number, number)
100
  @return: The PID, UID and GID of the connected foreign process.
101

102
  """
103
  peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED,
104
                             _STRUCT_UCRED_SIZE)
105
  return struct.unpack(_STRUCT_UCRED, peercred)
106

    
107

    
108
def IsValidInterface(ifname):
109
  """Validate an interface name.
110

111
  @type ifname: string
112
  @param ifname: Name of the network interface
113
  @return: boolean indicating whether the interface name is valid or not.
114

115
  """
116
  return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
117

    
118

    
119
def GetInterfaceIpAddresses(ifname):
120
  """Returns the IP addresses associated to the interface.
121

122
  @type ifname: string
123
  @param ifname: Name of the network interface
124
  @return: A dict having for keys the IP version (either
125
           L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for
126
           values the lists of IP addresses of the respective version
127
           associated to the interface
128

129
  """
130
  result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
131
                         ifname])
132

    
133
  if result.failed:
134
    logging.error("Error running the ip command while getting the IP"
135
                  " addresses of %s", ifname)
136
    return None
137

    
138
  return _GetIpAddressesFromIpOutput(result.output)
139

    
140

    
141
def GetHostname(name=None, family=None):
142
  """Returns a Hostname object.
143

144
  @type name: str
145
  @param name: hostname or None
146
  @type family: int
147
  @param family: AF_INET | AF_INET6 | None
148
  @rtype: L{Hostname}
149
  @return: Hostname object
150
  @raise errors.OpPrereqError: in case of errors in resolving
151

152
  """
153
  try:
154
    return Hostname(name=name, family=family)
155
  except errors.ResolverError, err:
156
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
157
                               (err[0], err[2]), errors.ECODE_RESOLVER)
158

    
159

    
160
class Hostname:
161
  """Class implementing resolver and hostname functionality.
162

163
  """
164
  _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
165

    
166
  def __init__(self, name=None, family=None):
167
    """Initialize the host name object.
168

169
    If the name argument is None, it will use this system's name.
170

171
    @type family: int
172
    @param family: AF_INET | AF_INET6 | None
173
    @type name: str
174
    @param name: hostname or None
175

176
    """
177
    self.name = self.GetFqdn(name)
178
    self.ip = self.GetIP(self.name, family=family)
179

    
180
  @classmethod
181
  def GetSysName(cls):
182
    """Legacy method the get the current system's name.
183

184
    """
185
    return cls.GetFqdn()
186

    
187
  @classmethod
188
  def GetFqdn(cls, hostname=None):
189
    """Return fqdn.
190

191
    If hostname is None the system's fqdn is returned.
192

193
    @type hostname: str
194
    @param hostname: name to be fqdn'ed
195
    @rtype: str
196
    @return: fqdn of given name, if it exists, unmodified name otherwise
197

198
    """
199
    if hostname is None:
200
      virtfqdn = vcluster.GetVirtualHostname()
201
      if virtfqdn:
202
        result = virtfqdn
203
      else:
204
        result = socket.getfqdn()
205
    else:
206
      result = socket.getfqdn(hostname)
207

    
208
    return cls.GetNormalizedName(result)
209

    
210
  @staticmethod
211
  def GetIP(hostname, family=None):
212
    """Return IP address of given hostname.
213

214
    Supports both IPv4 and IPv6.
215

216
    @type hostname: str
217
    @param hostname: hostname to look up
218
    @type family: int
219
    @param family: AF_INET | AF_INET6 | None
220
    @rtype: str
221
    @return: IP address
222
    @raise errors.ResolverError: in case of errors in resolving
223

224
    """
225
    try:
226
      if family in (socket.AF_INET, socket.AF_INET6):
227
        result = socket.getaddrinfo(hostname, None, family)
228
      else:
229
        result = socket.getaddrinfo(hostname, None)
230
    except (socket.gaierror, socket.herror, socket.error), err:
231
      # hostname not found in DNS, or other socket exception in the
232
      # (code, description format)
233
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
234

    
235
    # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
236
    # canonname, sockaddr). We return the first tuple's first address in
237
    # sockaddr
238
    try:
239
      return result[0][4][0]
240
    except IndexError, err:
241
      # we don't have here an actual error code, it's just that the
242
      # data type returned by getaddrinfo is not what we expected;
243
      # let's keep the same format in the exception arguments with a
244
      # dummy error code
245
      raise errors.ResolverError(hostname, 0,
246
                                 "Unknown error in getaddrinfo(): %s" % err)
247

    
248
  @classmethod
249
  def GetNormalizedName(cls, hostname):
250
    """Validate and normalize the given hostname.
251

252
    @attention: the validation is a bit more relaxed than the standards
253
        require; most importantly, we allow underscores in names
254
    @raise errors.OpPrereqError: when the name is not valid
255

256
    """
257
    hostname = hostname.lower()
258
    if (not cls._VALID_NAME_RE.match(hostname) or
259
        # double-dots, meaning empty label
260
        ".." in hostname or
261
        # empty initial label
262
        hostname.startswith(".")):
263
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
264
                                 errors.ECODE_INVAL)
265
    if hostname.endswith("."):
266
      hostname = hostname.rstrip(".")
267
    return hostname
268

    
269

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

273
  Check if the given IP is reachable by doing attempting a TCP connect
274
  to it.
275

276
  @type target: str
277
  @param target: the IP to ping
278
  @type port: int
279
  @param port: the port to connect to
280
  @type timeout: int
281
  @param timeout: the timeout on the connection attempt
282
  @type live_port_needed: boolean
283
  @param live_port_needed: whether a closed port will cause the
284
      function to return failure, as if there was a timeout
285
  @type source: str or None
286
  @param source: if specified, will cause the connect to be made
287
      from this specific source address; failures to bind other
288
      than C{EADDRNOTAVAIL} will be ignored
289

290
  """
291
  logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
292
                " of %s seconds", port, target, timeout)
293

    
294
  try:
295
    family = IPAddress.GetAddressFamily(target)
296
  except errors.IPAddressError, err:
297
    raise errors.ProgrammerError("Family of IP address given in parameter"
298
                                 " 'target' can't be determined: %s" % err)
299

    
300
  sock = socket.socket(family, socket.SOCK_STREAM)
301
  success = False
302

    
303
  if source is not None:
304
    try:
305
      sock.bind((source, 0))
306
    except socket.error, err:
307
      if err[0] == errno.EADDRNOTAVAIL:
308
        success = False
309

    
310
  sock.settimeout(timeout)
311

    
312
  try:
313
    sock.connect((target, port))
314
    sock.close()
315
    success = True
316
  except socket.timeout:
317
    success = False
318
  except socket.error, err:
319
    success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
320

    
321
  return success
322

    
323

    
324
def GetDaemonPort(daemon_name):
325
  """Get the daemon port for this cluster.
326

327
  Note that this routine does not read a ganeti-specific file, but
328
  instead uses C{socket.getservbyname} to allow pre-customization of
329
  this parameter outside of Ganeti.
330

331
  @type daemon_name: string
332
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
333
  @rtype: int
334

335
  """
336
  if daemon_name not in constants.DAEMONS_PORTS:
337
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
338

    
339
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
340
  try:
341
    port = socket.getservbyname(daemon_name, proto)
342
  except socket.error:
343
    port = default_port
344

    
345
  return port
346

    
347

    
348
class IPAddress(object):
349
  """Class that represents an IP address.
350

351
  """
352
  iplen = 0
353
  family = None
354
  loopback_cidr = None
355

    
356
  @staticmethod
357
  def _GetIPIntFromString(address):
358
    """Abstract method to please pylint.
359

360
    """
361
    raise NotImplementedError
362

    
363
  @classmethod
364
  def IsValid(cls, address):
365
    """Validate a IP address.
366

367
    @type address: str
368
    @param address: IP address to be checked
369
    @rtype: bool
370
    @return: True if valid, False otherwise
371

372
    """
373
    if cls.family is None:
374
      try:
375
        family = cls.GetAddressFamily(address)
376
      except errors.IPAddressError:
377
        return False
378
    else:
379
      family = cls.family
380

    
381
    try:
382
      socket.inet_pton(family, address)
383
      return True
384
    except socket.error:
385
      return False
386

    
387
  @classmethod
388
  def ValidateNetmask(cls, netmask):
389
    """Validate a netmask suffix in CIDR notation.
390

391
    @type netmask: int
392
    @param netmask: netmask suffix to validate
393
    @rtype: bool
394
    @return: True if valid, False otherwise
395

396
    """
397
    assert (isinstance(netmask, (int, long)))
398

    
399
    return 0 < netmask <= cls.iplen
400

    
401
  @classmethod
402
  def Own(cls, address):
403
    """Check if the current host has the the given IP address.
404

405
    This is done by trying to bind the given address. We return True if we
406
    succeed or false if a socket.error is raised.
407

408
    @type address: str
409
    @param address: IP address to be checked
410
    @rtype: bool
411
    @return: True if we own the address, False otherwise
412

413
    """
414
    if cls.family is None:
415
      try:
416
        family = cls.GetAddressFamily(address)
417
      except errors.IPAddressError:
418
        return False
419
    else:
420
      family = cls.family
421

    
422
    s = socket.socket(family, socket.SOCK_DGRAM)
423
    success = False
424
    try:
425
      try:
426
        s.bind((address, 0))
427
        success = True
428
      except socket.error:
429
        success = False
430
    finally:
431
      s.close()
432
    return success
433

    
434
  @classmethod
435
  def InNetwork(cls, cidr, address):
436
    """Determine whether an address is within a network.
437

438
    @type cidr: string
439
    @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
440
    @type address: str
441
    @param address: IP address
442
    @rtype: bool
443
    @return: True if address is in cidr, False otherwise
444

445
    """
446
    address_int = cls._GetIPIntFromString(address)
447
    subnet = cidr.split("/")
448
    assert len(subnet) == 2
449
    try:
450
      prefix = int(subnet[1])
451
    except ValueError:
452
      return False
453

    
454
    assert 0 <= prefix <= cls.iplen
455
    target_int = cls._GetIPIntFromString(subnet[0])
456
    # Convert prefix netmask to integer value of netmask
457
    netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
458
    # Calculate hostmask
459
    hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
460
    # Calculate network address by and'ing netmask
461
    network_int = target_int & netmask_int
462
    # Calculate broadcast address by or'ing hostmask
463
    broadcast_int = target_int | hostmask_int
464

    
465
    return network_int <= address_int <= broadcast_int
466

    
467
  @staticmethod
468
  def GetAddressFamily(address):
469
    """Get the address family of the given address.
470

471
    @type address: str
472
    @param address: ip address whose family will be returned
473
    @rtype: int
474
    @return: C{socket.AF_INET} or C{socket.AF_INET6}
475
    @raise errors.GenericError: for invalid addresses
476

477
    """
478
    try:
479
      return IP4Address(address).family
480
    except errors.IPAddressError:
481
      pass
482

    
483
    try:
484
      return IP6Address(address).family
485
    except errors.IPAddressError:
486
      pass
487

    
488
    raise errors.IPAddressError("Invalid address '%s'" % address)
489

    
490
  @staticmethod
491
  def GetVersionFromAddressFamily(family):
492
    """Convert an IP address family to the corresponding IP version.
493

494
    @type family: int
495
    @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
496
    @return: an int containing the IP version, one of L{constants.IP4_VERSION}
497
             or L{constants.IP6_VERSION}
498
    @raise errors.ProgrammerError: for unknown families
499

500
    """
501
    if family == socket.AF_INET:
502
      return constants.IP4_VERSION
503
    elif family == socket.AF_INET6:
504
      return constants.IP6_VERSION
505

    
506
    raise errors.ProgrammerError("%s is not a valid IP address family" % family)
507

    
508
  @staticmethod
509
  def GetAddressFamilyFromVersion(version):
510
    """Convert an IP version to the corresponding IP address family.
511

512
    @type version: int
513
    @param version: IP version, one of L{constants.IP4_VERSION} or
514
                    L{constants.IP6_VERSION}
515
    @return: an int containing the IP address family, one of C{socket.AF_INET}
516
             or C{socket.AF_INET6}
517
    @raise errors.ProgrammerError: for unknown IP versions
518

519
    """
520
    if version == constants.IP4_VERSION:
521
      return socket.AF_INET
522
    elif version == constants.IP6_VERSION:
523
      return socket.AF_INET6
524

    
525
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
526

    
527
  @staticmethod
528
  def GetClassFromIpVersion(version):
529
    """Return the IPAddress subclass for the given IP version.
530

531
    @type version: int
532
    @param version: IP version, one of L{constants.IP4_VERSION} or
533
                    L{constants.IP6_VERSION}
534
    @return: a subclass of L{netutils.IPAddress}
535
    @raise errors.ProgrammerError: for unknowo IP versions
536

537
    """
538
    if version == constants.IP4_VERSION:
539
      return IP4Address
540
    elif version == constants.IP6_VERSION:
541
      return IP6Address
542

    
543
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
544

    
545
  @staticmethod
546
  def GetClassFromIpFamily(family):
547
    """Return the IPAddress subclass for the given IP family.
548

549
    @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
550
    @return: a subclass of L{netutils.IPAddress}
551
    @raise errors.ProgrammerError: for unknowo IP versions
552

553
    """
554
    return IPAddress.GetClassFromIpVersion(
555
              IPAddress.GetVersionFromAddressFamily(family))
556

    
557
  @classmethod
558
  def IsLoopback(cls, address):
559
    """Determine whether it is a loopback address.
560

561
    @type address: str
562
    @param address: IP address to be checked
563
    @rtype: bool
564
    @return: True if loopback, False otherwise
565

566
    """
567
    try:
568
      return cls.InNetwork(cls.loopback_cidr, address)
569
    except errors.IPAddressError:
570
      return False
571

    
572

    
573
class IP4Address(IPAddress):
574
  """IPv4 address class.
575

576
  """
577
  iplen = 32
578
  family = socket.AF_INET
579
  loopback_cidr = "127.0.0.0/8"
580

    
581
  def __init__(self, address):
582
    """Constructor for IPv4 address.
583

584
    @type address: str
585
    @param address: IP address
586
    @raises errors.IPAddressError: if address invalid
587

588
    """
589
    IPAddress.__init__(self)
590
    if not self.IsValid(address):
591
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
592

    
593
    self.address = address
594

    
595
  @staticmethod
596
  def _GetIPIntFromString(address):
597
    """Get integer value of IPv4 address.
598

599
    @type address: str
600
    @param address: IPv6 address
601
    @rtype: int
602
    @return: integer value of given IP address
603

604
    """
605
    address_int = 0
606
    parts = address.split(".")
607
    assert len(parts) == 4
608
    for part in parts:
609
      address_int = (address_int << 8) | int(part)
610

    
611
    return address_int
612

    
613

    
614
class IP6Address(IPAddress):
615
  """IPv6 address class.
616

617
  """
618
  iplen = 128
619
  family = socket.AF_INET6
620
  loopback_cidr = "::1/128"
621

    
622
  def __init__(self, address):
623
    """Constructor for IPv6 address.
624

625
    @type address: str
626
    @param address: IP address
627
    @raises errors.IPAddressError: if address invalid
628

629
    """
630
    IPAddress.__init__(self)
631
    if not self.IsValid(address):
632
      raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
633
    self.address = address
634

    
635
  @staticmethod
636
  def _GetIPIntFromString(address):
637
    """Get integer value of IPv6 address.
638

639
    @type address: str
640
    @param address: IPv6 address
641
    @rtype: int
642
    @return: integer value of given IP address
643

644
    """
645
    doublecolons = address.count("::")
646
    assert not doublecolons > 1
647
    if doublecolons == 1:
648
      # We have a shorthand address, expand it
649
      parts = []
650
      twoparts = address.split("::")
651
      sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
652
      parts = twoparts[0].split(":")
653
      parts.extend(["0"] * (8 - sep))
654
      parts += twoparts[1].split(":")
655
    else:
656
      parts = address.split(":")
657

    
658
    address_int = 0
659
    for part in parts:
660
      address_int = (address_int << 16) + int(part or "0", 16)
661

    
662
    return address_int
663

    
664

    
665
def FormatAddress(address, family=None):
666
  """Format a socket address
667

668
  @type address: family specific (usually tuple)
669
  @param address: address, as reported by this class
670
  @type family: integer
671
  @param family: socket family (one of socket.AF_*) or None
672

673
  """
674
  if family is None:
675
    try:
676
      family = IPAddress.GetAddressFamily(address[0])
677
    except errors.IPAddressError:
678
      raise errors.ParameterError(address)
679

    
680
  if family == socket.AF_UNIX and len(address) == 3:
681
    return "pid=%s, uid=%s, gid=%s" % address
682

    
683
  if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
684
    host, port = address
685
    if family == socket.AF_INET6:
686
      res = "[%s]" % host
687
    else:
688
      res = host
689

    
690
    if port is not None:
691
      res += ":%s" % port
692

    
693
    return res
694

    
695
  raise errors.ParameterError(family, address)