From f29de21350a3e341b1cb9ed25653f155fbba4e1a Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Tue, 16 Sep 2025 11:05:28 +0200 Subject: [PATCH] [ML2/OVS] Maintain learning OF rules for GARP/ND in br-tun This patch adds 3 new OF rules in the br-tun bridge to learn with higher priority GARP/ND packets which are sent during e.g. failover. With those rules, packets will not be send to the old VXLAN tunnels after failover as GARP/ND packets sent will force to learn new tunnels for the MAC address. Closes-bug: #2123911 Change-Id: I6e74214908e70e3a35af266dee524d6cf90bad42 Signed-off-by: Slawek Kaplonski --- .../agent/openflow/native/br_tun.py | 88 ++++++---- .../agent/openflow/native/test_br_tun.py | 156 +++++++++--------- 2 files changed, 134 insertions(+), 110 deletions(-) diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py index 3af64bd1012..55208b16fbc 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py @@ -15,8 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib import constants as lib_constants from neutron_lib.plugins.ml2 import ovs_constants as constants from os_ken.lib.packet import ether_types +from os_ken.lib.packet import icmpv6 +from os_ken.lib.packet import in_proto from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \ import br_dvr_process @@ -33,6 +36,62 @@ class OVSTunnelBridge(ovs_bridge.OVSAgentBridge, dvr_process_next_table_id = constants.PATCH_LV_TO_TUN of_tables = constants.TUN_BR_ALL_TABLES + def _setup_learn_flows(self, ofpp, patch_int_ofport): + flow_specs = [ + ofpp.NXFlowSpecMatch(src=('vlan_tci', 0), + dst=('vlan_tci', 0), + n_bits=12), + ofpp.NXFlowSpecMatch(src=('eth_src', 0), + dst=('eth_dst', 0), + n_bits=48), + ofpp.NXFlowSpecLoad(src=0, + dst=('vlan_tci', 0), + n_bits=16), + ofpp.NXFlowSpecLoad(src=('tunnel_id', 0), + dst=('tunnel_id', 0), + n_bits=64), + ofpp.NXFlowSpecOutput(src=('in_port', 0), + dst='', + n_bits=32), + ] + actions = [ + ofpp.NXActionLearn(table_id=constants.UCAST_TO_TUN, + cookie=self.default_cookie, + priority=1, + hard_timeout=300, + specs=flow_specs), + ofpp.OFPActionOutput(patch_int_ofport, 0), + ] + + arp_match = ofpp.OFPMatch( + eth_type=ether_types.ETH_TYPE_ARP, + arp_tha=lib_constants.BROADCAST_MAC + ) + ipv6_ra_match = ofpp.OFPMatch( + eth_type=ether_types.ETH_TYPE_IPV6, + ip_proto=in_proto.IPPROTO_ICMPV6, + icmpv6_type=icmpv6.ND_ROUTER_ADVERT) # icmp_type=134 + ipv6_na_match = ofpp.OFPMatch( + eth_type=ether_types.ETH_TYPE_IPV6, + ip_proto=in_proto.IPPROTO_ICMPV6, + icmpv6_type=icmpv6.ND_NEIGHBOR_ADVERT) # icmp_type=136 + + self.install_apply_actions(table_id=constants.LEARN_FROM_TUN, + priority=2, + match=arp_match, + actions=actions) + self.install_apply_actions(table_id=constants.LEARN_FROM_TUN, + priority=2, + match=ipv6_ra_match, + actions=actions) + self.install_apply_actions(table_id=constants.LEARN_FROM_TUN, + priority=2, + match=ipv6_na_match, + actions=actions) + self.install_apply_actions(table_id=constants.LEARN_FROM_TUN, + priority=1, + actions=actions) + def setup_default_table( self, patch_int_ofport, arp_responder_enabled, dvr_enabled): (dp, ofp, ofpp) = self._get_dp() @@ -81,34 +140,7 @@ class OVSTunnelBridge(ovs_bridge.OVSAgentBridge, # dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac # addresses (assumes that lvid has already been set by a previous flow) # Once remote mac addresses are learnt, output packet to patch_int - flow_specs = [ - ofpp.NXFlowSpecMatch(src=('vlan_tci', 0), - dst=('vlan_tci', 0), - n_bits=12), - ofpp.NXFlowSpecMatch(src=('eth_src', 0), - dst=('eth_dst', 0), - n_bits=48), - ofpp.NXFlowSpecLoad(src=0, - dst=('vlan_tci', 0), - n_bits=16), - ofpp.NXFlowSpecLoad(src=('tunnel_id', 0), - dst=('tunnel_id', 0), - n_bits=64), - ofpp.NXFlowSpecOutput(src=('in_port', 0), - dst='', - n_bits=32), - ] - actions = [ - ofpp.NXActionLearn(table_id=constants.UCAST_TO_TUN, - cookie=self.default_cookie, - priority=1, - hard_timeout=300, - specs=flow_specs), - ofpp.OFPActionOutput(patch_int_ofport, 0), - ] - self.install_apply_actions(table_id=constants.LEARN_FROM_TUN, - priority=1, - actions=actions) + self._setup_learn_flows(ofpp, patch_int_ofport) # Egress unicast will be handled in table UCAST_TO_TUN, where remote # mac addresses will be learned. For now, just add a default flow that diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py index 096721a93fd..6f1d49ff51e 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py @@ -16,6 +16,7 @@ from unittest import mock +from neutron_lib import constants as lib_constants from neutron_lib.plugins.ml2 import ovs_constants as ovs_const from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \ @@ -48,6 +49,73 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, self.setup_bridge_mock('br-tun', self.br_tun_cls) self.stamp = self.br.default_cookie + def _get_learn_flows(self, ofpp, patch_int_ofport): + (dp, ofp, ofpp) = self._get_dp() + # flows_data is list of tuples (priority, match) + flows_data = [ + (2, ofpp.OFPMatch( + eth_type=self.ether_types.ETH_TYPE_ARP, + arp_tha=lib_constants.BROADCAST_MAC + )), + (2, ofpp.OFPMatch( + eth_type=self.ether_types.ETH_TYPE_IPV6, + ip_proto=self.in_proto.IPPROTO_ICMPV6, + icmpv6_type=self.icmpv6.ND_ROUTER_ADVERT + )), + (2, ofpp.OFPMatch( + eth_type=self.ether_types.ETH_TYPE_IPV6, + ip_proto=self.in_proto.IPPROTO_ICMPV6, + icmpv6_type=self.icmpv6.ND_NEIGHBOR_ADVERT + )), + (1, ofpp.OFPMatch()) + ] + learn_flows = [] + for priority, match in flows_data: + learn_flows.append( + call._send_msg( + ofpp.OFPFlowMod( + dp, + cookie=self.stamp, + instructions=[ + ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [ + ofpp.NXActionLearn( + cookie=self.stamp, + hard_timeout=300, + priority=1, + specs=[ + ofpp.NXFlowSpecMatch( + dst=('vlan_tci', 0), + n_bits=12, + src=('vlan_tci', 0)), + ofpp.NXFlowSpecMatch( + dst=('eth_dst', 0), + n_bits=48, + src=('eth_src', 0)), + ofpp.NXFlowSpecLoad( + dst=('vlan_tci', 0), + n_bits=16, + src=0), + ofpp.NXFlowSpecLoad( + dst=('tunnel_id', 0), + n_bits=64, + src=('tunnel_id', 0)), + ofpp.NXFlowSpecOutput( + dst='', + n_bits=32, + src=('in_port', 0)), + ], + table_id=20), + ofpp.OFPActionOutput(patch_int_ofport, 0), + ]), + ], + match=match, + priority=priority, + table_id=10), + active_bundle=None + ) + ) + return learn_flows + def test_setup_default_table(self): patch_int_ofport = 5555 arp_responder_enabled = False @@ -110,47 +178,9 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, instructions=[], match=ofpp.OFPMatch(), priority=0, table_id=6), - active_bundle=None), - call._send_msg( - ofpp.OFPFlowMod( - dp, - cookie=self.stamp, - instructions=[ - ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [ - ofpp.NXActionLearn( - cookie=self.stamp, - hard_timeout=300, - priority=1, - specs=[ - ofpp.NXFlowSpecMatch( - dst=('vlan_tci', 0), - n_bits=12, - src=('vlan_tci', 0)), - ofpp.NXFlowSpecMatch( - dst=('eth_dst', 0), - n_bits=48, - src=('eth_src', 0)), - ofpp.NXFlowSpecLoad( - dst=('vlan_tci', 0), - n_bits=16, - src=0), - ofpp.NXFlowSpecLoad( - dst=('tunnel_id', 0), - n_bits=64, - src=('tunnel_id', 0)), - ofpp.NXFlowSpecOutput( - dst='', - n_bits=32, - src=('in_port', 0)), - ], - table_id=20), - ofpp.OFPActionOutput(patch_int_ofport, 0), - ]), - ], - match=ofpp.OFPMatch(), - priority=1, - table_id=10), - active_bundle=None), + active_bundle=None)] + expected += self._get_learn_flows(ofpp, patch_int_ofport) + expected += [ call._send_msg( ofpp.OFPFlowMod(dp, cookie=self.stamp, @@ -243,47 +273,9 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, instructions=[], match=ofpp.OFPMatch(), priority=0, table_id=6), - active_bundle=None), - call._send_msg( - ofpp.OFPFlowMod( - dp, - cookie=self.stamp, - instructions=[ - ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [ - ofpp.NXActionLearn( - cookie=self.stamp, - hard_timeout=300, - priority=1, - specs=[ - ofpp.NXFlowSpecMatch( - dst=('vlan_tci', 0), - n_bits=12, - src=('vlan_tci', 0)), - ofpp.NXFlowSpecMatch( - dst=('eth_dst', 0), - n_bits=48, - src=('eth_src', 0)), - ofpp.NXFlowSpecLoad( - dst=('vlan_tci', 0), - n_bits=16, - src=0), - ofpp.NXFlowSpecLoad( - dst=('tunnel_id', 0), - n_bits=64, - src=('tunnel_id', 0)), - ofpp.NXFlowSpecOutput( - dst='', - n_bits=32, - src=('in_port', 0)), - ], - table_id=20), - ofpp.OFPActionOutput(patch_int_ofport, 0), - ]), - ], - match=ofpp.OFPMatch(), - priority=1, - table_id=10), - active_bundle=None), + active_bundle=None)] + expected += self._get_learn_flows(ofpp, patch_int_ofport) + expected += [ call._send_msg( ofpp.OFPFlowMod(dp, cookie=self.stamp,