Prevent "qbr" Linux Bridge from replying to ARP messages

The Linux Bridge in between the VM TAP interface and OVS should [1][2]:
- Reply only if the target IP address is local address configured
  on the incoming interface.
- Always use the best local address.

[1]http://kb.linuxvirtualserver.org/wiki/Using_arp_announce/arp_ignore_to_disable_ARP
[2]http://linux-ip.net/html/ether-arp.html#ether-arp-flux

Change-Id: I8721b680bbd9f59a67bd8e6855ffb291c208cdb8
Closes-Bug: #1825888
(cherry picked from commit 9ad9b84839)
(cherry picked from commit c42e7641f0)
(cherry picked from commit ca9963c294)
This commit is contained in:
Rodolfo Alonso Hernandez 2019-04-24 07:09:21 +00:00 committed by Matt Riedemann
parent e09d87900a
commit 7b84b527ec
5 changed files with 64 additions and 4 deletions

View File

@ -0,0 +1,8 @@
---
security:
- |
Prevent Linux Bridge from replying to ARP messages. It should reply only if
the target IP address is a local address configured on the incoming
interface and it should always use the best local address. See `The ARP
flux problem <http://linux-ip.net/html/ether-arp.html#ether-arp-flux>`_ for
more information.

View File

@ -56,6 +56,22 @@ def _ip_bridge_cmd(action, params, device):
return cmd
# TODO(ralonsoh): extract into common module
def _arp_filtering(bridge):
"""Prevent the bridge from replying to ARP messages with machine local IPs
1. Reply only if the target IP address is local address configured on the
incoming interface.
2. Always use the best local address.
"""
arp_params = [('/proc/sys/net/ipv4/conf/%s/arp_ignore' % bridge, '1'),
('/proc/sys/net/ipv4/conf/%s/arp_announce' % bridge, '2')]
for parameter, value in arp_params:
if os.path.exists(parameter):
with open(parameter, 'w') as f:
f.write(value)
@privsep.vif_plug.entrypoint
def ensure_vlan_bridge(vlan_num, bridge, bridge_interface,
net_attrs=None, mac_address=None,
@ -141,6 +157,7 @@ def _ensure_bridge_privileged(bridge, interface, net_attrs, gateway,
# instead it inherits the MAC address of the first device on the
# bridge, which will either be the vlan interface, or a
# physical NIC.
_arp_filtering(bridge)
ip_lib.set(bridge, state='up')
if interface:

View File

@ -138,12 +138,13 @@ class LinuxNetTest(testtools.TestCase):
mock.call('fake-interface', state='up')]
mock_ip_set.assert_has_calls(calls)
@mock.patch.object(linux_net, "_arp_filtering")
@mock.patch.object(ip_lib, "set")
@mock.patch.object(os.path, "exists", return_value=False)
@mock.patch.object(processutils, "execute")
@mock.patch.object(linux_net, "device_exists", return_value=False)
def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_exec,
mock_path_exists, mock_ip_set):
mock_path_exists, mock_ip_set, *args):
linux_net.ensure_bridge("br0", None, filtering=False)
calls = [mock.call('brctl', 'addbr', 'br0'),
@ -153,12 +154,13 @@ class LinuxNetTest(testtools.TestCase):
mock_dev_exists.assert_called_once_with("br0")
mock_ip_set.assert_called_once_with('br0', state='up')
@mock.patch.object(linux_net, "_arp_filtering")
@mock.patch.object(ip_lib, "set")
@mock.patch.object(os.path, "exists", return_value=True)
@mock.patch.object(processutils, "execute")
@mock.patch.object(linux_net, "device_exists", return_value=False)
def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_exec,
mock_path_exists, mock_ip_set):
mock_path_exists, mock_ip_set, *args):
linux_net.ensure_bridge("br0", None, filtering=False)
calls = [mock.call('brctl', 'addbr', 'br0'),

View File

@ -83,6 +83,22 @@ def _create_ovs_bridge_cmd(bridge, datapath_type):
'--', 'set', 'Bridge', bridge, 'datapath_type=%s' % datapath_type]
# TODO(ralonsoh): extract into common module
def _arp_filtering(bridge):
"""Prevent the bridge from replying to ARP messages with machine local IPs
1. Reply only if the target IP address is local address configured on the
incoming interface.
2. Always use the best local address.
"""
arp_params = [('/proc/sys/net/ipv4/conf/%s/arp_ignore' % bridge, '1'),
('/proc/sys/net/ipv4/conf/%s/arp_announce' % bridge, '2')]
for parameter, value in arp_params:
if os.path.exists(parameter):
with open(parameter, 'w') as f:
f.write(value)
@privsep.vif_plug.entrypoint
def create_ovs_vif_port(bridge, dev, iface_id, mac, instance_id,
mtu=None, interface_type=None, timeout=None,
@ -173,6 +189,7 @@ def ensure_bridge(bridge):
disv6,
process_input='1',
check_exit_code=[0, 1])
_arp_filtering(bridge)
# we bring up the bridge to allow it to switch packets
set_interface_state(bridge, 'up')

View File

@ -17,6 +17,7 @@ import testtools
from os_vif.internal.command import ip as ip_lib
from oslo_concurrency import processutils
from six.moves import builtins
from vif_plug_ovs import constants
from vif_plug_ovs import exception
@ -40,12 +41,13 @@ class LinuxNetTest(testtools.TestCase):
check_exit_code=[0, 2, 254])
mock_dev_exists.assert_has_calls([mock.call("br0")])
@mock.patch.object(linux_net, "_arp_filtering")
@mock.patch.object(ip_lib, "set")
@mock.patch.object(os.path, "exists", return_value=False)
@mock.patch.object(processutils, "execute")
@mock.patch.object(linux_net, "device_exists", return_value=False)
def test_ensure_bridge_new_ipv4(self, mock_dev_exists, mock_execute,
mock_path_exists, mock_ip_set):
mock_path_exists, mock_ip_set, *args):
linux_net.ensure_bridge("br0")
calls = [
@ -61,12 +63,13 @@ class LinuxNetTest(testtools.TestCase):
mock_ip_set.assert_called_once_with('br0', state='up',
check_exit_code=[0, 2, 254])
@mock.patch.object(linux_net, "_arp_filtering")
@mock.patch.object(ip_lib, "set")
@mock.patch.object(os.path, "exists", return_value=True)
@mock.patch.object(processutils, "execute")
@mock.patch.object(linux_net, "device_exists", return_value=False)
def test_ensure_bridge_new_ipv6(self, mock_dev_exists, mock_execute,
mock_path_exists, mock_ip_set):
mock_path_exists, mock_ip_set, *args):
linux_net.ensure_bridge("br0")
calls = [
@ -625,3 +628,16 @@ class LinuxNetTest(testtools.TestCase):
linux_net.get_vf_num_by_pci_address,
'0000:00:00.1'
)
@mock.patch.object(os.path, 'exists', return_value=True)
@mock.patch.object(builtins, 'open')
def test__arp_filtering(self, mock_open, *args):
mock_open.side_effect = mock.mock_open(read_data=mock.Mock())
linux_net._arp_filtering("br0")
mock_open.assert_has_calls([
mock.call('/proc/sys/net/ipv4/conf/br0/arp_ignore', 'w'),
mock.call('/proc/sys/net/ipv4/conf/br0/arp_announce', 'w')])
mock_open.side_effect.return_value.write.assert_has_calls([
mock.call('1'),
mock.call('2')])