Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ f7f03738

History | View | Annotate | Download (18.9 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.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
  @classmethod
181
  def GetFqdn(cls, 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
        result = virtfqdn
196
      else:
197
        result = socket.getfqdn()
198
    else:
199
      result = socket.getfqdn(hostname)
200

    
201
    return cls.GetNormalizedName(result)
202

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

207
    Supports both IPv4 and IPv6.
208

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

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

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

    
241
  @classmethod
242
  def GetNormalizedName(cls, hostname):
243
    """Validate and normalize the given hostname.
244

245
    @attention: the validation is a bit more relaxed than the standards
246
        require; most importantly, we allow underscores in names
247
    @raise errors.OpPrereqError: when the name is not valid
248

249
    """
250
    hostname = hostname.lower()
251
    if (not cls._VALID_NAME_RE.match(hostname) or
252
        # double-dots, meaning empty label
253
        ".." in hostname or
254
        # empty initial label
255
        hostname.startswith(".")):
256
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
257
                                 errors.ECODE_INVAL)
258
    if hostname.endswith("."):
259
      hostname = hostname.rstrip(".")
260
    return hostname
261

    
262

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

266
  Check if the given IP is reachable by doing attempting a TCP connect
267
  to it.
268

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

283
  """
284
  logging.debug("Attempting to reach TCP port %s on target %s with a timeout"
285
                " of %s seconds", port, target, timeout)
286

    
287
  try:
288
    family = IPAddress.GetAddressFamily(target)
289
  except errors.IPAddressError, err:
290
    raise errors.ProgrammerError("Family of IP address given in parameter"
291
                                 " 'target' can't be determined: %s" % err)
292

    
293
  sock = socket.socket(family, socket.SOCK_STREAM)
294
  success = False
295

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

    
303
  sock.settimeout(timeout)
304

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

    
314
  return success
315

    
316

    
317
def GetDaemonPort(daemon_name):
318
  """Get the daemon port for this cluster.
319

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

324
  @type daemon_name: string
325
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
326
  @rtype: int
327

328
  """
329
  if daemon_name not in constants.DAEMONS_PORTS:
330
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
331

    
332
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
333
  try:
334
    port = socket.getservbyname(daemon_name, proto)
335
  except socket.error:
336
    port = default_port
337

    
338
  return port
339

    
340

    
341
class IPAddress(object):
342
  """Class that represents an IP address.
343

344
  """
345
  iplen = 0
346
  family = None
347
  loopback_cidr = None
348

    
349
  @staticmethod
350
  def _GetIPIntFromString(address):
351
    """Abstract method to please pylint.
352

353
    """
354
    raise NotImplementedError
355

    
356
  @classmethod
357
  def IsValid(cls, address):
358
    """Validate a IP address.
359

360
    @type address: str
361
    @param address: IP address to be checked
362
    @rtype: bool
363
    @return: True if valid, False otherwise
364

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

    
374
    try:
375
      socket.inet_pton(family, address)
376
      return True
377
    except socket.error:
378
      return False
379

    
380
  @classmethod
381
  def ValidateNetmask(cls, netmask):
382
    """Validate a netmask suffix in CIDR notation.
383

384
    @type netmask: int
385
    @param netmask: netmask suffix to validate
386
    @rtype: bool
387
    @return: True if valid, False otherwise
388

389
    """
390
    assert (isinstance(netmask, (int, long)))
391

    
392
    return 0 < netmask <= cls.iplen
393

    
394
  @classmethod
395
  def Own(cls, address):
396
    """Check if the current host has the the given IP address.
397

398
    This is done by trying to bind the given address. We return True if we
399
    succeed or false if a socket.error is raised.
400

401
    @type address: str
402
    @param address: IP address to be checked
403
    @rtype: bool
404
    @return: True if we own the address, False otherwise
405

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

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

    
427
  @classmethod
428
  def InNetwork(cls, cidr, address):
429
    """Determine whether an address is within a network.
430

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

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

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

    
458
    return network_int <= address_int <= broadcast_int
459

    
460
  @staticmethod
461
  def GetAddressFamily(address):
462
    """Get the address family of the given address.
463

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

470
    """
471
    try:
472
      return IP4Address(address).family
473
    except errors.IPAddressError:
474
      pass
475

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

    
481
    raise errors.IPAddressError("Invalid address '%s'" % address)
482

    
483
  @staticmethod
484
  def GetVersionFromAddressFamily(family):
485
    """Convert an IP address family to the corresponding IP version.
486

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

493
    """
494
    if family == socket.AF_INET:
495
      return constants.IP4_VERSION
496
    elif family == socket.AF_INET6:
497
      return constants.IP6_VERSION
498

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

    
501
  @staticmethod
502
  def GetAddressFamilyFromVersion(version):
503
    """Convert an IP version to the corresponding IP address family.
504

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

512
    """
513
    if version == constants.IP4_VERSION:
514
      return socket.AF_INET
515
    elif version == constants.IP6_VERSION:
516
      return socket.AF_INET6
517

    
518
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
519

    
520
  @staticmethod
521
  def GetClassFromIpVersion(version):
522
    """Return the IPAddress subclass for the given IP version.
523

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

530
    """
531
    if version == constants.IP4_VERSION:
532
      return IP4Address
533
    elif version == constants.IP6_VERSION:
534
      return IP6Address
535

    
536
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
537

    
538
  @staticmethod
539
  def GetClassFromIpFamily(family):
540
    """Return the IPAddress subclass for the given IP family.
541

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

546
    """
547
    return IPAddress.GetClassFromIpVersion(
548
              IPAddress.GetVersionFromAddressFamily(family))
549

    
550
  @classmethod
551
  def IsLoopback(cls, address):
552
    """Determine whether it is a loopback address.
553

554
    @type address: str
555
    @param address: IP address to be checked
556
    @rtype: bool
557
    @return: True if loopback, False otherwise
558

559
    """
560
    try:
561
      return cls.InNetwork(cls.loopback_cidr, address)
562
    except errors.IPAddressError:
563
      return False
564

    
565

    
566
class IP4Address(IPAddress):
567
  """IPv4 address class.
568

569
  """
570
  iplen = 32
571
  family = socket.AF_INET
572
  loopback_cidr = "127.0.0.0/8"
573

    
574
  def __init__(self, address):
575
    """Constructor for IPv4 address.
576

577
    @type address: str
578
    @param address: IP address
579
    @raises errors.IPAddressError: if address invalid
580

581
    """
582
    IPAddress.__init__(self)
583
    if not self.IsValid(address):
584
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
585

    
586
    self.address = address
587

    
588
  @staticmethod
589
  def _GetIPIntFromString(address):
590
    """Get integer value of IPv4 address.
591

592
    @type address: str
593
    @param address: IPv6 address
594
    @rtype: int
595
    @return: integer value of given IP address
596

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

    
604
    return address_int
605

    
606

    
607
class IP6Address(IPAddress):
608
  """IPv6 address class.
609

610
  """
611
  iplen = 128
612
  family = socket.AF_INET6
613
  loopback_cidr = "::1/128"
614

    
615
  def __init__(self, address):
616
    """Constructor for IPv6 address.
617

618
    @type address: str
619
    @param address: IP address
620
    @raises errors.IPAddressError: if address invalid
621

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

    
628
  @staticmethod
629
  def _GetIPIntFromString(address):
630
    """Get integer value of IPv6 address.
631

632
    @type address: str
633
    @param address: IPv6 address
634
    @rtype: int
635
    @return: integer value of given IP address
636

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

    
651
    address_int = 0
652
    for part in parts:
653
      address_int = (address_int << 16) + int(part or "0", 16)
654

    
655
    return address_int
656

    
657

    
658
def FormatAddress(address, family=None):
659
  """Format a socket address
660

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

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

    
673
  if family == socket.AF_UNIX and len(address) == 3:
674
    return "pid=%s, uid=%s, gid=%s" % address
675

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

    
683
    if port is not None:
684
      res += ":%s" % port
685

    
686
    return res
687

    
688
  raise errors.ParameterError(family, address)