Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 61e062dd

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.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
      # we don't have here an actual error code, it's just that the
233
      # data type returned by getaddrinfo is not what we expected;
234
      # let's keep the same format in the exception arguments with a
235
      # dummy error code
236
      raise errors.ResolverError(hostname, 0,
237
                                 "Unknown error in getaddrinfo(): %s" % err)
238

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

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

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

    
260

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

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

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

281
  """
282
  try:
283
    family = IPAddress.GetAddressFamily(target)
284
  except errors.GenericError:
285
    return False
286

    
287
  sock = socket.socket(family, socket.SOCK_STREAM)
288
  success = False
289

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

    
297
  sock.settimeout(timeout)
298

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

    
308
  return success
309

    
310

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

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

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

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

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

    
332
  return port
333

    
334

    
335
class IPAddress(object):
336
  """Class that represents an IP address.
337

338
  """
339
  iplen = 0
340
  family = None
341
  loopback_cidr = None
342

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

347
    """
348
    raise NotImplementedError
349

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

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

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

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

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

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

383
    """
384
    assert (isinstance(netmask, (int, long)))
385

    
386
    return 0 < netmask <= cls.iplen
387

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

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

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

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

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

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

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

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

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

    
452
    return network_int <= address_int <= broadcast_int
453

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

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

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

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

    
475
    raise errors.IPAddressError("Invalid address '%s'" % address)
476

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

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

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

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

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

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

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

    
512
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
513

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

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

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

    
530
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
531

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

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

540
    """
541
    return IPAddress.GetClassFromIpVersion(
542
              IPAddress.GetVersionFromAddressFamily(family))
543

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

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

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

    
559

    
560
class IP4Address(IPAddress):
561
  """IPv4 address class.
562

563
  """
564
  iplen = 32
565
  family = socket.AF_INET
566
  loopback_cidr = "127.0.0.0/8"
567

    
568
  def __init__(self, address):
569
    """Constructor for IPv4 address.
570

571
    @type address: str
572
    @param address: IP address
573
    @raises errors.IPAddressError: if address invalid
574

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

    
580
    self.address = address
581

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

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

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

    
598
    return address_int
599

    
600

    
601
class IP6Address(IPAddress):
602
  """IPv6 address class.
603

604
  """
605
  iplen = 128
606
  family = socket.AF_INET6
607
  loopback_cidr = "::1/128"
608

    
609
  def __init__(self, address):
610
    """Constructor for IPv6 address.
611

612
    @type address: str
613
    @param address: IP address
614
    @raises errors.IPAddressError: if address invalid
615

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

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

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

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

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

    
649
    return address_int
650

    
651

    
652
def FormatAddress(address, family=None):
653
  """Format a socket address
654

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

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

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

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

    
677
    if port is not None:
678
      res += ":%s" % port
679

    
680
    return res
681

    
682
  raise errors.ParameterError(family, address)