Revision 37531236

b/lib/netutils.py
28 28

  
29 29

  
30 30
import errno
31
import os
31 32
import re
32 33
import socket
33 34
import struct
34 35
import IN
36
import logging
35 37

  
36 38
from ganeti import constants
37 39
from ganeti import errors
40
from ganeti import utils
38 41

  
39 42
# Structure definition for getsockopt(SOL_SOCKET, SO_PEERCRED, ...):
40 43
# struct ucred { pid_t pid; uid_t uid; gid_t gid; };
......
48 51
_STRUCT_UCRED = "iII"
49 52
_STRUCT_UCRED_SIZE = struct.calcsize(_STRUCT_UCRED)
50 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

  
51 86

  
52 87
def GetSocketCredentials(sock):
53 88
  """Returns the credentials of the foreign process connected to a socket.
......
62 97
  return struct.unpack(_STRUCT_UCRED, peercred)
63 98

  
64 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

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

  
......
366 434
    @type address: str
367 435
    @param address: ip address whose family will be returned
368 436
    @rtype: int
369
    @return: socket.AF_INET or socket.AF_INET6
437
    @return: C{socket.AF_INET} or C{socket.AF_INET6}
370 438
    @raise errors.GenericError: for invalid addresses
371 439

  
372 440
    """
......
382 450

  
383 451
    raise errors.IPAddressError("Invalid address '%s'" % address)
384 452

  
453
  @staticmethod
454
  def GetVersionFromAddressFamily(family):
455
    """Convert an IP address family to the corresponding IP version.
456

  
457
    @type family: int
458
    @param family: IP address family, one of socket.AF_INET or socket.AF_INET6
459
    @return: an int containing the IP version, one of L{constants.IP4_VERSION}
460
             or L{constants.IP6_VERSION}
461
    @raise errors.ProgrammerError: for unknown families
462

  
463
    """
464
    if family == socket.AF_INET:
465
      return constants.IP4_VERSION
466
    elif family == socket.AF_INET6:
467
      return constants.IP6_VERSION
468

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

  
471
  @staticmethod
472
  def GetAddressFamilyFromVersion(version):
473
    """Convert an IP version to the corresponding IP address family.
474

  
475
    @type version: int
476
    @param version: IP version, one of L{constants.IP4_VERSION} or
477
                    L{constants.IP6_VERSION}
478
    @return: an int containing the IP address family, one of C{socket.AF_INET}
479
             or C{socket.AF_INET6}
480
    @raise errors.ProgrammerError: for unknown IP versions
481

  
482
    """
483
    if version == constants.IP4_VERSION:
484
      return socket.AF_INET
485
    elif version == constants.IP6_VERSION:
486
      return socket.AF_INET6
487

  
488
    raise errors.ProgrammerError("%s is not a valid IP version" % version)
489

  
385 490
  @classmethod
386 491
  def IsLoopback(cls, address):
