Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 8a348b15

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
  _SO_PEERCRED = IN.SO_PEERCRED
58
except AttributeError:
59
  _SO_PEERCRED = 17
60

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

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

    
73

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

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

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

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

    
91
  return addr
92

    
93

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

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

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

    
106

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

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

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

    
117

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

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

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

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

    
137
  return _GetIpAddressesFromIpOutput(result.output)
138

    
139

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

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

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

    
158

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

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

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

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

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

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

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

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

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

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

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

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

    
207
    return cls.GetNormalizedName(result)
208

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

213
    Supports both IPv4 and IPv6.
214

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

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

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

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

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

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

    
268

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

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

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

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

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

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

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

    
309
  sock.settimeout(timeout)
310

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

    
320
  return success
321

    
322

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

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

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

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

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

    
344
  return port
345

    
346

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

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

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

359
    """
360
    raise NotImplementedError
361

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

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

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

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

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

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

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

    
398
    return 0 < netmask <= cls.iplen
399

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

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

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

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

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

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

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

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

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

    
464
    return network_int <= address_int <= broadcast_int
465

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
571

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

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

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

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

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

    
592
    self.address = address
593

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

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

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

    
610
    return address_int
611

    
612

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

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

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

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

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

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

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

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

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

    
661
    return address_int
662

    
663

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

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

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

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

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

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

    
692
    return res
693

    
694
  raise errors.ParameterError(family, address)