Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ c1912a48

History | View | Annotate | Download (18.6 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
  try:
285
    family = IPAddress.GetAddressFamily(target)
286
  except errors.GenericError:
287
    return False
288

    
289
  sock = socket.socket(family, socket.SOCK_STREAM)
290
  success = False
291

    
292
  if source is not None:
293
    try:
294
      sock.bind((source, 0))
295
    except socket.error, err:
296
      if err[0] == errno.EADDRNOTAVAIL:
297
        success = False
298

    
299
  sock.settimeout(timeout)
300

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

    
310
  return success
311

    
312

    
313
def GetDaemonPort(daemon_name):
314
  """Get the daemon port for this cluster.
315

316
  Note that this routine does not read a ganeti-specific file, but
317
  instead uses C{socket.getservbyname} to allow pre-customization of
318
  this parameter outside of Ganeti.
319

320
  @type daemon_name: string
321
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
322
  @rtype: int
323

324
  """
325
  if daemon_name not in constants.DAEMONS_PORTS:
326
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
327

    
328
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
329
  try:
330
    port = socket.getservbyname(daemon_name, proto)
331
  except socket.error:
332
    port = default_port
333

    
334
  return port
335

    
336

    
337
class IPAddress(object):
338
  """Class that represents an IP address.
339

340
  """
341
  iplen = 0
342
  family = None
343
  loopback_cidr = None
344

    
345
  @staticmethod
346
  def _GetIPIntFromString(address):
347
    """Abstract method to please pylint.
348

349
    """
350
    raise NotImplementedError
351

    
352
  @classmethod
353
  def IsValid(cls, address):
354
    """Validate a IP address.
355

356
    @type address: str
357
    @param address: IP address to be checked
358
    @rtype: bool
359
    @return: True if valid, False otherwise
360

361
    """
362
    if cls.family is None:
363
      try:
364
        family = cls.GetAddressFamily(address)
365
      except errors.IPAddressError:
366
        return False
367
    else:
368
      family = cls.family
369

    
370
    try:
371
      socket.inet_pton(family, address)
372
      return True
373
    except socket.error:
374
      return False
375

    
376
  @classmethod
377
  def ValidateNetmask(cls, netmask):
378
    """Validate a netmask suffix in CIDR notation.
379

380
    @type netmask: int
381
    @param netmask: netmask suffix to validate
382
    @rtype: bool
383
    @return: True if valid, False otherwise
384

385
    """
386
    assert (isinstance(netmask, (int, long)))
387

    
388
    return 0 < netmask <= cls.iplen
389

    
390
  @classmethod
391
  def Own(cls, address):
392
    """Check if the current host has the the given IP address.
393

394
    This is done by trying to bind the given address. We return True if we
395
    succeed or false if a socket.error is raised.
396

397
    @type address: str
398
    @param address: IP address to be checked
399
    @rtype: bool
400
    @return: True if we own the address, False otherwise
401

402
    """
403
    if cls.family is None:
404
      try:
405
        family = cls.GetAddressFamily(address)
406
      except errors.IPAddressError:
407
        return False
408
    else:
409
      family = cls.family
410

    
411
    s = socket.socket(family, socket.SOCK_DGRAM)
412
    success = False
413
    try:
414
      try:
415
        s.bind((address, 0))
416
        success = True
417
      except socket.error:
418
        success = False
419
    finally:
420
      s.close()
421
    return success
422

    
423
  @classmethod
424
  def InNetwork(cls, cidr, address):
425
    """Determine whether an address is within a network.
426

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

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

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

    
454
    return network_int <= address_int <= broadcast_int
455

    
456
  @staticmethod
457
  def GetAddressFamily(address):
458
    """Get the address family of the given address.
459

460
    @type address: str
461
    @param address: ip address whose family will be returned
462
    @rtype: int
463
    @return: C{socket.AF_INET} or C{socket.AF_INET6}
464
    @raise errors.GenericError: for invalid addresses
465

466
    """
467
    try:
468
      return IP4Address(address).family
469
    except errors.IPAddressError:
470
      pass
471

    
472
    try:
473
      return IP6Address(address).family
474
    except errors.IPAddressError:
475
      pass
476

    
477
    raise errors.IPAddressError("Invalid address '%s'" % address)
478

    
479
  @staticmethod
480
  def GetVersionFromAddressFamily(family):
481
    """Convert an IP address family to the corresponding IP version.
482

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

489
    """
490
    if family == socket.AF_INET:
491
      return constants.IP4_VERSION
492
    elif family == socket.AF_INET6:
493
      return constants.IP6_VERSION
494

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

    
497
  @staticmethod
498
  def GetAddressFamilyFromVersion(version):
499
    """Convert an IP version to the corresponding IP address family.
500

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

508
    """
509
    if version == constants.IP4_VERSION:
510
      return socket.AF_INET
511
    elif version == constants.IP6_VERSION:
512
      return socket.AF_INET6
513

    
514
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
515

    
516
  @staticmethod
517
  def GetClassFromIpVersion(version):
518
    """Return the IPAddress subclass for the given IP version.
519

520
    @type version: int
521
    @param version: IP version, one of L{constants.IP4_VERSION} or
522
                    L{constants.IP6_VERSION}
523
    @return: a subclass of L{netutils.IPAddress}
524
    @raise errors.ProgrammerError: for unknowo IP versions
525

526
    """
527
    if version == constants.IP4_VERSION:
528
      return IP4Address
529
    elif version == constants.IP6_VERSION:
530
      return IP6Address
531

    
532
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
533

    
534
  @staticmethod
535
  def GetClassFromIpFamily(family):
536
    """Return the IPAddress subclass for the given IP family.
537

538
    @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
539
    @return: a subclass of L{netutils.IPAddress}
540
    @raise errors.ProgrammerError: for unknowo IP versions
541

542
    """
543
    return IPAddress.GetClassFromIpVersion(
544
              IPAddress.GetVersionFromAddressFamily(family))
545

    
546
  @classmethod
547
  def IsLoopback(cls, address):
548
    """Determine whether it is a loopback address.
549

550
    @type address: str
551
    @param address: IP address to be checked
552
    @rtype: bool
553
    @return: True if loopback, False otherwise
554

555
    """
556
    try:
557
      return cls.InNetwork(cls.loopback_cidr, address)
558
    except errors.IPAddressError:
559
      return False
560

    
561

    
562
class IP4Address(IPAddress):
563
  """IPv4 address class.
564

565
  """
566
  iplen = 32
567
  family = socket.AF_INET
568
  loopback_cidr = "127.0.0.0/8"
569

    
570
  def __init__(self, address):
571
    """Constructor for IPv4 address.
572

573
    @type address: str
574
    @param address: IP address
575
    @raises errors.IPAddressError: if address invalid
576

577
    """
578
    IPAddress.__init__(self)
579
    if not self.IsValid(address):
580
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
581

    
582
    self.address = address
583

    
584
  @staticmethod
585
  def _GetIPIntFromString(address):
586
    """Get integer value of IPv4 address.
587

588
    @type address: str
589
    @param address: IPv6 address
590
    @rtype: int
591
    @return: integer value of given IP address
592

593
    """
594
    address_int = 0
595
    parts = address.split(".")
596
    assert len(parts) == 4
597
    for part in parts:
598
      address_int = (address_int << 8) | int(part)
599

    
600
    return address_int
601

    
602

    
603
class IP6Address(IPAddress):
604
  """IPv6 address class.
605

606
  """
607
  iplen = 128
608
  family = socket.AF_INET6
609
  loopback_cidr = "::1/128"
610

    
611
  def __init__(self, address):
612
    """Constructor for IPv6 address.
613

614
    @type address: str
615
    @param address: IP address
616
    @raises errors.IPAddressError: if address invalid
617

618
    """
619
    IPAddress.__init__(self)
620
    if not self.IsValid(address):
621
      raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
622
    self.address = address
623

    
624
  @staticmethod
625
  def _GetIPIntFromString(address):
626
    """Get integer value of IPv6 address.
627

628
    @type address: str
629
    @param address: IPv6 address
630
    @rtype: int
631
    @return: integer value of given IP address
632

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

    
647
    address_int = 0
648
    for part in parts:
649
      address_int = (address_int << 16) + int(part or "0", 16)
650

    
651
    return address_int
652

    
653

    
654
def FormatAddress(address, family=None):
655
  """Format a socket address
656

657
  @type address: family specific (usually tuple)
658
  @param address: address, as reported by this class
659
  @type family: integer
660
  @param family: socket family (one of socket.AF_*) or None
661

662
  """
663
  if family is None:
664
    try:
665
      family = IPAddress.GetAddressFamily(address[0])
666
    except errors.IPAddressError:
667
      raise errors.ParameterError(address)
668

    
669
  if family == socket.AF_UNIX and len(address) == 3:
670
    return "pid=%s, uid=%s, gid=%s" % address
671

    
672
  if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
673
    host, port = address
674
    if family == socket.AF_INET6:
675
      res = "[%s]" % host
676
    else:
677
      res = host
678

    
679
    if port is not None:
680
      res += ":%s" % port
681

    
682
    return res
683

    
684
  raise errors.ParameterError(family, address)