Statistics
| Branch: | Tag: | Revision:

root / lib / netutils.py @ 069a4bcf

History | View | Annotate | Download (18.8 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
# Workaround a bug in some linux distributions that don't define SO_PEERCRED
56
try:
57
  _SO_PEERCRED = IN.SO_PEERCRED
58
except AttributeError:
59
  _SO_PEERCRED = 17
60

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

    
66
# Dict used to convert from a string representing an IP family to an IP
67
# version
68
_NAME_TO_IP_VER = {
69
  "inet": constants.IP4_VERSION,
70
  "inet6": constants.IP6_VERSION,
71
  }
72

    
73

    
74
def _GetIpAddressesFromIpOutput(ip_output):
75
  """Parses the output of the ip command and retrieves the IP addresses and
76
  version.
77

78
  @param ip_output: string containing the output of the ip command;
79
  @rtype: dict; (int, list)
80
  @return: a dict having as keys the IP versions and as values the
81
           corresponding list of addresses found in the IP output.
82

83
  """
84
  addr = dict((i, []) for i in _NAME_TO_IP_VER.values())
85

    
86
  for row in ip_output.splitlines():
87
    match = _IP_FAMILY_RE.search(row)
88
    if match and IPAddress.IsValid(match.group("ip")):
89
      addr[_NAME_TO_IP_VER[match.group("family")]].append(match.group("ip"))
90

    
91
  return addr
92

    
93

    
94
def GetSocketCredentials(sock):
95
  """Returns the credentials of the foreign process connected to a socket.
96

97
  @param sock: Unix socket
98
  @rtype: tuple; (number, number, number)
99
  @return: The PID, UID and GID of the connected foreign process.
100

101
  """
102
  peercred = sock.getsockopt(socket.SOL_SOCKET, _SO_PEERCRED,
103
                             _STRUCT_UCRED_SIZE)
104
  return struct.unpack(_STRUCT_UCRED, peercred)
105

    
106

    
107
def IsValidInterface(ifname):
108
  """Validate an interface name.
109

110
  @type ifname: string
111
  @param ifname: Name of the network interface
112
  @return: boolean indicating whether the interface name is valid or not.
113

114
  """
115
  return os.path.exists(utils.PathJoin("/sys/class/net", ifname))
116

    
117

    
118
def GetInterfaceIpAddresses(ifname):
119
  """Returns the IP addresses associated to the interface.
120

121
  @type ifname: string
122
  @param ifname: Name of the network interface
123
  @return: A dict having for keys the IP version (either
124
           L{constants.IP4_VERSION} or L{constants.IP6_VERSION}) and for
125
           values the lists of IP addresses of the respective version
126
           associated to the interface
127

128
  """
129
  result = utils.RunCmd([constants.IP_COMMAND_PATH, "-o", "addr", "show",
130
                         ifname])
131

    
132
  if result.failed:
133
    logging.error("Error running the ip command while getting the IP"
134
                  " addresses of %s", ifname)
135
    return None
136

    
137
  return _GetIpAddressesFromIpOutput(result.output)
138

    
139

    
140
def GetHostname(name=None, family=None):
141
  """Returns a Hostname object.
142

143
  @type name: str
144
  @param name: hostname or None
145
  @type family: int
146
  @param family: AF_INET | AF_INET6 | None
147
  @rtype: L{Hostname}
148
  @return: Hostname object
149
  @raise errors.OpPrereqError: in case of errors in resolving
150

151
  """
152
  try:
153
    return Hostname(name=name, family=family)
154
  except errors.ResolverError, err:
155
    raise errors.OpPrereqError("The given name (%s) does not resolve: %s" %
156
                               (err[0], err[2]), errors.ECODE_RESOLVER)
157

    
158

    
159
class Hostname:
160
  """Class implementing resolver and hostname functionality.
161

162
  """
163
  _VALID_NAME_RE = re.compile("^[a-z0-9._-]{1,255}$")
164

    
165
  def __init__(self, name=None, family=None):
166
    """Initialize the host name object.
167

168
    If the name argument is None, it will use this system's name.
169

170
    @type family: int
171
    @param family: AF_INET | AF_INET6 | None
172
    @type name: str
173
    @param name: hostname or None
174

175
    """
176
    self.name = self.GetFqdn(name)
177
    self.ip = self.GetIP(self.name, family=family)
178

    
179
  @classmethod
180
  def GetSysName(cls):
181
    """Legacy method the get the current system's name.
182

183
    """
184
    return cls.GetFqdn()
185

    
186
  @classmethod
187
  def GetFqdn(cls, hostname=None):
188
    """Return fqdn.
189

190
    If hostname is None the system's fqdn is returned.
191

192
    @type hostname: str
193
    @param hostname: name to be fqdn'ed
194
    @rtype: str
195
    @return: fqdn of given name, if it exists, unmodified name otherwise
196

197
    """
198
    if hostname is None:
199
      virtfqdn = vcluster.GetVirtualHostname()
200
      if virtfqdn:
201
        result = virtfqdn
202
      else:
203
        result = socket.getfqdn()
204
    else:
205
      result = socket.getfqdn(hostname)
206

    
207
    return cls.GetNormalizedName(result)
208

    
209
  @staticmethod
210
  def GetIP(hostname, family=None):
211
    """Return IP address of given hostname.
212

213
    Supports both IPv4 and IPv6.
214

215
    @type hostname: str
216
    @param hostname: hostname to look up
217
    @type family: int
218
    @param family: AF_INET | AF_INET6 | None
219
    @rtype: str
220
    @return: IP address
221
    @raise errors.ResolverError: in case of errors in resolving
222

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

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

    
247
  @classmethod
248
  def GetNormalizedName(cls, hostname):
249
    """Validate and normalize the given hostname.
250

251
    @attention: the validation is a bit more relaxed than the standards
252
        require; most importantly, we allow underscores in names
253
    @raise errors.OpPrereqError: when the name is not valid
254

255
    """
256
    hostname = hostname.lower()
257
    if (not cls._VALID_NAME_RE.match(hostname) or
258
        # double-dots, meaning empty label
259
        ".." in hostname or
260
        # empty initial label
261
        hostname.startswith(".")):
262
      raise errors.OpPrereqError("Invalid hostname '%s'" % hostname,
263
                                 errors.ECODE_INVAL)
264
    if hostname.endswith("."):
265
      hostname = hostname.rstrip(".")
266
    return hostname
267

    
268

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

272
  Check if the given IP is reachable by doing attempting a TCP connect
273
  to it.
274

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

289
  """
290
  try:
291
    family = IPAddress.GetAddressFamily(target)
292
  except errors.GenericError:
293
    return False
294

    
295
  sock = socket.socket(family, socket.SOCK_STREAM)
296
  success = False
297

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

    
305
  sock.settimeout(timeout)
306

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

    
316
  return success
317

    
318

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

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

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

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

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

    
340
  return port
341

    
342

    
343
class IPAddress(object):
344
  """Class that represents an IP address.
345

346
  """
347
  iplen = 0
348
  family = None
349
  loopback_cidr = None
350

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

355
    """
356
    raise NotImplementedError
357

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

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

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

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

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

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

391
    """
392
    assert (isinstance(netmask, (int, long)))
393

    
394
    return 0 < netmask <= cls.iplen
395

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

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

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

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

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

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

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

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

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

    
460
    return network_int <= address_int <= broadcast_int
461

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

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

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

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

    
483
    raise errors.IPAddressError("Invalid address '%s'" % address)
484

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

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

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

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

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

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

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

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

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

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

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

    
538
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
539

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

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

548
    """
549
    return IPAddress.GetClassFromIpVersion(
550
              IPAddress.GetVersionFromAddressFamily(family))
551

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

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

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

    
567

    
568
class IP4Address(IPAddress):
569
  """IPv4 address class.
570

571
  """
572
  iplen = 32
573
  family = socket.AF_INET
574
  loopback_cidr = "127.0.0.0/8"
575

    
576
  def __init__(self, address):
577
    """Constructor for IPv4 address.
578

579
    @type address: str
580
    @param address: IP address
581
    @raises errors.IPAddressError: if address invalid
582

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

    
588
    self.address = address
589

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

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

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

    
606
    return address_int
607

    
608

    
609
class IP6Address(IPAddress):
610
  """IPv6 address class.
611

612
  """
613
  iplen = 128
614
  family = socket.AF_INET6
615
  loopback_cidr = "::1/128"
616

    
617
  def __init__(self, address):
618
    """Constructor for IPv6 address.
619

620
    @type address: str
621
    @param address: IP address
622
    @raises errors.IPAddressError: if address invalid
623

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

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

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

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

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

    
657
    return address_int
658

    
659

    
660
def FormatAddress(address, family=None):
661
  """Format a socket address
662

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

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

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

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

    
685
    if port is not None:
686
      res += ":%s" % port
687

    
688
    return res
689

    
690
  raise errors.ParameterError(family, address)