Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ e91b297c

History | View | Annotate | Download (18.3 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
# Regexes used to find IP addresses in the output of ip.
56
_IP_RE_TEXT = r"[.:a-z0-9]+"      # separate for testing purposes
57
_IP_FAMILY_RE = re.compile(r"(?P<family>inet6?)\s+(?P<ip>%s)/" % _IP_RE_TEXT,
58
                           re.IGNORECASE)
59

    
60
# Dict used to convert from a string representing an IP family to an IP
61
# version
62
_NAME_TO_IP_VER = {
63
  "inet": constants.IP4_VERSION,
64
  "inet6": constants.IP6_VERSION,
65
  }
66

    
67

    
68
def _GetIpAddressesFromIpOutput(ip_output):
69
  """Parses the output of the ip command and retrieves the IP addresses and
70
  version.
71

72
  @param ip_output: string containing the output of the ip command;
73
  @rtype: dict; (int, list)
74
  @return: a dict having as keys the IP versions and as values the
75
           corresponding list of addresses found in the IP output.
76

77
  """
78
  addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
79

    
80
  for row in ip_output.splitlines():
81
    match = _IP_FAMILY_RE.search(row)
82
    if match and IPAddress.IsValid(match.group("ip")):
83
      addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip"))
84

    
85
  return addr
86

    
87

    
88
def GetSocketCredentials(sock):
89
  """Returns the credentials of the foreign process connected to a socket.
90

91
  @param sock: Unix socket
92
  @rtype: tuple; (number, number, number)
93
  @return: The PID, UID and GID of the connected foreign process.
94

95
  """
96
  peercred = sock.getsockopt(socket.SOL_SOCKET, IN.SO_PEERCRED,
97
                             _STRUCT_UCRED_SIZE)
98
  return struct.unpack(_STRUCT_UCRED, peercred)
99

    
100

    
101
def IsValidInterface(ifname):
102
  """Validate an interface name.
103

104
  @type ifname: string
105
  @param ifname: Name of the network interface
106
  @return: boolean indicating whether the interface name is valid or not.
107

108
  """
109
  return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
110

    
111

    
112
def GetInterfaceIpAddresses(ifname):
113
  """Returns the IP addresses associated to the interface.
114

115
  @type ifname: string
116
  @param ifname: Name of the network interface
117
  @return: A dict having for keys the IP version (either
118
           L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for
119
           values the lists of IP addresses of the respective version
120
           associated to the interface
121

122
  """
123
  result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
124
                         ifname])
125

    
126
  if result.failed:
127
    logging.error("Error running the ip command while getting the IP"
128
                  " addresses of %s", ifname)
129
    return None
130

    
131
  return _GetIpAddressesFromIpOutput(result.output)
132

    
133

    
134
def GetHostname(name=None, family=None):
135
  """Returns a Hostname object.
136

137
  @type name: str
138
  @param name: hostname or None
139
  @type family: int
140
  @param family: AF_INET | AF_INET6 | None
141
  @rtype: L{Hostname}
142
  @return: Hostname object
143
  @raise errors.OpPrereqError: in case of errors in resolving
144

145
  """
146
  try:
147
    return Hostname(name=name, family=family)
148
  except errors.ResolverError, err:
149
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
150
                               (err[0], err[2]), errors.ECODE_RESOLVER)
151

    
152

    
153
class Hostname:
154
  """Class implementing resolver and hostname functionality.
155

156
  """
157
  _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
158

    
159
  def __init__(self, name=None, family=None):
160
    """Initialize the host name object.
161

162
    If the name argument is None, it will use this system's name.
163

164
    @type family: int
165
    @param family: AF_INET | AF_INET6 | None
166
    @type name: str
167
    @param name: hostname or None
168

169
    """
170
    self.name = self.GetNormalizedName(self.GetFqdn(name))
171
    self.ip = self.GetIP(self.name, family=family)
172

    
173
  @classmethod
174
  def GetSysName(cls):
175
    """Legacy method the get the current system's name.
176

177
    """
178
    return cls.GetFqdn()
179

    
180
  @staticmethod
181
  def GetFqdn(hostname=None):
182
    """Return fqdn.
183

184
    If hostname is None the system's fqdn is returned.
185

186
    @type hostname: str
187
    @param hostname: name to be fqdn'ed
188
    @rtype: str
189
    @return: fqdn of given name, if it exists, unmodified name otherwise
190

191
    """
192
    if hostname is None:
193
      virtfqdn = vcluster.GetVirtualHostname()
