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 <nranns@cisco.com>
diff --git a/test/test_dvr.py b/test/test_dvr.py
new file mode 100644
index 0000000..27522a5
--- /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 b799379..e670230 100644
--- a/test/vpp_ip_route.py
+++ b/test/vpp_ip_route.py
@@ -193,6 +193,8 @@
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 b6759ec..f9f54da 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -714,6 +714,7 @@
is_local=0,
is_classify=0,
is_multipath=0,
+ is_l2_bridged=0,
not_last=0):
"""
@@ -754,6 +755,7 @@
'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,