Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 93f1e606

History | View | Annotate | Download (19.7 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 ValidatePortNumber(port):
271
  """Returns the validated integer port number if it is valid.
272

273
  @param port: the port number to be validated
274

275
  @raise ValueError: if the port is not valid
276
  @rtype: int
277
  @return: the validated value.
278

279
  """
280

    
281
  try:
282
    port = int(port)
283
  except TypeError:
284
    raise errors.ProgrammerError("ValidatePortNumber called with non-numeric"
285
                                 " type %s." % port.__class__.__name__)
286
  except ValueError:
287
    raise ValueError("Invalid port value: '%s'" % port)
288

    
289
  if not 0 < port < 2 ** 16:
290
    raise ValueError("Invalid port value: '%d'" % port)
291

    
292
  return port
293

    
294

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

298
  Check if the given IP is reachable by doing attempting a TCP connect
299
  to it.
300

301
  @type target: str
302
  @param target: the IP to ping
303
  @type port: int
304
  @param port: the port to connect to
305
  @type timeout: int
306
  @param timeout: the timeout on the connection attempt
307
  @type live_port_needed: boolean
308
  @param live_port_needed: whether a closed port will cause the
309
      function to return failure, as if there was a timeout
310
  @type source: str or None
311
  @param source: if specified, will cause the connect to be made
312
      from this specific source address; failures to bind other
313
      than C{EADDRNOTAVAIL} will be ignored
314

315
  """
316
  logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
317
                " of %s seconds", port, target, timeout)
318

    
319
  try:
320
    family = IPAddress.GetAddressFamily(target)
321
  except errors.IPAddressError, err:
322
    raise errors.ProgrammerError("Family of IP address given in parameter"
323
                                 " 'target' can't be determined: %s" % err)
324

    
325
  sock = socket.socket(family, socket.SOCK_STREAM)
326
  success = False
327

    
328
  if source is not None:
329
    try:
330
      sock.bind((source, 0))
331
    except socket.error, err:
332
      if err[0] == errno.EADDRNOTAVAIL:
333
        success = False
334

    
335
  sock.settimeout(timeout)
336

    
337
  try:
338
    sock.connect((target, port))
339
    sock.close()
340
    success = True
341
  except socket.timeout:
342
    success = False
343
  except socket.error, err:
344
    success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
345

    
346
  return success
347

    
348

    
349
def GetDaemonPort(daemon_name):
350
  """Get the daemon port for this cluster.
351

352
  Note that this routine does not read a ganeti-specific file, but
353
  instead uses C{socket.getservbyname} to allow pre-customization of
354
  this parameter outside of Ganeti.
355

356
  @type daemon_name: string
357
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
358
  @rtype: int
359

360
  """
361
  if daemon_name not in constants.DAEMONS_PORTS:
362
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
363

    
364
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
365
  try:
366
    port = socket.getservbyname(daemon_name, proto)
367
  except socket.error:
368
    port = default_port
369

    
370
  return port
371

    
372

    
373
class IPAddress(object):
374
  """Class that represents an IP address.
375

376
  """
377
  iplen = 0
378
  family = None
379
  loopback_cidr = None
380

    
381
  @staticmethod
382
  def _GetIPIntFromString(address):
383
    """Abstract method to please pylint.
384

385
    """
386
    raise NotImplementedError
387

    
388
  @classmethod
389
  def IsValid(cls, address):
390
    """Validate a IP address.
391

392
    @type address: str
393
    @param address: IP address to be checked
394
    @rtype: bool
395
    @return: True if valid, False otherwise
396

397
    """
398
    if cls.family is None:
399
      try:
400
        family = cls.GetAddressFamily(address)
401
      except errors.IPAddressError:
402
        return False
403
    else:
404
      family = cls.family
405

    
406
    try:
407
      socket.inet_pton(family, address)
408
      return True
409
    except socket.error:
410
      return False
411

    
412
  @classmethod
413
  def ValidateNetmask(cls, netmask):
414
    """Validate a netmask suffix in CIDR notation.
415

416
    @type netmask: int
417
    @param netmask: netmask suffix to validate
418
    @rtype: bool
419
    @return: True if valid, False otherwise
420

421
    """
422
    assert (isinstance(netmask, (int, long)))
423

    
424
    return 0 < netmask <= cls.iplen
425

    
426
  @classmethod
427
  def Own(cls, address):
428
    """Check if the current host has the the given IP address.
429

430
    This is done by trying to bind the given address. We return True if we
431
    succeed or false if a socket.error is raised.
432

433
    @type address: str
434
    @param address: IP address to be checked
435
    @rtype: bool
436
    @return: True if we own the address, False otherwise
437

438
    """
439
    if cls.family is None:
440
      try:
441
        family = cls.GetAddressFamily(address)
442
      except errors.IPAddressError:
443
        return False
444
    else:
445
      family = cls.family
446

    
447
    s = socket.socket(family, socket.SOCK_DGRAM)
448
    success = False
449
    try:
450
      try:
451
        s.bind((address, 0))
452
        success = True
453
      except socket.error:
454
        success = False
455
    finally:
456
      s.close()
457
    return success
458

    
459
  @classmethod
460
  def InNetwork(cls, cidr, address):
461
    """Determine whether an address is within a network.
462

463
    @type cidr: string
464
    @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
465
    @type address: str
466
    @param address: IP address
467
    @rtype: bool
468
    @return: True if address is in cidr, False otherwise
469

470
    """
471
    address_int = cls._GetIPIntFromString(address)
472
    subnet = cidr.split("/")
473
    assert len(subnet) == 2
474
    try:
475
      prefix = int(subnet[1])
476
    except ValueError:
477
      return False
478

    
479
    assert 0 <= prefix <= cls.iplen
480
    target_int = cls._GetIPIntFromString(subnet[0])
481
    # Convert prefix netmask to integer value of netmask
482
    netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
483
    # Calculate hostmask
484
    hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
485
    # Calculate network address by and'ing netmask
486
    network_int = target_int & netmask_int
487
    # Calculate broadcast address by or'ing hostmask
488
    broadcast_int = target_int | hostmask_int
489

    
490
    return network_int <= address_int <= broadcast_int
491

    
492
  @staticmethod
493
  def GetAddressFamily(address):
494
    """Get the address family of the given address.
495

496
    @type address: str
497
    @param address: ip address whose family will be returned
498
    @rtype: int
499
    @return: C{socket.AF_INET} or C{socket.AF_INET6}
500
    @raise errors.GenericError: for invalid addresses
501

502
    """
503
    try:
504
      return IP4Address(address).family
505
    except errors.IPAddressError:
506
      pass
507

    
508
    try:
509
      return IP6Address(address).family
510
    except errors.IPAddressError:
511
      pass
512

    
513
    raise errors.IPAddressError("Invalid address '%s'" % address)
514

    
515
  @staticmethod
516
  def GetVersionFromAddressFamily(family):
517
    """Convert an IP address family to the corresponding IP version.
518

519
    @type family: int
520
    @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
521
    @return: an int containing the IP version, one of L{constants.IP4_VERSION}
522
             or L{constants.IP6_VERSION}
523
    @raise errors.ProgrammerError: for unknown families
524

525
    """
526
    if family == socket.AF_INET:
527
      return constants.IP4_VERSION
528
    elif family == socket.AF_INET6:
529
      return constants.IP6_VERSION
530

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

    
533
  @staticmethod
534
  def GetAddressFamilyFromVersion(version):
535
    """Convert an IP version to the corresponding IP address family.
536

537
    @type version: int
538
    @param version: IP version, one of L{constants.IP4_VERSION} or
539
                    L{constants.IP6_VERSION}
540
    @return: an int containing the IP address family, one of C{socket.AF_INET}
541
             or C{socket.AF_INET6}
542
    @raise errors.ProgrammerError: for unknown IP versions
543

544
    """
545
    if version == constants.IP4_VERSION:
546
      return socket.AF_INET
547
    elif version == constants.IP6_VERSION:
548
      return socket.AF_INET6
549

    
550
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
551

    
552
  @staticmethod
553
  def GetClassFromIpVersion(version):
554
    """Return the IPAddress subclass for the given IP version.
555

556
    @type version: int
557
    @param version: IP version, one of L{constants.IP4_VERSION} or
558
                    L{constants.IP6_VERSION}
559
    @return: a subclass of L{netutils.IPAddress}
560
    @raise errors.ProgrammerError: for unknowo IP versions
561

562
    """
563
    if version == constants.IP4_VERSION:
564
      return IP4Address
565
    elif version == constants.IP6_VERSION:
566
      return IP6Address
567

    
568
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
569

    
570
  @staticmethod
571
  def GetClassFromIpFamily(family):
572
    """Return the IPAddress subclass for the given IP family.
573

574
    @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
575
    @return: a subclass of L{netutils.IPAddress}
576
    @raise errors.ProgrammerError: for unknowo IP versions
577

578
    """
579
    return IPAddress.GetClassFromIpVersion(
580
              IPAddress.GetVersionFromAddressFamily(family))
581

    
582
  @classmethod
583
  def IsLoopback(cls, address):
584
    """Determine whether it is a loopback address.
585

586
    @type address: str
587
    @param address: IP address to be checked
588
    @rtype: bool
589
    @return: True if loopback, False otherwise
590

591
    """
592
    try:
593
      return cls.InNetwork(cls.loopback_cidr, address)
594
    except errors.IPAddressError:
595
      return False
596

    
597

    
598
class IP4Address(IPAddress):
599
  """IPv4 address class.
600

601
  """
602
  iplen = 32
603
  family = socket.AF_INET
604
  loopback_cidr = "127.0.0.0/8"
605

    
606
  def __init__(self, address):
607
    """Constructor for IPv4 address.
608

609
    @type address: str
610
    @param address: IP address
611
    @raises errors.IPAddressError: if address invalid
612

613
    """
614
    IPAddress.__init__(self)
615
    if not self.IsValid(address):
616
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
617

    
618
    self.address = address
619

    
620
  @staticmethod
621
  def _GetIPIntFromString(address):
622
    """Get integer value of IPv4 address.
623

624
    @type address: str
625
    @param address: IPv6 address
626
    @rtype: int
627
    @return: integer value of given IP address
628

629
    """
630
    address_int = 0
631
    parts = address.split(".")
632
    assert len(parts) == 4
633
    for part in parts:
634
      address_int = (address_int << 8) | int(part)
635

    
636
    return address_int
637

    
638

    
639
class IP6Address(IPAddress):
640
  """IPv6 address class.
641

642
  """
643
  iplen = 128
644
  family = socket.AF_INET6
645
  loopback_cidr = "::1/128"
646

    
647
  def __init__(self, address):
648
    """Constructor for IPv6 address.
649

650
    @type address: str
651
    @param address: IP address
652
    @raises errors.IPAddressError: if address invalid
653

654
    """
655
    IPAddress.__init__(self)
656
    if not self.IsValid(address):
657
      raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
658
    self.address = address
659

    
660
  @staticmethod
661
  def _GetIPIntFromString(address):
662
    """Get integer value of IPv6 address.
663

664
    @type address: str
665
    @param address: IPv6 address
666
    @rtype: int
667
    @return: integer value of given IP address
668

669
    """
670
    doublecolons = address.count("::")
671
    assert not doublecolons > 1
672
    if doublecolons == 1:
673
      # We have a shorthand address, expand it
674
      parts = []
675
      twoparts = address.split("::")
676
      sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
677
      parts = twoparts[0].split(":")
678
      parts.extend(["0"] * (8 - sep))
679
      parts += twoparts[1].split(":")
680
    else:
681
      parts = address.split(":")
682

    
683
    address_int = 0
684
    for part in parts:
685
      address_int = (address_int << 16) + int(part or "0", 16)
686

    
687
    return address_int
688

    
689

    
690
def FormatAddress(address, family=None):
691
  """Format a socket address
692

693
  @type address: family specific (usually tuple)
694
  @param address: address, as reported by this class
695
  @type family: integer
696
  @param family: socket family (one of socket.AF_*) or None
697

698
  """
699
  if family is None:
700
    try:
701
      family = IPAddress.GetAddressFamily(address[0])
702
    except errors.IPAddressError:
703
      raise errors.ParameterError(address)
704

    
705
  if family == socket.AF_UNIX and len(address) == 3:
706
    return "pid=%s, uid=%s, gid=%s" % address
707

    
708
  if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
709
    host, port = address
710
    if family == socket.AF_INET6:
711
      res = "[%s]" % host
712
    else:
713
      res = host
714

    
715
    if port is not None:
716
      res += ":%s" % port
717

    
718
    return res
719

    
720
  raise errors.ParameterError(family, address)