194
      if virtfqdn:
195
        return virtfqdn
196
      else:
197
        return socket.getfqdn()
198
    else:
199
      return socket.getfqdn(hostname)
200

    
201
  @staticmethod
202
  def GetIP(hostname, family=None):
203
    """Return IP address of given hostname.
204

205
    Supports both IPv4 and IPv6.
206

207
    @type hostname: str
208
    @param hostname: hostname to look up
209
    @type family: int
210
    @param family: AF_INET | AF_INET6 | None
211
    @rtype: str
212
    @return: IP address
213
    @raise errors.ResolverError: in case of errors in resolving
214

215
    """
216
    try:
217
      if family in (socket.AF_INET, socket.AF_INET6):
218
        result = socket.getaddrinfo(hostname, None, family)
219
      else:
220
        result = socket.getaddrinfo(hostname, None)
221
    except (socket.gaierror, socket.herror, socket.error), err:
222
      # hostname not found in DNS, or other socket exception in the
223
      # (code, description format)
224
      raise errors.ResolverError(hostname, err.args[0], err.args[1])
225

    
226
    # getaddrinfo() returns a list of 5-tupes (family, socktype, proto,
227
    # canonname, sockaddr). We return the first tuple's first address in
228
    # sockaddr
229
    try:
230
      return result[0][4][0]
231
    except IndexError, err:
232
      raise errors.ResolverError("Unknown error in getaddrinfo(): %s" % err)
233

    
234
  @classmethod
235
  def GetNormalizedName(cls, hostname):
236
    """Validate and normalize the given hostname.
237

238
    @attention: the validation is a bit more relaxed than the standards
239
        require; most importantly, we allow underscores in names
240
    @raise errors.OpPrereqError: when the name is not valid
241

242
    """
243
    hostname = hostname.lower()
244
    if (not cls._VALID_NAME_RE.match(hostname) or
245
        # double-dots, meaning empty label
246
        ".." in hostname or
247
        # empty initial label
248
        hostname.startswith(".")):
249
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
250
                                 errors.ECODE_INVAL)
251
    if hostname.endswith("."):
252
      hostname = hostname.rstrip(".")
253
    return hostname
254

    
255

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

259
  Check if the given IP is reachable by doing attempting a TCP connect
260
  to it.
261

262
  @type target: str
263
  @param target: the IP to ping
264
  @type port: int
265
  @param port: the port to connect to
266
  @type timeout: int
267
  @param timeout: the timeout on the connection attempt
268
  @type live_port_needed: boolean
269
  @param live_port_needed: whether a closed port will cause the
270
      function to return failure, as if there was a timeout
271
  @type source: str or None
272
  @param source: if specified, will cause the connect to be made
273
      from this specific source address; failures to bind other
274
      than C{EADDRNOTAVAIL} will be ignored
275

276
  """
277
  try:
278
    family = IPAddress.GetAddressFamily(target)
279
  except errors.GenericError:
280
    return False
281

    
282
  sock = socket.socket(family, socket.SOCK_STREAM)
283
  success = False
284

    
285
  if source is not None:
286
    try:
287
      sock.bind((source, 0))
288
    except socket.error, err:
289
      if err[0] == errno.EADDRNOTAVAIL:
290
        success = False
291

    
292
  sock.settimeout(timeout)
293

    
294
  try:
295
    sock.connect((target, port))
296
    sock.close()
297
    success = True
298
  except socket.timeout:
299
    success = False
300
  except socket.error, err:
301
    success = (not live_port_needed) and (err[0] == errno.ECONNREFUSED)
302

    
303
  return success
304

    
305

    
306
def GetDaemonPort(daemon_name):
307
  """Get the daemon port for this cluster.
308

309
  Note that this routine does not read a ganeti-specific file, but
310
  instead uses C{socket.getservbyname} to allow pre-customization of
311
  this parameter outside of Ganeti.
312

313
  @type daemon_name: string
314
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
315
  @rtype: int
316

317
  """
318
  if daemon_name not in constants.DAEMONS_PORTS:
319
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
320

    
321
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
322
  try:
323
    port = socket.getservbyname(daemon_name, proto)
324
  except socket.error:
325
    port = default_port
326

    
327
  return port
328

    
329

    
330
class IPAddress(object):
331
  """Class that represents an IP address.
332

333
  """
334
  iplen = 0
335
  family = None
336
  loopback_cidr = None
337

    
338
  @staticmethod
339
  def _GetIPIntFromString(address):
340
    """Abstract method to please pylint.
