Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ a3e964cf

History | View | Annotate | Download (18.8 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
  try:
292
    family = IPAddress.GetAddressFamily(target)
293
  except errors.GenericError:
294
    return False
295

    
296
  sock = socket.socket(family, socket.SOCK_STREAM)
297
  success = False
298

    
299
  if source is not None:
300
    try:
301
      sock.bind((source, 0))
302
    except socket.error, err:
303
      if err[0] == errno.EADDRNOTAVAIL:
304
        success = False
305

    
306
  sock.settimeout(timeout)
307

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

    
317
  return success
318

    
319

    
320
def GetDaemonPort(daemon_name):
321
  """Get the daemon port for this cluster.
322

323
  Note that this routine does not read a ganeti-specific file, but
324
  instead uses C{socket.getservbyname} to allow pre-customization of
325
  this parameter outside of Ganeti.
326

327
  @type daemon_name: string
328
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
329
  @rtype: int
330

331
  """
332
  if daemon_name not in constants.DAEMONS_PORTS:
333
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
334

    
335
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
336
  try:
337
    port = socket.getservbyname(daemon_name, proto)
338
  except socket.error:
339
    port = default_port
340

    
341
  return port
342

    
343

    
344
class IPAddress(object):
345
  """Class that represents an IP address.
346

347
  """
348
  iplen = 0
349
  family = None
350
  loopback_cidr = None
351

    
352
  @staticmethod
353
  def _GetIPIntFromString(address):
354
    """Abstract method to please pylint.
355

356
    """
357
    raise NotImplementedError
358

    
359
  @classmethod
360
  def IsValid(cls, address):
361
    """Validate a IP address.
362

363
    @type address: str
364
    @param address: IP address to be checked
365
    @rtype: bool
366
    @return: True if valid, False otherwise
367

368
    """
369
    if cls.family is None:
370
      try:
371
        family = cls.GetAddressFamily(address)
372
      except errors.IPAddressError:
373
        return False
374
    else:
375
      family = cls.family
376

    
377
    try:
378
      socket.inet_pton(family, address)
379
      return True
380
    except socket.error:
381
      return False
382

    
383
  @classmethod
384
  def ValidateNetmask(cls, netmask):
385
    """Validate a netmask suffix in CIDR notation.
386

387
    @type netmask: int
388
    @param netmask: netmask suffix to validate
389
    @rtype: bool
390
    @return: True if valid, False otherwise
391

392
    """
393
    assert (isinstance(netmask, (int, long)))
394

    
395
    return 0 < netmask <= cls.iplen
396

    
397
  @classmethod
398
  def Own(cls, address):
399
    """Check if the current host has the the given IP address.
400

401
    This is done by trying to bind the given address. We return True if we
402
    succeed or false if a socket.error is raised.
403

404
    @type address: str
405
    @param address: IP address to be checked
406
    @rtype: bool
407
    @return: True if we own the address, False otherwise
408

409
    """
410
    if cls.family is None:
411
      try:
412
        family = cls.GetAddressFamily(address)
413
      except errors.IPAddressError:
414
        return False
415
    else:
416
      family = cls.family
417

    
418
    s = socket.socket(family, socket.SOCK_DGRAM)
419
    success = False
420
    try:
421
      try:
422
        s.bind((address, 0))
423
        success = True
424
      except socket.error:
425
        success = False
426
    finally:
427
      s.close()
428
    return success
429

    
430
  @classmethod
431
  def InNetwork(cls, cidr, address):
432
    """Determine whether an address is within a network.
433

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

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

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

    
461
    return network_int <= address_int <= broadcast_int
462

    
463
  @staticmethod
464
  def GetAddressFamily(address):
465
    """Get the address family of the given address.
466

467
    @type address: str
468
    @param address: ip address whose family will be returned
469
    @rtype: int
470
    @return: C{socket.AF_INET} or C{socket.AF_INET6}
471
    @raise errors.GenericError: for invalid addresses
472

473
    """
474
    try:
475
      return IP4Address(address).family
476
    except errors.IPAddressError:
477
      pass
478

    
479
    try:
480
      return IP6Address(address).family
481
    except errors.IPAddressError:
482
      pass
483

    
484
    raise errors.IPAddressError("Invalid address '%s'" % address)
485

    
486
  @staticmethod
487
  def GetVersionFromAddressFamily(family):
488
    """Convert an IP address family to the corresponding IP version.
489

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

496
    """
497
    if family == socket.AF_INET:
498
      return constants.IP4_VERSION
499
    elif family == socket.AF_INET6:
500
      return constants.IP6_VERSION
501

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

    
504
  @staticmethod
505
  def GetAddressFamilyFromVersion(version):
506
    """Convert an IP version to the corresponding IP address family.
507

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

515
    """
516
    if version == constants.IP4_VERSION:
517
      return socket.AF_INET
518
    elif version == constants.IP6_VERSION:
519
      return socket.AF_INET6
520

    
521
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
522

    
523
  @staticmethod
524
  def GetClassFromIpVersion(version):
525
    """Return the IPAddress subclass for the given IP version.
526

527
    @type version: int
528
    @param version: IP version, one of L{constants.IP4_VERSION} or
529
                    L{constants.IP6_VERSION}
530
    @return: a subclass of L{netutils.IPAddress}
531
    @raise errors.ProgrammerError: for unknowo IP versions
532

533
    """
534
    if version == constants.IP4_VERSION:
535
      return IP4Address
536
    elif version == constants.IP6_VERSION:
537
      return IP6Address
538

    
539
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
540

    
541
  @staticmethod
542
  def GetClassFromIpFamily(family):
543
    """Return the IPAddress subclass for the given IP family.
544

545
    @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
546
    @return: a subclass of L{netutils.IPAddress}
547
    @raise errors.ProgrammerError: for unknowo IP versions
548

549
    """
550
    return IPAddress.GetClassFromIpVersion(
551
              IPAddress.GetVersionFromAddressFamily(family))
552

    
553
  @classmethod
554
  def IsLoopback(cls, address):
555
    """Determine whether it is a loopback address.
556

557
    @type address: str
558
    @param address: IP address to be checked
559
    @rtype: bool
560
    @return: True if loopback, False otherwise
561

562
    """
563
    try:
564
      return cls.InNetwork(cls.loopback_cidr, address)
565
    except errors.IPAddressError:
566
      return False
567

    
568

    
569
class IP4Address(IPAddress):
570
  """IPv4 address class.
571

572
  """
573
  iplen = 32
574
  family = socket.AF_INET
575
  loopback_cidr = "127.0.0.0/8"
576

    
577
  def __init__(self, address):
578
    """Constructor for IPv4 address.
579

580
    @type address: str
581
    @param address: IP address
582
    @raises errors.IPAddressError: if address invalid
583

584
    """
585
    IPAddress.__init__(self)
586
    if not self.IsValid(address):
587
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
588

    
589
    self.address = address
590

    
591
  @staticmethod
592
  def _GetIPIntFromString(address):
593
    """Get integer value of IPv4 address.
594

595
    @type address: str
596
    @param address: IPv6 address
597
    @rtype: int
598
    @return: integer value of given IP address
599

600
    """
601
    address_int = 0
602
    parts = address.split(".")
603
    assert len(parts) == 4
604
    for part in parts:
605
      address_int = (address_int << 8) | int(part)
606

    
607
    return address_int
608

    
609

    
610
class IP6Address(IPAddress):
611
  """IPv6 address class.
612

613
  """
614
  iplen = 128
615
  family = socket.AF_INET6
616
  loopback_cidr = "::1/128"
617

    
618
  def __init__(self, address):
619
    """Constructor for IPv6 address.
620

621
    @type address: str
622
    @param address: IP address
623
    @raises errors.IPAddressError: if address invalid
624

625
    """
626
    IPAddress.__init__(self)
627
    if not self.IsValid(address):
628
      raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
629
    self.address = address
630

    
631
  @staticmethod
632
  def _GetIPIntFromString(address):
633
    """Get integer value of IPv6 address.
634

635
    @type address: str
636
    @param address: IPv6 address
637
    @rtype: int
638
    @return: integer value of given IP address
639

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

    
654
    address_int = 0
655
    for part in parts:
656
      address_int = (address_int << 16) + int(part or "0", 16)
657

    
658
    return address_int
659

    
660

    
661
def FormatAddress(address, family=None):
662
  """Format a socket address
663

664
  @type address: family specific (usually tuple)
665
  @param address: address, as reported by this class
666
  @type family: integer
667
  @param family: socket family (one of socket.AF_*) or None
668

669
  """
670
  if family is None:
671
    try:
672
      family = IPAddress.GetAddressFamily(address[0])
673
    except errors.IPAddressError:
674
      raise errors.ParameterError(address)
675

    
676
  if family == socket.AF_UNIX and len(address) == 3:
677
    return "pid=%s, uid=%s, gid=%s" % address
678

    
679
  if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
680
    host, port = address
681
    if family == socket.AF_INET6:
682
      res = "[%s]" % host
683
    else:
684
      res = host
685

    
686
    if port is not None:
687
      res += ":%s" % port
688

    
689
    return res
690

    
691
  raise errors.ParameterError(family, address)