Add TC filtering for VXLAN traffic
This new function in neutron.agent.linux.tc_lib creates a TC filter in a device depending on the VXLAN ID (VNI) and the source MAC address (usually the VM TAP MAC address). This filter will send all the egress tunneled traffic from the VM to a TC class in other to shape it (QoS). Change-Id: Ic04b52bc0aca7a18fa06ea89e981c80d67f42eb1 Related-Bug: #1560963
This commit is contained in:
parent
dfc2586fb1
commit
5f99c79a50
|
@ -58,6 +58,17 @@ TC_QDISC_PARENT = {'root': rtnl.TC_H_ROOT,
|
|||
'ingress': rtnl.TC_H_INGRESS}
|
||||
TC_QDISC_PARENT_NAME = {v: k for k, v in TC_QDISC_PARENT.items()}
|
||||
|
||||
TC_CLASS_MAX_FLOWID = 0xffff
|
||||
|
||||
# NOTE(ralonsoh): VXLAN header: +28 bytes from the outer MAC header (TC
|
||||
# initial offset)
|
||||
# - VXLAN flags: 1 byte
|
||||
# - Reserved: 3 bytes
|
||||
# - VNI: 3 bytes --> VXLAN_VNI_OFFSET = 32 (+32 from the TC initial offset)
|
||||
# - Reserved: 1 byte
|
||||
VXLAN_INNER_SRC_MAC_OFFSET = 42
|
||||
VXLAN_VNI_OFFSET = 32
|
||||
|
||||
|
||||
class InvalidKernelHzValue(exceptions.NeutronException):
|
||||
message = _("Kernel HZ value %(value)s is not valid. This value must be "
|
||||
|
@ -469,6 +480,24 @@ def delete_tc_policy_class(device, parent, classid, namespace=None):
|
|||
namespace=namespace)
|
||||
|
||||
|
||||
def add_tc_filter_vxlan(device, parent, classid, src_mac, vxlan_id,
|
||||
namespace=None):
|
||||
"""Add a TC filter to match VXLAN traffic based on the VM mac and the VNI.
|
||||
|
||||
:param device: (string) device name
|
||||
:param parent: (string) qdisc parent class ('root', 'ingress', '2:10')
|
||||
:param classid: (string) major:minor handler identifier ('10:20')
|
||||
:param src_mac: (string) source MAC address to match (VM mac)
|
||||
:param vxlan_id: (int) VXLAN ID (VNI)
|
||||
:param namespace: (string) (optional) namespace name
|
||||
"""
|
||||
keys = [hex(int(vxlan_id << 8)) + '/0xffffff00+' + str(VXLAN_VNI_OFFSET)]
|
||||
keys += [key['key'] for key in
|
||||
_mac_to_pyroute2_keys(src_mac, VXLAN_INNER_SRC_MAC_OFFSET)]
|
||||
priv_tc_lib.add_tc_filter_match32(device, parent, 1, classid, keys,
|
||||
namespace=namespace)
|
||||
|
||||
|
||||
def add_tc_filter_match_mac(device, parent, classid, mac, offset=0, priority=0,
|
||||
protocol=None, namespace=None):
|
||||
"""Add a TC filter in a device to match a MAC address.
|
||||
|
|
|
@ -13,9 +13,12 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import random
|
||||
|
||||
import netaddr
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.agent.linux import bridge_lib
|
||||
from neutron.agent.linux import ip_lib
|
||||
from neutron.agent.linux import tc_lib
|
||||
from neutron.privileged.agent.linux import ip_lib as priv_ip_lib
|
||||
|
@ -135,3 +138,104 @@ class TcPolicyClassTestCase(functional_base.BaseSudoTestCase):
|
|||
namespace=self.ns[0])
|
||||
self.assertGreater(tc_classes[0]['stats']['bytes'], bytes)
|
||||
self.assertGreater(tc_classes[0]['stats']['packets'], packets)
|
||||
|
||||
|
||||
class TcFiltersTestCase(functional_base.BaseSudoTestCase):
|
||||
|
||||
def _remove_ns(self, namespace):
|
||||
priv_ip_lib.remove_netns(namespace)
|
||||
|
||||
def _create_two_namespaces_connected_using_vxlan(self):
|
||||
"""Create two namespaces connected with a veth pair and VXLAN
|
||||
|
||||
--------------------------------- ----------------------------------
|
||||
(ns1) | | (ns2)
|
||||
int1: 10.0.100.1/24 <-----------|----|------------> int2: 10.0.100.2/24
|
||||
| | | |
|
||||
|> int1_vxlan1: 10.0.200.1/24 | | int1_vxlan2: 10.0.200.2/24 <|
|
||||
--------------------------------- ----------------------------------
|
||||
"""
|
||||
self.vxlan_id = 100
|
||||
self.ns = ['ns1_' + uuidutils.generate_uuid(),
|
||||
'ns2_' + uuidutils.generate_uuid()]
|
||||
self.device = ['int1', 'int2']
|
||||
self.device_vxlan = ['int_vxlan1', 'int_vxlan2']
|
||||
self.mac_vxlan = []
|
||||
self.ip = ['10.100.0.1/24', '10.100.0.2/24']
|
||||
self.ip_vxlan = ['10.200.0.1/24', '10.200.0.2/24']
|
||||
for i in range(len(self.ns)):
|
||||
priv_ip_lib.create_netns(self.ns[i])
|
||||
self.addCleanup(self._remove_ns, self.ns[i])
|
||||
ip_wrapper = ip_lib.IPWrapper(self.ns[i])
|
||||
if i == 0:
|
||||
ip_wrapper.add_veth(self.device[0], self.device[1], self.ns[1])
|
||||
ip_wrapper.add_vxlan(self.device_vxlan[i], self.vxlan_id,
|
||||
dev=self.device[i])
|
||||
ip_device = ip_lib.IPDevice(self.device[i], self.ns[i])
|
||||
ip_device.link.set_up()
|
||||
ip_device.addr.add(self.ip[i])
|
||||
ip_device_vxlan = ip_lib.IPDevice(self.device_vxlan[i], self.ns[i])
|
||||
self.mac_vxlan.append(ip_device_vxlan.link.address)
|
||||
ip_device_vxlan.link.set_up()
|
||||
ip_device_vxlan.addr.add(self.ip_vxlan[i])
|
||||
|
||||
bridge_lib.FdbInterface.append(
|
||||
'00:00:00:00:00:00', self.device_vxlan[0], namespace=self.ns[0],
|
||||
ip_dst=str(netaddr.IPNetwork(self.ip[1]).ip))
|
||||
bridge_lib.FdbInterface.append(
|
||||
'00:00:00:00:00:00', self.device_vxlan[1], namespace=self.ns[1],
|
||||
ip_dst=str(netaddr.IPNetwork(self.ip[0]).ip))
|
||||
|
||||
def test_add_tc_filter_vxlan(self):
|
||||
# The traffic control is applied on the veth pair device of the first
|
||||
# namespace (self.ns[0]). The traffic created from the VXLAN interface
|
||||
# when replying to the ping (sent from the other namespace), is
|
||||
# encapsulated in a VXLAN frame and goes through the veth pair
|
||||
# interface.
|
||||
self._create_two_namespaces_connected_using_vxlan()
|
||||
|
||||
tc_lib.add_tc_qdisc(self.device[0], 'htb', parent='root', handle='1:',
|
||||
namespace=self.ns[0])
|
||||
classes = tc_lib.list_tc_policy_class(self.device[0],
|
||||
namespace=self.ns[0])
|
||||
self.assertEqual(0, len(classes))
|
||||
|
||||
class_ids = []
|
||||
for i in range(1, 10):
|
||||
class_id = '1:%s' % i
|
||||
class_ids.append(class_id)
|
||||
tc_lib.add_tc_policy_class(
|
||||
self.device[0], '1:', class_id, namespace=self.ns[0],
|
||||
min_kbps=1000, max_kbps=2000, burst_kb=1600)
|
||||
|
||||
# Add a filter for a randomly chosen created class, in the first
|
||||
# namespace veth pair device, with the VXLAN MAC address. The traffic
|
||||
# from the VXLAN device must go through this chosen class.
|
||||
chosen_class_id = random.choice(class_ids)
|
||||
tc_lib.add_tc_filter_vxlan(
|
||||
self.device[0], '1:', chosen_class_id, self.mac_vxlan[0],
|
||||
self.vxlan_id, namespace=self.ns[0])
|
||||
|
||||
tc_classes = tc_lib.list_tc_policy_class(self.device[0],
|
||||
namespace=self.ns[0])
|
||||
for tc_class in (c for c in tc_classes if
|
||||
c['classid'] == chosen_class_id):
|
||||
bytes = tc_class['stats']['bytes']
|
||||
packets = tc_class['stats']['packets']
|
||||
break
|
||||
else:
|
||||
self.fail('TC class %(class_id)s is not present in the device '
|
||||
'%(device)s' % {'class_id': chosen_class_id,
|
||||
'device': self.device[0]})
|
||||
|
||||
net_helpers.assert_ping(
|
||||
self.ns[1], netaddr.IPNetwork(self.ip_vxlan[0]).ip, count=1)
|
||||
tc_classes = tc_lib.list_tc_policy_class(self.device[0],
|
||||
namespace=self.ns[0])
|
||||
for tc_class in tc_classes:
|
||||
if tc_class['classid'] == chosen_class_id:
|
||||
self.assertGreater(tc_class['stats']['bytes'], bytes)
|
||||
self.assertGreater(tc_class['stats']['packets'], packets)
|
||||
else:
|
||||
self.assertEqual(0, tc_class['stats']['bytes'])
|
||||
self.assertEqual(0, tc_class['stats']['packets'])
|
||||
|
|
|
@ -388,3 +388,12 @@ class TcFilterTestCase(base.BaseTestCase):
|
|||
'key': '0x89ab0000/0xffff0000+14'}
|
||||
self.assertEqual(high, keys[0])
|
||||
self.assertEqual(low, keys[1])
|
||||
|
||||
@mock.patch.object(priv_tc_lib, 'add_tc_filter_match32')
|
||||
def test_add_tc_filter_vxlan(self, mock_add_filter):
|
||||
tc_lib.add_tc_filter_vxlan('device', 'parent', 'classid',
|
||||
'12:34:56:78:90:ab', 52, namespace='ns')
|
||||
keys = ['0x3400/0xffffff00+32', '0x12345678/0xffffffff+42',
|
||||
'0x90ab0000/0xffff0000+46']
|
||||
mock_add_filter.assert_called_once_with(
|
||||
'device', 'parent', 1, 'classid', keys, namespace='ns')
|
||||
|
|
Loading…
Reference in New Issue