Merge "Prevent binding IPv6 addresses to Neutron interfaces" into stable/liberty
commit
1c51a0a9e6
|
@ -16,12 +16,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.i18n import _LE
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BridgeDevice(ip_lib.IPDevice):
|
||||
|
@ -30,32 +25,6 @@ class BridgeDevice(ip_lib.IPDevice):
|
|||
ip_wrapper = ip_lib.IPWrapper(self.namespace)
|
||||
return ip_wrapper.netns.execute(cmd, run_as_root=True)
|
||||
|
||||
def _sysctl(self, cmd):
|
||||
"""execute() doesn't return the exit status of the command it runs,
|
||||
it returns stdout and stderr. Setting check_exit_code=True will cause
|
||||
it to raise a RuntimeError if the exit status of the command is
|
||||
non-zero, which in sysctl's case is an error. So we're normalizing
|
||||
that into zero (success) and one (failure) here to mimic what
|
||||
"echo $?" in a shell would be.
|
||||
|
||||
This is all because sysctl is too verbose and prints the value you
|
||||
just set on success, unlike most other utilities that print nothing.
|
||||
|
||||
execute() will have dumped a message to the logs with the actual
|
||||
output on failure, so it's not lost, and we don't need to print it
|
||||
here.
|
||||
"""
|
||||
cmd = ['sysctl', '-w'] + cmd
|
||||
ip_wrapper = ip_lib.IPWrapper(self.namespace)
|
||||
try:
|
||||
ip_wrapper.netns.execute(cmd, run_as_root=True,
|
||||
check_exit_code=True)
|
||||
except RuntimeError:
|
||||
LOG.exception(_LE("Failed running %s"), cmd)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def addbr(cls, name, namespace=None):
|
||||
bridge = cls(name, namespace)
|
||||
|
@ -76,7 +45,3 @@ class BridgeDevice(ip_lib.IPDevice):
|
|||
|
||||
def disable_stp(self):
|
||||
return self._brctl(['stp', self.name, 'off'])
|
||||
|
||||
def disable_ipv6(self):
|
||||
cmd = 'net.ipv6.conf.%s.disable_ipv6=1' % self.name
|
||||
return self._sysctl([cmd])
|
||||
|
|
|
@ -328,6 +328,7 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
|
|||
root_dev, ns_dev = ip.add_veth(tap_name,
|
||||
device_name,
|
||||
namespace2=namespace)
|
||||
root_dev.disable_ipv6()
|
||||
else:
|
||||
ns_dev = ip.device(device_name)
|
||||
|
||||
|
@ -435,6 +436,7 @@ class IVSInterfaceDriver(LinuxInterfaceDriver):
|
|||
tap_name = self._get_tap_name(device_name, prefix)
|
||||
|
||||
root_dev, ns_dev = ip.add_veth(tap_name, device_name)
|
||||
root_dev.disable_ipv6()
|
||||
|
||||
self._ivs_add_port(tap_name, port_id, mac_address)
|
||||
|
||||
|
@ -482,6 +484,7 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver):
|
|||
# Create ns_veth in a namespace if one is configured.
|
||||
root_veth, ns_veth = ip.add_veth(tap_name, device_name,
|
||||
namespace2=namespace)
|
||||
root_veth.disable_ipv6()
|
||||
ns_veth.link.set_address(mac_address)
|
||||
|
||||
if self.conf.network_device_mtu:
|
||||
|
|
|
@ -203,6 +203,12 @@ class IPWrapper(SubProcessBase):
|
|||
if self.namespace:
|
||||
device.link.set_netns(self.namespace)
|
||||
|
||||
def add_vlan(self, name, physical_interface, vlan_id):
|
||||
cmd = ['add', 'link', physical_interface, 'name', name,
|
||||
'type', 'vlan', 'id', vlan_id]
|
||||
self._as_root([], 'link', cmd)
|
||||
return IPDevice(name, namespace=self.namespace)
|
||||
|
||||
def add_vxlan(self, name, vni, group=None, dev=None, ttl=None, tos=None,
|
||||
local=None, port=None, proxy=False):
|
||||
cmd = ['add', name, 'type', 'vxlan', 'id', vni]
|
||||
|
@ -284,6 +290,37 @@ class IPDevice(SubProcessBase):
|
|||
LOG.exception(_LE("Failed deleting egress connection state of"
|
||||
" floatingip %s"), ip_str)
|
||||
|
||||
def _sysctl(self, cmd):
|
||||
"""execute() doesn't return the exit status of the command it runs,
|
||||
it returns stdout and stderr. Setting check_exit_code=True will cause
|
||||
it to raise a RuntimeError if the exit status of the command is
|
||||
non-zero, which in sysctl's case is an error. So we're normalizing
|
||||
that into zero (success) and one (failure) here to mimic what
|
||||
"echo $?" in a shell would be.
|
||||
|
||||
This is all because sysctl is too verbose and prints the value you
|
||||
just set on success, unlike most other utilities that print nothing.
|
||||
|
||||
execute() will have dumped a message to the logs with the actual
|
||||
output on failure, so it's not lost, and we don't need to print it
|
||||
here.
|
||||
"""
|
||||
cmd = ['sysctl', '-w'] + cmd
|
||||
ip_wrapper = IPWrapper(self.namespace)
|
||||
try:
|
||||
ip_wrapper.netns.execute(cmd, run_as_root=True,
|
||||
check_exit_code=True)
|
||||
except RuntimeError:
|
||||
LOG.exception(_LE("Failed running %s"), cmd)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
def disable_ipv6(self):
|
||||
sysctl_name = re.sub(r'\.', '/', self.name)
|
||||
cmd = 'net.ipv6.conf.%s.disable_ipv6=1' % sysctl_name
|
||||
return self._sysctl([cmd])
|
||||
|
||||
|
||||
class IpCommandBase(object):
|
||||
COMMAND = ''
|
||||
|
@ -870,6 +907,14 @@ class IpNetnsCommand(IpCommandBase):
|
|||
return False
|
||||
|
||||
|
||||
def vlan_in_use(segmentation_id, namespace=None):
|
||||
"""Return True if VLAN ID is in use by an interface, else False."""
|
||||
ip_wrapper = IPWrapper(namespace=namespace)
|
||||
interfaces = ip_wrapper.netns.execute(["ip", "-d", "link", "list"],
|
||||
check_exit_code=True)
|
||||
return '802.1Q id %s ' % segmentation_id in interfaces
|
||||
|
||||
|
||||
def vxlan_in_use(segmentation_id, namespace=None):
|
||||
"""Return True if VXLAN VNID is in use by an interface, else False."""
|
||||
ip_wrapper = IPWrapper(namespace=namespace)
|
||||
|
|
|
@ -293,14 +293,19 @@ class LinuxBridgeManager(object):
|
|||
"%(physical_interface)s",
|
||||
{'interface': interface, 'vlan_id': vlan_id,
|
||||
'physical_interface': physical_interface})
|
||||
if utils.execute(['ip', 'link', 'add', 'link',
|
||||
physical_interface,
|
||||
'name', interface, 'type', 'vlan', 'id',
|
||||
vlan_id], run_as_root=True):
|
||||
return
|
||||
if utils.execute(['ip', 'link', 'set',
|
||||
interface, 'up'], run_as_root=True):
|
||||
return
|
||||
try:
|
||||
int_vlan = self.ip.add_vlan(interface, physical_interface,
|
||||
vlan_id)
|
||||
except RuntimeError:
|
||||
with excutils.save_and_reraise_exception() as ctxt:
|
||||
if ip_lib.vlan_in_use(vlan_id):
|
||||
ctxt.reraise = False
|
||||
LOG.error(_LE("Unable to create VLAN interface for "
|
||||
"VLAN ID %s because it is in use by "
|
||||
"another interface."), vlan_id)
|
||||
return
|
||||
int_vlan.disable_ipv6()
|
||||
int_vlan.link.set_up()
|
||||
LOG.debug("Done creating subinterface %s", interface)
|
||||
return interface
|
||||
|
||||
|
@ -334,6 +339,7 @@ class LinuxBridgeManager(object):
|
|||
"VNI %s because it is in use by another "
|
||||
"interface."), segmentation_id)
|
||||
return None
|
||||
int_vxlan.disable_ipv6()
|
||||
int_vxlan.link.set_up()
|
||||
LOG.debug("Done creating vxlan interface %s", interface)
|
||||
return interface
|
||||
|
|
|
@ -435,6 +435,17 @@ class TestIpWrapper(base.BaseTestCase):
|
|||
self.assertNotIn(mock.call().delete('ns'),
|
||||
ip_ns_cmd_cls.mock_calls)
|
||||
|
||||
def test_add_vlan(self):
|
||||
retval = ip_lib.IPWrapper().add_vlan('eth0.1', 'eth0', '1')
|
||||
self.assertIsInstance(retval, ip_lib.IPDevice)
|
||||
self.assertEqual(retval.name, 'eth0.1')
|
||||
self.execute.assert_called_once_with([], 'link',
|
||||
['add', 'link', 'eth0',
|
||||
'name', 'eth0.1',
|
||||
'type', 'vlan', 'id', '1'],
|
||||
run_as_root=True, namespace=None,
|
||||
log_fail_as_error=True)
|
||||
|
||||
def test_add_vxlan_valid_port_length(self):
|
||||
retval = ip_lib.IPWrapper().add_vxlan('vxlan0', 'vni0',
|
||||
group='group0',
|
||||
|
|
|
@ -52,6 +52,9 @@ class FakeIpDevice(object):
|
|||
def __init__(self):
|
||||
self.link = FakeIpLinkCommand()
|
||||
|
||||
def disable_ipv6(self):
|
||||
pass
|
||||
|
||||
|
||||
def get_linuxbridge_manager(bridge_mappings, interface_mappings):
|
||||
with mock.patch.object(ip_lib.IPWrapper, 'get_device_by_ip',
|
||||
|
@ -729,14 +732,14 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||
de_fn.return_value = True
|
||||
self.assertEqual(self.lbm.ensure_vlan("eth0", "1"), "eth0.1")
|
||||
de_fn.return_value = False
|
||||
with mock.patch.object(utils, 'execute') as exec_fn:
|
||||
exec_fn.return_value = False
|
||||
self.assertEqual(self.lbm.ensure_vlan("eth0", "1"), "eth0.1")
|
||||
# FIXME(kevinbenton): validate the params to the exec_fn calls
|
||||
self.assertEqual(exec_fn.call_count, 2)
|
||||
exec_fn.return_value = True
|
||||
self.assertIsNone(self.lbm.ensure_vlan("eth0", "1"))
|
||||
self.assertEqual(exec_fn.call_count, 3)
|
||||
vlan_dev = FakeIpDevice()
|
||||
with mock.patch.object(vlan_dev, 'disable_ipv6') as dv6_fn,\
|
||||
mock.patch.object(self.lbm.ip, 'add_vlan',
|
||||
return_value=vlan_dev) as add_vlan_fn:
|
||||
retval = self.lbm.ensure_vlan("eth0", "1")
|
||||
self.assertEqual("eth0.1", retval)
|
||||
add_vlan_fn.assert_called_with('eth0.1', 'eth0', '1')
|
||||
dv6_fn.assert_called_once_with()
|
||||
|
||||
def test_ensure_vxlan(self):
|
||||
seg_id = "12345678"
|
||||
|
@ -746,14 +749,16 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||
de_fn.return_value = True
|
||||
self.assertEqual(self.lbm.ensure_vxlan(seg_id), "vxlan-" + seg_id)
|
||||
de_fn.return_value = False
|
||||
with mock.patch.object(self.lbm.ip,
|
||||
'add_vxlan') as add_vxlan_fn:
|
||||
add_vxlan_fn.return_value = FakeIpDevice()
|
||||
self.assertEqual(self.lbm.ensure_vxlan(seg_id),
|
||||
"vxlan-" + seg_id)
|
||||
vxlan_dev = FakeIpDevice()
|
||||
with mock.patch.object(vxlan_dev, 'disable_ipv6') as dv6_fn,\
|
||||
mock.patch.object(self.lbm.ip, 'add_vxlan',
|
||||
return_value=vxlan_dev) as add_vxlan_fn:
|
||||
retval = self.lbm.ensure_vxlan(seg_id)
|
||||
self.assertEqual("vxlan-" + seg_id, retval)
|
||||
add_vxlan_fn.assert_called_with("vxlan-" + seg_id, seg_id,
|
||||
group="224.0.0.1",
|
||||
dev=self.lbm.local_int)
|
||||
dv6_fn.assert_called_once_with()
|
||||
cfg.CONF.set_override('l2_population', 'True', 'VXLAN')
|
||||
self.assertEqual(self.lbm.ensure_vxlan(seg_id),
|
||||
"vxlan-" + seg_id)
|
||||
|
|
Loading…
Reference in New Issue