From 6f6311560380d0e992f710558e213df1b098ef94 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Tue, 3 Oct 2017 08:20:21 -0700 Subject: Distributed Virtual Router Support A distributed virtual router works by attmpeting to switch a packet, but on failing to find a local consumer (i.e. the packet is destined to a locally attached host) then the packet is sent unmodified 'upstream' to where the rest of the 'distributed' router is present. When L3 switching a packet this means the L2 header must not be modifed. This patch adds a 'l2-bridge' object to the L3 FIB which re-injects packets from the L3 path back into the L2 path - use with extreme caution. Change-Id: I069724eb45956647d7980cbe40a80a788ee6ee82 Signed-off-by: Neale Ranns --- test/test_dvr.py | 178 ++++++++++++++++++++++++++++++++++++++++++++++ test/vpp_ip_route.py | 2 + test/vpp_papi_provider.py | 2 + 3 files changed, 182 insertions(+) create mode 100644 test/test_dvr.py (limited to 'test') diff --git a/test/test_dvr.py b/test/test_dvr.py new file mode 100644 index 00000000000..27522a54eea --- /dev/null +++ b/test/test_dvr.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python +import random +import socket +import unittest + +from framework import VppTestCase, VppTestRunner +from vpp_sub_interface import VppSubInterface, VppDot1QSubint +from vpp_ip_route import VppIpRoute, VppRoutePath, DpoProto +from vpp_papi_provider import L2_VTR_OP + +from scapy.packet import Raw +from scapy.layers.l2 import Ether, Dot1Q, ARP +from scapy.layers.inet import IP, UDP +from util import ppp + + +class TestDVR(VppTestCase): + """ IPv4 Load-Balancing """ + + def setUp(self): + super(TestDVR, self).setUp() + + self.create_pg_interfaces(range(4)) + self.create_loopback_interfaces(range(1)) + + for i in self.pg_interfaces: + i.admin_up() + + self.loop0.config_ip4() + + def tearDown(self): + for i in self.pg_interfaces: + i.admin_down() + self.loop0.unconfig_ip4() + + super(TestDVR, self).tearDown() + + def test_dvr(self): + """ Distributed Virtual Router """ + + # + # A packet destined to an IP address that is L2 bridged via + # a non-tag interface + # + ip_non_tag_bridged = "10.10.10.10" + ip_tag_bridged = "10.10.10.11" + any_src_addr = "1.1.1.1" + + pkt_no_tag = (Ether(src=self.pg0.remote_mac, + dst=self.loop0.local_mac) / + IP(src=any_src_addr, + dst=ip_non_tag_bridged) / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + pkt_tag = (Ether(src=self.pg0.remote_mac, + dst=self.loop0.local_mac) / + IP(src=any_src_addr, + dst=ip_tag_bridged) / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + + # + # Two sub-interfaces so we can test VLAN tag push/pop + # + sub_if_on_pg2 = VppDot1QSubint(self, self.pg2, 92) + sub_if_on_pg3 = VppDot1QSubint(self, self.pg3, 93) + sub_if_on_pg2.admin_up() + sub_if_on_pg3.admin_up() + + # + # Put all the interfaces into a new bridge domain + # + self.vapi.sw_interface_set_l2_bridge(self.pg0.sw_if_index, 1) + self.vapi.sw_interface_set_l2_bridge(self.pg1.sw_if_index, 1) + self.vapi.sw_interface_set_l2_bridge(sub_if_on_pg2.sw_if_index, 1) + self.vapi.sw_interface_set_l2_bridge(sub_if_on_pg3.sw_if_index, 1) + self.vapi.sw_interface_set_l2_bridge(self.loop0.sw_if_index, 1, bvi=1) + + self.vapi.sw_interface_set_l2_tag_rewrite(sub_if_on_pg2.sw_if_index, + L2_VTR_OP.L2_POP_1, + 92) + self.vapi.sw_interface_set_l2_tag_rewrite(sub_if_on_pg3.sw_if_index, + L2_VTR_OP.L2_POP_1, + 93) + + self.logger.error(self.vapi.ppcli("show bridge-domain 1 detail")) + + # + # Add routes to bridge the traffic via a tagged an nontagged interface + # + route_no_tag = VppIpRoute( + self, ip_non_tag_bridged, 32, + [VppRoutePath("0.0.0.0", + self.pg1.sw_if_index, + proto=DpoProto.DPO_PROTO_ETHERNET)]) + route_no_tag.add_vpp_config() + + # + # Inject the packet that arrives and leaves on a non-tagged interface + # Since it's 'bridged' expect that the MAC headed is unchanged. + # + self.pg0.add_stream(pkt_no_tag) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + + self.assertEqual(rx[0][Ether].dst, pkt_no_tag[Ether].dst) + self.assertEqual(rx[0][Ether].src, pkt_no_tag[Ether].src) + + # + # Add routes to bridge the traffic via a tagged interface + # + route_no_tag = VppIpRoute( + self, ip_tag_bridged, 32, + [VppRoutePath("0.0.0.0", + sub_if_on_pg3.sw_if_index, + proto=DpoProto.DPO_PROTO_ETHERNET)]) + route_no_tag.add_vpp_config() + + # + # Inject the packet that arrives and leaves on a non-tagged interface + # Since it's 'bridged' expect that the MAC headed is unchanged. + # + self.pg0.add_stream(pkt_tag) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][Ether].dst, pkt_tag[Ether].dst) + self.assertEqual(rx[0][Ether].src, pkt_tag[Ether].src) + self.assertEqual(rx[0][Dot1Q].vlan, 93) + + # + # Tag to tag + # + pkt_tag_to_tag = (Ether(src=self.pg2.remote_mac, + dst=self.loop0.local_mac) / + Dot1Q(vlan=92) / + IP(src=any_src_addr, + dst=ip_tag_bridged) / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + + self.pg2.add_stream(pkt_tag_to_tag) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][Ether].dst, pkt_tag_to_tag[Ether].dst) + self.assertEqual(rx[0][Ether].src, pkt_tag_to_tag[Ether].src) + self.assertEqual(rx[0][Dot1Q].vlan, 93) + + # + # Tag to non-Tag + # + pkt_tag_to_non_tag = (Ether(src=self.pg2.remote_mac, + dst=self.loop0.local_mac) / + Dot1Q(vlan=92) / + IP(src=any_src_addr, + dst=ip_non_tag_bridged) / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + + self.pg2.add_stream(pkt_tag_to_non_tag) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg1.get_capture(1) + + self.assertEqual(rx[0][Ether].dst, pkt_tag_to_tag[Ether].dst) + self.assertEqual(rx[0][Ether].src, pkt_tag_to_tag[Ether].src) + self.assertFalse(rx[0].haslayer(Dot1Q)) + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_ip_route.py b/test/vpp_ip_route.py index b79937939c0..e670230487a 100644 --- a/test/vpp_ip_route.py +++ b/test/vpp_ip_route.py @@ -193,6 +193,8 @@ class VppIpRoute(VppObject): next_hop_via_label=path.nh_via_label, next_hop_table_id=path.nh_table_id, is_ipv6=self.is_ip6, + is_l2_bridged=1 + if path.proto == DpoProto.DPO_PROTO_ETHERNET else 0, is_resolve_host=path.is_resolve_host, is_resolve_attached=path.is_resolve_attached, is_multipath=1 if len(self.paths) > 1 else 0) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index b6759ec349e..f9f54da2606 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -714,6 +714,7 @@ class VppPapiProvider(object): is_local=0, is_classify=0, is_multipath=0, + is_l2_bridged=0, not_last=0): """ @@ -754,6 +755,7 @@ class VppPapiProvider(object): 'is_multipath': is_multipath, 'is_resolve_host': is_resolve_host, 'is_resolve_attached': is_resolve_attached, + 'is_l2_bridged': is_l2_bridged, 'not_last': not_last, 'next_hop_weight': next_hop_weight, 'dst_address_length': dst_address_length, -- cgit 1.2.3-korg