341

342
    """
343
    raise NotImplementedError
344

    
345
  @classmethod
346
  def IsValid(cls, address):
347
    """Validate a IP address.
348

349
    @type address: str
350
    @param address: IP address to be checked
351
    @rtype: bool
352
    @return: True if valid, False otherwise
353

354
    """
355
    if cls.family is None:
356
      try:
357
        family = cls.GetAddressFamily(address)
358
      except errors.IPAddressError:
359
        return False
360
    else:
361
      family = cls.family
362

    
363
    try:
364
      socket.inet_pton(family, address)
365
      return True
366
    except socket.error:
367
      return False
368

    
369
  @classmethod
370
  def ValidateNetmask(cls, netmask):
371
    """Validate a netmask suffix in CIDR notation.
372

373
    @type netmask: int
374
    @param netmask: netmask suffix to validate
375
    @rtype: bool
376
    @return: True if valid, False otherwise
377

378
    """
379
    assert (isinstance(netmask, (int, long)))
380

    
381
    return 0 < netmask <= cls.iplen
382

    
383
  @classmethod
384
  def Own(cls, address):
385
    """Check if the current host has the the given IP address.
386

387
    This is done by trying to bind the given address. We return True if we
388
    succeed or false if a socket.error is raised.
389

390
    @type address: str
391
    @param address: IP address to be checked
392
    @rtype: bool
393
    @return: True if we own the address, False otherwise
394

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

    
404
    s = socket.socket(family, socket.SOCK_DGRAM)
405
    success = False
406
    try:
407
      try:
408
        s.bind((address, 0))
409
        success = True
410
      except socket.error:
411
        success = False
412
    finally:
413
      s.close()
414
    return success
415

    
416
  @classmethod
417
  def InNetwork(cls, cidr, address):
418
    """Determine whether an address is within a network.
419

420
    @type cidr: string
421
    @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
422
    @type address: str
423
    @param address: IP address
424
    @rtype: bool
425
    @return: True if address is in cidr, False otherwise
426

427
    """
428
    address_int = cls._GetIPIntFromString(address)
429
    subnet = cidr.split("/")
430
    assert len(subnet) == 2
431
    try:
432
      prefix = int(subnet[1])
433
    except ValueError:
434
      return False
435

    
436
    assert 0 <= prefix <= cls.iplen
437
    target_int = cls._GetIPIntFromString(subnet[0])
438
    # Convert prefix netmask to integer value of netmask
439
    netmask_int = (2 ** cls.iplen) - 1 ^ ((2 ** cls.iplen) - 1 >> prefix)
440
    # Calculate hostmask
441
    hostmask_int = netmask_int ^ (2 ** cls.iplen) - 1
442
    # Calculate network address by and'ing netmask
443
    network_int = target_int & netmask_int
444
    # Calculate broadcast address by or'ing hostmask
445
    broadcast_int = target_int | hostmask_int
446

    
447
    return network_int <= address_int <= broadcast_int
448

    
449
  @staticmethod
450
  def GetAddressFamily(address):
451
    """Get the address family of the given address.
452

453
    @type address: str
454
    @param address: ip address whose family will be returned
455
    @rtype: int
456
    @return: C{socket.AF_INET} or C{socket.AF_INET6}
457
    @raise errors.GenericError: for invalid addresses
458

459
    """
460
    try:
461
      return IP4Address(address).family
462
    except errors.IPAddressError:
463
      pass
464

    
465
    try:
466
      return IP6Address(address).family
467
    except errors.IPAddressError:
468
      pass
469

    
470
    raise errors.IPAddressError("Invalid address '%s'" % address)
471

    
472
  @staticmethod
473
  def GetVersionFromAddressFamily(family):
474
    """Convert an IP address family to the corresponding IP version.
475

476
    @type family: int
477
    @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
478
    @return: an int containing the IP version, one of L{constants.IP4_VERSION}
479
             or L{constants.IP6_VERSION}
480
    @raise errors.ProgrammerError: for unknown families
481

482
    """
483
    if family == socket.AF_INET:
484
      return constants.IP4_VERSION
485
    elif family == socket.AF_INET6:
486
      return constants.IP6_VERSION
487

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

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

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

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

    
507
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
508

    
509
  @staticmethod
510
  def GetClassFromIpVersion(version):
