You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
11 KiB
281 lines
11 KiB
# Lint as: python2, python3
|
|
# Copyright 2015 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""
|
|
Base class for DHCPv6 tests. This class just sets up a little bit of plumbing,
|
|
like a virtual ethernet device with one end that looks like a real ethernet
|
|
device to shill and a DHCPv6 test server on the end that doesn't look like a
|
|
real ethernet interface to shill. Child classes should override test_body()
|
|
with the logic of their test.
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import logging
|
|
from six.moves import filter
|
|
from six.moves import range
|
|
import time
|
|
import traceback
|
|
|
|
from autotest_lib.client.bin import test
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib.cros import virtual_ethernet_pair
|
|
from autotest_lib.client.cros import dhcpv6_test_server
|
|
from autotest_lib.client.cros.networking import shill_proxy
|
|
|
|
# These are keys that may be used with the DBus dictionary returned from
|
|
# Dhcpv6TestBase.get_interface_ipconfig().
|
|
DHCPV6_KEY_ADDRESS = 'Address'
|
|
DHCPV6_KEY_DELEGATED_PREFIX = 'DelegatedPrefix'
|
|
DHCPV6_KEY_DELEGATED_PREFIX_LENGTH = 'DelegatedPrefixLength'
|
|
DHCPV6_KEY_NAMESERVERS = 'NameServers'
|
|
DHCPV6_KEY_SEARCH_DOMAIN_LIST = 'SearchDomains'
|
|
|
|
# After DHCPv6 completes, an ipconfig should appear shortly after
|
|
IPCONFIG_POLL_COUNT = 5
|
|
IPCONFIG_POLL_PERIOD_SECONDS = 1
|
|
|
|
class Dhcpv6TestBase(test.test):
|
|
"""Parent class for tests that work verify DHCPv6 behavior."""
|
|
version = 1
|
|
|
|
def get_device(self, interface_name):
|
|
"""Finds the corresponding Device object for an interface with
|
|
the name |interface_name|.
|
|
|
|
@param interface_name string The name of the interface to check.
|
|
|
|
@return DBus interface object representing the associated device.
|
|
|
|
"""
|
|
return self.shill_proxy.find_object('Device',
|
|
{'Name': interface_name})
|
|
|
|
|
|
def find_ethernet_service(self, interface_name):
|
|
"""Finds the corresponding service object for an Ethernet interface.
|
|
|
|
@param interface_name string The name of the associated interface
|
|
|
|
@return Service object representing the associated service.
|
|
|
|
"""
|
|
device = self.get_device(interface_name)
|
|
device_path = shill_proxy.ShillProxy.dbus2primitive(device.object_path)
|
|
return self.shill_proxy.find_object('Service', {'Device': device_path})
|
|
|
|
|
|
def get_interface_ipconfig_objects(self, interface_name):
|
|
"""
|
|
Returns a list of dbus object proxies for |interface_name|.
|
|
Returns an empty list if no such interface exists.
|
|
|
|
@param interface_name string name of the device to query (e.g., "eth0").
|
|
|
|
@return list of objects representing DBus IPConfig RPC endpoints.
|
|
|
|
"""
|
|
device = self.get_device(interface_name)
|
|
if device is None:
|
|
return []
|
|
|
|
device_properties = device.GetProperties(utf8_strings=True)
|
|
proxy = self.shill_proxy
|
|
|
|
ipconfig_object = proxy.DBUS_TYPE_IPCONFIG
|
|
return list(filter(bool,
|
|
[ proxy.get_dbus_object(ipconfig_object, property_path)
|
|
for property_path in device_properties['IPConfigs'] ]))
|
|
|
|
|
|
def get_interface_ipconfig(self, interface_name):
|
|
"""
|
|
Returns a dictionary containing settings for an |interface_name| set
|
|
via DHCPv6. Returns None if no such interface or setting bundle on
|
|
that interface can be found in shill.
|
|
|
|
@param interface_name string name of the device to query (e.g., "eth0").
|
|
|
|
@return dict containing the the properties of the IPConfig stripped
|
|
of DBus meta-data or None.
|
|
|
|
"""
|
|
dhcp_properties = None
|
|
for ipconfig in self.get_interface_ipconfig_objects(interface_name):
|
|
logging.info('Looking at ipconfig %r', ipconfig)
|
|
ipconfig_properties = ipconfig.GetProperties(utf8_strings=True)
|
|
if 'Method' not in ipconfig_properties:
|
|
logging.info('Found ipconfig object with no method field')
|
|
continue
|
|
if ipconfig_properties['Method'] != 'dhcp6':
|
|
logging.info('Found ipconfig object with method != dhcp6')
|
|
continue
|
|
if dhcp_properties != None:
|
|
raise error.TestFail('Found multiple ipconfig objects '
|
|
'with method == dhcp6')
|
|
dhcp_properties = ipconfig_properties
|
|
if dhcp_properties is None:
|
|
logging.info('Did not find IPConfig object with method == dhcp6')
|
|
return None
|
|
logging.info('Got raw dhcp config dbus object: %s.', dhcp_properties)
|
|
return shill_proxy.ShillProxy.dbus2primitive(dhcp_properties)
|
|
|
|
|
|
def run_once(self):
|
|
self._server = None
|
|
self._server_ip = None
|
|
self._ethernet_pair = None
|
|
self._shill_proxy = shill_proxy.ShillProxy()
|
|
try:
|
|
# TODO(zqiu): enable DHCPv6 for peer interface, either by restarting
|
|
# shill with appropriate command line options or via a new DBUS
|
|
# command.
|
|
self._ethernet_pair = virtual_ethernet_pair.VirtualEthernetPair(
|
|
interface_ip=None,
|
|
peer_interface_name='pseudoethernet0',
|
|
peer_interface_ip=None,
|
|
interface_ipv6=dhcpv6_test_server.DHCPV6_SERVER_ADDRESS)
|
|
self._ethernet_pair.setup()
|
|
if not self._ethernet_pair.is_healthy:
|
|
raise error.TestFail('Could not create virtual ethernet pair.')
|
|
self._server_ip = self._ethernet_pair.interface_ip
|
|
self._server = dhcpv6_test_server.Dhcpv6TestServer(
|
|
self._ethernet_pair.interface_name)
|
|
self._server.start()
|
|
self.test_body()
|
|
except (error.TestFail, error.TestNAError):
|
|
# Pass these through without modification.
|
|
raise
|
|
except Exception as e:
|
|
logging.error('Caught exception: %s.', str(e))
|
|
logging.error('Trace: %s', traceback.format_exc())
|
|
raise error.TestFail('Caught exception: %s.' % str(e))
|
|
finally:
|
|
if self._server is not None:
|
|
self._server.stop()
|
|
if self._ethernet_pair is not None:
|
|
self._ethernet_pair.teardown()
|
|
|
|
def test_body(self):
|
|
"""
|
|
Override this method with the body of your test. You may safely assume
|
|
that the the properties exposed by DhcpTestBase correctly return
|
|
references to the test apparatus.
|
|
"""
|
|
raise error.TestFail('No test body implemented')
|
|
|
|
@property
|
|
def server_ip(self):
|
|
"""
|
|
Return the IP address of the side of the interface that the DHCPv6 test
|
|
server is bound to. The server itself is bound the the broadcast
|
|
address on the interface.
|
|
"""
|
|
return self._server_ip
|
|
|
|
@property
|
|
def server(self):
|
|
"""
|
|
Returns a reference to the DHCP test server. Use this to add handlers
|
|
and run tests.
|
|
"""
|
|
return self._server
|
|
|
|
@property
|
|
def ethernet_pair(self):
|
|
"""
|
|
Returns a reference to the virtual ethernet pair created to run DHCP
|
|
tests on.
|
|
"""
|
|
return self._ethernet_pair
|
|
|
|
@property
|
|
def shill_proxy(self):
|
|
"""
|
|
Returns a the shill proxy instance.
|
|
"""
|
|
return self._shill_proxy
|
|
|
|
|
|
def check_dhcpv6_config(self):
|
|
"""
|
|
Compare the DHCPv6 ipconfig with DHCP lease parameters to ensure
|
|
that the DUT attained the correct values.
|
|
|
|
"""
|
|
# Retrieve DHCPv6 configuration.
|
|
for attempt in range(IPCONFIG_POLL_COUNT):
|
|
dhcpv6_config = self.get_interface_ipconfig(
|
|
self.ethernet_pair.peer_interface_name)
|
|
# Wait until both IP address and delegated prefix are obtained.
|
|
if (dhcpv6_config is not None and
|
|
dhcpv6_config.get(DHCPV6_KEY_ADDRESS) and
|
|
dhcpv6_config.get(DHCPV6_KEY_DELEGATED_PREFIX)):
|
|
break;
|
|
time.sleep(IPCONFIG_POLL_PERIOD_SECONDS)
|
|
else:
|
|
raise error.TestFail('Failed to retrieve DHCPv6 ipconfig object '
|
|
'from shill.')
|
|
|
|
# Verify Non-temporary Address prefix.
|
|
address = dhcpv6_config.get(DHCPV6_KEY_ADDRESS)
|
|
actual_prefix = address[:address.index('::')]
|
|
expected_prefix = dhcpv6_test_server.DHCPV6_SERVER_SUBNET_PREFIX[:
|
|
dhcpv6_test_server.DHCPV6_SERVER_SUBNET_PREFIX.index('::')]
|
|
if actual_prefix != expected_prefix:
|
|
raise error.TestFail('Address prefix mismatch: '
|
|
'actual %s expected %s.' %
|
|
(actual_prefix, expected_prefix))
|
|
# Verify Non-temporary Address suffix.
|
|
actual_suffix = int(address[address.index('::')+2:], 16)
|
|
if (actual_suffix < dhcpv6_test_server.DHCPV6_ADDRESS_RANGE_LOW or
|
|
actual_suffix > dhcpv6_test_server.DHCPV6_ADDRESS_RANGE_HIGH):
|
|
raise error.TestFail('Invalid address suffix: '
|
|
'actual %x expected (%x-%x)' %
|
|
(actual_suffix,
|
|
dhcpv6_test_server.DHCPV6_ADDRESS_RANGE_LOW,
|
|
dhcpv6_test_server.DHCPV6_ADDRESS_RANGE_HIGH))
|
|
|
|
# Verify delegated prefix.
|
|
delegated_prefix = dhcpv6_config.get(DHCPV6_KEY_DELEGATED_PREFIX)
|
|
for x in range(
|
|
dhcpv6_test_server.DHCPV6_PREFIX_DELEGATION_INDEX_LOW,
|
|
dhcpv6_test_server.DHCPV6_PREFIX_DELEGATION_INDEX_HIGH+1):
|
|
valid_prefix = \
|
|
dhcpv6_test_server.DHCPV6_PREFIX_DELEGATION_RANGE_FORMAT % x
|
|
if delegated_prefix == valid_prefix:
|
|
break;
|
|
else:
|
|
raise error.TestFail('Invalid delegated prefix: %s' %
|
|
(delegated_prefix))
|
|
# Verify delegated prefix length.
|
|
delegated_prefix_length = \
|
|
int(dhcpv6_config.get(DHCPV6_KEY_DELEGATED_PREFIX_LENGTH))
|
|
expected_delegated_prefix_length = \
|
|
dhcpv6_test_server.DHCPV6_PREFIX_DELEGATION_PREFIX_LENGTH
|
|
if delegated_prefix_length != expected_delegated_prefix_length:
|
|
raise error.TestFail('Delegated prefix length mismatch: '
|
|
'actual %d expected %d' %
|
|
(delegated_prefix_length,
|
|
expected_delegated_prefix_length))
|
|
|
|
# Verify name servers.
|
|
actual_name_servers = dhcpv6_config.get(DHCPV6_KEY_NAMESERVERS)
|
|
expected_name_servers = \
|
|
dhcpv6_test_server.DHCPV6_NAME_SERVERS.split(',')
|
|
if actual_name_servers != expected_name_servers:
|
|
raise error.TestFail('Name servers mismatch: actual %r expected %r'
|
|
% (actual_name_servers, expected_name_servers))
|
|
# Verify domain search.
|
|
actual_domain_search = dhcpv6_config.get(DHCPV6_KEY_SEARCH_DOMAIN_LIST)
|
|
expected_domain_search = \
|
|
dhcpv6_test_server.DHCPV6_DOMAIN_SEARCH.split(',')
|
|
if actual_domain_search != expected_domain_search:
|
|
raise error.TestFail('Domain search list mismatch: '
|
|
'actual %r expected %r' %
|
|
(actual_domain_search, expected_domain_search))
|