387 492
    """Determine whether it is a loopback address.
b/test/data/ip-addr-show-dummy0.txt
1
7: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN
2
    link/ether 06:d2:06:24:99:dc brd ff:ff:ff:ff:ff:ff
3
    inet 192.0.2.1/32 scope global dummy0
4
    inet6 2001:db8:85a3::8a2e:370:7334/128 scope global
5
       valid_lft forever preferred_lft forever
b/test/data/ip-addr-show-lo-ipv4.txt
1
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
2
    inet 127.0.0.1/8 scope host lo
b/test/data/ip-addr-show-lo-ipv6.txt
1
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436
2
    inet6 ::1/128 scope host
3
       valid_lft forever preferred_lft forever
b/test/data/ip-addr-show-lo-oneline-ipv4.txt
1
1: lo    inet 127.0.0.1/8 scope host lo
b/test/data/ip-addr-show-lo-oneline-ipv6.txt
1
1: lo    inet6 ::1/128 scope host \       valid_lft forever preferred_lft forever
b/test/data/ip-addr-show-lo-oneline.txt
1
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN \    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2
1: lo    inet 127.0.0.1/8 scope host lo
3
1: lo    inet6 ::1/128 scope host \       valid_lft forever preferred_lft forever
b/test/data/ip-addr-show-lo.txt
1
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
2
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3
    inet 127.0.0.1/8 scope host lo
4
    inet6 ::1/128 scope host
5
       valid_lft forever preferred_lft forever
b/test/ganeti.netutils_unittest.py
22 22
"""Script for unittesting the netutils module"""
23 23

  
24 24
import os
25
import re
25 26
import shutil
26 27
import socket
27 28
import tempfile
......
171 172
    self.assertFalse(netutils.IPAddress.Own("192.0.2.1"),
172 173
                     "Should not own IP address 192.0.2.1")
173 174

  
175
  def testFamilyVersionConversions(self):
176
    # IPAddress.GetAddressFamilyFromVersion
177
    self.assertEqual(
178
        netutils.IPAddress.GetAddressFamilyFromVersion(constants.IP4_VERSION),
179
        socket.AF_INET)
180
    self.assertEqual(
181
        netutils.IPAddress.GetAddressFamilyFromVersion(constants.IP6_VERSION),
182
        socket.AF_INET6)
183
    self.assertRaises(errors.ProgrammerError,
184
        netutils.IPAddress.GetAddressFamilyFromVersion, 3)
185

  
186
    # IPAddress.GetVersionFromAddressFamily
187
    self.assertEqual(
188
        netutils.IPAddress.GetVersionFromAddressFamily(socket.AF_INET),
189
        constants.IP4_VERSION)
190
    self.assertEqual(
191
        netutils.IPAddress.GetVersionFromAddressFamily(socket.AF_INET6),
192
        constants.IP6_VERSION)
193
    self.assertRaises(errors.ProgrammerError,
194
        netutils.IPAddress.GetVersionFromAddressFamily, socket.AF_UNIX)
195

  
174 196

  
175 197
class TestIP4Address(unittest.TestCase):
176 198
  def testGetIPIntFromString(self):
......
421 443
    self.assertRaises(errors.ParameterError, netutils.FormatAddress,
422 444
                      ("::1"), family=socket.AF_INET )
423 445

  
446
class TestIpParsing(testutils.GanetiTestCase):
447
  """Test the code that parses the ip command output"""
448

  
449
  def testIp4(self):
450
    valid_addresses = [constants.IP4_ADDRESS_ANY,
451
                       constants.IP4_ADDRESS_LOCALHOST,
452
                       "192.0.2.1",     # RFC5737, IPv4 address blocks for docs
453
                       "198.51.100.1",
454
                       "203.0.113.1",
455
                      ]
456
    for addr in valid_addresses:
457
      self.failUnless(re.search(netutils._IP_RE_TEXT, addr))
458

  
459
  def testIp6(self):
460
    valid_addresses = [constants.IP6_ADDRESS_ANY,
461
                       constants.IP6_ADDRESS_LOCALHOST,
462
                       "0:0:0:0:0:0:0:1", # other form for IP6_ADDRESS_LOCALHOST
463
                       "0:0:0:0:0:0:0:0", # other form for IP6_ADDRESS_ANY
464
                       "2001:db8:85a3::8a2e:370:7334", # RFC3849 IP6 docs block
465
                       "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
466
                       "0:0:0:0:0:FFFF:192.0.2.1",  # IPv4-compatible IPv6
467
                       "::FFFF:192.0.2.1",
468
                       "0:0:0:0:0:0:203.0.113.1",   # IPv4-mapped IPv6
469
                       "::203.0.113.1",
470
                      ]
471
    for addr in valid_addresses:
472
      self.failUnless(re.search(netutils._IP_RE_TEXT, addr))
473

  
474
  def testParseIpCommandOutput(self):
475
    # IPv4-only, fake loopback interface
476
    tests = ["ip-addr-show-lo-ipv4.txt", "ip-addr-show-lo-oneline-ipv4.txt"]
477
    for test_file in tests:
478
      data = self._ReadTestData(test_file)
479
      addr = netutils._GetIpAddressesFromIpOutput(data)
480
      self.failUnless(len(addr[4]) == 1 and addr[4][0] == "127.0.0.1" and not
481
                      addr[6])
482

  
483
    # IPv6-only, fake loopback interface
484
    tests = ["ip-addr-show-lo-ipv6.txt", "ip-addr-show-lo-ipv6.txt"]
485
    for test_file in tests:
486
      data = self._ReadTestData(test_file)
487
      addr = netutils._GetIpAddressesFromIpOutput(data)
488
      self.failUnless(len(addr[6]) == 1 and addr[6][0] == "::1" and not addr[4])
489

  
490
    # IPv4 and IPv6, fake loopback interface
491
    tests = ["ip-addr-show-lo.txt", "ip-addr-show-lo-oneline.txt"]
492
    for test_file in tests:
493
      data = self._ReadTestData(test_file)
494
      addr = netutils._GetIpAddressesFromIpOutput(data)
495
      self.failUnless(len(addr[6]) == 1 and addr[6][0] == "::1" and
496
                      len(addr[4]) == 1 and addr[4][0] == "127.0.0.1")
497

  
498
    # IPv4 and IPv6, dummy interface
499
    data = self._ReadTestData("ip-addr-show-dummy0.txt")
500
    addr = netutils._GetIpAddressesFromIpOutput(data)
501
    self.failUnless(len(addr[6]) == 1 and
502
                    addr[6][0] == "2001:db8:85a3::8a2e:370:7334" and
503
                    len(addr[4]) == 1 and
504
                    addr[4][0] == "192.0.2.1")
505

  
424 506

  
425 507
if __name__ == "__main__":
426 508
  testutils.GanetiTestProgram()

Also available in: Unified diff