511
    """Return the IPAddress subclass for the given IP version.
512

513
    @type version: int
514
    @param version: IP version, one of L{constants.IP4_VERSION} or
515
                    L{constants.IP6_VERSION}
516
    @return: a subclass of L{netutils.IPAddress}
517
    @raise errors.ProgrammerError: for unknowo IP versions
518

519
    """
520
    if version == constants.IP4_VERSION:
521
      return IP4Address
522
    elif version == constants.IP6_VERSION:
523
      return IP6Address
524

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

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

531
    @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
532
    @return: a subclass of L{netutils.IPAddress}
533
    @raise errors.ProgrammerError: for unknowo IP versions
534

535
    """
536
    return IPAddress.GetClassFromIpVersion(
537
              IPAddress.GetVersionFromAddressFamily(family))
538

    
539
  @classmethod
540
  def IsLoopback(cls, address):
541
    """Determine whether it is a loopback address.
542

543
    @type address: str
544
    @param address: IP address to be checked
545
    @rtype: bool
546
    @return: True if loopback, False otherwise
547

548
    """
549
    try:
550
      return cls.InNetwork(cls.loopback_cidr, address)
551
    except errors.IPAddressError:
552
      return False
553

    
554

    
555
class IP4Address(IPAddress):
556
  """IPv4 address class.
557

558
  """
559
  iplen = 32
560
  family = socket.AF_INET
561
  loopback_cidr = "127.0.0.0/8"
562

    
563
  def __init__(self, address):
564
    """Constructor for IPv4 address.
565

566
    @type address: str
567
    @param address: IP address
568
    @raises errors.IPAddressError: if address invalid
569

570
    """
571
    IPAddress.__init__(self)
572
    if not self.IsValid(address):
573
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
574

    
575
    self.address = address
576

    
577
  @staticmethod
578
  def _GetIPIntFromString(address):
579
    """Get integer value of IPv4 address.
580

581
    @type address: str
582
    @param address: IPv6 address
583
    @rtype: int
584
    @return: integer value of given IP address
585

586
    """
587
    address_int = 0
588
    parts = address.split(".")
589
    assert len(parts) == 4
590
    for part in parts:
591
      address_int = (address_int << 8) | int(part)
592

    
593
    return address_int
594

    
595

    
596
class IP6Address(IPAddress):
597
  """IPv6 address class.
598

599
  """
600
  iplen = 128
601
  family = socket.AF_INET6
602
  loopback_cidr = "::1/128"
603

    
604
  def __init__(self, address):
605
    """Constructor for IPv6 address.
606

607
    @type address: str
608
    @param address: IP address
609
    @raises errors.IPAddressError: if address invalid
610

611
    """
612
    IPAddress.__init__(self)
613
    if not self.IsValid(address):
614
      raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
615
    self.address = address
616

    
617
  @staticmethod
618
  def _GetIPIntFromString(address):
619
    """Get integer value of IPv6 address.
620

621
    @type address: str
622
    @param address: IPv6 address
623
    @rtype: int
624
    @return: integer value of given IP address
625

626
    """
627
    doublecolons = address.count("::")
628
    assert not doublecolons > 1
629
    if doublecolons == 1:
630
      # We have a shorthand address, expand it
631
      parts = []
632
      twoparts = address.split("::")
633
      sep = len(twoparts[0].split(":")) + len(twoparts[1].split(":"))
634
      parts = twoparts[0].split(":")
635
      parts.extend(["0"] * (8 - sep))
636
      parts += twoparts[1].split(":")
637
    else:
638
      parts = address.split(":")
639

    
640
    address_int = 0
641
    for part in parts:
642
      address_int = (address_int << 16) + int(part or "0", 16)
643

    
644
    return address_int
645

    
646

    
647
def FormatAddress(address, family=None):
648
  """Format a socket address
649

650
  @type address: family specific (usually tuple)
651
  @param address: address, as reported by this class
652
  @type family: integer
653
  @param family: socket family (one of socket.AF_*) or None
654

655
  """
656
  if family is None:
657
    try:
658
      family = IPAddress.GetAddressFamily(address[0])
659
    except errors.IPAddressError:
660
      raise errors.ParameterError(address)
661

    
662
  if family == socket.AF_UNIX and len(address) == 3:
663
    return "pid=%s, uid=%s, gid=%s" % address
664

    
665
  if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
666
    host, port = address
667
    if family == socket.AF_INET6:
668
      res = "[%s]" % host
669
    else:
670
      res = host
671

    
672
    if port is not None:
673
      res += ":%s" % port
674

    
675
    return res
676

    
677
  raise errors.ParameterError(family, address)