Disable 'accept_ra' in DHCP agent namespace

Currently the DHCP agent relies on the acceptance of an
RA to configure its IPv6 address with SLAAC or DHCPv6-stateless
network modes.  It should explicitly assign addresses to the
agent based on the data model instead.

In order to do this we must disable RAs in the namespace so
that a static assignment doesn't conflict with a previously
created dynamically-generated address.

Change-Id: I1b38d131249d59fa486a07024d4b1ec61e693d59
Related-bug: #1627902
This commit is contained in:
Brian Haley 2016-10-14 11:37:54 -04:00
parent 97f4a3fdbb
commit 904f85e2f9
6 changed files with 43 additions and 11 deletions

View File

@ -626,7 +626,8 @@ class RouterInfo(object):
return
# There is no IPv6 gw_ip, use RouterAdvt for default route.
self.driver.configure_ipv6_ra(ns_name, interface_name)
self.driver.configure_ipv6_ra(ns_name, interface_name,
n_const.ACCEPT_RA_WITH_FORWARDING)
def _external_gateway_added(self, ex_gw_port, interface_name,
ns_name, preserve_ips):

View File

@ -40,7 +40,6 @@ from neutron.agent.linux import iptables_manager
from neutron.cmd.sanity import checks
from neutron.common import constants as n_const
from neutron.common import exceptions as n_exc
from neutron.common import ipv6_utils
from neutron.common import utils as common_utils
from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.ipam import utils as ipam_utils
@ -1347,6 +1346,15 @@ class DeviceManager(object):
def plug(self, network, port, interface_name):
"""Plug device settings for the network's DHCP on this host."""
# Disable acceptance of RAs in the namespace so we don't
# auto-configure an IPv6 address since we explicitly configure
# them on the device. This must be done before any interfaces
# are plugged since it could receive an RA by the time
# plug() returns, so we have to create the namespace first.
if network.namespace:
ip_lib.IPWrapper().ensure_namespace(network.namespace)
self.driver.configure_ipv6_ra(network.namespace, 'default',
n_const.ACCEPT_RA_DISABLED)
self.driver.plug(network.id,
port.id,
interface_name,
@ -1383,10 +1391,9 @@ class DeviceManager(object):
ip_cidrs = []
for fixed_ip in port.fixed_ips:
subnet = fixed_ip.subnet
if not ipv6_utils.is_auto_address_subnet(subnet):
net = netaddr.IPNetwork(subnet.cidr)
ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
ip_cidrs.append(ip_cidr)
net = netaddr.IPNetwork(subnet.cidr)
ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
ip_cidrs.append(ip_cidr)
if self.driver.use_gateway_ips:
# For each DHCP-enabled subnet, add that subnet's gateway

View File

@ -213,10 +213,12 @@ class LinuxInterfaceDriver(object):
return (self.DEV_NAME_PREFIX + port.id)[:self.DEV_NAME_LEN]
@staticmethod
def configure_ipv6_ra(namespace, dev_name):
"""Configure acceptance of IPv6 route advertisements on an intf."""
# Learn the default router's IP address via RAs
cmd = ['net.ipv6.conf.%s.accept_ra=2' % dev_name]
def configure_ipv6_ra(namespace, dev_name, value):
"""Configure handling of IPv6 Router Advertisements on an
interface. See common/constants.py for possible values.
"""
cmd = ['net.ipv6.conf.%(dev)s.accept_ra=%(value)s' % {'dev': dev_name,
'value': value}]
ip_lib.sysctl(cmd, namespace=namespace)
@abc.abstractmethod

View File

@ -126,6 +126,18 @@ IP_ALLOWED_VERSIONS = [lib_constants.IP_VERSION_4, lib_constants.IP_VERSION_6]
PORT_RANGE_MIN = 1
PORT_RANGE_MAX = 65535
# Configuration values for accept_ra sysctl, copied from linux kernel
# networking (netdev) tree, file Documentation/networking/ip-sysctl.txt
#
# Possible values are:
# 0 Do not accept Router Advertisements.
# 1 Accept Router Advertisements if forwarding is disabled.
# 2 Overrule forwarding behaviour. Accept Router Advertisements
# even if forwarding is enabled.
ACCEPT_RA_DISABLED = 0
ACCEPT_RA_WITHOUT_FORWARDING = 1
ACCEPT_RA_WITH_FORWARDING = 2
# Some components communicate using private address ranges, define
# them all here. These address ranges should not cause any issues
# even if they overlap since they are used in disjoint namespaces,

View File

@ -161,12 +161,18 @@ class DHCPAgentOVSTestFramework(base.BaseSudoTestCase):
iface_name = self.get_interface_name(network, port)
self.assertEqual(dhcp_enabled, ovs.port_exists(iface_name))
self.assert_dhcp_namespace(network.namespace, dhcp_enabled)
self.assert_accept_ra_disabled(network.namespace)
self.assert_dhcp_device(network.namespace, iface_name, dhcp_enabled)
def assert_dhcp_namespace(self, namespace, dhcp_enabled):
ip = ip_lib.IPWrapper()
self.assertEqual(dhcp_enabled, ip.netns.exists(namespace))
def assert_accept_ra_disabled(self, namespace):
actual = ip_lib.IPWrapper(namespace=namespace).netns.execute(
['sysctl', '-b', 'net.ipv6.conf.default.accept_ra'])
self.assertEqual('0', actual)
def assert_dhcp_device(self, namespace, dhcp_iface_name, dhcp_enabled):
dev = ip_lib.IPDevice(dhcp_iface_name, namespace)
self.assertEqual(dhcp_enabled, ip_lib.device_exists(

View File

@ -1444,7 +1444,8 @@ class TestDeviceManager(base.BaseTestCase):
'device_id': mock.ANY}})])
if port == fake_ipv6_port:
expected_ips = ['169.254.169.254/16']
expected_ips = ['2001:db8::a8bb:ccff:fedd:ee99/64',
'169.254.169.254/16']
else:
expected_ips = ['172.9.9.9/24', '169.254.169.254/16']
expected = [
@ -1456,6 +1457,9 @@ class TestDeviceManager(base.BaseTestCase):
if not device_is_ready:
expected.insert(1,
mock.call.configure_ipv6_ra(net.namespace,
'default', 0))
expected.insert(2,
mock.call.plug(net.id,
port.id,
'tap12345678-12',