Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 7142485a

History | View | Annotate | Download (18.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2010 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

    
42
# Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
43
# struct ucred { pid_t pid; uid_t uid; gid_t gid; };
44
#
45
# The GNU C Library defines gid_t and uid_t to be "unsigned int" and
46
# pid_t to "int".
47
#
48
# IEEE Std 1003.1-2008:
49
# "nlink_t, uid_t, gid_t, and id_t shall be integer types"
50
# "blksize_t, pid_t, and ssize_t shall be signed integer types"
51
_STRUCT_UCRED = "iII"
52
_STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
53

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

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

    
66

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

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

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

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

    
84
  return addr
85

    
86

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

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

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

    
99

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

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

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

    
110

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

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

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

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

    
130
  return _GetIpAddressesFromIpOutput(result.output)
131

    
132

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

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

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

    
151

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

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

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

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

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

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

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

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

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

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

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

190
    """
191
    if hostname is None:
192
      return socket.getfqdn()
193
    else:
194
      return socket.getfqdn(hostname)
195

    
196
  @staticmethod
197
  def GetIP(hostname, family=None):
198
    """Return IP address of given hostname.
199

200
    Supports both IPv4 and IPv6.
201

202
    @type hostname: str
203
    @param hostname: hostname to look up
204
    @type family: int
205
    @param family: AF_INET | AF_INET6 | None
206
    @rtype: str
207
    @return: IP address
208
    @raise errors.ResolverError: in case of errors in resolving
209

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

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

    
229
  @classmethod
230
  def GetNormalizedName(cls, hostname):
231
    """Validate and normalize the given hostname.
232

233
    @attention: the validation is a bit more relaxed than the standards
234
        require; most importantly, we allow underscores in names
235
    @raise errors.OpPrereqError: when the name is not valid
236

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

    
250

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

254
  Check if the given IP is reachable by doing attempting a TCP connect
255
  to it.
256

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

271
  """
272
  try:
273
    family = IPAddress.GetAddressFamily(target)
274
  except errors.GenericError:
275
    return False
276

    
277
  sock = socket.socket(family, socket.SOCK_STREAM)
278
  success = False
279

    
280
  if source is not None:
281
    try:
282
      sock.bind((source, 0))
283
    except socket.error, (errcode, _):
284
      if errcode == errno.EADDRNOTAVAIL:
285
        success = False
286

    
287
  sock.settimeout(timeout)
288

    
289
  try:
290
    sock.connect((target, port))
291
    sock.close()
292
    success = True
293
  except socket.timeout:
294
    success = False
295
  except socket.error, (errcode, _):
296
    success = (not live_port_needed) and (errcode == errno.ECONNREFUSED)
297

    
298
  return success
299

    
300

    
301
def GetDaemonPort(daemon_name):
302
  """Get the daemon port for this cluster.
303

304
  Note that this routine does not read a ganeti-specific file, but
305
  instead uses C{socket.getservbyname} to allow pre-customization of
306
  this parameter outside of Ganeti.
307

308
  @type daemon_name: string
309
  @param daemon_name: daemon name (in constants.DAEMONS_PORTS)
310
  @rtype: int
311

312
  """
313
  if daemon_name not in constants.DAEMONS_PORTS:
314
    raise errors.ProgrammerError("Unknown daemon: %s" % daemon_name)
315

    
316
  (proto, default_port) = constants.DAEMONS_PORTS[daemon_name]
317
  try:
318
    port = socket.getservbyname(daemon_name, proto)
319
  except socket.error:
320
    port = default_port
321

    
322
  return port
323

    
324

    
325
class IPAddress(object):
326
  """Class that represents an IP address.
327

328
  """
329
  iplen = 0
330
  family = None
331
  loopback_cidr = None
332

    
333
  @staticmethod
334
  def _GetIPIntFromString(address):
335
    """Abstract method to please pylint.
336

337
    """
338
    raise NotImplementedError
339

    
340
  @classmethod
341
  def IsValid(cls, address):
342
    """Validate a IP address.
343

344
    @type address: str
345
    @param address: IP address to be checked
346
    @rtype: bool
347
    @return: True if valid, False otherwise
348

349
    """
350
    if cls.family is None:
351
      try:
352
        family = cls.GetAddressFamily(address)
353
      except errors.IPAddressError:
354
        return False
355
    else:
356
      family = cls.family
357

    
358
    try:
359
      socket.inet_pton(family, address)
360
      return True
361
    except socket.error:
362
      return False
363

    
364
  @classmethod
365
  def ValidateNetmask(cls, netmask):
366
    """Validate a netmask suffix in CIDR notation.
367

368
    @type netmask: int
369
    @param netmask: netmask suffix to validate
370
    @rtype: bool
371
    @return: True if valid, False otherwise
372

373
    """
374
    assert (isinstance(netmask, (int, long)))
375

    
376
    return 0 < netmask <= cls.iplen
377

    
378
  @classmethod
379
  def Own(cls, address):
380
    """Check if the current host has the the given IP address.
381

382
    This is done by trying to bind the given address. We return True if we
383
    succeed or false if a socket.error is raised.
384

385
    @type address: str
386
    @param address: IP address to be checked
387
    @rtype: bool
388
    @return: True if we own the address, False otherwise
389

390
    """
391
    if cls.family is None:
392
      try:
393
        family = cls.GetAddressFamily(address)
394
      except errors.IPAddressError:
395
        return False
396
    else:
397
      family = cls.family
398

    
399
    s = socket.socket(family, socket.SOCK_DGRAM)
400
    success = False
401
    try:
402
      try:
403
        s.bind((address, 0))
404
        success = True
405
      except socket.error:
406
        success = False
407
    finally:
408
      s.close()
409
    return success
410

    
411
  @classmethod
412
  def InNetwork(cls, cidr, address):
413
    """Determine whether an address is within a network.
414

415
    @type cidr: string
416
    @param cidr: Network in CIDR notation, e.g. '192.0.2.0/24', '2001:db8::/64'
417
    @type address: str
418
    @param address: IP address
419
    @rtype: bool
420
    @return: True if address is in cidr, False otherwise
421

422
    """
423
    address_int = cls._GetIPIntFromString(address)
424
    subnet = cidr.split("/")
425
    assert len(subnet) == 2
426
    try:
427
      prefix = int(subnet[1])
428
    except ValueError:
429
      return False
430

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

    
442
    return network_int <= address_int <= broadcast_int
443

    
444
  @staticmethod
445
  def GetAddressFamily(address):
446
    """Get the address family of the given address.
447

448
    @type address: str
449
    @param address: ip address whose family will be returned
450
    @rtype: int
451
    @return: C{socket.AF_INET} or C{socket.AF_INET6}
452
    @raise errors.GenericError: for invalid addresses
453

454
    """
455
    try:
456
      return IP4Address(address).family
457
    except errors.IPAddressError:
458
      pass
459

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

    
465
    raise errors.IPAddressError("Invalid address '%s'" % address)
466

    
467
  @staticmethod
468
  def GetVersionFromAddressFamily(family):
469
    """Convert an IP address family to the corresponding IP version.
470

471
    @type family: int
472
    @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
473
    @return: an int containing the IP version, one of L{constants.IP4_VERSION}
474
             or L{constants.IP6_VERSION}
475
    @raise errors.ProgrammerError: for unknown families
476

477
    """
478
    if family == socket.AF_INET:
479
      return constants.IP4_VERSION
480
    elif family == socket.AF_INET6:
481
      return constants.IP6_VERSION
482

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

    
485
  @staticmethod
486
  def GetAddressFamilyFromVersion(version):
487
    """Convert an IP version to the corresponding IP address family.
488

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

496
    """
497
    if version == constants.IP4_VERSION:
498
      return socket.AF_INET
499
    elif version == constants.IP6_VERSION:
500
      return socket.AF_INET6
501

    
502
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
503

    
504
  @staticmethod
505
  def GetClassFromIpVersion(version):
506
    """Return the IPAddress subclass for the given IP version.
507

508
    @type version: int
509
    @param version: IP version, one of L{constants.IP4_VERSION} or
510
                    L{constants.IP6_VERSION}
511
    @return: a subclass of L{netutils.IPAddress}
512
    @raise errors.ProgrammerError: for unknowo IP versions
513

514
    """
515
    if version == constants.IP4_VERSION:
516
      return IP4Address
517
    elif version == constants.IP6_VERSION:
518
      return IP6Address
519

    
520
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
521

    
522
  @staticmethod
523
  def GetClassFromIpFamily(family):
524
    """Return the IPAddress subclass for the given IP family.
525

526
    @param family: IP family (one of C{socket.AF_INET} or C{socket.AF_INET6}
527
    @return: a subclass of L{netutils.IPAddress}
528
    @raise errors.ProgrammerError: for unknowo IP versions
529

530
    """
531
    return IPAddress.GetClassFromIpVersion(
532
              IPAddress.GetVersionFromAddressFamily(family))
533

    
534
  @classmethod
535
  def IsLoopback(cls, address):
536
    """Determine whether it is a loopback address.
537

538
    @type address: str
539
    @param address: IP address to be checked
540
    @rtype: bool
541
    @return: True if loopback, False otherwise
542

543
    """
544
    try:
545
      return cls.InNetwork(cls.loopback_cidr, address)
546
    except errors.IPAddressError:
547
      return False
548

    
549

    
550
class IP4Address(IPAddress):
551
  """IPv4 address class.
552

553
  """
554
  iplen = 32
555
  family = socket.AF_INET
556
  loopback_cidr = "127.0.0.0/8"
557

    
558
  def __init__(self, address):
559
    """Constructor for IPv4 address.
560

561
    @type address: str
562
    @param address: IP address
563
    @raises errors.IPAddressError: if address invalid
564

565
    """
566
    IPAddress.__init__(self)
567
    if not self.IsValid(address):
568
      raise errors.IPAddressError("IPv4 Address %s invalid" % address)
569

    
570
    self.address = address
571

    
572
  @staticmethod
573
  def _GetIPIntFromString(address):
574
    """Get integer value of IPv4 address.
575

576
    @type address: str
577
    @param address: IPv6 address
578
    @rtype: int
579
    @return: integer value of given IP address
580

581
    """
582
    address_int = 0
583
    parts = address.split(".")
584
    assert len(parts) == 4
585
    for part in parts:
586
      address_int = (address_int << 8) | int(part)
587

    
588
    return address_int
589

    
590

    
591
class IP6Address(IPAddress):
592
  """IPv6 address class.
593

594
  """
595
  iplen = 128
596
  family = socket.AF_INET6
597
  loopback_cidr = "::1/128"
598

    
599
  def __init__(self, address):
600
    """Constructor for IPv6 address.
601

602
    @type address: str
603
    @param address: IP address
604
    @raises errors.IPAddressError: if address invalid
605

606
    """
607
    IPAddress.__init__(self)
608
    if not self.IsValid(address):
609
      raise errors.IPAddressError("IPv6 Address [%s] invalid" % address)
610
    self.address = address
611

    
612
  @staticmethod
613
  def _GetIPIntFromString(address):
614
    """Get integer value of IPv6 address.
615

616
    @type address: str
617
    @param address: IPv6 address
618
    @rtype: int
619
    @return: integer value of given IP address
620

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

    
635
    address_int = 0
636
    for part in parts:
637
      address_int = (address_int << 16) + int(part or "0", 16)
638

    
639
    return address_int
640

    
641

    
642
def FormatAddress(address, family=None):
643
  """Format a socket address
644

645
  @type address: family specific (usually tuple)
646
  @param address: address, as reported by this class
647
  @type family: integer
648
  @param family: socket family (one of socket.AF_*) or None
649

650
  """
651
  if family is None:
652
    try:
653
      family = IPAddress.GetAddressFamily(address[0])
654
    except errors.IPAddressError:
655
      raise errors.ParameterError(address)
656

    
657
  if family == socket.AF_UNIX and len(address) == 3:
658
    return "pid=%s, uid=%s, gid=%s" % address
659

    
660
  if family in (socket.AF_INET, socket.AF_INET6) and len(address) == 2:
661
    host, port = address
662
    if family == socket.AF_INET6:
663
      res = "[%s]" % host
664
    else:
665
      res = host
666

    
667
    if port is not None:
668
      res += ":%s" % port
669

    
670
    return res
671

    
672
  raise errors.ParameterError(family, address)