Notification time stamped 2021-03-01 07:12:10 UTC
From a102681dd6f669b3ec3a9e1c200749963dcf3300 Mon Sep 17 00:00:00 2001
From: Numan Siddique <numans(a)ovn.org>
Date: Mar 01 2021 07:11:55 +0000
Subject: Backport "tests: Eliminate most "sleep" calls."
Backport "tests: Add more checking to "3 HVs, 1 LS, 3 lports/HV"
test."
Backport "tests: Improve debuggability of tests."
Backport "Implement SCTP-specific reject() action."
Backport "Add sctp_abort logical flow action."
Backport "controller: Split mac learning code to a separate file."
Backport "MAC learning: Add a new FDB table in southbound db."
Backport "MAC learning: Add new actions - put_fdb, get_fdb and lookup_fdb."
Backport "controller: MAC learning: Add OF rules for the FDB entries."
Backport "northd: MAC learning: Add logical flows for fdb."
Backport "Fix the failing test case - ovn -- ACL skip hints for stateless
config."
Backport "northd: Cleanup stale FDB entries."
Backport "mac-learn: Fix build due to missing newline at EOF."
Backport "Properly handle hairpin traffic for VIPs with shared backends."
Backport "lflow: Avoid matching on conntrack original tuple if possible."
Backport "northd: Avoid matching on ct.dnat flags for load balancers."
Resolves: #1841009
Resolves: #1672625
Resovles: #1931599
Signed-off-by: Numan Siddique <numans(a)ovn.org>
---
diff --git a/0001-tests-Eliminate-most-sleep-calls.patch
b/0001-tests-Eliminate-most-sleep-calls.patch
new file mode 100644
index 0000000..e2e7e87
--- /dev/null
+++ b/0001-tests-Eliminate-most-sleep-calls.patch
@@ -0,0 +1,1132 @@
+From 1883c14bb0eb136f2339196218e5c107cf0f79aa Mon Sep 17 00:00:00 2001
+From: Ben Pfaff <blp(a)ovn.org>
+Date: Thu, 5 Nov 2020 16:37:35 -0800
+Subject: [PATCH 01/16] tests: Eliminate most "sleep" calls.
+
+Many of these could be replaced by "ovn-nbctl sync". Some weren't
+really needed at all because they were adjacent to something that itself
+called sync or otherwise used --wait. Some were more appropriately
+done with explicit waits for what was really needed.
+
+I left some "sleep"s. Some were because they were "negative"
sleeps:
+they were giving time for something to happen that should *not* happen
+(in other words, if you wait for it to happen, you'll wait forever,
+unless there's a bug). Some were because I didn't know how to properly
+wait for what they were waiting for, or because I didn't understand
+the circumstances deeply enough.
+
+Signed-off-by: Ben Pfaff <blp(a)ovn.org>
+Acked-by: Dumitru Ceara <dceara(a)redhat.com>
+(cherry picked from upstream commit 0c0a7a9216b0af73cc2a649d8d1e781767971f74)
+
+Change-Id: I055a7566c72f73abeaf7f18f038ade8e93d0ed3a
+---
+ tests/ovn-macros.at | 16 ++++
+ tests/ovn-northd.at | 11 ++-
+ tests/ovn.at | 259 ++++++++++++++++++++++++++--------------------------
+ 3 files changed, 155 insertions(+), 131 deletions(-)
+
+diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
+index 59e500c..2ba29a9 100644
+--- a/tests/ovn-macros.at
++++ b/tests/ovn-macros.at
+@@ -417,6 +417,22 @@ wait_column() {
+ echo "$column in $db table $table has value $found, from the following
rows:"
+ ovn-${db}ctl list $table])
+ }
++
++# wait_for_ports_up [PORT...]
++#
++# With arguments, waits for specified Logical_Switch_Ports to come up.
++# Without arguments, waits for all "plain" and router
++# Logical_Switch_Ports to come up.
++wait_for_ports_up() {
++ if test $# = 0; then
++ wait_row_count nb:Logical_Switch_Port 0 up!=true type='""'
++ wait_row_count nb:Logical_Switch_Port 0 up!=true type=router
++ else
++ for port; do
++ wait_row_count nb:Logical_Switch_Port 1 up=true name=$port
++ done
++ fi
++}
+ OVS_END_SHELL_HELPERS
+
+ m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])])
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index dc87ec9..b907673 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -849,7 +849,7 @@ uuid=$(fetch_column Port_Binding _uuid logical_port=cr-DR-S1)
+ echo "CR-LRP UUID is: " $uuid
+
+ check ovn-nbctl set Logical_Router $cr_uuid options:chassis=gw1
+-check ovn-nbctl --wait=hv sync
++check ovn-nbctl --wait=sb sync
+
+ ovn-nbctl create Address_Set name=allowed_range addresses=\"1.1.1.1\"
+ ovn-nbctl create Address_Set name=disallowed_range addresses=\"2.2.2.2\"
+@@ -1090,6 +1090,7 @@ ovn-nbctl --wait=sb -- --id=@hc create \
+ Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \
+ health_check @hc
+ wait_row_count Service_Monitor 2
++check ovn-nbctl --wait=sb sync
+
+ ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
+ AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
+@@ -1109,6 +1110,7 @@ OVS_WAIT_FOR_OUTPUT(
+ # Set the service monitor for sw1-p1 to offline
+ check ovn-sbctl set service_monitor sw1-p1 status=offline
+ wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=offline
++check ovn-nbctl --wait=sb sync
+
+ AT_CAPTURE_FILE([sbflows4])
+ OVS_WAIT_FOR_OUTPUT(
+@@ -1120,6 +1122,7 @@ OVS_WAIT_FOR_OUTPUT(
+ ovn-sbctl set service_monitor $sm_sw0_p1 status=offline
+
+ wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
++check ovn-nbctl --wait=sb sync
+
+ AT_CAPTURE_FILE([sbflows5])
+ OVS_WAIT_FOR_OUTPUT(
+@@ -1136,6 +1139,7 @@ ovn-sbctl set service_monitor $sm_sw0_p1 status=online
+ ovn-sbctl set service_monitor $sm_sw1_p1 status=online
+
+ wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online
++check ovn-nbctl --wait=sb sync
+
+ AT_CAPTURE_FILE([sbflows7])
+ OVS_WAIT_FOR_OUTPUT(
+@@ -1146,6 +1150,7 @@ OVS_WAIT_FOR_OUTPUT(
+ # Set the service monitor for sw1-p1 to error
+ ovn-sbctl set service_monitor $sm_sw1_p1 status=error
+ wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=error
++check ovn-nbctl --wait=sb sync
+
+ ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst ==
80" \
+ | grep priority=120 > lflows.txt
+@@ -1184,6 +1189,7 @@ OVS_WAIT_FOR_OUTPUT(
+ check ovn-sbctl set service_monitor sw1-p1 status=online
+
+ wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online
++check ovn-nbctl --wait=sb sync
+
+ AT_CAPTURE_FILE([sbflows10])
+ OVS_WAIT_FOR_OUTPUT(
+@@ -1214,6 +1220,7 @@ AT_CHECK([ovn-nbctl -- --id=@hc create Load_Balancer_Health_Check
vip="10.0.0.10
+
+ check ovn-nbctl ls-lb-add sw0 lb2
+ check ovn-nbctl ls-lb-add sw1 lb2
++check ovn-nbctl --wait=sb sync
+
+ wait_row_count Service_Monitor 5
+
+@@ -1729,7 +1736,7 @@ check ovn-nbctl pg-add pg0 sw0-p1 sw1-p1
+ check ovn-nbctl acl-add pg0 from-lport 1002 "inport == @pg0 && ip4
&& tcp && tcp.dst == 80" reject
+ check ovn-nbctl acl-add pg0 to-lport 1003 "outport == @pg0 && ip6
&& udp" reject
+
+-check ovn-nbctl --wait=hv sync
++check ovn-nbctl --wait=sb sync
+
+ AS_BOX([1])
+
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 6cb4a4c..47052d2 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -1878,16 +1878,13 @@ get_lsp_uuid () {
+ ovn-nbctl create Port_Group name=pg1 ports=`get_lsp_uuid lp22`,`get_lsp_uuid lp33`
+ ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport ==
@pg1' drop
+ check ovn-nbctl --wait=hv sync
++wait_for_ports_up
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+ # packets for ARP resolution (native tunneling doesn't queue packets
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
+-
+ # Make sure there is no attempt to adding duplicated flows by ovn-controller
+ AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
+ AT_FAIL_IF([test -n "`grep duplicate hv2/ovn-controller.log`"])
+@@ -2082,11 +2079,7 @@ done
+
+ # set address for lp13 with invalid characters.
+ # lp13 should be configured with only 192.168.0.13.
+-ovn-nbctl lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid
192.169.0.13"
+-
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++ovn-nbctl --wait=hv lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid
192.169.0.13"
+
+ sip=`ip_to_hex 192 168 0 11`
+ tip=`ip_to_hex 192 168 0 13`
+@@ -2159,7 +2152,11 @@ for i in 1 2; do
+ done
+ done
+
+-sleep 1
++# Wait for bindings to take effect.
++wait_row_count Port_Binding 1 logical_port=lp11 'encap!=[[]]'
++wait_row_count Port_Binding 1 logical_port=lp12 'encap!=[[]]'
++wait_row_count Port_Binding 1 logical_port=lp21 'encap!=[[]]'
++wait_row_count Port_Binding 1 logical_port=lp22 'encap!=[[]]'
+
+ # dump port bindings; since we have vxlan and geneve tunnels, we expect the
+ # ports to be bound to geneve tunnels.
+@@ -2179,9 +2176,8 @@ check_row_count Port_Binding 1 logical_port=lp22 encap=$encap_rec
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Make sure there is no attempt to adding duplicated flows by ovn-controller
+ AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
+@@ -2571,6 +2567,7 @@ for i in 1 2; do
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+ done
+ done
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+ ovn-sbctl dump-flows > sbflows
+ AT_CAPTURE_FILE([sbflows])
+@@ -2737,6 +2734,7 @@ for hv in 1 2; do
+ done
+
+
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+
+ ovn-sbctl dump-flows > sbflows
+@@ -2870,6 +2868,7 @@ for hv in 1 2; do
+ done
+
+
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+ ovn-nbctl show
+ ovn-sbctl dump-flows > sbflows
+@@ -3007,6 +3006,7 @@ for i in 1 2; do
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+ done
+
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+ ovn-nbctl show
+ ovn-sbctl dump-flows > sbflows
+@@ -3217,6 +3217,7 @@ for tag in 10 20; do
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
+ done
+ done
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+ ovn-sbctl dump-flows
+
+@@ -3375,9 +3376,8 @@ ovs-vsctl add-port br-phys vif3 -- set Interface vif3
options:tx_pcap=hv3/vif3-t
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # test_packet INPORT DST SRC ETHTYPE OUTPORT...
+ #
+@@ -3541,9 +3541,8 @@ ovs-vsctl add-port br-phys vif3 -- set Interface vif3
options:tx_pcap=hv3/vif3-t
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # test_packet INPORT DST SRC ETHTYPE OUTPORT...
+ #
+@@ -3732,6 +3731,7 @@ for i in 1 2 3; do
+ done
+ done
+
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -3739,9 +3739,6 @@ check ovn-nbctl --wait=hv sync
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-
+ # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+ #
+ # This shell function causes a packet to be received on INPORT. The packet's
+@@ -4138,8 +4135,8 @@ done
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+ #
+@@ -4311,8 +4308,8 @@ done
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Given the name of a logical port, prints the name of the hypervisor
+ # on which it is located.
+@@ -4744,8 +4741,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Packet to send.
+ packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac &&
eth.dst==$rp_ls1_mac &&
+@@ -4855,9 +4852,8 @@ ovs-vsctl -- add-port br-int vif2 -- \
+ ofport-request=1
+
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Send ip packets between the two ports.
+
+@@ -4889,11 +4885,7 @@ as hv1 ovs-ofctl dump-flows br-int
+
+
+ #Disable router R1
+-ovn-nbctl set Logical_Router R1 enabled=false
+-
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++ovn-nbctl --wait=hv set Logical_Router R1 enabled=false
+
+ echo "---------SB dump-----"
+ ovn-sbctl list datapath_binding
+@@ -4968,10 +4960,9 @@ ovs-vsctl -- add-port br-int vif2 -- \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=1
+
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
+
+ # Send ip packets between the two ports.
+
+@@ -5002,7 +4993,7 @@ echo "------ hv1 dump ----------"
+ as hv1 ovs-ofctl dump-flows br-int
+
+ #Disable router R1
+-ovn-nbctl set Logical_Router R1 enabled=false
++ovn-nbctl --wait=hv set Logical_Router R1 enabled=false
+
+ echo "---------SB dump-----"
+ ovn-sbctl list datapath_binding
+@@ -5013,9 +5004,6 @@ echo "---------------------"
+ echo "------ hv1 dump ----------"
+ as hv1 ovs-ofctl dump-flows br-int
+
+-# Allow some time for the disabling of logical router R1 to propagate.
+-# XXX This should be more systematic.
+-sleep 1
+
+ as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+
+@@ -5118,8 +5106,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Send ip packets between foo1 and alice1
+ src_mac="f00000010203"
+@@ -5337,8 +5325,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Send ip packets between foo1 and alice1
+ src_mac="f00000010203"
+@@ -5473,7 +5461,8 @@ as hv1 ovs-appctl vlog/set dbg
+
+ OVN_POPULATE_ARP
+
+-sleep 2
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ as hv1 ovs-vsctl show
+
+@@ -6193,7 +6182,8 @@ ovs-vsctl -- add-port br-int hv1-vif5 -- \
+
+ OVN_POPULATE_ARP
+
+-sleep 2
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+@@ -6473,10 +6463,8 @@ ovn-nbctl lsp-add foo foo1 \
+ ovn-nbctl lsp-add alice alice1 \
+ -- lsp-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2"
+
+-
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 2
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Send ip packets between foo1 and alice1
+ src_mac="f00000010203"
+@@ -6539,7 +6527,8 @@ ip_prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \
+ R2 static_routes @lrt
+
+ # Wait for ovn-controller to catch up.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Send the packet again.
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+@@ -6609,10 +6598,9 @@ ovs-vsctl -- add-port br-int vif2 -- \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+ ofport-request=1
+
+-
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+
+ for i in 1 2; do
+@@ -6768,10 +6756,6 @@ ovs-vsctl -- add-port br-int vif3 -- \
+ options:rxq_pcap=pbr-hv/vif3-rx.pcap \
+ ofport-request=1
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
+-
+ ls1_ro_mac=00:00:00:01:02:f1
+ ls1_ro_ip=192.168.1.1
+
+@@ -6956,10 +6940,6 @@ ovs-vsctl -- add-port br-int vif3 -- \
+ options:rxq_pcap=pbr-hv/vif3-rx.pcap \
+ ofport-request=1
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
+-
+ ls1_ro_mac=00:00:00:01:02:f1
+ ls1_ro_ip=2001::1
+
+@@ -7162,6 +7142,7 @@ ovn-nbctl lsp-del lp1
+ ovn-nbctl ls-del ls1
+
+ # wait for earlier changes to take effect
++wait_for_ports_up
+ check ovn-nbctl --wait=sb sync
+
+ # ensure OF rules are no longer present. There used to be a bug here.
+@@ -7208,14 +7189,12 @@ ovn-nbctl acl-add lsw0 to-lport 1002 'outport ==
"lp1" && ip6 && icmp6' allow-r
+ ovn-nbctl acl-add lsw0 to-lport 1002 'outport == "lp2" && ip6
&& icmp6' allow-related
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
++# XXX The "sleep" here seems to be essential for ovn-northd-ddlog,
++# which may indicate that it needs improvement.
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+ sleep 1
+
+-# Given the name of a logical port, prints the name of the hypervisor
+-# on which it is located.
+-vif_to_hv() {
+- echo hv1${1%?}
+-}
+ for i in 1 2; do
+ : > $i.expected
+ done
+@@ -7254,9 +7233,7 @@ ovn_attach n1 br-phys 192.168.0.1
+
+ row=`ovn-nbctl create Address_Set name=set1 addresses=\"1.1.1.1\"`
+ ovn-nbctl set Address_Set $row name=set1 addresses=\"1.1.1.1,1.1.1.2\"
+-ovn-nbctl destroy Address_Set $row
+-
+-sleep 1
++ovn-nbctl --wait=hv destroy Address_Set $row
+
+ # A bug previously existed in the address set support code
+ # that caused ovn-controller to crash after an address set
+@@ -7644,8 +7621,8 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ ofport-request=3
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Send ip packets between foo1 and foo2
+ src_mac="0a0000a80103"
+@@ -7852,8 +7829,8 @@ ovs-vsctl -- add-port br-int hv1-ls2lp2 -- \
+ ofport-request=2
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ echo "---------NB dump-----"
+ ovn-nbctl show
+@@ -8323,6 +8300,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2
+ ovn-nbctl lsp-set-addresses lp1 $lp1_mac
+ ovn-nbctl lsp-set-addresses lp2 $lp2_mac
+ ovn-nbctl --wait=sb sync
++wait_for_ports_up
+
+ ovn-nbctl acl-add lsw0 to-lport 1000 'tcp.dst==80' drop
+ ovn-nbctl --log --severity=alert --name=drop-flow acl-add lsw0 to-lport 1000
'tcp.dst==81' drop
+@@ -8429,6 +8407,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2
+ ovn-nbctl lsp-set-addresses lp1 $lp1_mac
+ ovn-nbctl lsp-set-addresses lp2 $lp2_mac
+ ovn-nbctl --wait=sb sync
++wait_for_ports_up
+
+
+ # Add an ACL that rate-limits logs at 10 per second.
+@@ -8519,6 +8498,7 @@ ovn-nbctl --wait=sb lsp-add lsw0 lp2
+ ovn-nbctl lsp-set-addresses lp1 $lp1_mac
+ ovn-nbctl lsp-set-addresses lp2 $lp2_mac
+ ovn-nbctl --wait=sb sync
++wait_for_ports_up
+
+ ovn-appctl -t ovn-controller vlog/set file:dbg
+
+@@ -8566,6 +8546,7 @@ check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+ check ovs-vsctl add-port br-int vif1 -- set Interface vif1 external-ids:iface-id=lp1
options:tx_pcap=vif1-tx.pcap options:rxq_pcap=vif1-rx.pcap ofport-request=1
+ check ovs-vsctl add-port br-int vif2 -- set Interface vif2 external-ids:iface-id=lp2
options:tx_pcap=vif2-tx.pcap options:rxq_pcap=vif2-rx.pcap ofport-request=2
++wait_for_ports_up lp1 lp2
+
+ AT_CAPTURE_FILE([trace])
+ ovn_trace () {
+@@ -8964,8 +8945,8 @@ ovs-vsctl -- add-port br-int vm2 -- \
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Test that ovn-controllers create ct-zone entry for container ports.
+ foo1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-foo1)
+@@ -8990,6 +8971,7 @@ bar2_zoneid=$(as hv2 ovs-vsctl get bridge br-int
external_ids:ct-zone-bar2)
+ AT_CHECK([test -z $bar2_zoneid])
+
+ # Add back bar2
++wait_for_ports_up
+ ovn-nbctl lsp-add bar bar2 vm2 1 \
+ -- lsp-set-addresses bar2 "f0:00:00:01:02:08 192.168.2.3"
+ wait_for_ports_up
+@@ -9279,8 +9261,8 @@ ovn-nbctl --wait=hv lsp-add bob bob1 \
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+@@ -9387,7 +9369,8 @@ ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ ofport-request=2
+
+ OVN_POPULATE_ARP
+-sleep 2
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+ as hv1 ovs-vsctl show
+
+ echo "*************************"
+@@ -9880,6 +9863,7 @@ check as gw1 ovs-vsctl set open .
external-ids:ovn-bridge-mappings=phys:br-phys
+ check as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ check as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
++wait_for_ports_up
+ AT_CHECK([ovn-nbctl --wait=sb sync], [0], [ignore])
+
+ ovn-sbctl dump-flows > sbflows
+@@ -9947,13 +9931,9 @@ test_ip_packet()
+ fi
+ as ext1 reset_pcap_file ext1-vif1 ext1/vif1
+
+- sleep 1
+-
+ # Resend packet from foo1 to outside1
+ check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+- sleep 1
+-
+ AT_CAPTURE_FILE([exp])
+ AT_CAPTURE_FILE([rcv])
+ check_packets() {
+@@ -10143,6 +10123,7 @@ as gw1 ovs-vsctl set open .
external-ids:ovn-bridge-mappings=phys:br-phys
+ as gw2 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ as ext1 ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
++wait_for_ports_up
+ check ovn-nbctl --wait=sb sync
+
+ ovn-sbctl dump-flows > sbflows
+@@ -10155,8 +10136,7 @@ hv1_ch_uuid=$(fetch_column Chassis _uuid name=hv1)
+ wait_column "$hv1_ch_uuid" HA_Chassis_Group ref_chassis
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 2
++check ovn-nbctl --wait=hv sync
+
+ reset_pcap_file() {
+ local iface=$1
+@@ -10354,6 +10334,7 @@ check as hv3 ovs-vsctl set open .
external-ids:ovn-bridge-mappings=phys:br-phys
+
+
+ dnl Allow some time for ovn-northd and ovn-controller to catch up.
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ (echo "---------NB dump-----"
+@@ -10473,6 +10454,7 @@ OVS_WAIT_UNTIL([test 1 = `as hv2 ovs-vsctl show | \
+ grep "Port patch-br-int-to-ln-alice" | wc -l`])
+
+ dnl Allow some time for ovn-controller to catch up.
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ # ARP for router IP address from outside1
+@@ -10546,8 +10528,8 @@ ovn-nbctl lsp-add foo foo2 \
+ -- lsp-set-addresses foo2 "f0:00:00:01:02:06 192.168.1.3"
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ : > hv1-vif2.expected
+
+@@ -10636,10 +10618,6 @@ AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
+ AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
+ AT_CHECK([ovn-nbctl --wait=hv lsp-set-options ln_port network_name=physnet1])
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 2
+-
+ # Expect no packets when hv2 bridge-mapping is not present
+ : > packets
+ OVN_CHECK_PACKETS([hv1/snoopvif-tx.pcap], [packets])
+@@ -10859,16 +10837,16 @@ ovn-nbctl lsp-set-addresses ln-outside unknown
+ ovn-nbctl lsp-set-type ln-outside localnet
+ ovn-nbctl lsp-set-options ln-outside network_name=phys
+
+-# Allow some time for ovn-northd and ovn-controller to catch up.
+-check ovn-nbctl --wait=hv sync
+-
+ # Check that there is a logical flow in logical switch foo's pipeline
+ # to set the outport to rp-foo (which is expected).
+ OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \
+ grep rp-foo | grep -v is_chassis_resident | grep priority=50 -c`])
+
+ # Set the option 'reside-on-redirect-chassis' for foo
+-check ovn-nbctl --wait=hv set logical_router_port foo
options:reside-on-redirect-chassis=true
++check ovn-nbctl set logical_router_port foo options:reside-on-redirect-chassis=true
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
++
+ # Check that there is a logical flow in logical switch foo's pipeline
+ # to set the outport to rp-foo with the condition is_chassis_redirect.
+ ovn-sbctl dump-flows foo
+@@ -11095,8 +11073,8 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+ ofport-request=3
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ reset_pcap_file() {
+ local iface=$1
+@@ -11349,8 +11327,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Send ip packets between foo1 and alice1
+ src_mac="f00000010203"
+@@ -11414,6 +11392,7 @@ for i in 1 2; do
+ OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp${i}1` = xup])
+ done
+
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+ ovn-sbctl dump-flows
+
+@@ -11565,6 +11544,7 @@ ovn-nbctl lsp-set-type ln-outside localnet
+ ovn-nbctl lsp-set-options ln-outside network_name=phys
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ echo "---------NB dump-----"
+@@ -11688,6 +11668,7 @@ ovn-nbctl --id=@gc0 create Gateway_Chassis \
+ set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]'
+
+
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # we make sure that the hypervisors noticed, and inverted the slave ports
+@@ -12053,6 +12034,7 @@ AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
+ AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
+
+ # wait for earlier changes to take effect
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ reset_pcap_file() {
+@@ -12253,6 +12235,7 @@ ovn-nbctl lsp-set-type ln-outside localnet
+ ovn-nbctl lsp-set-options ln-outside network_name=phys
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # currently when ovn-controller is restarted, the old entry is deleted
+@@ -12916,8 +12899,6 @@ for i in 1 2 3; do
+ done
+
+ OVN_POPULATE_ARP
+-# allow some time for ovn-northd and ovn-controller to catch up.
+-sleep 1
+
+ for i in 1 2 3; do
+ : > vif${i}1.expected
+@@ -12928,6 +12909,7 @@ check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport ==
\"sw0-p11\"" reject
+ check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport ==
\"sw0-p21\"" reject
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ ovn-sbctl dump-flows > sbflows
+@@ -13074,8 +13056,8 @@ done
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
+ #
+@@ -13154,10 +13136,6 @@ for is in 1 2 3; do
+ done
+ done
+
+-# Allow some time for packet forwarding.
+-# XXX This can be improved.
+-sleep 1
+-
+ # Now check the packets actually received against the ones expected.
+ for i in 1 2 3; do
+ for j in 1 2 3; do
+@@ -13296,8 +13274,8 @@ done
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ lsp_to_mac() {
+ echo f0:00:00:00:0${1:0:1}:${1:1:2}
+@@ -13403,10 +13381,6 @@ for is in 1 2 3; do
+ done
+ done
+
+-# Allow some time for packet forwarding.
+-# XXX This can be improved.
+-sleep 1
+-
+ # Now check the packets actually received against the ones expected.
+ for i in 1 2 3; do
+ for j in 1 2 3; do
+@@ -13716,6 +13690,7 @@ grep conjunction.*conjunction.*conjunction | wc -l`])
+ ovn-nbctl acl-del ls1 to-lport 1001 \
+ 'ip4 && ip4.src == $set1 && ip4.dst == $set1'
+
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+ # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.10 actions=conjunction(10,1/2)
+ # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.8 actions=conjunction(11,1/2)
+@@ -13798,6 +13773,7 @@ ovn-nbctl acl-add ls1 to-lport 2 'arp' allow
+ ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop
+ ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.2)
&& (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
+ ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.42)
&& (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
+@@ -13898,8 +13874,6 @@ reset_pcap_file hv1-vif2 hv1/vif2
+ rm -f 2.packets
+ > 2.expected
+
+-#sleep infinity
+-
+ # Remove the first less restrictive allow ACL.
+ ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
+ ovn-nbctl --wait=hv sync
+@@ -14046,8 +14020,8 @@ ovn-nbctl create Address_Set name=set1
addresses=\"f0:00:00:00:00:11\",\"f0:00:0
+ OVN_POPULATE_ARP
+
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+-# XXX This should be more systematic.
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ # Make sure there is no attempt to adding duplicated flows by ovn-controller
+ AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"])
+@@ -14287,6 +14261,7 @@ done
+
+ OVN_POPULATE_ARP
+ # allow some time for ovn-northd and ovn-controller to catch up.
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192
168 2 1) $(ip_to_hex 192 168 1 254) 0000 f87c ea96
+@@ -14428,6 +14403,7 @@ done
+
+ OVN_POPULATE_ARP
+ # allow some time for ovn-northd and ovn-controller to catch up.
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192
168 1 254) 11 0000 f87c f485 0303
+@@ -14502,7 +14478,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+
+ OVN_POPULATE_ARP
+
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac &&
eth.dst==$sw1_ro_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip &&
ip4.dst==$sw2_p1_ip &&
+@@ -15427,7 +15404,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+
+ OVN_POPULATE_ARP
+
+-sleep 1
++wait_for_ports_up
++check ovn-nbctl --wait=hv sync
+
+ packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac &&
eth.dst==$sw1_ro_mac &&
+ ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip &&
ip4.dst==$sw2_p1_ip &&
+@@ -15710,6 +15688,7 @@ test_ip6_packet_larger() {
+ fi
+ }
+
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ ovn-nbctl show > nbdump
+@@ -15869,6 +15848,7 @@ ovn-nbctl lr-nat-add lr0 snat 172.16.1.1 192.168.1.0/24
+ ovn-nbctl lr-nat-add lr0 snat 2002::1 2001::/64
+
+ OVN_POPULATE_ARP
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ ovn-sbctl dump-flows > sbflows
+@@ -16125,6 +16105,7 @@ for i in 1 2 3 4 5; do
+ done
+
+ dnl Wait for the changes to be propagated
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ dnl Assert that each Chassis has a tunnel formed to every other Chassis
+@@ -16404,6 +16385,7 @@ ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05
192.168.2.3/24
+ ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router
options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router
+ ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router
options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router
+
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+ #ovn-sbctl dump-flows
+
+@@ -16580,6 +16562,7 @@ ovn-nbctl lsp-set-type sw0-vir virtual
+ ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10
+ ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3
+
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ # Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline
+@@ -16914,6 +16897,7 @@ uuid_lb2=$(ovn-nbctl --bare --columns=_uuid find load_balancer
name=lb2)
+ ovn-nbctl --wait=hv meter-add event-elb drop 100 pktps 10
+
+ OVN_POPULATE_ARP
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+ ovn-sbctl lflow-list > sbflows
+ AT_CAPTURE_FILE([sbflows])
+@@ -17883,6 +17867,7 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \
+ ofport-request=1
+ check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ AT_CAPTURE_FILE([sbflows])
+@@ -18563,6 +18548,7 @@ m4_define([DVR_N_S_ARP_HANDLING],
+
+ # Set a hypervisor as gateway chassis, for router port 172.31.0.1
+ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
++ wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+
+ wait_row_count Port_Binding 1 logical_port=cr-router-to-underlay
+@@ -18782,6 +18768,7 @@ m4_define([DVR_N_S_PING],
+ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
+ ovn-nbctl lrp-set-redirect-type router-to-underlay bridged
+
++ wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+
+
+@@ -19012,6 +18999,7 @@ ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
+ ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
+
+ OVN_POPULATE_ARP
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ as hv1 ovs-appctl -t ovn-controller vlog/set dbg
+@@ -19193,8 +19181,7 @@ ovn-nbctl lsp-add ls1 lp11
+ ovn-nbctl lsp-set-addresses lp11 "f0:00:00:00:00:11"
+ ovn-nbctl lsp-set-port-security lp11 f0:00:00:00:00:11
+
+-OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp11` = xup])
+-
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+
+ ovn-nbctl show
+@@ -19363,6 +19350,7 @@ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3
+ ovn-nbctl --stateless lr-nat-add router dnat_and_snat 172.31.0.100 192.168.1.1
+ ovn-nbctl lrp-set-redirect-type router-to-underlay bridged
+
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+
+
+@@ -19627,6 +19615,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public
+ check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
+
+ OVN_POPULATE_ARP
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ wait_row_count Service_Monitor 2
+@@ -19815,6 +19804,7 @@ ovn-nbctl lsp-set-options ln-public network_name=public
+ ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20
+
+ OVN_POPULATE_ARP
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ # And now for the anticlimax. We need to ensure that there is no
+@@ -19954,6 +19944,7 @@ check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ ofport-request=3
+
+ OVN_POPULATE_ARP
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ ovn-sbctl dump-flows > sbflows
+@@ -20309,6 +20300,7 @@ ovn-nbctl lsp-add lsw0 lp1
+ ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:01 10.0.0.1"
+ ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
+
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # Trace with --ovs should see ovs flow related to the ACL
+@@ -20403,6 +20395,7 @@ for az in `seq 1 $n_az`; do
+ done
+ check ovn-nbctl --wait=hv sync
+ ovn-sbctl list Port_Binding > az$az.ports
++ wait_for_ports_up
+ done
+
+ # Pre-populate the hypervisors' ARP tables so that we don't lose any
+@@ -20578,6 +20571,7 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \
+
+ # wait for earlier changes to take effect
+ check ovn-nbctl --wait=hv sync
++wait_for_ports_up
+
+ ovn-sbctl dump-flows > sbflows
+ AT_CAPTURE_FILE([sbflows])
+@@ -20840,6 +20834,7 @@ ovn-nbctl lsp-add sw sw-rtr \
+ -- lsp-set-options sw-rtr router-port=rtr-sw
+
+ ovn-nbctl --wait=hv sync
++wait_for_ports_up
+
+ ovn-sbctl dump-flows > sbflows
+ AT_CAPTURE_FILE([sbflows])
+@@ -21188,6 +21183,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public
+ check ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20
+ check ovn-nbctl lr-nat-add lr0 snat 172.168.0.100 10.0.0.0/24
+ check ovn-nbctl --wait=hv sync
++wait_for_ports_up
+
+ wait_row_count datapath_binding 1 external-ids:name=lr0
+ lr0_dp_uuid=$(ovn-sbctl --bare --columns _uuid list datapath_binding lr0)
+@@ -21975,6 +21971,7 @@ check ovn-nbctl --policy="src-ip" lr-route-add DR
10.0.0.0/24 20.0.0.2
+ check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW
10.0.0.0/24 172.16.0.2
+ check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW
10.0.0.0/24 172.16.0.3
+
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # Ensure ECMP symmetric reply flows are not present on any hypervisor.
+@@ -22108,6 +22105,7 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.0.1], [1], [])
+@@ -22186,10 +22184,7 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \
+ options:rxq_pcap=hv1/vif4-rx.pcap \
+ ofport-request=4
+
+-OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
+-OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup])
+-OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p3) = xup])
+-OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p4) = xup])
++wait_for_ports_up
+
+ ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
+ ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 &&
tcp.dst >= 80 && tcp.dst <= 82" allow
+@@ -22488,6 +22483,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ # for ARP resolution).
+ OVN_POPULATE_ARP
+
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1)
+@@ -22604,6 +22600,7 @@ check test "$hvt2" -gt 0
+ # Then wait for 9 out of 10
+ sleep 1
+ check as hv3 ovn-appctl -t ovn-controller exit --restart
++wait_for_ports_up
+ ovn-nbctl --wait=sb sync
+ wait_row_count Chassis_Private 9 name!=hv3 nb_cfg=2
+ check_row_count Chassis_Private 1 name=hv3 nb_cfg=1
+@@ -22777,6 +22774,7 @@ ovn-nbctl set logical_router gw_router options:chassis=hv3
+ ovn-nbctl lr-nat-add gw_router snat 172.16.0.200 30.0.0.0/24
+ ovn-nbctl lr-nat-add gw_router snat 172.16.0.201 30.0.0.3
+
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ # Create an interface in br-phys in hv2 and send ARP request for 172.16.0.100
+@@ -22966,6 +22964,7 @@ check ovn-nbctl acl-add ls1 to-lport 1001 \
+ check ovn-nbctl acl-add ls1 to-lport 1001 \
+ 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2,
10.0.0.3}' allow
+
++wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ sip=`ip_to_hex 10 0 0 2`
+@@ -23134,6 +23133,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+
++wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ # Expected conjunction flows:
+@@ -23192,6 +23192,7 @@ as hv1 ovs-vsctl \
+ ovn-nbctl --wait=hv sync
+
+ # hv1 ovn-controller should not bind sw0-p2.
++wait_for_ports_up sw0-p1
+ check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$c
+
+ # Trigger recompute and sw0-p2 should not be claimed.
+--
+1.8.3.1
+
diff --git a/0002-tests-Add-more-checking-to-3-HVs-1-LS-3-lports-HV-te.patch
b/0002-tests-Add-more-checking-to-3-HVs-1-LS-3-lports-HV-te.patch
new file mode 100644
index 0000000..156a79d
--- /dev/null
+++ b/0002-tests-Add-more-checking-to-3-HVs-1-LS-3-lports-HV-te.patch
@@ -0,0 +1,87 @@
+From 19f0bbfbb4f3b5c0a382154c60b6a53f43cff7a6 Mon Sep 17 00:00:00 2001
+From: Ben Pfaff <blp(a)ovn.org>
+Date: Sun, 22 Nov 2020 12:53:50 -0800
+Subject: [PATCH 02/16] tests: Add more checking to "3 HVs, 1 LS, 3 lports/HV"
+ test.
+
+Signed-off-by: Ben Pfaff <blp(a)ovn.org>
+Acked-by: Dumitru Ceara <dceara(a)redhat.com>
+(cherry picked from upstream commit 66e53912951532785cbff12d1fb521238ec81bc0)
+
+Change-Id: I1e94b0c66c6bae16cf62718042e7ef8e46659821
+---
+ tests/ovn.at | 32 ++++++++++++++------------------
+ 1 file changed, 14 insertions(+), 18 deletions(-)
+
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 47052d2..5645f04 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -1841,42 +1841,38 @@ ovn_start
+ # Turn on port security on all the vifs except vif[123]1.
+ # Make vif13, vif2[23], vif3[123] destinations for unknown MACs.
+ # Add some ACLs for Ethertypes 1234, 1235, 1236.
+-ovn-nbctl ls-add lsw0
++check ovn-nbctl ls-add lsw0
+ net_add n1
+ for i in 1 2 3; do
+ sim_add hv$i
+ as hv$i
+- ovs-vsctl add-br br-phys
++ check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.$i
+
+ for j in 1 2 3; do
+- ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j
external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap
options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
+- ovn-nbctl lsp-add lsw0 lp$i$j
++ check ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j
external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap
options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j
++ check ovn-nbctl lsp-add lsw0 lp$i$j
+ if test $j = 1; then
+- ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j
192.168.0.$i$j" unknown
++ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j
192.168.0.$i$j" unknown
+ else
+ if test $j = 3; then
+ ip_addrs="192.168.0.$i$j fe80::ea2a:eaff:fe28:$i$j/64
192.169.0.$i$j"
+ else
+ ip_addrs="192.168.0.$i$j"
+ fi
+- ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j
$ip_addrs"
+- ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j
++ check ovn-nbctl lsp-set-addresses lp$i$j "f0:00:00:00:00:$i$j
$ip_addrs"
++ check ovn-nbctl lsp-set-port-security lp$i$j f0:00:00:00:00:$i$j
+ fi
+ done
+ done
+-ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
+-ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport ==
"lp11"' drop
+-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport ==
"lp33"' drop
++check ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop
++check ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1235 && inport
== "lp11"' drop
++check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1236 && outport ==
"lp33"' drop
+ ovn-nbctl create Address_Set name=set1
addresses=\"f0:00:00:00:00:11\",\"f0:00:00:00:00:21\",\"f0:00:00:00:00:31\"
+-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src == $set1
&& outport == "lp33"' drop
+-
+-get_lsp_uuid () {
+- ovn-nbctl lsp-list lsw0 | grep $1 | awk '{ print $1 }'
+-}
++check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1237 && eth.src ==
$set1 && outport == "lp33"' drop
+
+-ovn-nbctl create Port_Group name=pg1 ports=`get_lsp_uuid lp22`,`get_lsp_uuid lp33`
+-ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport ==
@pg1' drop
++check ovn-nbctl pg-add pg1 lp22 lp33
++check ovn-nbctl acl-add lsw0 to-lport 1000 'eth.type == 0x1238 && outport ==
@pg1' drop
+ check ovn-nbctl --wait=hv sync
+ wait_for_ports_up
+
+@@ -2079,7 +2075,7 @@ done
+
+ # set address for lp13 with invalid characters.
+ # lp13 should be configured with only 192.168.0.13.
+-ovn-nbctl --wait=hv lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13 invalid
192.169.0.13"
++check ovn-nbctl --wait=hv lsp-set-addresses lp13 "f0:00:00:00:00:13 192.168.0.13
invalid 192.169.0.13"
+
+ sip=`ip_to_hex 192 168 0 11`
+ tip=`ip_to_hex 192 168 0 13`
+--
+1.8.3.1
+
diff --git a/0003-tests-Improve-debuggability-of-tests.patch
b/0003-tests-Improve-debuggability-of-tests.patch
new file mode 100644
index 0000000..64cabd4
--- /dev/null
+++ b/0003-tests-Improve-debuggability-of-tests.patch
@@ -0,0 +1,1138 @@
+From 5fdd36b0afc25a20bde632278878bbfa7fcbc7ce Mon Sep 17 00:00:00 2001
+From: Ben Pfaff <blp(a)ovn.org>
+Date: Thu, 5 Nov 2020 16:43:23 -0800
+Subject: [PATCH 03/16] tests: Improve debuggability of tests.
+
+These changes should make it easier to debug various tests.
+
+Signed-off-by: Ben Pfaff <blp(a)ovn.org>
+Acked-by: Dumitru Ceara <dceara(a)redhat.com>
+(cherry picked from upstream commit 5907591d748d0d4d08ef139c9f9e45ef00d5b9b7)
+
+Change-Id: If4b7629c71df8bbc06f402e3e6031233b8fe0d31
+---
+ tests/ovn-controller-vtep.at | 12 +-
+ tests/ovn-northd.at | 230 ++++++++++--------------
+ tests/ovn.at | 404 ++++++++++++++++++++-----------------------
+ tests/ovs-macros.at | 20 ++-
+ 4 files changed, 306 insertions(+), 360 deletions(-)
+
+diff --git a/tests/ovn-controller-vtep.at b/tests/ovn-controller-vtep.at
+index cb58281..b2261d2 100644
+--- a/tests/ovn-controller-vtep.at
++++ b/tests/ovn-controller-vtep.at
+@@ -177,22 +177,22 @@ AT_CLEANUP
+ AT_SETUP([ovn-controller-vtep - binding 1])
+ OVN_CONTROLLER_VTEP_START
+
+-# adds logical switch 'lswitch0' and vlan_bindings.
++AS_BOX([add logical switch 'lswitch0' and vlan_bindings])
+ AT_CHECK([vtep-ctl add-ls lswitch0 -- bind-ls br-vtep p0 100 lswitch0 -- bind-ls br-vtep
p1 300 lswitch0])
+ # adds logical switch port in ovn-nb database, and sets the type and options.
+ OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0], [br-vtep], [lswitch0])
+-check ovn-sbctl wait-until Port_Binding br-vtep_lswitch0 chassis!='[[]]'
++wait_row_count Port_Binding 1 logical_port=br-vtep_lswitch0 chassis!='[[]]'
+ # should see one binding, associated to chassis of 'br-vtep'.
+ chassis_uuid=$(ovn-sbctl --columns=_uuid list Chassis br-vtep | cut -d ':' -f2 |
tr -d ' ')
+ AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding br-vtep_lswitch0 | cut
-d ':' -f2 | tr -d ' '], [0], [dnl
+ ${chassis_uuid}
+ ])
+
+-# adds another logical switch 'lswitch1' and vlan_bindings.
++AS_BOX([add another logical switch 'lswitch1' and vlan_bindings])
+ AT_CHECK([vtep-ctl add-ls lswitch1 -- bind-ls br-vtep p0 200 lswitch1])
+ # adds logical switch port in ovn-nb database for lswitch1.
+ OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch1], [br-vtep], [lswitch1])
+-check ovn-sbctl wait-until Port_Binding br-vtep_lswitch1 chassis!='[[]]'
++wait_row_count Port_Binding 1 logical_port=br-vtep_lswitch1 chassis!='[[]]'
+ # This is allowed, but not recommended, to have two vlan_bindings (to different vtep
logical switches)
+ # from one vtep gateway physical port in one ovn-nb logical swithch.
+ AT_CHECK_UNQUOTED([ovn-sbctl --columns=chassis list Port_Binding | cut -d ':'
-f2 | tr -d ' ' | sort], [0], [dnl
+@@ -201,7 +201,7 @@ ${chassis_uuid}
+ ${chassis_uuid}
+ ])
+
+-# adds another logical switch port in ovn-nb database for lswitch0.
++AS_BOX([add another logical switch port in ovn-nb database for lswitch0])
+ OVN_NB_ADD_VTEP_PORT([br-test], [br-vtep_lswitch0_dup], [br-vtep], [lswitch0])
+
+ # confirms the warning log.
+@@ -228,7 +228,7 @@ ${chassis_uuid}
+ ${chassis_uuid}
+ ])
+
+-# deletes physical ports from vtep.
++AS_BOX([delete physical ports from vtep])
+ AT_CHECK([ovs-vsctl del-port p0 -- del-port p1])
+ OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep --
br-vtep_lswitch`"])
+ OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p0`"])
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index b907673..e4831ae 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -624,61 +624,66 @@ AT_CLEANUP
+ AT_SETUP([ovn -- ovn-northd pause and resume])
+ ovn_start
+
+-AT_CHECK([test xfalse = x`as northd ovn-appctl -t ovn-northd is-paused`])
+-AT_CHECK([as northd ovn-appctl -t ovn-northd status], [0], [Status: active
+-])
+-AT_CHECK([test xfalse = x`as northd-backup ovn-appctl -t ovn-northd \
+-is-paused`])
+-AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0],
+-[Status: standby
+-])
+-
+-ovn-nbctl ls-add sw0
+-
+-OVS_WAIT_UNTIL([
+- ovn-sbctl lflow-list sw0
+- test 0 = $?])
++get_northd_status() {
++ as northd ovn-appctl -t ovn-northd is-paused
++ as northd ovn-appctl -t ovn-northd status
++ as northd-backup ovn-appctl -t ovn-northd is-paused
++ as northd-backup ovn-appctl -t ovn-northd status
++}
+
+-ovn-nbctl ls-del sw0
+-OVS_WAIT_UNTIL([
+- ovn-sbctl lflow-list sw0
+- test 1 = $?])
++AS_BOX([Pause the backup])
++# This forces the main northd to become active (otherwise there's no
++# guarantee, ovn_start is racy).
++check as northd-backup ovs-appctl -t ovn-northd pause
++OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false
++Status: active
++true
++Status: paused
++])
++
++AS_BOX([Resume the backup])
++check as northd-backup ovs-appctl -t ovn-northd resume
++OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false
++Status: active
++false
++Status: standby
++])
++
++AS_BOX([Check that ovn-northd is active])
++# Check that ovn-northd is active, by verifying that it creates and
++# destroys southbound datapaths as one would expect.
++check_row_count Datapath_Binding 0
++check ovn-nbctl --wait=sb ls-add sw0
++check_row_count Datapath_Binding 1
++check ovn-nbctl --wait=sb ls-del sw0
++check_row_count Datapath_Binding 0
+
+-# Now pause the ovn-northd
+-as northd ovs-appctl -t ovn-northd pause
+-as northd-backup ovs-appctl -t ovn-northd pause
+-AT_CHECK([test xtrue = x`as northd ovn-appctl -t ovn-northd is-paused`])
+-AT_CHECK([as northd ovn-appctl -t ovn-northd status], [0], [Status: paused
+-])
+-AT_CHECK([test xtrue = x`as northd-backup ovn-appctl -t ovn-northd is-paused`])
+-AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0],
+-[Status: paused
++AS_BOX([Pause the main northd])
++check as northd ovs-appctl -t ovn-northd pause
++check as northd-backup ovs-appctl -t ovn-northd pause
++AT_CHECK([get_northd_status], [0], [true
++Status: paused
++true
++Status: paused
+ ])
+
+-ovn-nbctl ls-add sw0
+-
+-# There should be no logical flows for sw0 datapath.
+-OVS_WAIT_UNTIL([
+- ovn-sbctl lflow-list sw0
+- test 1 = $?])
+-
+-# Now resume ovn-northd
+-as northd ovs-appctl -t ovn-northd resume
+-AT_CHECK([test xfalse = x`as northd ovn-appctl -t ovn-northd is-paused`])
+-OVS_WAIT_UNTIL([as northd ovn-appctl -t ovn-northd status], [0],
+-[Status: active
+-])
++AS_BOX([Verify that ovn-northd is paused])
++# Now ovn-northd won't respond by adding a datapath, because it's paused.
++check ovn-nbctl ls-add sw0
++check sleep 5
++check_row_count Datapath_Binding 0
+
+-as northd-backup ovs-appctl -t ovn-northd resume
+-AT_CHECK([test xfalse = x`as northd-backup ovn-appctl -t ovn-northd \
+-is-paused`])
+-AT_CHECK([as northd-backup ovn-appctl -t ovn-northd status], [0],
+-[Status: standby
++AS_BOX([Resume the main northd])
++check as northd ovs-appctl -t ovn-northd resume
++check as northd-backup ovs-appctl -t ovn-northd resume
++OVS_WAIT_FOR_OUTPUT([get_northd_status], [0], [false
++Status: active
++false
++Status: standby
+ ])
+
+-OVS_WAIT_UNTIL([
+- ovn-sbctl lflow-list sw0
+- test 0 = $?])
++check ovn-nbctl --wait=sb sync
++check_row_count Datapath_Binding 1
+
+ AT_CLEANUP
+
+@@ -1048,7 +1053,7 @@ health_check @hc | uuidfilt], [0], [<0>
+
+ wait_row_count Service_Monitor 0
+
+-# create logical switches and ports
++AS_BOX([create logical switches and ports])
+ ovn-nbctl ls-add sw0
+ ovn-nbctl --wait=sb lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 \
+ "00:00:00:00:00:03 10.0.0.3"
+@@ -1075,7 +1080,7 @@ OVS_WAIT_FOR_OUTPUT(
+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+-# Delete the Load_Balancer_Health_Check
++AS_BOX([Delete the Load_Balancer_Health_Check])
+ ovn-nbctl --wait=sb clear load_balancer . health_check
+ wait_row_count Service_Monitor 0
+
+@@ -1085,7 +1090,7 @@ OVS_WAIT_FOR_OUTPUT(
+ [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+-# Create the Load_Balancer_Health_Check again.
++AS_BOX([Create the Load_Balancer_Health_Check again.])
+ ovn-nbctl --wait=sb -- --id=@hc create \
+ Load_Balancer_Health_Check vip="10.0.0.10\:80" -- add Load_Balancer . \
+ health_check @hc
+@@ -1097,7 +1102,7 @@ AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+-# Get the uuid of both the service_monitor
++AS_BOX([Get the uuid of both the service_monitor])
+ sm_sw0_p1=$(fetch_column Service_Monitor _uuid logical_port=sw0-p1)
+ sm_sw1_p1=$(fetch_column Service_Monitor _uuid logical_port=sw1-p1)
+
+@@ -1107,7 +1112,7 @@ OVS_WAIT_FOR_OUTPUT(
+ [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+-# Set the service monitor for sw1-p1 to offline
++AS_BOX([Set the service monitor for sw1-p1 to offline])
+ check ovn-sbctl set service_monitor sw1-p1 status=offline
+ wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=offline
+ check ovn-nbctl --wait=sb sync
+@@ -1118,7 +1123,7 @@ OVS_WAIT_FOR_OUTPUT(
+ [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
+ ])
+
+-# Set the service monitor for sw0-p1 to offline
++AS_BOX([Set the service monitor for sw0-p1 to offline])
+ ovn-sbctl set service_monitor $sm_sw0_p1 status=offline
+
+ wait_row_count Service_Monitor 1 logical_port=sw0-p1 status=offline
+@@ -1134,7 +1139,7 @@ OVS_WAIT_FOR_OUTPUT(
+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(drop;)
+ ])
+
+-# Set the service monitor for sw0-p1 and sw1-p1 to online
++AS_BOX([Set the service monitor for sw0-p1 and sw1-p1 to online])
+ ovn-sbctl set service_monitor $sm_sw0_p1 status=online
+ ovn-sbctl set service_monitor $sm_sw1_p1 status=online
+
+@@ -1147,7 +1152,7 @@ OVS_WAIT_FOR_OUTPUT(
+ [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+-# Set the service monitor for sw1-p1 to error
++AS_BOX([Set the service monitor for sw1-p1 to error])
+ ovn-sbctl set service_monitor $sm_sw1_p1 status=error
+ wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=error
+ check ovn-nbctl --wait=sb sync
+@@ -1158,10 +1163,10 @@ AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0],
[dnl
+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
+ ])
+
+-# Add one more vip to lb1
++AS_BOX([Add one more vip to lb1])
+ check ovn-nbctl set load_balancer . vip:10.0.0.40\\:1000=10.0.0.3:1000,20.0.0.3:80
+
+-# create health_check for new vip - 10.0.0.40
++AS_BOX([create health_check for new vip - 10.0.0.40])
+ AT_CHECK(
+ [ovn-nbctl --wait=sb \
+ -- --id=@hc create Load_Balancer_Health_Check vip=10.0.0.40\\:1000 \
+@@ -1185,7 +1190,7 @@ OVS_WAIT_FOR_OUTPUT(
+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000);)
+ ])
+
+-# Set the service monitor for sw1-p1 to online
++AS_BOX([Set the service monitor for sw1-p1 to online])
+ check ovn-sbctl set service_monitor sw1-p1 status=online
+
+ wait_row_count Service_Monitor 1 logical_port=sw1-p1 status=online
+@@ -1199,7 +1204,7 @@ OVS_WAIT_FOR_OUTPUT(
+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
+ ])
+
+-# Associate lb1 to sw1
++AS_BOX([Associate lb1 to sw1])
+ check ovn-nbctl --wait=sb ls-lb-add sw1 lb1
+ AT_CAPTURE_FILE([sbflows11])
+ OVS_WAIT_FOR_OUTPUT(
+@@ -1209,7 +1214,7 @@ OVS_WAIT_FOR_OUTPUT(
+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
+ ])
+
+-# Now create lb2 same as lb1 but udp protocol.
++AS_BOX([Now create lb2 same as lb1 but udp protocol.])
+ check ovn-nbctl lb-add lb2 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 udp
+ check ovn-nbctl --wait=sb set load_balancer lb2
ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2
+ check ovn-nbctl --wait=sb set load_balancer lb2
ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2
+@@ -1224,12 +1229,13 @@ check ovn-nbctl --wait=sb sync
+
+ wait_row_count Service_Monitor 5
+
+-# Change the svc_monitor_mac. This should get reflected in service_monitor table rows.
++AS_BOX([Change the svc_monitor_mac.])
++# This should get reflected in service_monitor table rows.
+ check ovn-nbctl set NB_Global . options:svc_monitor_mac="fe:a0:65:a2:01:03"
+
+ wait_row_count Service_Monitor 5 src_mac='"fe:a0:65:a2:01:03"'
+
+-# Change the source ip for 10.0.0.3 backend ip in lb2
++AS_BOX([Change the source ip for 10.0.0.3 backend ip in lb2])
+ check ovn-nbctl --wait=sb set load_balancer lb2
ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.100
+
+ wait_row_count Service_Monitor 1 logical_port=sw0-p1 src_ip=10.0.0.100
+@@ -1745,28 +1751,12 @@ AT_CAPTURE_FILE([sw0flows])
+ ovn-sbctl dump-flows sw1 > sw1flows
+ AT_CAPTURE_FILE([sw1flows])
+
+-AT_CHECK([grep "ls_in_acl" sw0flows | grep pg0 | sort], [0], [dnl
+- table=7 (ls_in_acl ), priority=2002 , dnl
+-match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
+-])
+-
+-AT_CHECK([grep "ls_in_acl" sw1flows | grep pg0 | sort], [0], [dnl
+- table=7 (ls_in_acl ), priority=2002 , dnl
+-match=(inport == @pg0 && ip4 && tcp && tcp.dst == 80), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=egress,table=6); };)
+-])
+-
+-AT_CHECK([grep "ls_out_acl" sw0flows | grep pg0 | sort], [0], [dnl
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=(outport == @pg0 && ip6 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+-])
+-
+-AT_CHECK([grep "ls_out_acl" sw1flows | grep pg0 | sort], [0], [dnl
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=(outport == @pg0 && ip6 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
++AT_CHECK(
++ [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl
++sw0flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw0flows: table=7 (ls_in_acl ), priority=2002 , match=(inport == @pg0
&& ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=egress,table=6); };)
++sw1flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw1flows: table=7 (ls_in_acl ), priority=2002 , match=(inport == @pg0
&& ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=egress,table=6); };)
+ ])
+
+ AS_BOX([2])
+@@ -1778,22 +1768,11 @@ AT_CAPTURE_FILE([sw0flows2])
+ ovn-sbctl dump-flows sw1 > sw1flows2
+ AT_CAPTURE_FILE([sw1flows2])
+
+-AT_CHECK([grep "ls_out_acl" sw0flows2 | grep pg0 | sort], [0], [dnl
+- table=5 (ls_out_acl ), priority=2002 , dnl
+-match=(outport == @pg0 && ip4 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=(outport == @pg0 && ip6 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+-])
+-
+-AT_CHECK([grep "ls_out_acl" sw1flows2 | grep pg0 | sort], [0], [dnl
+- table=5 (ls_out_acl ), priority=2002 , dnl
+-match=(outport == @pg0 && ip4 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=(outport == @pg0 && ip6 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
++AT_CHECK([grep "ls_out_acl" sw0flows2 sw1flows2 | grep pg0 | sort], [0], [dnl
++sw0flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0
&& ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw0flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw1flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0
&& ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw1flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+ ])
+
+ AS_BOX([3])
+@@ -1805,42 +1784,19 @@ AT_CAPTURE_FILE([sw0flows3])
+ ovn-sbctl dump-flows sw1 > sw1flows3
+ AT_CAPTURE_FILE([sw1flows3])
+
+-AT_CHECK([grep "ls_out_acl" sw0flows3 | grep pg0 | sort], [0], [dnl
+- table=5 (ls_out_acl ), priority=2001 , dnl
+-match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] =
1; next;)
+- table=5 (ls_out_acl ), priority=2001 , dnl
+-match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
+- table=5 (ls_out_acl ), priority=2002 , dnl
+-match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
+-action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <->
eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2002 , dnl
+-match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
+-action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <->
eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+-])
+-
+-AT_CHECK([grep "ls_out_acl" sw1flows3 | grep pg0 | sort], [0], [dnl
+- table=5 (ls_out_acl ), priority=2001 , dnl
+-match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] =
1; next;)
+- table=5 (ls_out_acl ), priority=2001 , dnl
+-match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;)
+- table=5 (ls_out_acl ), priority=2002 , dnl
+-match=((reg0[[10]] == 1) && outport == @pg0 && ip4 && udp), dnl
+-action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <->
eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2002 , dnl
+-match=((reg0[[9]] == 1) && outport == @pg0 && ip4 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=((reg0[[10]] == 1) && outport == @pg0 && ip6 && udp), dnl
+-action=(ct_commit { ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <->
eth.src; ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+- table=5 (ls_out_acl ), priority=2003 , dnl
+-match=((reg0[[9]] == 1) && outport == @pg0 && ip6 && udp), dnl
+-action=(reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst <-> ip.src; is
implicit. */ outport <-> inport; next(pipeline=ingress,table=21); };)
++AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl
++sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1
&& (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
++sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1
&& (outport == @pg0 && ip)), action=(next;)
++sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip4 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
++sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip6 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1
&& (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
++sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1
&& (outport == @pg0 && ip)), action=(next;)
++sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip4 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip6 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
+ ])
+
+ AT_CLEANUP
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 5645f04..4f585a4 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -4959,6 +4959,8 @@ ovs-vsctl -- add-port br-int vif2 -- \
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
++ovs-sbctl dump-flows > sbflows
++AT_CAPTURE_FILE([sbflows])
+
+ # Send ip packets between the two ports.
+
+@@ -4970,36 +4972,11 @@ dst_ip=`ip_to_hex 172 16 1 2`
+
packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+ as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+
+-
+-echo "---------NB dump-----"
+-ovn-nbctl show
+-echo "---------------------"
+-ovn-nbctl list logical_router
+-echo "---------------------"
+-ovn-nbctl list logical_router_port
+-echo "---------------------"
+-
+-echo "---------SB dump-----"
+-ovn-sbctl list datapath_binding
+-echo "---------------------"
+-ovn-sbctl list logical_flow
+-echo "---------------------"
+-
+-echo "------ hv1 dump ----------"
+-as hv1 ovs-ofctl dump-flows br-int
+-
+ #Disable router R1
+ ovn-nbctl --wait=hv set Logical_Router R1 enabled=false
+
+-echo "---------SB dump-----"
+-ovn-sbctl list datapath_binding
+-echo "---------------------"
+-ovn-sbctl list logical_flow
+-echo "---------------------"
+-
+-echo "------ hv1 dump ----------"
+-as hv1 ovs-ofctl dump-flows br-int
+-
++ovs-sbctl dump-flows > sbflows2
++AT_CAPTURE_FILE([sbflows2])
+
+ as hv1 ovs-appctl netdev-dummy/receive vif1 $packet
+
+@@ -5105,6 +5082,9 @@ OVN_POPULATE_ARP
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
++ovn-sbctl dump-flows > sbflows
++AT_CAPTURE_FILE([sbflows])
++
+ # Send ip packets between foo1 and alice1
+ src_mac="f00000010203"
+ dst_mac="000000010203"
+@@ -5121,25 +5101,6 @@ dst_ip=`ip_to_hex 172 16 2 2`
+
packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+
+-echo "---------NB dump-----"
+-ovn-nbctl show
+-echo "---------------------"
+-ovn-nbctl list logical_router
+-echo "---------------------"
+-ovn-nbctl list logical_router_port
+-echo "---------------------"
+-
+-echo "---------SB dump-----"
+-ovn-sbctl list datapath_binding
+-echo "---------------------"
+-ovn-sbctl list port_binding
+-echo "---------------------"
+-
+-echo "------ hv1 dump ----------"
+-as hv1 ovs-ofctl dump-flows br-int
+-echo "------ hv2 dump ----------"
+-as hv2 ovs-ofctl dump-flows br-int
+-
+ # Packet to Expect at bob1
+ src_mac="000000010205"
+ dst_mac="f00000010205"
+@@ -6597,7 +6558,8 @@ ovs-vsctl -- add-port br-int vif2 -- \
+ # Allow some time for ovn-northd and ovn-controller to catch up.
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+-
++ovn-nbctl dump-flows > sbflows
++AT_CAPTURE_FILE([sbflows])
+
+ for i in 1 2; do
+ : > vif$i.expected
+@@ -7191,6 +7153,9 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+ sleep 1
+
++ovn-nbctl dump-flows > sbflows
++AT_CAPTURE_FILE([sbflows])
++
+ for i in 1 2; do
+ : > $i.expected
+ done
+@@ -7204,11 +7169,6 @@
na_packet=fa163e940598fa163ea1f9ae86dd6000000000203afffd81ce49a9480000f8163efffe
+ as hv1 ovs-appctl netdev-dummy/receive vif1 $ns_packet
+ echo $na_packet >> 1.expected
+
+-echo "------ hv1 dump ------"
+-as hv1 ovs-vsctl show
+-as hv1 ovs-ofctl -O OpenFlow13 show br-int
+-as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int
+-
+ for i in 1 2; do
+ OVN_CHECK_PACKETS([hv1/vif$i-tx.pcap], [$i.expected])
+ done
+@@ -7828,29 +7788,8 @@ ovs-vsctl -- add-port br-int hv1-ls2lp2 -- \
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+-echo "---------NB dump-----"
+-ovn-nbctl show
+-echo "---------------------"
+-ovn-nbctl list logical_router
+-echo "---------------------"
+-ovn-nbctl list logical_router_port
+-echo "---------------------"
+-
+-echo "---------SB dump-----"
+-ovn-sbctl list datapath_binding
+-echo "---------------------"
+-ovn-sbctl list port_binding
+-echo "---------------------"
+-ovn-sbctl dump-flows
+-echo "---------------------"
+-ovn-sbctl list chassis
+-ovn-sbctl list encap
+-echo "---------------------"
+-
+-echo "------Flows dump-----"
+-as hv1
+-ovs-ofctl dump-flows
+-echo "---------------------"
++ovn-sbctl dump-flows > sbflows
++AT_CAPTURE_FILE([sbflows])
+
+ src_mac="f00000000003"
+ dst_mac="f00000000001"
+@@ -10845,38 +10784,42 @@ check ovn-nbctl --wait=hv sync
+
+ # Check that there is a logical flow in logical switch foo's pipeline
+ # to set the outport to rp-foo with the condition is_chassis_redirect.
+-ovn-sbctl dump-flows foo
+-OVS_WAIT_UNTIL([test 1 = `ovn-sbctl dump-flows foo | grep ls_in_l2_lkup | \
++ovn-sbctl dump-flows foo > sbflows
++AT_CAPTURE_FILE([sbflows])
++OVS_WAIT_UNTIL([test 1 = `grep ls_in_l2_lkup sbflows | \
+ grep rp-foo | grep is_chassis_resident | grep priority=50 -c`])
+
+-echo "---------NB dump-----"
+-ovn-nbctl show
+-echo "---------------------"
+-ovn-nbctl list logical_router
+-echo "---------------------"
+-ovn-nbctl list nat
+-echo "---------------------"
+-ovn-nbctl list logical_router_port
+-echo "---------------------"
++(echo "---------NB dump-----"
++ ovn-nbctl show
++ echo "---------------------"
++ ovn-nbctl list logical_router
++ echo "---------------------"
++ ovn-nbctl list nat
++ echo "---------------------"
++ ovn-nbctl list logical_router_port
++ echo "---------------------") > nbdump
++AT_CAPTURE_FILE([nbdump])
+
+-echo "---------SB dump-----"
+-ovn-sbctl list datapath_binding
+-echo "---------------------"
+-ovn-sbctl list port_binding
+-echo "---------------------"
+-ovn-sbctl dump-flows
+-echo "---------------------"
+-ovn-sbctl list chassis
+-echo "---------------------"
++(echo "---------SB dump-----"
++ ovn-sbctl list datapath_binding
++ echo "---------------------"
++ ovn-sbctl list port_binding
++ echo "---------------------"
++ ovn-sbctl list chassis
++ echo "---------------------") > sbdump
++AT_CAPTURE_FILE([sbdump])
+
+ for chassis in hv1 hv2 hv3; do
+- as $chassis
+- echo "------ $chassis dump ----------"
+- ovs-vsctl show br-int
+- ovs-ofctl show br-int
+- ovs-ofctl dump-flows br-int
+- echo "--------------------------"
++ (as $chassis
++ echo "------ $chassis dump ----------"
++ ovs-vsctl show
++ ovs-ofctl show br-int
++ ovs-ofctl dump-flows br-int
++ echo "--------------------------") > ${chassis}dump
+ done
++AT_CAPTURE_FILE([hv1dump])
++AT_CAPTURE_FILE([hv2dump])
++AT_CAPTURE_FILE([hv3dump])
+
+ foo1_ip=$(ip_to_hex 192 168 1 2)
+ gw_ip=$(ip_to_hex 172 16 1 6)
+@@ -11326,6 +11269,9 @@ OVN_POPULATE_ARP
+ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
++ovn-sbctl dump-flows > sbflows
++AT_CAPTURE_FILE([sbflows])
++
+ # Send ip packets between foo1 and alice1
+ src_mac="f00000010203"
+ dst_mac="000000010203"
+@@ -13708,27 +13654,30 @@ AT_CLEANUP
+ AT_SETUP([ovn -- Superseding ACLs with conjunction])
+ ovn_start
+
+-ovn-nbctl ls-add ls1
++check ovn-nbctl set nb_global . options:svc_monitor_mac=66:66:66:66:66:66
++check ovn-nbctl ls-add ls1
+
+-ovn-nbctl lsp-add ls1 ls1-lp1 \
+--- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01"
++check ovn-nbctl lsp-add ls1 ls1-lp1 \
++-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01" \
++-- set logical_switch_port ls1-lp1 options:requested-tnl-key=1
+
+-ovn-nbctl lsp-add ls1 ls1-lp2 \
+--- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02"
++check ovn-nbctl lsp-add ls1 ls1-lp2 \
++-- lsp-set-addresses ls1-lp2 "f0:00:00:00:00:02" \
++-- set logical_switch_port ls1-lp1 options:requested-tnl-key=2
+
+ net_add n1
+ sim_add hv1
+
+ as hv1
+-ovs-vsctl add-br br-phys
++check ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+-ovs-vsctl -- add-port br-int hv1-vif1 -- \
++check ovs-vsctl -- add-port br-int hv1-vif1 -- \
+ set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \
+ options:tx_pcap=hv1/vif1-tx.pcap \
+ options:rxq_pcap=hv1/vif1-rx.pcap \
+ ofport-request=1
+
+-ovs-vsctl -- add-port br-int hv1-vif2 -- \
++check ovs-vsctl -- add-port br-int hv1-vif2 -- \
+ set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \
+ options:tx_pcap=hv1/vif2-tx.pcap \
+ options:rxq_pcap=hv1/vif2-rx.pcap \
+@@ -13748,7 +13697,8 @@ test_ip() {
+ local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\
+ ${dst_ip}0035111100080000
+ shift; shift; shift; shift; shift
+- as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
++ check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
++ ovs-appctl ofproto/trace br-int in_port=hv1-vif1 "$packet" > trace
+ for outport; do
+ echo $packet >> $outport.expected
+ done
+@@ -13757,20 +13707,23 @@ ${dst_ip}0035111100080000
+ reset_pcap_file() {
+ local iface=$1
+ local pcap_file=$2
+- ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
++ check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
+ options:rxq_pcap=dummy-rx.pcap
+ rm -f ${pcap_file}*.pcap
+- ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
++ check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
+ options:rxq_pcap=${pcap_file}-rx.pcap
+ }
+
+ # Add a default deny ACL and an allow ACL for specific IP traffic.
+-ovn-nbctl acl-add ls1 to-lport 2 'arp' allow
+-ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop
+-ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.2)
&& (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
+-ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.42)
&& (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
++check ovn-nbctl acl-add ls1 to-lport 2 'arp' allow
++check ovn-nbctl acl-add ls1 to-lport 1 'ip4' drop
++check ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.2)
&& (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
++check ovn-nbctl acl-add ls1 to-lport 3 '(ip4.src==10.0.0.1 || ip4.src==10.0.0.42)
&& (ip4.dst == 10.0.0.3 || ip4.dst == 10.0.0.4)' allow
+ wait_for_ports_up
+-ovn-nbctl --wait=hv sync
++check ovn-nbctl --wait=hv sync
++
++ovn-sbctl dump-flows > sbflows
++AT_CAPTURE_FILE([sbflows])
+
+ # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed.
+ for src in `seq 1 2`; do
+@@ -13826,9 +13779,9 @@ rm -f 2.packets
+ > 2.expected
+
+ # Add two less restrictive allow ACLs for src IP 10.0.0.1.
+-ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1' allow
+-ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
+-ovn-nbctl --wait=hv sync
++check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
allow
++check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
++check ovn-nbctl --wait=hv sync
+
+ # Check OVS flows, the less restrictive flows should have been installed.
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
+@@ -13871,8 +13824,8 @@ rm -f 2.packets
+ > 2.expected
+
+ # Remove the first less restrictive allow ACL.
+-ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
+-ovn-nbctl --wait=hv sync
++check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1 || ip4.src==10.0.0.1'
++check ovn-nbctl --wait=hv sync
+
+ # Check OVS flows, the second less restrictive allow ACL should have been installed.
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
+@@ -13888,8 +13841,8 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 |
ofctl_strip_all | \
+ ])
+
+ # Remove the less restrictive allow ACL.
+-ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1'
+-ovn-nbctl --wait=hv sync
++check ovn-nbctl acl-del ls1 to-lport 3 'ip4.src==10.0.0.1'
++check ovn-nbctl --wait=hv sync
+
+ # Check OVS flows, the 10.0.0.1 conjunction should have been reinstalled.
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
+@@ -13927,8 +13880,8 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in"
hv1/vif2-tx.pcap > 2.packets
+ AT_CHECK([cat 2.packets], [0], [expout])
+
+ # Re-add the less restrictive allow ACL for src IP 10.0.0.1
+-ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
+-ovn-nbctl --wait=hv sync
++check ovn-nbctl acl-add ls1 to-lport 3 'ip4.src==10.0.0.1' allow
++check ovn-nbctl --wait=hv sync
+
+ # Check OVS flows, the less restrictive flows should have been installed.
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \
+@@ -17232,6 +17185,7 @@ AT_CAPTURE_FILE([sbflows3])
+ cp ovn-sb/ovn-sb.db ovn-sb3.db
+ ovn-sbctl dump-flows > sbflows3
+
++AS_BOX([IGMP traffic test 1])
+ # Send traffic and make sure it gets forwarded only on the two ports that
+ # joined.
+ > expected
+@@ -17280,6 +17234,7 @@ send_igmp_v3_report hv1-vif1 hv1 \
+ wait_row_count IGMP_Group 1 address=239.0.1.68
+ check ovn-nbctl --wait=hv sync
+
++AS_BOX([IGMP traffic test 2])
+ # Send traffic and make sure it gets forwarded only on the port that joined.
+ as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+ as hv2 reset_pcap_file hv2-vif1 hv2/vif1
+@@ -17319,6 +17274,7 @@ send_igmp_v3_report hv1-vif1 hv1 \
+ # Check that the IGMP Group is learned.
+ wait_row_count IGMP_Group 1 address=224.0.0.42
+
++AS_BOX([IGMP traffic test 3])
+ # Send traffic and make sure it gets flooded to all ports.
+ as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+ as hv1 reset_pcap_file hv1-vif2 hv1/vif2
+@@ -17348,6 +17304,7 @@ check ovn-nbctl set Logical_Switch sw2 \
+ other_config:mcast_eth_src="00:00:00:00:02:fe" \
+ other_config:mcast_ip4_src="20.0.0.254"
+
++AS_BOX([IGMP traffic test 4])
+ # Wait for 1 query interval (1 sec) and check that two queries are generated.
+ > expected
+ store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected
+@@ -17369,6 +17326,7 @@ check ovn-nbctl set Logical_Switch sw3 \
+
+ check ovn-nbctl --wait=hv sync
+
++AS_BOX([IGMP traffic test 5])
+ # Send traffic from sw3 and make sure rtr doesn't relay it.
+ > expected_empty
+
+@@ -17418,6 +17376,7 @@ send_igmp_v3_report hv2-vif3 hv2 \
+ wait_row_count IGMP_Group 2 address=239.0.1.68
+ check ovn-nbctl --wait=hv sync
+
++AS_BOX([IGMP traffic test 6])
+ # Send traffic from sw3 and make sure it is relayed by rtr.
+ # to ports that joined.
+ > expected_routed_sw1
+@@ -17467,6 +17426,7 @@ send_igmp_v3_report hv1-vif4 hv1 \
+ wait_row_count IGMP_Group 3 address=239.0.1.68
+ check ovn-nbctl --wait=hv sync
+
++AS_BOX([IGMP traffic test 7])
+ # Send traffic from sw3 and make sure it is relayed by rtr
+ # to ports that joined.
+ > expected_routed_sw1
+@@ -17566,6 +17526,7 @@ send_igmp_v3_report hv1-vif2 hv1 \
+ wait_row_count IGMP_Group 1 address=239.0.1.68
+ check ovn-nbctl --wait=hv sync
+
++AS_BOX([IGMP traffic test 8])
+ # Send traffic from sw1-p21
+ send_ip_multicast_pkt hv2-vif1 hv2 \
+ 000000000001 01005e000144 \
+@@ -21998,26 +21959,25 @@ ovn-nbctl set Logical_Router $gw_uuid options:chassis=hv1
+ ovn-nbctl --wait=hv sync
+
+ # And ensure that ECMP symmetric reply flows are present only on hv1
+-AT_CHECK([
+- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=15 | \
+- grep "priority=100" | \
+- grep
"ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
-c)
+-])
+-AT_CHECK([
+- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=22 | \
+- grep "priority=200" | \
+- grep
"actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c)
+-])
++as hv1 ovs-ofctl dump-flows br-int > hv1flows
++AT_CAPTURE_FILE([hv1flows])
++as hv2 ovs-ofctl dump-flows br-int > hv2flows
++AT_CAPTURE_FILE([hv2flows])
+
+ AT_CHECK([
+- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=15 | \
+- grep "priority=100" | \
+- grep
"ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
-c)
+-])
+-AT_CHECK([
+- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=22 | \
+- grep "priority=200" | \
+- grep
"actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c)
++ for hv in 1 2; do
++ grep table=15 hv${hv}flows | \
++ grep "priority=100" | \
++ grep -c
"ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))"
++
++ grep table=22 hv${hv}flows | \
++ grep "priority=200" | \
++ grep -c
"actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]"
++ done; :], [0], [dnl
++1
++1
++0
++0
+ ])
+
+ OVN_CLEANUP([hv1], [hv2])
+@@ -22141,22 +22101,22 @@ as hv1
+ ovs-vsctl add-br br-phys
+ ovn_attach n1 br-phys 192.168.0.1
+
+-ovn-nbctl ls-add sw0
+-ovn-nbctl lsp-add sw0 sw0-p1
+-ovn-nbctl lsp-set-addresses sw0-p1 "10:14:00:00:00:03 10.0.0.3"
+-ovn-nbctl lsp-set-port-security sw0-p1 "10:14:00:00:00:03 10.0.0.3"
++check ovn-nbctl ls-add sw0
++check ovn-nbctl lsp-add sw0 sw0-p1
++check ovn-nbctl lsp-set-addresses sw0-p1 "10:14:00:00:00:03 10.0.0.3"
++check ovn-nbctl lsp-set-port-security sw0-p1 "10:14:00:00:00:03 10.0.0.3"
+
+-ovn-nbctl lsp-add sw0 sw0-p2
+-ovn-nbctl lsp-set-addresses sw0-p2 "10:14:00:00:00:04 10.0.0.4"
+-ovn-nbctl lsp-set-port-security sw0-p2 "10:14:00:00:00:04 10.0.0.4"
++check ovn-nbctl lsp-add sw0 sw0-p2
++check ovn-nbctl lsp-set-addresses sw0-p2 "10:14:00:00:00:04 10.0.0.4"
++check ovn-nbctl lsp-set-port-security sw0-p2 "10:14:00:00:00:04 10.0.0.4"
+
+-ovn-nbctl lsp-add sw0 sw0-p3
+-ovn-nbctl lsp-set-addresses sw0-p3 "10:14:00:00:00:05 10.0.0.5"
+-ovn-nbctl lsp-set-port-security sw0-p3 "10:14:00:00:00:05 10.0.0.5"
++check ovn-nbctl lsp-add sw0 sw0-p3
++check ovn-nbctl lsp-set-addresses sw0-p3 "10:14:00:00:00:05 10.0.0.5"
++check ovn-nbctl lsp-set-port-security sw0-p3 "10:14:00:00:00:05 10.0.0.5"
+
+-ovn-nbctl lsp-add sw0 sw0-p4
+-ovn-nbctl lsp-set-addresses sw0-p4 "10:14:00:00:00:06 10.0.0.6"
+-ovn-nbctl lsp-set-port-security sw0-p4 "10:14:00:00:00:06 10.0.0.6"
++check ovn-nbctl lsp-add sw0 sw0-p4
++check ovn-nbctl lsp-set-addresses sw0-p4 "10:14:00:00:00:06 10.0.0.6"
++check ovn-nbctl lsp-set-port-security sw0-p4 "10:14:00:00:00:06 10.0.0.6"
+
+ as hv1
+ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+@@ -22182,83 +22142,99 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \
+
+ wait_for_ports_up
+
+-ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
+-ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 &&
tcp.dst >= 80 && tcp.dst <= 82" allow
+-ovn-nbctl --wait=hv sync
++check ovn-nbctl pg-add pg0 sw0-p1 sw0-p2
++check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4
&& tcp.dst >= 80 && tcp.dst <= 82" allow
++check ovn-nbctl --wait=hv sync
+
+-OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=2")])
++# wait_conj_id_count COUNT ["ID COUNT [MATCH]"]...
++#
++# Waits until COUNT flows matching against conj_id appear in the
++# table 45 on hv1's br-int bridge. Makes the flows available in
++# "hv1flows", which will be logged on error.
++#
++# In addition, for each quoted "ID COUNT" or "ID COUNT MATCH",
++# verifies that there are COUNT flows in table 45 that match
++# aginst conj_id=ID and (if MATCH) is nonempty, match MATCH.
++wait_conj_id_count() {
++ AT_CAPTURE_FILE([hv1flows])
++ local retval
++ case $1 in
++ (0) retval=1 ;;
++ (*) retval=0 ;;
++ esac
++
++ echo "waiting for $1 conj_id flows..."
++ OVS_WAIT_FOR_OUTPUT_UNQUOTED(
++ [ovs-ofctl dump-flows br-int > hv1flows
++ grep table=45 hv1flows | grep -c conj_id],
++ [$retval], [$1
++])
++
++ shift
++ for arg; do
++ set -- $arg; id=$1 count=$2 match=$3
++ echo "checking that there are $count ${match:+$match }flows with
conj_id=$id..."
++ AT_CHECK_UNQUOTED(
++ [grep table=45 hv1flows | grep "$match" | grep -c conj_id=$id],
++ [0], [$count
++])
++ done
++}
+
+-# Add sw0-p3 to the port group pg0. The conj_id should be 2.
+-ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
+-OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=2")])
++AS_BOX([Add sw0-p3 to the port group pg0. The conj_id should be 2.])
++check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
++wait_conj_id_count 1 "2 1"
+
+-# Add sw0p4 to the port group pg0. The conj_id should be 2.
+-ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 sw0-p4
+-OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=2")])
++AS_BOX([Add sw0p4 to the port group pg0. The conj_id should be 2.])
++check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 sw0-p4
++wait_conj_id_count 1 "2 1"
+
+-# Add another ACL with conjunction.
+-ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 &&
udp.dst >= 80 && udp.dst <= 82" allow
+-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c
"conj_id=2")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c
"conj_id=3")])
++AS_BOX([Add another ACL with conjunction.])
++check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4
&& udp.dst >= 80 && udp.dst <= 82" allow
++wait_conj_id_count 2 "2 1 tcp" "3 1 udp"
+
+-# Delete tcp ACL.
+-ovn-nbctl acl-del pg0 to-lport 1002 "outport == @pg0 && ip4 &&
tcp.dst >= 80 && tcp.dst <= 82"
+-OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c
"conj_id=3")])
++AS_BOX([Delete tcp ACL.])
++check ovn-nbctl --wait=hv acl-del pg0 to-lport 1002 "outport == @pg0 && ip4
&& tcp.dst >= 80 && tcp.dst <= 82"
++wait_conj_id_count 1 "3 1 udp"
+
+-# Add back the tcp ACL.
+-ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 &&
tcp.dst >= 80 && tcp.dst <= 82" allow
+-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
++AS_BOX([Add back the tcp ACL.])
++check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4
&& tcp.dst >= 80 && tcp.dst <= 82" allow
++wait_conj_id_count 2 "3 1 udp" "4 1 tcp"
+ AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c
"conj_id=3")])
+ AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c
"conj_id=4")])
+
+-ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0
&& ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow
+-OVS_WAIT_UNTIL([test 3 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c
"conj_id=3")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c
"conj_id=4")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c
"conj_id=5")])
++AS_BOX([Add another tcp ACL.])
++check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 &&
inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <=
86" allow
++wait_conj_id_count 3 "3 1 udp" "4 1 tcp" "5 1 tcp"
+
+-ovn-nbctl clear port_group pg0 acls
+-OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
++AS_BOX([Clear ACLs.])
++check ovn-nbctl --wait=hv clear port_group pg0 acls
++wait_conj_id_count 0
+
+-ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4
&& tcp.dst >= 80 && tcp.dst <= 82" allow
+-ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4
&& udp.dst >= 80 && udp.dst <= 82" allow
+-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c
"conj_id=6")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c
"conj_id=7")])
++AS_BOX([Add TCP ACL.])
++check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4
&& tcp.dst >= 80 && tcp.dst <= 82" allow
++check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4
&& udp.dst >= 80 && udp.dst <= 82" allow
++wait_conj_id_count 2 "6 1 tcp" "7 1 udp"
+
+-# Flush the lflow cache.
++AS_BOX([Flush lflow cache.])
+ as hv1 ovn-appctl -t ovn-controller flush-lflow-cache
+-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=2")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=3")])
+-
+-# Disable lflow caching.
++wait_conj_id_count 2 "2 1" "3 1"
+
++AS_BOX([Disable lflow caching.])
+ as hv1 ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false
+
+-# Wait until ovn-enble-lflow-cache is processed by ovn-controller.
+-OVS_WAIT_UNTIL([
+- test $(ovn-sbctl get chassis hv1 other_config:ovn-enable-lflow-cache) =
'"false"'
+-])
+-
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=2")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=3")])
++AS_BOX([Wait until ovn-enble-lflow-cache is processed by ovn-controller.])
++wait_row_count Chassis 1 name=hv1 other_config:ovn-enable-lflow-cache=false
++wait_conj_id_count 2 "2 1" "3 1"
+
+-# Remove port sw0-p4 from port group.
+-ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
+-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=4")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=5")])
++AS_BOX([Remove port sw0-p4 from port group.])
++check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3
++wait_conj_id_count 2 "4 1" "5 1"
+
++AS_BOX([Recompute.])
+ as hv1 ovn-appctl -t ovn-controller recompute
+
+-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id")])
+-OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=2")])
+-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c
"conj_id=3")])
++wait_conj_id_count 2 "2 1" "3 1"
+
+ OVN_CLEANUP([hv1])
+
+diff --git a/tests/ovs-macros.at b/tests/ovs-macros.at
+index 05b17eb..8b1b03e 100644
+--- a/tests/ovs-macros.at
++++ b/tests/ovs-macros.at
+@@ -266,14 +266,16 @@ m4_define([OVS_WAIT_UNTIL],
+ [OVS_WAIT([$1], [$2], [AT_LINE], [until $1])])
+
+ dnl OVS_WAIT_FOR_OUTPUT(COMMAND, EXIT-STATUS, STDOUT, STDERR)
++dnl OVS_WAIT_FOR_OUTPUT_UNQUOTED(COMMAND, EXIT-STATUS, STDOUT, STDERR)
+ dnl
+ dnl Executes shell COMMAND in a loop until it exits with status EXIT-STATUS,
+ dnl prints STDOUT on stdout, and prints STDERR on stderr. If this doesn't
+ dnl happen within a reasonable time limit, then the test fails.
+-m4_define([OVS_WAIT_FOR_OUTPUT], [dnl
++dnl
++dnl The UNQUOTED version expands shell $variables, $(command)s, and so on.
++dnl The plain version does not
++m4_define([OVS_WAIT_FOR_OUTPUT__], [dnl
+ wait_expected_status=m4_if([$2], [], [0], [$2])
+-AT_DATA([wait-expected-stdout], [$3])
+-AT_DATA([wait-expected-stderr], [$4])
+ ovs_wait_command() {
+ $1
+ }
+@@ -293,6 +295,18 @@ ovs_wait_failed () {
+ }
+ ovs_wait "AS_ESCAPE([AT_LINE])" "for output from AS_ESCAPE([$1])"
+ ])
++m4_define([OVS_WAIT_FOR_OUTPUT], [dnl
++AT_DATA([wait-expected-stdout], [$3])
++AT_DATA([wait-expected-stderr], [$4])
++OVS_WAIT_FOR_OUTPUT__([$1], [$2])
++])
++m4_define([OVS_WAIT_FOR_OUTPUT_UNQUOTED], [dnl
++cat > wait-expected-stdout <<EOF
++$3[]EOF
++cat > wait-expected-stderr <<EOF
++$4[]EOF
++OVS_WAIT_FOR_OUTPUT__([$1], [$2])
++])
+
+ dnl OVS_WAIT_WHILE(COMMAND[, IF-FAILED])
+ dnl
+--
+1.8.3.1
+
diff --git a/0004-Implement-SCTP-specific-reject-action.patch
b/0004-Implement-SCTP-specific-reject-action.patch
new file mode 100644
index 0000000..afc6bdc
--- /dev/null
+++ b/0004-Implement-SCTP-specific-reject-action.patch
@@ -0,0 +1,375 @@
+From 35afc86abf71ae557acf2fb7030ca8eedf214449 Mon Sep 17 00:00:00 2001
+From: Mark Michelson <mmichels(a)redhat.com>
+Date: Mon, 11 Jan 2021 13:01:04 -0500
+Subject: [PATCH 04/16] Implement SCTP-specific reject() action.
+
+Currently in OVN, if an SCTP packet hits a reject() action, OVN responds
+with an ICMP packet. Instead, we should send an SCTP packet with an
+ABORT chunk. This will either end the current association or will
+prevent an association from being created, depending on which stage of
+the SCTP state machine we currently are in.
+
+This patch adds the desired behavior for SCTP. The reject() action will
+now send an SCTP ABORT if the incoming packet is SCTP.
+
+Signed-off-by: Mark Michelson <mmichels(a)redhat.com>
+Acked-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit 646b150fbbc53973178540fae997a5cbd0e8fd43)
+
+Change-Id: I2f8eab2d15209c1201d5e691e90bc37d12f03952
+---
+ controller/pinctrl.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++
+ lib/ovn-util.h | 33 +++++++++++++++
+ tests/ovn.at | 43 +++++++++++++++++++
+ utilities/ovn-trace.c | 87 ++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 276 insertions(+)
+
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index dfb59c6..786f22a 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -38,6 +38,7 @@
+ #include "openvswitch/ofp-util.h"
+ #include "openvswitch/vlog.h"
+ #include "lib/random.h"
++#include "lib/crc32c.h"
+
+ #include "lib/dhcp.h"
+ #include "ovn-controller.h"
+@@ -1786,6 +1787,116 @@ pinctrl_handle_tcp_reset(struct rconn *swconn, const struct flow
*ip_flow,
+ dp_packet_uninit(&packet);
+ }
+
++static void dp_packet_put_sctp_abort(struct dp_packet *packet,
++ bool reflect_tag)
++{
++ struct sctp_chunk_header abort = {
++ .sctp_chunk_type = SCTP_CHUNK_TYPE_ABORT,
++ .sctp_chunk_flags = reflect_tag ? SCTP_ABORT_CHUNK_FLAG_T : 0,
++ .sctp_chunk_len = htons(SCTP_CHUNK_HEADER_LEN),
++ };
++
++ dp_packet_put(packet, &abort, sizeof abort);
++}
++
++static void
++pinctrl_handle_sctp_abort(struct rconn *swconn, const struct flow *ip_flow,
++ struct dp_packet *pkt_in,
++ const struct match *md, struct ofpbuf *userdata,
++ bool loopback)
++{
++ if (ip_flow->nw_proto != IPPROTO_SCTP) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
++ VLOG_WARN_RL(&rl, "SCTP_ABORT action on non-SCTP packet");
++ return;
++ }
++
++ struct sctp_header *sh_in = dp_packet_l4(pkt_in);
++ if (!sh_in) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
++ VLOG_WARN_RL(&rl, "SCTP_ABORT action on malformed SCTP packet");
++ return;
++ }
++
++ const struct sctp_chunk_header *sh_in_chunk =
++ dp_packet_get_sctp_payload(pkt_in);
++ if (!sh_in_chunk) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
++ VLOG_WARN_RL(&rl, "SCTP_ABORT action on SCTP packet with no
chunks");
++ return;
++ }
++
++ if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_ABORT) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
++ VLOG_WARN_RL(&rl, "sctp_abort action on incoming SCTP ABORT.");
++ return;
++ }
++
++ const struct sctp_init_chunk *sh_in_init = NULL;
++ if (sh_in_chunk->sctp_chunk_type == SCTP_CHUNK_TYPE_INIT) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
++ sh_in_init = dp_packet_at(pkt_in, pkt_in->l4_ofs +
++ SCTP_HEADER_LEN +
++ SCTP_CHUNK_HEADER_LEN,
++ SCTP_INIT_CHUNK_LEN);
++ if (!sh_in_init) {
++ VLOG_WARN_RL(&rl, "Incomplete SCTP INIT chunk. Ignoring
packet.");
++ return;
++ }
++ }
++
++ uint64_t packet_stub[128 / 8];
++ struct dp_packet packet;
++
++ dp_packet_use_stub(&packet, packet_stub, sizeof packet_stub);
++
++ struct eth_addr eth_src = loopback ? ip_flow->dl_dst : ip_flow->dl_src;
++ struct eth_addr eth_dst = loopback ? ip_flow->dl_src : ip_flow->dl_dst;
++
++ if (get_dl_type(ip_flow) == htons(ETH_TYPE_IPV6)) {
++ const struct in6_addr *ip6_src =
++ loopback ? &ip_flow->ipv6_dst : &ip_flow->ipv6_src;
++ const struct in6_addr *ip6_dst =
++ loopback ? &ip_flow->ipv6_src : &ip_flow->ipv6_dst;
++ pinctrl_compose_ipv6(&packet, eth_src, eth_dst,
++ (struct in6_addr *) ip6_src,
++ (struct in6_addr *) ip6_dst,
++ IPPROTO_SCTP, 63, SCTP_HEADER_LEN +
++ SCTP_CHUNK_HEADER_LEN);
++ } else {
++ ovs_be32 nw_src = loopback ? ip_flow->nw_dst : ip_flow->nw_src;
++ ovs_be32 nw_dst = loopback ? ip_flow->nw_src : ip_flow->nw_dst;
++ pinctrl_compose_ipv4(&packet, eth_src, eth_dst, nw_src, nw_dst,
++ IPPROTO_SCTP, 63, SCTP_HEADER_LEN +
++ SCTP_CHUNK_HEADER_LEN);
++ }
++
++ struct sctp_header *sh = dp_packet_put_zeros(&packet, sizeof *sh);
++ dp_packet_set_l4(&packet, sh);
++ sh->sctp_dst = ip_flow->tp_src;
++ sh->sctp_src = ip_flow->tp_dst;
++ put_16aligned_be32(&sh->sctp_csum, 0);
++
++ bool tag_reflected;
++ if (get_16aligned_be32(&sh_in->sctp_vtag) == 0 && sh_in_init) {
++ /* See RFC 4960 Section 8.4, item 3. */
++ put_16aligned_be32(&sh->sctp_vtag, sh_in_init->initiate_tag);
++ tag_reflected = false;
++ } else {
++ /* See RFC 4960 Section 8.4, item 8. */
++ sh->sctp_vtag = sh_in->sctp_vtag;
++ tag_reflected = true;
++ }
++
++ dp_packet_put_sctp_abort(&packet, tag_reflected);
++
++ put_16aligned_be32(&sh->sctp_csum, crc32c((void *) sh,
++ dp_packet_l4_size(&packet)));
++
++ set_actions_and_enqueue_msg(swconn, &packet, md, userdata);
++ dp_packet_uninit(&packet);
++}
++
+ static void
+ pinctrl_handle_reject(struct rconn *swconn, const struct flow *ip_flow,
+ struct dp_packet *pkt_in,
+@@ -1793,6 +1904,8 @@ pinctrl_handle_reject(struct rconn *swconn, const struct flow
*ip_flow,
+ {
+ if (ip_flow->nw_proto == IPPROTO_TCP) {
+ pinctrl_handle_tcp_reset(swconn, ip_flow, pkt_in, md, userdata, true);
++ } else if (ip_flow->nw_proto == IPPROTO_SCTP) {
++ pinctrl_handle_sctp_abort(swconn, ip_flow, pkt_in, md, userdata, true);
+ } else {
+ pinctrl_handle_icmp(swconn, ip_flow, pkt_in, md, userdata, true, true);
+ }
+diff --git a/lib/ovn-util.h b/lib/ovn-util.h
+index a711363..0823461 100644
+--- a/lib/ovn-util.h
++++ b/lib/ovn-util.h
+@@ -228,4 +228,37 @@ bool ip_address_and_port_from_lb_key(const char *key, char
**ip_address,
+ * value. */
+ char *ovn_get_internal_version(void);
+
++
++/* OVN Packet definitions. These may eventually find a home in OVS's
++ * packets.h file. For the time being, they live here because OVN uses them
++ * and OVS does not.
++ */
++#define SCTP_CHUNK_HEADER_LEN 4
++struct sctp_chunk_header {
++ uint8_t sctp_chunk_type;
++ uint8_t sctp_chunk_flags;
++ ovs_be16 sctp_chunk_len;
++};
++BUILD_ASSERT_DECL(SCTP_CHUNK_HEADER_LEN == sizeof(struct sctp_chunk_header));
++
++#define SCTP_INIT_CHUNK_LEN 16
++struct sctp_init_chunk {
++ ovs_be32 initiate_tag;
++ ovs_be32 a_rwnd;
++ ovs_be16 num_outbound_streams;
++ ovs_be16 num_inbound_streams;
++ ovs_be32 initial_tsn;
++};
++BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct sctp_init_chunk));
++
++/* These are the only SCTP chunk types that OVN cares about.
++ * There is no need to define the other chunk types until they are
++ * needed.
++ */
++#define SCTP_CHUNK_TYPE_INIT 1
++#define SCTP_CHUNK_TYPE_ABORT 6
++
++/* See RFC 4960 Sections 3.3.7 and 8.5.1 for information on this flag. */
++#define SCTP_ABORT_CHUNK_FLAG_T (1 << 0)
++
+ #endif
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 4f585a4..a6d2c2d 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -12815,6 +12815,45 @@ test_tcp_syn_packet() {
+ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ }
+
++# test_sctp_init_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM SCTP_SPORT
SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_IP_CHKSUM EXP_SCTP_ABORT_CHKSUM
++#
++# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP
INIT chunk with
++# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, SCTP_SPORT, SCTP_DPORT, and
SCTP_CHKSUM as specified.
++# The INIT "initiate_tag" will be set to SCTP_INIT_TAG.
++# EXP_IP_CHKSUM and EXP_SCTP_CHKSUM are the ip and sctp checksums of the SCTP ABORT
chunk generated from the ACL rule hit
++#
++# INPORT is an lport number, e.g. 11 for vif11.
++# HV is a hypervisor number.
++# ETH_SRC and ETH_DST are each 12 hex digits.
++# IPV4_SRC and IPV4_DST are each 8 hex digits.
++# SCTP_SPORT and SCTP_DPORT are 4 hex digits.
++# IP_CHKSUM and EXP_IP_CHKSUM are 4 hex digits.
++# SCTP_CHKSUM and EXP_SCTP_CHKSUM are 8 hex digits.
++test_sctp_init_packet() {
++ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7
++ local sctp_sport=$8 sctp_dport=$9 sctp_init_tag=${10} sctp_chksum=${11}
++ local exp_ip_chksum=${12} exp_sctp_abort_chksum=${13}
++
++ local ip_ttl=ff
++ local eth_hdr=${eth_dst}${eth_src}0800
++ local ip_hdr=4500002500004000${ip_ttl}84${ip_chksum}${ipv4_src}${ipv4_dst}
++ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum}
++ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag}
++
++ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init}
++
++ local sctp_abort_ttl=3f
++ local reply_eth_hdr=${eth_src}${eth_dst}0800
++ local
reply_ip_hdr=4500002400004000${sctp_abort_ttl}84${exp_ip_chksum}${ipv4_dst}${ipv4_src}
++ local
reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum}
++ local reply_sctp_abort=06000004
++
++ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort}
++ echo $reply >> vif$inport.expected
++
++ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
++}
++
+ # Create hypervisors hv[123].
+ # Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3.
+ # Add all of the vifs to a single logical switch sw0.
+@@ -12867,6 +12906,10 @@ test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex
192 168 1 11) $(i
+ test_tcp_syn_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex
192 168 1 11) 0000 8b40 3039 0000 b85f 70e4
+ test_tcp_syn_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex
192 168 1 12) 0000 8b40 3039 0000 b854 70d9
+
++test_sctp_init_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11)
$(ip_to_hex 192 168 1 21) 0000 8b40 3039 00000001 82112601 b7e5 10fe95b6
++test_sctp_init_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21)
$(ip_to_hex 192 168 1 11) 0000 8b40 3039 00000002 C0379D5A b7e5 39f23aaf
++test_sctp_init_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31)
$(ip_to_hex 192 168 1 12) 0000 8b40 3039 00000003 028E263C b7da 7124045b
++
+ for i in 1 2 3; do
+ OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [vif${i}1.expected])
+ done
+diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
+index e3aa73f..0d5284e 100644
+--- a/utilities/ovn-trace.c
++++ b/utilities/ovn-trace.c
+@@ -1806,6 +1806,91 @@ execute_tcp_reset(const struct ovnact_nest *on,
+ execute_tcp6_reset(on, dp, uflow, table_id, loopback, pipeline, super);
+ }
+ }
++
++static void
++execute_sctp4_abort(const struct ovnact_nest *on,
++ const struct ovntrace_datapath *dp,
++ const struct flow *uflow, uint8_t table_id,
++ bool loopback, enum ovnact_pipeline pipeline,
++ struct ovs_list *super)
++{
++ struct flow sctp_flow = *uflow;
++
++ /* Update fields for TCP SCTP. */
++ if (loopback) {
++ sctp_flow.dl_dst = uflow->dl_src;
++ sctp_flow.dl_src = uflow->dl_dst;
++ sctp_flow.nw_dst = uflow->nw_src;
++ sctp_flow.nw_src = uflow->nw_dst;
++ } else {
++ sctp_flow.dl_dst = uflow->dl_dst;
++ sctp_flow.dl_src = uflow->dl_src;
++ sctp_flow.nw_dst = uflow->nw_dst;
++ sctp_flow.nw_src = uflow->nw_src;
++ }
++ sctp_flow.nw_proto = IPPROTO_SCTP;
++ sctp_flow.nw_ttl = 255;
++ sctp_flow.tp_src = uflow->tp_src;
++ sctp_flow.tp_dst = uflow->tp_dst;
++
++ struct ovntrace_node *node = ovntrace_node_append(
++ super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort");
++
++ trace_actions(on->nested, on->nested_len, dp, &sctp_flow,
++ table_id, pipeline, &node->subs);
++}
++
++static void
++execute_sctp6_abort(const struct ovnact_nest *on,
++ const struct ovntrace_datapath *dp,
++ const struct flow *uflow, uint8_t table_id,
++ bool loopback, enum ovnact_pipeline pipeline,
++ struct ovs_list *super)
++{
++ struct flow sctp_flow = *uflow;
++
++ /* Update fields for SCTP. */
++ if (loopback) {
++ sctp_flow.dl_dst = uflow->dl_src;
++ sctp_flow.dl_src = uflow->dl_dst;
++ sctp_flow.ipv6_dst = uflow->ipv6_src;
++ sctp_flow.ipv6_src = uflow->ipv6_dst;
++ } else {
++ sctp_flow.dl_dst = uflow->dl_dst;
++ sctp_flow.dl_src = uflow->dl_src;
++ sctp_flow.ipv6_dst = uflow->ipv6_dst;
++ sctp_flow.ipv6_src = uflow->ipv6_src;
++ }
++ sctp_flow.nw_proto = IPPROTO_TCP;
++ sctp_flow.nw_ttl = 255;
++ sctp_flow.tp_src = uflow->tp_src;
++ sctp_flow.tp_dst = uflow->tp_dst;
++ sctp_flow.tcp_flags = htons(TCP_RST);
++
++ struct ovntrace_node *node = ovntrace_node_append(
++ super, OVNTRACE_NODE_TRANSFORMATION, "sctp_abort");
++
++ trace_actions(on->nested, on->nested_len, dp, &sctp_flow,
++ table_id, pipeline, &node->subs);
++}
++
++static void
++execute_sctp_abort(const struct ovnact_nest *on,
++ const struct ovntrace_datapath *dp,
++ const struct flow *uflow, uint8_t table_id,
++ bool loopback, enum ovnact_pipeline pipeline,
++ struct ovs_list *super)
++{
++ if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) {
++ execute_sctp4_abort(on, dp, uflow, table_id, loopback,
++ pipeline, super);
++ } else {
++ execute_sctp6_abort(on, dp, uflow, table_id, loopback,
++ pipeline, super);
++ }
++}
++
++
+ static void
+ execute_reject(const struct ovnact_nest *on,
+ const struct ovntrace_datapath *dp,
+@@ -1814,6 +1899,8 @@ execute_reject(const struct ovnact_nest *on,
+ {
+ if (uflow->nw_proto == IPPROTO_TCP) {
+ execute_tcp_reset(on, dp, uflow, table_id, true, pipeline, super);
++ } else if (uflow->nw_proto == IPPROTO_SCTP) {
++ execute_sctp_abort(on, dp, uflow, table_id, true, pipeline, super);
+ } else {
+ if (get_dl_type(uflow) == htons(ETH_TYPE_IP)) {
+ execute_icmp4(on, dp, uflow, table_id, true, pipeline, super);
+--
+1.8.3.1
+
diff --git a/0005-Add-sctp_abort-logical-flow-action.patch
b/0005-Add-sctp_abort-logical-flow-action.patch
new file mode 100644
index 0000000..c0e8e3c
--- /dev/null
+++ b/0005-Add-sctp_abort-logical-flow-action.patch
@@ -0,0 +1,337 @@
+From 180afce292e2c46966ba56cce128f5b3842133c5 Mon Sep 17 00:00:00 2001
+From: Mark Michelson <mmichels(a)redhat.com>
+Date: Mon, 11 Jan 2021 13:03:44 -0500
+Subject: [PATCH 05/16] Add sctp_abort logical flow action.
+
+This is used in similar situations as tcp_reset, only for SCTP flows.
+
+The "router port unreachable" test has been expanded to test SCTP. In
+doing so, two test cases were removed because they specified SCTP as the
+protocol and expected ICMP replies.
+
+Signed-off-by: Mark Michelson <mmichels(a)redhat.com>
+Acked-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit eba2a1e1ab125f4411eaaaa072ee92a2e6aa676e)
+
+Change-Id: I01a188db2d75feb975703a9ed51ef6e54c7627dd
+---
+ controller/pinctrl.c | 5 +++
+ include/ovn/actions.h | 7 +++++
+ lib/actions.c | 22 +++++++++++++
+ northd/ovn-northd.8.xml | 5 +--
+ northd/ovn-northd.c | 28 +++++++++++++++--
+ tests/ovn.at | 84 +++++++++++++++++++++++++++++++++++++++++++++++--
+ utilities/ovn-trace.c | 5 +++
+ 7 files changed, 150 insertions(+), 6 deletions(-)
+
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index 786f22a..e9549b6 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -3062,6 +3062,11 @@ process_packet_in(struct rconn *swconn, const struct ofp_header
*msg)
+ &userdata, false);
+ break;
+
++ case ACTION_OPCODE_SCTP_ABORT:
++ pinctrl_handle_sctp_abort(swconn, &headers, &packet,
++ &pin.flow_metadata, &userdata, false);
++ break;
++
+ case ACTION_OPCODE_REJECT:
+ pinctrl_handle_reject(swconn, &headers, &packet,
&pin.flow_metadata,
+ &userdata);
+diff --git a/include/ovn/actions.h b/include/ovn/actions.h
+index d104d4d..be87e61 100644
+--- a/include/ovn/actions.h
++++ b/include/ovn/actions.h
+@@ -106,6 +106,7 @@ struct ovn_extend_table;
+ OVNACT(CHK_LB_HAIRPIN_REPLY, ovnact_result) \
+ OVNACT(CT_SNAT_TO_VIP, ovnact_null) \
+ OVNACT(BFD_MSG, ovnact_null) \
++ OVNACT(SCTP_ABORT, ovnact_nest) \
+
+ /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
+ enum OVS_PACKED_ENUM ovnact_type {
+@@ -634,6 +635,12 @@ enum action_opcode {
+ * The actions, in OpenFlow 1.3 format, follow the action_header.
+ */
+ ACTION_OPCODE_BFD_MSG,
++
++ /* "sctp_abort { ...actions... }".
++ *
++ * The actions, in OpenFlow 1.3 format, follow the action_header.
++ */
++ ACTION_OPCODE_SCTP_ABORT,
+ };
+
+ /* Header. */
+diff --git a/lib/actions.c b/lib/actions.c
+index 86be97f..56b8fab 100644
+--- a/lib/actions.c
++++ b/lib/actions.c
+@@ -1491,6 +1491,12 @@ parse_TCP_RESET(struct action_context *ctx)
+ }
+
+ static void
++parse_SCTP_ABORT(struct action_context *ctx)
++{
++ parse_nested_action(ctx, OVNACT_SCTP_ABORT, "sctp", ctx->scope);
++}
++
++static void
+ parse_ND_NA(struct action_context *ctx)
+ {
+ parse_nested_action(ctx, OVNACT_ND_NA, "nd_ns", ctx->scope);
+@@ -1572,6 +1578,12 @@ format_TCP_RESET(const struct ovnact_nest *nest, struct ds *s)
+ }
+
+ static void
++format_SCTP_ABORT(const struct ovnact_nest *nest, struct ds *s)
++{
++ format_nested_action(nest, "sctp_abort", s);
++}
++
++static void
+ format_ND_NA(const struct ovnact_nest *nest, struct ds *s)
+ {
+ format_nested_action(nest, "nd_na", s);
+@@ -1701,6 +1713,14 @@ encode_TCP_RESET(const struct ovnact_nest *on,
+ }
+
+ static void
++encode_SCTP_ABORT(const struct ovnact_nest *on,
++ const struct ovnact_encode_params *ep,
++ struct ofpbuf *ofpacts)
++{
++ encode_nested_actions(on, ep, ACTION_OPCODE_SCTP_ABORT, ofpacts);
++}
++
++static void
+ encode_REJECT(const struct ovnact_nest *on,
+ const struct ovnact_encode_params *ep,
+ struct ofpbuf *ofpacts)
+@@ -3837,6 +3857,8 @@ parse_action(struct action_context *ctx)
+ ovnact_put_IGMP(ctx->ovnacts);
+ } else if (lexer_match_id(ctx->lexer, "tcp_reset")) {
+ parse_TCP_RESET(ctx);
++ } else if (lexer_match_id(ctx->lexer, "sctp_abort")) {
++ parse_SCTP_ABORT(ctx);
+ } else if (lexer_match_id(ctx->lexer, "nd_na")) {
+ parse_ND_NA(ctx);
+ } else if (lexer_match_id(ctx->lexer, "nd_na_router")) {
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index 023fe09..9488d8a 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -519,8 +519,9 @@
+ flows with the
+ <code>tcp_reset { output <-> inport;
+ next(pipeline=egress,table=5);}</code>
+- action for TCP connections and <code>icmp4/icmp6</code> action
+- for UDP connections.
++ action for TCP connections,<code>icmp4/icmp6</code> action
++ for UDP connections, and <code>sctp_abort {output <-%gt; inport;
++ next(pipeline=egress,table=5);}</code> action for SCTP associations.
+ </li>
+ <li>
+ Other ACLs translate to <code>drop;</code> for new or untracked
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index 3d81187..a9bc061 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -10637,7 +10637,7 @@ build_ipv6_input_flows_for_lrouter_port(
+ &op->nbrp->header_, lflows);
+ }
+
+- /* UDP/TCP port unreachable */
++ /* UDP/TCP/SCTP port unreachable */
+ if (!smap_get(&op->od->nbr->options, "chassis")
+ && !op->od->l3dgw_port) {
+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
+@@ -10655,6 +10655,18 @@ build_ipv6_input_flows_for_lrouter_port(
+
+ ds_clear(match);
+ ds_put_format(match,
++ "ip6 && ip6.dst == %s &&
!ip.later_frag && sctp",
++ op->lrp_networks.ipv6_addrs[i].addr_s);
++ action = "sctp_abort {"
++ "eth.dst <-> eth.src; "
++ "ip6.dst <-> ip6.src; "
++ "next; };";
++ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
++ 80, ds_cstr(match), action,
++ &op->nbrp->header_);
++
++ ds_clear(match);
++ ds_put_format(match,
+ "ip6 && ip6.dst == %s &&
!ip.later_frag && udp",
+ op->lrp_networks.ipv6_addrs[i].addr_s);
+ action = "icmp6 {"
+@@ -10914,7 +10926,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
+
+ if (!smap_get(&op->od->nbr->options, "chassis")
+ && !op->od->l3dgw_port) {
+- /* UDP/TCP port unreachable. */
++ /* UDP/TCP/SCTP port unreachable. */
+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ ds_clear(match);
+ ds_put_format(match,
+@@ -10945,6 +10957,18 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
+
+ ds_clear(match);
+ ds_put_format(match,
++ "ip4 && ip4.dst == %s &&
!ip.later_frag && sctp",
++ op->lrp_networks.ipv4_addrs[i].addr_s);
++ action = "sctp_abort {"
++ "eth.dst <-> eth.src; "
++ "ip4.dst <-> ip4.src; "
++ "next; };";
++ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT,
++ 80, ds_cstr(match), action,
++ &op->nbrp->header_);
++
++ ds_clear(match);
++ ds_put_format(match,
+ "ip4 && ip4.dst == %s &&
!ip.later_frag",
+ op->lrp_networks.ipv4_addrs[i].addr_s);
+ action = "icmp4 {"
+diff --git a/tests/ovn.at b/tests/ovn.at
+index a6d2c2d..ead4e83 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -1637,6 +1637,17 @@ tcp_reset { };
+ encodes as controller(userdata=00.00.00.0b.00.00.00.00)
+ has prereqs tcp
+
++# sctp_abort
++sctp_abort {eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
++ formats as sctp_abort { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
++ encodes as
controller(userdata=00.00.00.18.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
++ has prereqs sctp
++
++sctp_abort { };
++ formats as sctp_abort { drop; };
++ encodes as controller(userdata=00.00.00.18.00.00.00.00)
++ has prereqs sctp
++
+ # reject
+ reject { eth.dst = ff:ff:ff:ff:ff:ff; output; }; output;
+ encodes as
controller(userdata=00.00.00.16.00.00.00.00.00.19.00.10.80.00.06.06.ff.ff.ff.ff.ff.ff.00.00.ff.ff.00.10.00.00.23.20.00.0e.ff.f8.40.00.00.00),resubmit(,64)
+@@ -14324,6 +14335,45 @@ test_tcp_syn_packet() {
+ as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ }
+
++# test_sctp_init_packet INPORT HV ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM SCTP_SPORT
SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_IP_CHKSUM EXP_SCTP_ABORT_CHKSUM
++#
++# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP
INIT chunk with
++# ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHKSUM, SCTP_SPORT, SCTP_DPORT, and
SCTP_CHKSUM as specified.
++# The INIT "initiate_tag" will be set to SCTP_INIT_TAG.
++# EXP_IP_CHKSUM and EXP_SCTP_CHKSUM are the ip and sctp checksums of the SCTP ABORT
chunk generated by OVN logical router
++#
++# INPORT is an lport number, e.g. 1 for vif1.
++# HV is a hypervisor number.
++# ETH_SRC and ETH_DST are each 12 hex digits.
++# IPV4_SRC and IPV4_DST are each 8 hex digits.
++# SCTP_SPORT and SCTP_DPORT are 4 hex digits.
++# IP_CHKSUM and EXP_IP_CHKSUM are 4 hex digits.
++# SCTP_CHKSUM and EXP_SCTP_CHKSUM are 8 hex digits.
++test_sctp_init_packet() {
++ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7
++ local sctp_sport=$8 sctp_dport=$9 sctp_init_tag=${10} sctp_chksum=${11}
++ local exp_ip_chksum=${12} exp_sctp_abort_chksum=${13}
++
++ local ip_ttl=ff
++ local eth_hdr=${eth_dst}${eth_src}0800
++ local ip_hdr=4500002500004000${ip_ttl}84${ip_chksum}${ipv4_src}${ipv4_dst}
++ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum}
++ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag}
++
++ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init}
++
++ local sctp_abort_ttl=3e
++ local reply_eth_hdr=${eth_src}${eth_dst}0800
++ local
reply_ip_hdr=4500002400004000${sctp_abort_ttl}84${exp_ip_chksum}${ipv4_dst}${ipv4_src}
++ local
reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum}
++ local reply_sctp_abort=06000004
++
++ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort}
++ echo $reply >> vif$inport.expected
++
++ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
++}
++
+ # test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER TCP_SPORT TCP_DPORT
TCP_CHKSUM EXP_TCP_RST_CHKSUM
+ #
+ # Causes a packet to be received on INPORT of the hypervisor HV. The packet is a TCP syn
segment with
+@@ -14344,6 +14394,36 @@ test_tcp6_packet() {
+ as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
+ }
+
++# test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER SCTP_SPORT SCTP_DPORT
SCTP_INIT_TAG SCTP_CHKSUM EXP_SCTP_ABORT_CHKSUM
++#
++# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP
INIT chunk with
++# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_ROUTER, SCTP_SPORT, SCTP_DPORT and SCTP_CHKSUM as
specified.
++# The INIT "initiate_tag" will be set to SCTP_INIT_TAG.
++# EXP_SCTP_CHKSUM is the sctp checksum of the SCTP ABORT chunk generated by OVN logical
router
++test_sctp6_packet() {
++ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv6_src=$5 ipv6_router=$6
++ local sctp_sport=$7 sctp_dport=$8 sctp_init_tag=$9 sctp_chksum=${10}
++ local exp_sctp_abort_chksum=${11}
++ shift 11
++
++ local eth_hdr=${eth_dst}${eth_src}86dd
++ local ip_hdr=60000000002084ff${ipv6_src}${ipv6_router}
++ local sctp_hdr=${sctp_sport}${sctp_dport}00000000${sctp_chksum}
++ local sctp_init=01000014${sctp_init_tag}0000000000010001${sctp_init_tag}
++
++ local packet=${eth_hdr}${ip_hdr}${sctp_hdr}${sctp_init}
++
++ local reply_eth_hdr=${eth_src}${eth_dst}86dd
++ local reply_ip_hdr=600000000010843e${ipv6_router}${ipv6_src}
++ local
reply_sctp_hdr=${sctp_dport}${sctp_sport}${sctp_init_tag}${exp_sctp_abort_chksum}
++ local reply_sctp_abort=06000004
++
++ local reply=${reply_eth_hdr}${reply_ip_hdr}${reply_sctp_hdr}${reply_sctp_abort}
++ echo $reply >> vif$inport.expected
++
++ check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet
++}
++
+ # test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA
EXP_ICMP_CODE EXP_ICMP_CHKSUM
+ #
+ # Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6
+@@ -14399,13 +14479,13 @@ wait_for_ports_up
+ ovn-nbctl --wait=hv sync
+
+ test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192
168 1 254) 11 0000 f87c f485 0303
+-test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192
168 1 254) 84 0000 f87c f413 0302
+ test_ip6_packet 1 1 000000000001 00000000ff01 20010db8000100000000000000000011
20010db8000100000000000000000001 11 0015 dbb8303900155bac6b646f65206676676e6d66720a 0104
1d31
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+ test_tcp_syn_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex
192 168 2 254) 0000 8b40 3039 0000 b680 6e05
+-test_ip6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011
20010db8000200000000000000000001 84 0004 01020304 0103 5e74
++test_sctp_init_packet 2 2 000000000002 00000000ff02 $(ip_to_hex 192 168 2 1) $(ip_to_hex
192 168 2 254) 0000 8b40 3039 00000001 82112601 b606 10fe95b6
+ test_tcp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011
20010db8000200000000000000000001 8b40 3039 0000 98cd
++test_sctp6_packet 2 2 000000000002 00000000ff02 20010db8000200000000000000000011
20010db8000200000000000000000001 8b40 3039 00000002 C0379D5A 39f23aaf
+ OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [vif2.expected])
+
+ OVN_CLEANUP([hv1], [hv2])
+diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
+index 0d5284e..906e507 100644
+--- a/utilities/ovn-trace.c
++++ b/utilities/ovn-trace.c
+@@ -2594,6 +2594,11 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
+ false, pipeline, super);
+ break;
+
++ case OVNACT_SCTP_ABORT:
++ execute_sctp_abort(ovnact_get_SCTP_ABORT(a), dp, uflow, table_id,
++ false, pipeline, super);
++ break;
++
+ case OVNACT_OVNFIELD_LOAD:
+ execute_ovnfield_load(ovnact_get_OVNFIELD_LOAD(a), super);
+ break;
+--
+1.8.3.1
+
diff --git a/0006-controller-Split-mac-learning-code-to-a-separate-fil.patch
b/0006-controller-Split-mac-learning-code-to-a-separate-fil.patch
new file mode 100644
index 0000000..3203e5a
--- /dev/null
+++ b/0006-controller-Split-mac-learning-code-to-a-separate-fil.patch
@@ -0,0 +1,408 @@
+From 1feebd8577989a0c01bc27d4ee5a8b9eb323ac47 Mon Sep 17 00:00:00 2001
+From: Numan Siddique <numans(a)ovn.org>
+Date: Thu, 26 Nov 2020 18:38:01 +0530
+Subject: [PATCH 06/16] controller: Split mac learning code to a separate file.
+
+This patch moves the 'struct put_mac_binding' from controller/pinctrl.c
+to controller/mac-learn.c as a 'struct mac_binding' and adds the
+relevant functions to access it.
+
+Acked-by: Mark Michelson <mmichels(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit 2c5e546f34862587e345993d629f5ee12dc1ae6c)
+
+Change-Id: Iaf542aab3e026da75a57c2e7b6fd57965320f59c
+---
+ controller/automake.mk | 5 ++-
+ controller/mac-learn.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++
+ controller/mac-learn.h | 46 +++++++++++++++++++++
+ controller/pinctrl.c | 106 +++++++++++++++----------------------------------
+ 4 files changed, 182 insertions(+), 76 deletions(-)
+ create mode 100644 controller/mac-learn.c
+ create mode 100644 controller/mac-learn.h
+
+diff --git a/controller/automake.mk b/controller/automake.mk
+index 480578e..9b8debd 100644
+--- a/controller/automake.mk
++++ b/controller/automake.mk
+@@ -27,7 +27,10 @@ controller_ovn_controller_SOURCES = \
+ controller/ovn-controller.c \
+ controller/ovn-controller.h \
+ controller/physical.c \
+- controller/physical.h
++ controller/physical.h \
++ controller/mac-learn.c \
++ controller/mac-learn.h
++
+ controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
+ man_MANS += controller/ovn-controller.8
+ EXTRA_DIST += controller/ovn-controller.8.xml
+diff --git a/controller/mac-learn.c b/controller/mac-learn.c
+new file mode 100644
+index 0000000..36a6d6e
+--- /dev/null
++++ b/controller/mac-learn.c
+@@ -0,0 +1,101 @@
++/* Copyright (c) 2020, Red Hat, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at:
++ *
++ *
http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include <config.h>
++
++#include "mac-learn.h"
++
++/* OpenvSwitch lib includes. */
++#include "openvswitch/vlog.h"
++#include "lib/smap.h"
++
++VLOG_DEFINE_THIS_MODULE(mac_learn);
++
++#define MAX_MAC_BINDINGS 1000
++
++static size_t mac_binding_hash(uint32_t dp_key, uint32_t port_key,
++ struct in6_addr *);
++static struct mac_binding *mac_binding_find(struct hmap *mac_bindings,
++ uint32_t dp_key,
++ uint32_t port_key,
++ struct in6_addr *ip, size_t hash);
++
++void
++ovn_mac_bindings_init(struct hmap *mac_bindings)
++{
++ hmap_init(mac_bindings);
++}
++
++void
++ovn_mac_bindings_flush(struct hmap *mac_bindings)
++{
++ struct mac_binding *mb;
++ HMAP_FOR_EACH_POP (mb, hmap_node, mac_bindings) {
++ free(mb);
++ }
++}
++
++void
++ovn_mac_bindings_destroy(struct hmap *mac_bindings)
++{
++ ovn_mac_bindings_flush(mac_bindings);
++ hmap_destroy(mac_bindings);
++}
++
++struct mac_binding *
++ovn_mac_binding_add(struct hmap *mac_bindings, uint32_t dp_key,
++ uint32_t port_key, struct in6_addr *ip,
++ struct eth_addr mac)
++{
++ uint32_t hash = mac_binding_hash(dp_key, port_key, ip);
++
++ struct mac_binding *mb =
++ mac_binding_find(mac_bindings, dp_key, port_key, ip, hash);
++ if (!mb) {
++ if (hmap_count(mac_bindings) >= MAX_MAC_BINDINGS) {
++ return NULL;
++ }
++
++ mb = xmalloc(sizeof *mb);
++ mb->dp_key = dp_key;
++ mb->port_key = port_key;
++ mb->ip = *ip;
++ hmap_insert(mac_bindings, &mb->hmap_node, hash);
++ }
++ mb->mac = mac;
++
++ return mb;
++}
++
++static size_t
++mac_binding_hash(uint32_t dp_key, uint32_t port_key, struct in6_addr *ip)
++{
++ return hash_bytes(ip, sizeof *ip, hash_2words(dp_key, port_key));
++}
++
++static struct mac_binding *
++mac_binding_find(struct hmap *mac_bindings, uint32_t dp_key,
++ uint32_t port_key, struct in6_addr *ip, size_t hash)
++{
++ struct mac_binding *mb;
++ HMAP_FOR_EACH_WITH_HASH (mb, hmap_node, hash, mac_bindings) {
++ if (mb->dp_key == dp_key && mb->port_key == port_key &&
++ IN6_ARE_ADDR_EQUAL(&mb->ip, ip)) {
++ return mb;
++ }
++ }
++
++ return NULL;
++}
+diff --git a/controller/mac-learn.h b/controller/mac-learn.h
+new file mode 100644
+index 0000000..3a8520c
+--- /dev/null
++++ b/controller/mac-learn.h
+@@ -0,0 +1,46 @@
++/*
++ * Copyright (c) 2020 Red Hat, Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at:
++ *
++ *
http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#ifndef OVN_MAC_LEARN_H
++#define OVN_MAC_LEARN_H 1
++
++#include <sys/types.h>
++#include <netinet/in.h>
++#include "openvswitch/hmap.h"
++
++struct mac_binding {
++ struct hmap_node hmap_node; /* In a hmap. */
++
++ /* Key. */
++ uint32_t dp_key;
++ uint32_t port_key; /* Port from where this mac_binding is learnt. */
++ struct in6_addr ip;
++
++ /* Value. */
++ struct eth_addr mac;
++};
++
++void ovn_mac_bindings_init(struct hmap *mac_bindings);
++void ovn_mac_bindings_flush(struct hmap *mac_bindings);
++void ovn_mac_bindings_destroy(struct hmap *mac_bindings);
++
++struct mac_binding *ovn_mac_binding_add(struct hmap *mac_bindings,
++ uint32_t dp_key, uint32_t port_key,
++ struct in6_addr *ip,
++ struct eth_addr mac);
++
++
++#endif /* OVN_MAC_LEARN_H */
+\ No newline at end of file
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index e9549b6..0f258ba 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -26,6 +26,7 @@
+ #include "flow.h"
+ #include "ha-chassis.h"
+ #include "lport.h"
++#include "mac-learn.h"
+ #include "nx-match.h"
+ #include "latch.h"
+ #include "lib/packets.h"
+@@ -193,7 +194,6 @@ static void run_put_mac_bindings(
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip)
+ OVS_REQUIRES(pinctrl_mutex);
+ static void wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn);
+-static void flush_put_mac_bindings(void);
+ static void send_mac_binding_buffered_pkts(struct rconn *swconn)
+ OVS_REQUIRES(pinctrl_mutex);
+
+@@ -3893,47 +3893,20 @@ pinctrl_destroy(void)
+ * available. */
+
+ /* Buffered "put_mac_binding" operation. */
+-struct put_mac_binding {
+- struct hmap_node hmap_node; /* In 'put_mac_bindings'. */
+
+- /* Key. */
+- uint32_t dp_key;
+- uint32_t port_key;
+- struct in6_addr ip_key;
+-
+- /* Value. */
+- struct eth_addr mac;
+-};
+-
+-/* Contains "struct put_mac_binding"s. */
++/* Contains "struct mac_binding"s. */
+ static struct hmap put_mac_bindings;
+
+ static void
+ init_put_mac_bindings(void)
+ {
+- hmap_init(&put_mac_bindings);
++ ovn_mac_bindings_init(&put_mac_bindings);
+ }
+
+ static void
+ destroy_put_mac_bindings(void)
+ {
+- flush_put_mac_bindings();
+- hmap_destroy(&put_mac_bindings);
+-}
+-
+-static struct put_mac_binding *
+-pinctrl_find_put_mac_binding(uint32_t dp_key, uint32_t port_key,
+- const struct in6_addr *ip_key, uint32_t hash)
+-{
+- struct put_mac_binding *pa;
+- HMAP_FOR_EACH_WITH_HASH (pa, hmap_node, hash, &put_mac_bindings) {
+- if (pa->dp_key == dp_key
+- && pa->port_key == port_key
+- && IN6_ARE_ADDR_EQUAL(&pa->ip_key, ip_key)) {
+- return pa;
+- }
+- }
+- return NULL;
++ ovn_mac_bindings_destroy(&put_mac_bindings);
+ }
+
+ /* Called with in the pinctrl_handler thread context. */
+@@ -3953,23 +3926,14 @@ pinctrl_handle_put_mac_binding(const struct flow *md,
+ ovs_be128 ip6 = hton128(flow_get_xxreg(md, 0));
+ memcpy(&ip_key, &ip6, sizeof ip_key);
+ }
+- uint32_t hash = hash_bytes(&ip_key, sizeof ip_key,
+- hash_2words(dp_key, port_key));
+- struct put_mac_binding *pmb
+- = pinctrl_find_put_mac_binding(dp_key, port_key, &ip_key, hash);
+- if (!pmb) {
+- if (hmap_count(&put_mac_bindings) >= 1000) {
+- COVERAGE_INC(pinctrl_drop_put_mac_binding);
+- return;
+- }
+
+- pmb = xmalloc(sizeof *pmb);
+- hmap_insert(&put_mac_bindings, &pmb->hmap_node, hash);
+- pmb->dp_key = dp_key;
+- pmb->port_key = port_key;
+- pmb->ip_key = ip_key;
++ struct mac_binding *mb = ovn_mac_binding_add(&put_mac_bindings, dp_key,
++ port_key, &ip_key,
++ headers->dl_src);
++ if (!mb) {
++ COVERAGE_INC(pinctrl_drop_put_mac_binding);
++ return;
+ }
+- pmb->mac = headers->dl_src;
+
+ /* We can send the buffered packet once the main ovn-controller
+ * thread calls pinctrl_run() and it writes the mac_bindings stored
+@@ -4012,12 +3976,12 @@ mac_binding_lookup(struct ovsdb_idl_index
*sbrec_mac_binding_by_lport_ip,
+ /* Update or add an IP-MAC binding for 'logical_port'.
+ * Caller should make sure that 'ovnsb_idl_txn' is valid. */
+ static void
+-mac_binding_add(struct ovsdb_idl_txn *ovnsb_idl_txn,
+- struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+- const char *logical_port,
+- const struct sbrec_datapath_binding *dp,
+- struct eth_addr ea, const char *ip,
+- bool update_only)
++mac_binding_add_to_sb(struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
++ const char *logical_port,
++ const struct sbrec_datapath_binding *dp,
++ struct eth_addr ea, const char *ip,
++ bool update_only)
+ {
+ /* Convert ethernet argument to string form for database. */
+ char mac_string[ETH_ADDR_STRLEN + 1];
+@@ -4073,9 +4037,9 @@ send_garp_locally(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ds ip_s = DS_EMPTY_INITIALIZER;
+
+ ip_format_masked(ip, OVS_BE32_MAX, &ip_s);
+- mac_binding_add(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
+- remote->logical_port, remote->datapath,
+- ea, ds_cstr(&ip_s), update_only);
++ mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
++ remote->logical_port, remote->datapath,
++ ea, ds_cstr(&ip_s), update_only);
+ ds_destroy(&ip_s);
+ }
+ }
+@@ -4085,30 +4049,30 @@ run_put_mac_binding(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
+ struct ovsdb_idl_index *sbrec_port_binding_by_key,
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+- const struct put_mac_binding *pmb)
++ const struct mac_binding *mb)
+ {
+ /* Convert logical datapath and logical port key into lport. */
+ const struct sbrec_port_binding *pb = lport_lookup_by_key(
+ sbrec_datapath_binding_by_key, sbrec_port_binding_by_key,
+- pmb->dp_key, pmb->port_key);
++ mb->dp_key, mb->port_key);
+ if (!pb) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+ VLOG_WARN_RL(&rl, "unknown logical port with datapath
%"PRIu32" "
+- "and port %"PRIu32, pmb->dp_key, pmb->port_key);
++ "and port %"PRIu32, mb->dp_key, mb->port_key);
+ return;
+ }
+
+ /* Convert ethernet argument to string form for database. */
+ char mac_string[ETH_ADDR_STRLEN + 1];
+ snprintf(mac_string, sizeof mac_string,
+- ETH_ADDR_FMT, ETH_ADDR_ARGS(pmb->mac));
++ ETH_ADDR_FMT, ETH_ADDR_ARGS(mb->mac));
+
+ struct ds ip_s = DS_EMPTY_INITIALIZER;
+- ipv6_format_mapped(&pmb->ip_key, &ip_s);
+- mac_binding_add(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
+- pb->logical_port, pb->datapath, pmb->mac,
ds_cstr(&ip_s),
+- false);
++ ipv6_format_mapped(&mb->ip, &ip_s);
++ mac_binding_add_to_sb(ovnsb_idl_txn, sbrec_mac_binding_by_lport_ip,
++ pb->logical_port, pb->datapath, mb->mac,
++ ds_cstr(&ip_s), false);
+ ds_destroy(&ip_s);
+ }
+
+@@ -4125,14 +4089,14 @@ run_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ return;
+ }
+
+- const struct put_mac_binding *pmb;
+- HMAP_FOR_EACH (pmb, hmap_node, &put_mac_bindings) {
++ const struct mac_binding *mb;
++ HMAP_FOR_EACH (mb, hmap_node, &put_mac_bindings) {
+ run_put_mac_binding(ovnsb_idl_txn, sbrec_datapath_binding_by_key,
+ sbrec_port_binding_by_key,
+ sbrec_mac_binding_by_lport_ip,
+- pmb);
++ mb);
+ }
+- flush_put_mac_bindings();
++ ovn_mac_bindings_flush(&put_mac_bindings);
+ }
+
+ static void
+@@ -4188,14 +4152,6 @@ wait_put_mac_bindings(struct ovsdb_idl_txn *ovnsb_idl_txn)
+ }
+ }
+
+-static void
+-flush_put_mac_bindings(void)
+-{
+- struct put_mac_binding *pmb;
+- HMAP_FOR_EACH_POP (pmb, hmap_node, &put_mac_bindings) {
+- free(pmb);
+- }
+-}
+
+ /*
+ * Send gratuitous/reverse ARP for vif on localnet.
+--
+1.8.3.1
+
diff --git a/0007-MAC-learning-Add-a-new-FDB-table-in-southbound-db.patch
b/0007-MAC-learning-Add-a-new-FDB-table-in-southbound-db.patch
new file mode 100644
index 0000000..75706d9
--- /dev/null
+++ b/0007-MAC-learning-Add-a-new-FDB-table-in-southbound-db.patch
@@ -0,0 +1,473 @@
+From 853c8bfd101109fce7b7422486ddaff4e2e6aa03 Mon Sep 17 00:00:00 2001
+From: Numan Siddique <numans(a)ovn.org>
+Date: Fri, 29 Jan 2021 14:46:30 +0530
+Subject: [PATCH 07/16] MAC learning: Add a new FDB table in southbound db.
+
+This patch
+ - Adds a new table 'FDB' in Southbound database.
+ - Adds a new pinctrl_handler action - ACTION_OPCODE_PUT_FDB
+ which learns the port-to-mac bindings and stores it in the
+ 'FDB' table. This handler will be used later.
+ - Upcoming patch adds necessary OVN actions to learn the port-to-mac
+ bindings.
+
+Unlike MAC_Binding Southbound table, FDB table stores the datapath
+tunnel key and port key. This makes it easier for ovn-controller to
+program the OF rules and it doesn't need to do any logical port
+lookup in the Port_Binding table.
+
+Acked-by: Mark Michelson <mmichels(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit 6ec3b12590f9193659d549e30d96b1a22bbb1288)
+
+Change-Id: Iaff80667e71d20fbab434fb3031b2222e22425aa
+---
+ controller/mac-learn.c | 79 +++++++++++++++++++++++++++++
+ controller/mac-learn.h | 20 ++++++++
+ controller/ovn-controller.c | 5 ++
+ controller/pinctrl.c | 118 ++++++++++++++++++++++++++++++++++++++++++++
+ controller/pinctrl.h | 1 +
+ include/ovn/actions.h | 4 ++
+ ovn-sb.ovsschema | 17 ++++++-
+ ovn-sb.xml | 24 +++++++++
+ 8 files changed, 266 insertions(+), 2 deletions(-)
+
+diff --git a/controller/mac-learn.c b/controller/mac-learn.c
+index 36a6d6e..27634dc 100644
+--- a/controller/mac-learn.c
++++ b/controller/mac-learn.c
+@@ -19,11 +19,13 @@
+
+ /* OpenvSwitch lib includes. */
+ #include "openvswitch/vlog.h"
++#include "lib/packets.h"
+ #include "lib/smap.h"
+
+ VLOG_DEFINE_THIS_MODULE(mac_learn);
+
+ #define MAX_MAC_BINDINGS 1000
++#define MAX_FDB_ENTRIES 1000
+
+ static size_t mac_binding_hash(uint32_t dp_key, uint32_t port_key,
+ struct in6_addr *);
+@@ -31,7 +33,12 @@ static struct mac_binding *mac_binding_find(struct hmap
*mac_bindings,
+ uint32_t dp_key,
+ uint32_t port_key,
+ struct in6_addr *ip, size_t hash);
++static size_t fdb_entry_hash(uint32_t dp_key, struct eth_addr *);
+
++static struct fdb_entry *fdb_entry_find(struct hmap *fdbs, uint32_t dp_key,
++ struct eth_addr *mac, size_t hash);
++
++/* mac_binding functions. */
+ void
+ ovn_mac_bindings_init(struct hmap *mac_bindings)
+ {
+@@ -79,6 +86,55 @@ ovn_mac_binding_add(struct hmap *mac_bindings, uint32_t dp_key,
+ return mb;
+ }
+
++/* fdb functions. */
++void
++ovn_fdb_init(struct hmap *fdbs)
++{
++ hmap_init(fdbs);
++}
++
++void
++ovn_fdbs_flush(struct hmap *fdbs)
++{
++ struct fdb_entry *fdb_e;
++ HMAP_FOR_EACH_POP (fdb_e, hmap_node, fdbs) {
++ free(fdb_e);
++ }
++}
++
++void
++ovn_fdbs_destroy(struct hmap *fdbs)
++{
++ ovn_fdbs_flush(fdbs);
++ hmap_destroy(fdbs);
++}
++
++struct fdb_entry *
++ovn_fdb_add(struct hmap *fdbs, uint32_t dp_key, struct eth_addr mac,
++ uint32_t port_key)
++{
++ uint32_t hash = fdb_entry_hash(dp_key, &mac);
++
++ struct fdb_entry *fdb_e =
++ fdb_entry_find(fdbs, dp_key, &mac, hash);
++ if (!fdb_e) {
++ if (hmap_count(fdbs) >= MAX_FDB_ENTRIES) {
++ return NULL;
++ }
++
++ fdb_e = xzalloc(sizeof *fdb_e);
++ fdb_e->dp_key = dp_key;
++ fdb_e->mac = mac;
++ hmap_insert(fdbs, &fdb_e->hmap_node, hash);
++ }
++ fdb_e->port_key = port_key;
++
++ return fdb_e;
++
++}
++
++/* mac_binding related static functions. */
++
+ static size_t
+ mac_binding_hash(uint32_t dp_key, uint32_t port_key, struct in6_addr *ip)
+ {
+@@ -99,3 +155,26 @@ mac_binding_find(struct hmap *mac_bindings, uint32_t dp_key,
+
+ return NULL;
+ }
++
++/* fdb related static functions. */
++
++static size_t
++fdb_entry_hash(uint32_t dp_key, struct eth_addr *mac)
++{
++ uint64_t mac64 = eth_addr_to_uint64(*mac);
++ return hash_2words(dp_key, hash_uint64(mac64));
++}
++
++static struct fdb_entry *
++fdb_entry_find(struct hmap *fdbs, uint32_t dp_key,
++ struct eth_addr *mac, size_t hash)
++{
++ struct fdb_entry *fdb_e;
++ HMAP_FOR_EACH_WITH_HASH (fdb_e, hmap_node, hash, fdbs) {
++ if (fdb_e->dp_key == dp_key && eth_addr_equals(fdb_e->mac, *mac))
{
++ return fdb_e;
++ }
++ }
++
++ return NULL;
++}
+diff --git a/controller/mac-learn.h b/controller/mac-learn.h
+index 3a8520c..7a7897e 100644
+--- a/controller/mac-learn.h
++++ b/controller/mac-learn.h
+@@ -43,4 +43,24 @@ struct mac_binding *ovn_mac_binding_add(struct hmap *mac_bindings,
+ struct eth_addr mac);
+
+
++
++struct fdb_entry {
++ struct hmap_node hmap_node; /* In a hmap. */
++
++ /* Key. */
++ uint32_t dp_key;
++ struct eth_addr mac;
++
++ /* value. */
++ uint32_t port_key;
++};
++
++void ovn_fdb_init(struct hmap *fdbs);
++void ovn_fdbs_flush(struct hmap *fdbs);
++void ovn_fdbs_destroy(struct hmap *fdbs);
++
++struct fdb_entry *ovn_fdb_add(struct hmap *fdbs,
++ uint32_t dp_key, struct eth_addr mac,
++ uint32_t port_key);
++
+ #endif /* OVN_MAC_LEARN_H */
+\ No newline at end of file
+diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
+index fc2a767..cc002db 100644
+--- a/controller/ovn-controller.c
++++ b/controller/ovn-controller.c
+@@ -2471,6 +2471,10 @@ main(int argc, char *argv[])
+ = ip_mcast_index_create(ovnsb_idl_loop.idl);
+ struct ovsdb_idl_index *sbrec_igmp_group
+ = igmp_group_index_create(ovnsb_idl_loop.idl);
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac
++ = ovsdb_idl_index_create2(ovnsb_idl_loop.idl,
++ &sbrec_fdb_col_mac,
++ &sbrec_fdb_col_dp_key);
+
+ ovsdb_idl_track_add_all(ovnsb_idl_loop.idl);
+ ovsdb_idl_omit_alert(ovnsb_idl_loop.idl,
+@@ -2864,6 +2868,7 @@ main(int argc, char *argv[])
+ sbrec_mac_binding_by_lport_ip,
+ sbrec_igmp_group,
+ sbrec_ip_multicast,
++ sbrec_fdb_by_dp_key_mac,
+ sbrec_dns_table_get(ovnsb_idl_loop.idl),
+ sbrec_controller_event_table_get(
+ ovnsb_idl_loop.idl),
+diff --git a/controller/pinctrl.c b/controller/pinctrl.c
+index 0f258ba..3dc1038 100644
+--- a/controller/pinctrl.c
++++ b/controller/pinctrl.c
+@@ -340,6 +340,22 @@ static void bfd_monitor_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ const struct sbrec_chassis *chassis,
+ const struct sset *active_tunnels)
+ OVS_REQUIRES(pinctrl_mutex);
++static void init_fdb_entries(void);
++static void destroy_fdb_entries(void);
++static const struct sbrec_fdb *fdb_lookup(
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
++ uint32_t dp_key, const char *mac);
++static void run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
++ const struct fdb_entry *fdb_e)
++ OVS_REQUIRES(pinctrl_mutex);
++static void run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
++ OVS_REQUIRES(pinctrl_mutex);
++static void wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn);
++static void pinctrl_handle_put_fdb(const struct flow *md,
++ const struct flow *headers)
++ OVS_REQUIRES(pinctrl_mutex);
+
+ COVERAGE_DEFINE(pinctrl_drop_put_mac_binding);
+ COVERAGE_DEFINE(pinctrl_drop_buffered_packets_map);
+@@ -506,6 +522,7 @@ pinctrl_init(void)
+ init_put_vport_bindings();
+ init_svc_monitors();
+ bfd_monitor_init();
++ init_fdb_entries();
+ pinctrl.br_int_name = NULL;
+ pinctrl_handler_seq = seq_create();
+ pinctrl_main_seq = seq_create();
+@@ -3020,6 +3037,12 @@ process_packet_in(struct rconn *swconn, const struct ofp_header
*msg)
+ ovs_mutex_unlock(&pinctrl_mutex);
+ break;
+
++ case ACTION_OPCODE_PUT_FDB:
++ ovs_mutex_lock(&pinctrl_mutex);
++ pinctrl_handle_put_fdb(&pin.flow_metadata.flow, &headers);
++ ovs_mutex_unlock(&pinctrl_mutex);
++ break;
++
+ case ACTION_OPCODE_PUT_DHCPV6_OPTS:
+ pinctrl_handle_put_dhcpv6_opts(swconn, &packet, &pin, &userdata,
+ &continuation);
+@@ -3297,6 +3320,7 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+ struct ovsdb_idl_index *sbrec_igmp_groups,
+ struct ovsdb_idl_index *sbrec_ip_multicast_opts,
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+ const struct sbrec_dns_table *dns_table,
+ const struct sbrec_controller_event_table *ce_table,
+ const struct sbrec_service_monitor_table *svc_mon_table,
+@@ -3333,6 +3357,7 @@ pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ chassis);
+ bfd_monitor_run(ovnsb_idl_txn, bfd_table, sbrec_port_binding_by_name,
+ chassis, active_tunnels);
++ run_put_fdbs(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac);
+ ovs_mutex_unlock(&pinctrl_mutex);
+ }
+
+@@ -3856,6 +3881,7 @@ pinctrl_wait(struct ovsdb_idl_txn *ovnsb_idl_txn)
+ wait_put_vport_bindings(ovnsb_idl_txn);
+ int64_t new_seq = seq_read(pinctrl_main_seq);
+ seq_wait(pinctrl_main_seq, new_seq);
++ wait_put_fdbs(ovnsb_idl_txn);
+ }
+
+ /* Called by ovn-controller. */
+@@ -3877,6 +3903,7 @@ pinctrl_destroy(void)
+ ip_mcast_snoop_destroy();
+ destroy_svc_monitors();
+ bfd_monitor_destroy();
++ destroy_fdb_entries();
+ seq_destroy(pinctrl_main_seq);
+ seq_destroy(pinctrl_handler_seq);
+ }
+@@ -7495,3 +7522,94 @@ pinctrl_handle_svc_check(struct rconn *swconn, const struct flow
*ip_flow,
+ svc_mon->next_send_time = time_msec() + svc_mon->interval;
+ }
+ }
++
++static struct hmap put_fdbs;
++
++/* MAC learning (fdb) related functions. Runs within the main
++ * ovn-controller thread context. */
++
++static void
++init_fdb_entries(void)
++{
++ ovn_fdb_init(&put_fdbs);
++}
++
++static void
++destroy_fdb_entries(void)
++{
++ ovn_fdbs_destroy(&put_fdbs);
++}
++
++static const struct sbrec_fdb *
++fdb_lookup(struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac, uint32_t dp_key,
++ const char *mac)
++{
++ struct sbrec_fdb *fdb = sbrec_fdb_index_init_row(sbrec_fdb_by_dp_key_mac);
++ sbrec_fdb_index_set_dp_key(fdb, dp_key);
++ sbrec_fdb_index_set_mac(fdb, mac);
++
++ const struct sbrec_fdb *retval
++ = sbrec_fdb_index_find(sbrec_fdb_by_dp_key_mac, fdb);
++
++ sbrec_fdb_index_destroy_row(fdb);
++
++ return retval;
++}
++
++static void
++run_put_fdb(struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
++ const struct fdb_entry *fdb_e)
++{
++ /* Convert ethernet argument to string form for database. */
++ char mac_string[ETH_ADDR_STRLEN + 1];
++ snprintf(mac_string, sizeof mac_string,
++ ETH_ADDR_FMT, ETH_ADDR_ARGS(fdb_e->mac));
++
++ /* Update or add an FDB entry. */
++ const struct sbrec_fdb *sb_fdb =
++ fdb_lookup(sbrec_fdb_by_dp_key_mac, fdb_e->dp_key, mac_string);
++ if (!sb_fdb) {
++ sb_fdb = sbrec_fdb_insert(ovnsb_idl_txn);
++ sbrec_fdb_set_dp_key(sb_fdb, fdb_e->dp_key);
++ sbrec_fdb_set_mac(sb_fdb, mac_string);
++ }
++ sbrec_fdb_set_port_key(sb_fdb, fdb_e->port_key);
++}
++
++static void
++run_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn,
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac)
++ OVS_REQUIRES(pinctrl_mutex)
++{
++ if (!ovnsb_idl_txn) {
++ return;
++ }
++
++ const struct fdb_entry *fdb_e;
++ HMAP_FOR_EACH (fdb_e, hmap_node, &put_fdbs) {
++ run_put_fdb(ovnsb_idl_txn, sbrec_fdb_by_dp_key_mac, fdb_e);
++ }
++ ovn_fdbs_flush(&put_fdbs);
++}
++
++
++static void
++wait_put_fdbs(struct ovsdb_idl_txn *ovnsb_idl_txn)
++{
++ if (ovnsb_idl_txn && !hmap_is_empty(&put_fdbs)) {
++ poll_immediate_wake();
++ }
++}
++
++/* Called with in the pinctrl_handler thread context. */
++static void
++pinctrl_handle_put_fdb(const struct flow *md, const struct flow *headers)
++ OVS_REQUIRES(pinctrl_mutex)
++{
++ uint32_t dp_key = ntohll(md->metadata);
++ uint32_t port_key = md->regs[MFF_LOG_INPORT - MFF_REG0];
++
++ ovn_fdb_add(&put_fdbs, dp_key, headers->dl_src, port_key);
++ notify_pinctrl_main();
++}
+diff --git a/controller/pinctrl.h b/controller/pinctrl.h
+index 8555d98..cc0a519 100644
+--- a/controller/pinctrl.h
++++ b/controller/pinctrl.h
+@@ -42,6 +42,7 @@ void pinctrl_run(struct ovsdb_idl_txn *ovnsb_idl_txn,
+ struct ovsdb_idl_index *sbrec_mac_binding_by_lport_ip,
+ struct ovsdb_idl_index *sbrec_igmp_groups,
+ struct ovsdb_idl_index *sbrec_ip_multicast_opts,
++ struct ovsdb_idl_index *sbrec_fdb_by_dp_key_mac,
+ const struct sbrec_dns_table *,
+ const struct sbrec_controller_event_table *,
+ const struct sbrec_service_monitor_table *,
+diff --git a/include/ovn/actions.h b/include/ovn/actions.h
+index be87e61..a2d28c6 100644
+--- a/include/ovn/actions.h
++++ b/include/ovn/actions.h
+@@ -641,6 +641,10 @@ enum action_opcode {
+ * The actions, in OpenFlow 1.3 format, follow the action_header.
+ */
+ ACTION_OPCODE_SCTP_ABORT,
++
++ /* put_fdb(inport, eth.src).
++ */
++ ACTION_OPCODE_PUT_FDB,
+ };
+
+ /* Header. */
+diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
+index 0d20f08..246e390 100644
+--- a/ovn-sb.ovsschema
++++ b/ovn-sb.ovsschema
+@@ -1,7 +1,7 @@
+ {
+ "name": "OVN_Southbound",
+- "version": "20.15.0",
+- "cksum": "539683023 25965",
++ "version": "20.16.0",
++ "cksum": "1219580357 26536",
+ "tables": {
+ "SB_Global": {
+ "columns": {
+@@ -513,6 +513,19 @@
+ "type": {"key": "string",
"value": "string",
+ "min": 0, "max":
"unlimited"}}},
+ "indexes": [["logical_port", "dst_ip",
"src_port", "disc"]],
++ "isRoot": true},
++ "FDB": {
++ "columns": {
++ "mac": {"type": "string"},
++ "dp_key": {
++ "type": {"key": {"type":
"integer",
++ "minInteger": 1,
++ "maxInteger": 16777215}}},
++ "port_key": {
++ "type": {"key": {"type":
"integer",
++ "minInteger": 1,
++ "maxInteger": 16777215}}}},
++ "indexes": [["mac", "dp_key"]],
+ "isRoot": true}
+ }
+ }
+diff --git a/ovn-sb.xml b/ovn-sb.xml
+index 2f251bd..b8912b3 100644
+--- a/ovn-sb.xml
++++ b/ovn-sb.xml
+@@ -4330,4 +4330,28 @@ tcp.flags = RST;
+ </column>
+ </group>
+ </table>
++
++ <table name="FDB" title="Port to MAC bindings">
++ <p>
++ This table is primarily used to learn the MACs observed on a VIF
++ which belongs to a <code>Logical_Switch_Port</code> record in
++ <code>OVN_Northbound</code> whose port security is disabled
++ and 'unknown' address set. If port security is disabled on a
++ <code>Logical_Switch_Port</code> record, OVN should allow traffic
++ with any source mac from the VIF. This table will be used to deliver
++ a packet to the VIF, If a packet's <code>eth.dst</code> is
learnt.
++ </p>
++
++ <column name="mac">
++ The learnt mac address.
++ </column>
++
++ <column name="dp_key">
++ The key of the datapath on which this FDB was learnt.
++ </column>
++
++ <column name="port_key">
++ The key of the port binding on which this FDB was learnt.
++ </column>
++ </table>
+ </database>
+--
+1.8.3.1
+
diff --git a/0008-MAC-learning-Add-new-actions-put_fdb-get_fdb-and-loo.patch
b/0008-MAC-learning-Add-new-actions-put_fdb-get_fdb-and-loo.patch
new file mode 100644
index 0000000..22bc0d1
--- /dev/null
+++ b/0008-MAC-learning-Add-new-actions-put_fdb-get_fdb-and-loo.patch
@@ -0,0 +1,711 @@
+From cfa19b0905752503056e1e8e4246a629da1d8fbd Mon Sep 17 00:00:00 2001
+From: Numan Siddique <numans(a)ovn.org>
+Date: Sat, 30 Jan 2021 00:17:17 +0530
+Subject: [PATCH 08/16] MAC learning: Add new actions - put_fdb, get_fdb and
+ lookup_fdb.
+
+This patch adds these OVN actions to learn port-to-mac bindings on
+the logical ports whose port security is disabled and are configured
+to accept unknown destination packets.
+
+put_fdb(inport, mac) will add an entry in the Southbound 'FDB'
+table.
+
+get_fdb(mac) will get the port key on which the mac is learnt.
+
+lookup_fdb(inport, mac) will check if the port-to-mac entry is already
+present or not. This is added to limit using the action - put_fdb()
+only if required.
+
+An upcoming patch in the series will add the necessary logical flows
+which makes use of these actions.
+
+Acked-by: Mark Michelson <mmichels(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit f819ce8fa2a008d3853f39548f1cef5382ea9cdb)
+
+Change-Id: Iddb4272d93704e5d18a3d87a36cf3b4c9fe52fdb
+---
+ controller/lflow.c | 2 +
+ controller/lflow.h | 2 +
+ include/ovn/actions.h | 29 +++++++
+ include/ovn/logical-fields.h | 4 +
+ lib/actions.c | 176 +++++++++++++++++++++++++++++++++++++++++++
+ ovn-sb.xml | 62 +++++++++++++++
+ tests/ovn.at | 58 ++++++++++++++
+ tests/test-ovn.c | 2 +
+ utilities/ovn-trace.c | 138 ++++++++++++++++++++++++++++++++-
+ 9 files changed, 472 insertions(+), 1 deletion(-)
+
+diff --git a/controller/lflow.c b/controller/lflow.c
+index 21ff51d..97457a8 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -683,6 +683,8 @@ add_matches_to_flow_table(const struct sbrec_logical_flow *lflow,
+ .lb_hairpin_ptable = OFTABLE_CHK_LB_HAIRPIN,
+ .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY,
+ .ct_snat_vip_ptable = OFTABLE_CT_SNAT_FOR_VIP,
++ .fdb_ptable = OFTABLE_GET_FDB,
++ .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
+ };
+ ovnacts_encode(ovnacts->data, ovnacts->size, &ep, &ofpacts);
+
+diff --git a/controller/lflow.h b/controller/lflow.h
+index ba79cc3..d790d51 100644
+--- a/controller/lflow.h
++++ b/controller/lflow.h
+@@ -71,6 +71,8 @@ struct uuid;
+ #define OFTABLE_CHK_LB_HAIRPIN 68
+ #define OFTABLE_CHK_LB_HAIRPIN_REPLY 69
+ #define OFTABLE_CT_SNAT_FOR_VIP 70
++#define OFTABLE_GET_FDB 71
++#define OFTABLE_LOOKUP_FDB 72
+
+ /* The number of tables for the ingress and egress pipelines. */
+ #define LOG_PIPELINE_LEN 24
+diff --git a/include/ovn/actions.h b/include/ovn/actions.h
+index a2d28c6..0402131 100644
+--- a/include/ovn/actions.h
++++ b/include/ovn/actions.h
+@@ -107,6 +107,9 @@ struct ovn_extend_table;
+ OVNACT(CT_SNAT_TO_VIP, ovnact_null) \
+ OVNACT(BFD_MSG, ovnact_null) \
+ OVNACT(SCTP_ABORT, ovnact_nest) \
++ OVNACT(PUT_FDB, ovnact_put_fdb) \
++ OVNACT(GET_FDB, ovnact_get_fdb) \
++ OVNACT(LOOKUP_FDB, ovnact_lookup_fdb) \
+
+ /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
+ enum OVS_PACKED_ENUM ovnact_type {
+@@ -415,6 +418,28 @@ struct ovnact_fwd_group {
+ uint8_t ltable; /* Logical table ID of next table. */
+ };
+
++/* OVNACT_PUT_FDB. */
++struct ovnact_put_fdb {
++ struct ovnact ovnact;
++ struct expr_field port; /* Logical port name. */
++ struct expr_field mac; /* 48-bit Ethernet address. */
++};
++
++/* OVNACT_GET_FDB. */
++struct ovnact_get_fdb {
++ struct ovnact ovnact;
++ struct expr_field mac; /* 48-bit Ethernet address. */
++ struct expr_field dst; /* 32-bit destination field. */
++};
++
++/* OVNACT_LOOKUP_FDB. */
++struct ovnact_lookup_fdb {
++ struct ovnact ovnact;
++ struct expr_field mac; /* 48-bit Ethernet address. */
++ struct expr_field port; /* Logical port name. */
++ struct expr_field dst; /* 1-bit destination field. */
++};
++
+ /* Internal use by the helpers below. */
+ void ovnact_init(struct ovnact *, enum ovnact_type, size_t len);
+ void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len);
+@@ -766,6 +791,10 @@ struct ovnact_encode_params {
+ * 'chk_lb_hairpin_reply' to resubmit. */
+ uint8_t ct_snat_vip_ptable; /* OpenFlow table for
+ * 'ct_snat_to_vip' to resubmit. */
++ uint8_t fdb_ptable; /* OpenFlow table for
++ * 'get_fdb' to resubmit. */
++ uint8_t fdb_lookup_ptable; /* OpenFlow table for
++ * 'lookup_fdb' to resubmit. */
+ };
+
+ void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
+diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
+index aee4748..9d8d07f 100644
+--- a/include/ovn/logical-fields.h
++++ b/include/ovn/logical-fields.h
+@@ -59,6 +59,7 @@ enum mff_log_flags_bits {
+ MLF_NESTED_CONTAINER_BIT = 5,
+ MLF_LOOKUP_MAC_BIT = 6,
+ MLF_LOOKUP_LB_HAIRPIN_BIT = 7,
++ MLF_LOOKUP_FDB_BIT = 8,
+ };
+
+ /* MFF_LOG_FLAGS_REG flag assignments */
+@@ -92,6 +93,9 @@ enum mff_log_flags {
+ MLF_LOOKUP_MAC = (1 << MLF_LOOKUP_MAC_BIT),
+
+ MLF_LOOKUP_LB_HAIRPIN = (1 << MLF_LOOKUP_LB_HAIRPIN_BIT),
++
++ /* Indicate that the lookup in the fdb table was successful. */
++ MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT),
+ };
+
+ /* OVN logical fields
+diff --git a/lib/actions.c b/lib/actions.c
+index 56b8fab..b3433f4 100644
+--- a/lib/actions.c
++++ b/lib/actions.c
+@@ -3743,6 +3743,172 @@ encode_CT_SNAT_TO_VIP(const struct ovnact_null *null OVS_UNUSED,
+ emit_resubmit(ofpacts, ep->ct_snat_vip_ptable);
+ }
+
++static void
++format_PUT_FDB(const struct ovnact_put_fdb *put_fdb, struct ds *s)
++{
++ ds_put_cstr(s, "put_fdb(");
++ expr_field_format(&put_fdb->port, s);
++ ds_put_cstr(s, ", ");
++ expr_field_format(&put_fdb->mac, s);
++ ds_put_cstr(s, ");");
++}
++
++static void
++encode_PUT_FDB(const struct ovnact_put_fdb *put_fdb,
++ const struct ovnact_encode_params *ep OVS_UNUSED,
++ struct ofpbuf *ofpacts)
++{
++ const struct arg args[] = {
++ { expr_resolve_field(&put_fdb->port), MFF_LOG_INPORT },
++ { expr_resolve_field(&put_fdb->mac), MFF_ETH_SRC }
++ };
++ encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
++ encode_controller_op(ACTION_OPCODE_PUT_FDB, ofpacts);
++ encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
++}
++
++static void
++parse_put_fdb(struct action_context *ctx, struct ovnact_put_fdb *put_fdb)
++{
++ lexer_force_match(ctx->lexer, LEX_T_LPAREN);
++ action_parse_field(ctx, 0, false, &put_fdb->port);
++ lexer_force_match(ctx->lexer, LEX_T_COMMA);
++ action_parse_field(ctx, 48, false, &put_fdb->mac);
++ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
++}
++
++static void
++ovnact_put_fdb_free(struct ovnact_put_fdb *put_fdb OVS_UNUSED)
++{
++}
++
++static void
++format_GET_FDB(const struct ovnact_get_fdb *get_fdb, struct ds *s)
++{
++ expr_field_format(&get_fdb->dst, s);
++ ds_put_cstr(s, " = get_fdb(");
++ expr_field_format(&get_fdb->mac, s);
++ ds_put_cstr(s, ");");
++}
++
++static void
++encode_GET_FDB(const struct ovnact_get_fdb *get_fdb,
++ const struct ovnact_encode_params *ep,
++ struct ofpbuf *ofpacts)
++{
++ struct mf_subfield dst = expr_resolve_field(&get_fdb->dst);
++ ovs_assert(dst.field);
++
++ const struct arg args[] = {
++ { expr_resolve_field(&get_fdb->mac), MFF_ETH_DST },
++ };
++ encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
++ put_load(0, MFF_LOG_OUTPORT, 0, 32, ofpacts);
++ emit_resubmit(ofpacts, ep->fdb_ptable);
++ encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
++
++ if (dst.field->id != MFF_LOG_OUTPORT) {
++ struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
++ orm->dst = dst;
++ orm->src.field = mf_from_id(MFF_LOG_OUTPORT);
++ orm->src.ofs = 0;
++ orm->src.n_bits = 32;
++ }
++}
++
++static void
++parse_get_fdb(struct action_context *ctx,
++ struct expr_field *dst,
++ struct ovnact_get_fdb *get_fdb)
++{
++ lexer_get(ctx->lexer); /* Skip get_bfd. */
++ lexer_get(ctx->lexer); /* Skip '('. */
++
++ /* Validate that the destination is a 32-bit, modifiable field if it
++ is not a string field (i.e 'inport' or 'outport'). */
++ if (dst->n_bits) {
++ char *error = expr_type_check(dst, 32, true, ctx->scope);
++ if (error) {
++ lexer_error(ctx->lexer, "%s", error);
++ free(error);
++ return;
++ }
++ }
++ get_fdb->dst = *dst;
++
++ action_parse_field(ctx, 48, false, &get_fdb->mac);
++ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
++}
++
++static void
++ovnact_get_fdb_free(struct ovnact_get_fdb *get_fdb OVS_UNUSED)
++{
++}
++
++static void
++format_LOOKUP_FDB(const struct ovnact_lookup_fdb *lookup_fdb, struct ds *s)
++{
++ expr_field_format(&lookup_fdb->dst, s);
++ ds_put_cstr(s, " = lookup_fdb(");
++ expr_field_format(&lookup_fdb->port, s);
++ ds_put_cstr(s, ", ");
++ expr_field_format(&lookup_fdb->mac, s);
++ ds_put_cstr(s, ");");
++}
++
++static void
++encode_LOOKUP_FDB(const struct ovnact_lookup_fdb *lookup_fdb,
++ const struct ovnact_encode_params *ep,
++ struct ofpbuf *ofpacts)
++{
++ const struct arg args[] = {
++ { expr_resolve_field(&lookup_fdb->port), MFF_LOG_INPORT },
++ { expr_resolve_field(&lookup_fdb->mac), MFF_ETH_SRC },
++ };
++ encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
++
++ struct mf_subfield dst = expr_resolve_field(&lookup_fdb->dst);
++ ovs_assert(dst.field);
++
++ put_load(0, MFF_LOG_FLAGS, MLF_LOOKUP_FDB_BIT, 1, ofpacts);
++ emit_resubmit(ofpacts, ep->fdb_lookup_ptable);
++ encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
++
++ struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
++ orm->dst = dst;
++ orm->src.field = mf_from_id(MFF_LOG_FLAGS);
++ orm->src.ofs = MLF_LOOKUP_FDB_BIT;
++ orm->src.n_bits = 1;
++}
++
++static void
++parse_lookup_fdb(struct action_context *ctx,
++ struct expr_field *dst,
++ struct ovnact_lookup_fdb *lookup_fdb)
++{
++ lexer_get(ctx->lexer); /* Skip lookup_bfd. */
++ lexer_get(ctx->lexer); /* Skip '('. */
++
++ /* Validate that the destination is a 1-bit, modifiable field. */
++ char *error = expr_type_check(dst, 1, true, ctx->scope);
++ if (error) {
++ lexer_error(ctx->lexer, "%s", error);
++ free(error);
++ return;
++ }
++ lookup_fdb->dst = *dst;
++
++ action_parse_field(ctx, 0, false, &lookup_fdb->port);
++ lexer_force_match(ctx->lexer, LEX_T_COMMA);
++ action_parse_field(ctx, 48, false, &lookup_fdb->mac);
++ lexer_force_match(ctx->lexer, LEX_T_RPAREN);
++}
++
++static void
++ovnact_lookup_fdb_free(struct ovnact_lookup_fdb *get_fdb OVS_UNUSED)
++{
++}
++
+ /* Parses an assignment or exchange or put_dhcp_opts action. */
+ static void
+ parse_set_action(struct action_context *ctx)
+@@ -3803,6 +3969,14 @@ parse_set_action(struct action_context *ctx)
+ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
+ parse_chk_lb_hairpin_reply(
+ ctx, &lhs, ovnact_put_CHK_LB_HAIRPIN_REPLY(ctx->ovnacts));
++ } else if (!strcmp(ctx->lexer->token.s, "get_fdb")
++ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
++ parse_get_fdb(
++ ctx, &lhs, ovnact_put_GET_FDB(ctx->ovnacts));
++ } else if (!strcmp(ctx->lexer->token.s, "lookup_fdb")
++ && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
++ parse_lookup_fdb(
++ ctx, &lhs, ovnact_put_LOOKUP_FDB(ctx->ovnacts));
+ } else {
+ parse_assignment_action(ctx, false, &lhs);
+ }
+@@ -3895,6 +4069,8 @@ parse_action(struct action_context *ctx)
+ parse_REJECT(ctx);
+ } else if (lexer_match_id(ctx->lexer, "ct_snat_to_vip")) {
+ ovnact_put_CT_SNAT_TO_VIP(ctx->ovnacts);
++ } else if (lexer_match_id(ctx->lexer, "put_fdb")) {
++ parse_put_fdb(ctx, ovnact_put_PUT_FDB(ctx->ovnacts));
+ } else {
+ lexer_syntax_error(ctx->lexer, "expecting action");
+ }
+diff --git a/ovn-sb.xml b/ovn-sb.xml
+index b8912b3..35000ed 100644
+--- a/ovn-sb.xml
++++ b/ovn-sb.xml
+@@ -1526,6 +1526,68 @@
+ </p>
+ </dd>
+
++ <dt><code><var>P</var> =
get_fdb(<var>A</var>);</code></dt>
++
++ <dd>
++ <p>
++ <b>Parameters</b>:48-bit MAC address field
<var>A</var>.
++ </p>
++
++ <p>
++ Looks up <var>A</var> in fdb table. If an entry is found,
stores
++ the logical port key to the out parameter <code>P</code>.
++ </p>
++
++ <p><b>Example:</b> <code>outport =
get_fdb(eth.src);</code></p>
++ </dd>
++
++ <dt>
++ <code>put_fdb(<var>P</var>,
<var>A</var>);</code>
++ </dt>
++
++ <dd>
++ <p>
++ <b>Parameters</b>: logical port string field
<var>P</var>, 48-bit
++ MAC address field <var>A</var>.
++ </p>
++
++ <p>
++ Adds or updates the entry for Ethernet address <var>A</var> in
++ fdb table, setting its logical port key to <var>P</var>.
++ </p>
++
++ <p><b>Example:</b> <code>put_fdb(inport,
arp.spa);</code></p>
++ </dd>
++
++ <dt>
++ <code><var>R</var> = lookup_fdb(<var>P</var>,
<var>A</var>);</code>
++ </dt>
++
++ <dd>
++ <p>
++ <b>Parameters</b>: 48-bit MAC address field
<var>M</var>,
++ logical port string field <var>P</var>.
++ </p>
++
++ <p>
++ <b>Result</b>: stored to a 1-bit subfield
<var>R</var>.
++ </p>
++
++ <p>
++ Looks up <var>A</var> in fdb table. If an entry is found
++ and the the logical port key is <var>P</var>,
<code>P</code>,
++ stores <code>1</code> in the 1-bit subfield
++ <var>R</var>, else 0.
++ </p>
++
++ <p>
++ <b>Example:</b>
++ <code>
++ reg0[0] = lookup_fdb(inport, eth.src);
++ </code>
++ </p>
++ </dd>
++
+ <dt><code>nd_ns { <var>action</var>;
</code>...<code> };</code></dt>
+ <dd>
+ <p>
+diff --git a/tests/ovn.at b/tests/ovn.at
+index ead4e83..c13f5d6 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -1822,6 +1822,64 @@ ct_snat_to_vip(foo);
+ handle_bfd_msg();
+ encodes as controller(userdata=00.00.00.17.00.00.00.00)
+
++# put_fdb
++put_fdb(inport, arp.sha);
++ encodes as
push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.19.00.00.00.00),pop:NXM_OF_ETH_SRC[]
++ has prereqs eth.type == 0x806
++
++put_fdb(inport, eth.src);
++ encodes as controller(userdata=00.00.00.19.00.00.00.00)
++
++put_fdb(inport, ip4.src);
++ Cannot use 32-bit field ip4.src[0..31] where 48-bit field is required.
++
++# get_fdb
++outport = get_fdb(eth.dst);
++ encodes as set_field:0->reg15,resubmit(,71)
++
++outport = get_fdb(eth.src);
++ encodes as
push:NXM_OF_ETH_DST[],push:NXM_OF_ETH_SRC[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[]
++
++inport = get_fdb(arp.sha);
++ encodes as
push:NXM_OF_ETH_DST[],push:NXM_NX_ARP_SHA[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[],move:NXM_NX_REG15[]->NXM_NX_REG14[]
++ has prereqs eth.type == 0x806
++
++reg0 = get_fdb(arp.tha);
++ encodes as
push:NXM_OF_ETH_DST[],push:NXM_NX_ARP_THA[],pop:NXM_OF_ETH_DST[],set_field:0->reg15,resubmit(,71),pop:NXM_OF_ETH_DST[],move:NXM_NX_REG15[]->NXM_NX_XXREG0[96..127]
++ has prereqs eth.type == 0x806
++
++reg0[1..3] = get_fdb(eth.src);
++ Cannot use 3-bit field reg0[1..3] where 32-bit field is required.
++
++reg15 = get_fdb(eth.dst);
++ Syntax error at `reg15' expecting field name.
++
++outport = get_fdb(ip4.dst);
++ Cannot use 32-bit field ip4.dst[0..31] where 48-bit field is required.
++
++# lookup_fdb
++reg0[0] = lookup_fdb(inport, eth.src);
++ encodes as
set_field:0/0x100->reg10,resubmit(,72),move:NXM_NX_REG10[8]->NXM_NX_XXREG0[96]
++
++reg1[4] = lookup_fdb(outport, eth.dst);
++ encodes as
push:NXM_NX_REG14[],push:NXM_OF_ETH_SRC[],push:NXM_OF_ETH_DST[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_OF_ETH_SRC[],set_field:0/0x100->reg10,resubmit(,72),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG14[],move:NXM_NX_REG10[8]->NXM_NX_XXREG0[68]
++
++reg0[0] = lookup_fdb(outport, arp.sha);
++ encodes as
push:NXM_NX_REG14[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_NX_REG15[],pop:NXM_NX_REG14[],pop:NXM_OF_ETH_SRC[],set_field:0/0x100->reg10,resubmit(,72),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG14[],move:NXM_NX_REG10[8]->NXM_NX_XXREG0[96]
++ has prereqs eth.type == 0x806
++
++reg0 = lookup_fdb(outport, arp.sha);
++ Cannot use 32-bit field reg0[0..31] where 1-bit field is required.
++
++outport = lookup_fdb(outport, arp.sha);
++ Cannot use string field outport where numeric field is required.
++
++reg1[1] = lookup_fdb(outport, ip4.src);
++ Cannot use 32-bit field ip4.src[0..31] where 48-bit field is required.
++
++reg1[1] = lookup_fdb(ip4.src, eth.src);
++ Cannot use numeric field ip4.src where string field is required.
++
+ # Miscellaneous negative tests.
+ ;
+ Syntax error at `;'.
+diff --git a/tests/test-ovn.c b/tests/test-ovn.c
+index 49a1947..3fbe90b 100644
+--- a/tests/test-ovn.c
++++ b/tests/test-ovn.c
+@@ -1346,6 +1346,8 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
+ .lb_hairpin_ptable = OFTABLE_CHK_LB_HAIRPIN,
+ .lb_hairpin_reply_ptable = OFTABLE_CHK_LB_HAIRPIN_REPLY,
+ .ct_snat_vip_ptable = OFTABLE_CT_SNAT_FOR_VIP,
++ .fdb_ptable = OFTABLE_GET_FDB,
++ .fdb_lookup_ptable = OFTABLE_LOOKUP_FDB,
+ };
+ struct ofpbuf ofpacts;
+ ofpbuf_init(&ofpacts, 0);
+diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
+index 906e507..8376309 100644
+--- a/utilities/ovn-trace.c
++++ b/utilities/ovn-trace.c
+@@ -405,6 +405,7 @@ struct ovntrace_datapath {
+ size_t n_flows, allocated_flows;
+
+ struct hmap mac_bindings; /* Contains "struct ovntrace_mac_binding"s.
*/
++ struct hmap fdbs; /* Contains "struct ovntrace_fdb"s. */
+
+ bool has_local_l3gateway;
+ };
+@@ -453,12 +454,24 @@ struct ovntrace_mac_binding {
+ struct eth_addr mac;
+ };
+
++struct ovntrace_fdb {
++ struct hmap_node node;
++ uint16_t port_key;
++ struct eth_addr mac;
++};
++
+ static inline uint32_t
+ hash_mac_binding(uint16_t port_key, const struct in6_addr *ip)
+ {
+ return hash_bytes(ip, sizeof *ip, port_key);
+ }
+
++static inline uint32_t
++hash_fdb(const struct eth_addr *mac)
++{
++ return hash_bytes(mac, sizeof *mac, 0);
++}
++
+ /* Every ovntrace_datapath, by southbound Datapath_Binding record UUID. */
+ static struct hmap datapaths;
+
+@@ -518,6 +531,18 @@ ovntrace_datapath_find_by_name(const char *name)
+ return match;
+ }
+
++static struct ovntrace_datapath *
++ovntrace_datapath_find_by_key(uint32_t tunnel_key)
++{
++ struct ovntrace_datapath *dp;
++ HMAP_FOR_EACH (dp, sb_uuid_node, &datapaths) {
++ if (dp->tunnel_key == tunnel_key) {
++ return dp;
++ }
++ }
++ return NULL;
++}
++
+ static const struct ovntrace_port *
+ ovntrace_port_find_by_key(const struct ovntrace_datapath *dp,
+ uint16_t tunnel_key)
+@@ -598,6 +623,20 @@ ovntrace_mac_binding_find_mac_ip(const struct ovntrace_datapath
*dp,
+ return NULL;
+ }
+
++static const struct ovntrace_fdb *
++ovntrace_fdb_find(const struct ovntrace_datapath *dp,
++ const struct eth_addr *mac)
++{
++ const struct ovntrace_fdb *fdb;
++ HMAP_FOR_EACH_WITH_HASH (fdb, node, hash_fdb(mac),
++ &dp->fdbs) {
++ if (eth_addr_equals(fdb->mac, *mac)) {
++ return fdb;
++ }
++ }
++ return NULL;
++}
++
+ /* If 's' ends with a UUID, returns a copy of it with the UUID truncated to
+ * just the first 6 characters; otherwise, returns a copy of 's'. */
+ static char *
+@@ -638,7 +677,7 @@ read_datapaths(void)
+
+ ovs_list_init(&dp->mcgroups);
+ hmap_init(&dp->mac_bindings);
+-
++ hmap_init(&dp->fdbs);
+ hmap_insert(&datapaths, &dp->sb_uuid_node,
uuid_hash(&dp->sb_uuid));
+ }
+ }
+@@ -1054,6 +1093,30 @@ read_mac_bindings(void)
+ }
+
+ static void
++read_fdbs(void)
++{
++ const struct sbrec_fdb *fdb;
++ SBREC_FDB_FOR_EACH (fdb, ovnsb_idl) {
++ struct eth_addr mac;
++ if (!eth_addr_from_string(fdb->mac, &mac)) {
++ VLOG_WARN("%s: bad Ethernet address", fdb->mac);
++ continue;
++ }
++
++ struct ovntrace_datapath *dp =
++ ovntrace_datapath_find_by_key(fdb->dp_key);
++ if (!dp) {
++ continue;
++ }
++
++ struct ovntrace_fdb *fdb_t = xmalloc(sizeof *fdb_t);
++ fdb_t->mac = mac;
++ fdb_t->port_key = fdb->port_key;
++ hmap_insert(&dp->fdbs, &fdb_t->node, hash_fdb(&mac));
++ }
++}
++
++static void
+ read_db(void)
+ {
+ read_datapaths();
+@@ -1064,6 +1127,7 @@ read_db(void)
+ read_gen_opts();
+ read_flows();
+ read_mac_bindings();
++ read_fdbs();
+ }
+
+ static const struct ovntrace_port *
+@@ -2030,6 +2094,66 @@ execute_lookup_mac_bind_ip(const struct ovnact_lookup_mac_bind_ip
*bind,
+ }
+
+ static void
++execute_lookup_fdb(const struct ovnact_lookup_fdb *lookup_fdb,
++ const struct ovntrace_datapath *dp,
++ struct flow *uflow,
++ struct ovs_list *super)
++{
++ /* Get logical port number.*/
++ struct mf_subfield port_sf = expr_resolve_field(&lookup_fdb->port);
++ ovs_assert(port_sf.n_bits == 32);
++ uint32_t port_key = mf_get_subfield(&port_sf, uflow);
++
++ /* Get MAC. */
++ struct mf_subfield mac_sf = expr_resolve_field(&lookup_fdb->mac);
++ ovs_assert(mac_sf.n_bits == 48);
++ union mf_subvalue mac_sv;
++ mf_read_subfield(&mac_sf, uflow, &mac_sv);
++
++ const struct ovntrace_fdb *fdb_t
++ = ovntrace_fdb_find(dp, &mac_sv.mac);
++
++ struct mf_subfield dst = expr_resolve_field(&lookup_fdb->dst);
++ uint8_t val = 0;
++
++ if (fdb_t && fdb_t->port_key == port_key) {
++ val = 1;
++ ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
++ "/* MAC lookup for "ETH_ADDR_FMT" found in
"
++ "FDB. */", ETH_ADDR_ARGS(uflow->dl_dst));
++ } else {
++ ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
++ "/* lookup mac failed in mac learning table.
*/");
++ }
++ union mf_subvalue sv = { .u8_val = val };
++ mf_write_subfield_flow(&dst, &sv, uflow);
++}
++
++static void
++execute_get_fdb(const struct ovnact_get_fdb *get_fdb,
++ const struct ovntrace_datapath *dp,
++ struct flow *uflow)
++{
++ /* Get MAC. */
++ struct mf_subfield mac_sf = expr_resolve_field(&get_fdb->mac);
++ ovs_assert(mac_sf.n_bits == 48);
++ union mf_subvalue mac_sv;
++ mf_read_subfield(&mac_sf, uflow, &mac_sv);
++
++ const struct ovntrace_fdb *fdb_t
++ = ovntrace_fdb_find(dp, &mac_sv.mac);
++
++ struct mf_subfield dst = expr_resolve_field(&get_fdb->dst);
++ uint32_t val = 0;
++ if (fdb_t) {
++ val = fdb_t->port_key;
++ }
++
++ union mf_subvalue sv = { .be32_int = htonl(val) };
++ mf_write_subfield_flow(&dst, &sv, uflow);
++}
++
++static void
+ execute_put_opts(const struct ovnact_put_opts *po,
+ const char *name, struct flow *uflow,
+ struct ovs_list *super)
+@@ -2638,6 +2762,18 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
+ break;
+ case OVNACT_BFD_MSG:
+ break;
++
++ case OVNACT_PUT_FDB:
++ /* Nothing to do for tracing. */
++ break;
++
++ case OVNACT_GET_FDB:
++ execute_get_fdb(ovnact_get_GET_FDB(a), dp, uflow);
++ break;
++
++ case OVNACT_LOOKUP_FDB:
++ execute_lookup_fdb(ovnact_get_LOOKUP_FDB(a), dp, uflow, super);
++ break;
+ }
+ }
+ ds_destroy(&s);
+--
+1.8.3.1
+
diff --git a/0009-controller-MAC-learning-Add-OF-rules-for-the-FDB-ent.patch
b/0009-controller-MAC-learning-Add-OF-rules-for-the-FDB-ent.patch
new file mode 100644
index 0000000..d9655d7
--- /dev/null
+++ b/0009-controller-MAC-learning-Add-OF-rules-for-the-FDB-ent.patch
@@ -0,0 +1,241 @@
+From 54ae01ced6d791f87fd9ca8ef6a8d6680ce7373f Mon Sep 17 00:00:00 2001
+From: Numan Siddique <numans(a)ovn.org>
+Date: Sat, 30 Jan 2021 12:48:12 +0530
+Subject: [PATCH 09/16] controller: MAC learning: Add OF rules for the FDB
+ entries.
+
+This patch adds the OF rules in table 71 and 72 to support
+'get_fdb' and 'lookup_fdb' actions.
+
+Acked-by: Mark Michelson <mmichels(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit 94bab31ac8d2b8092e4d7a4fa73db9dac2f9ff77)
+
+Change-Id: I0a9f80aa234f9bd6bfe59a59ed3c7ad42ba3746a
+---
+ controller/lflow.c | 103 ++++++++++++++++++++++++++++++++++++++++++++
+ controller/lflow.h | 2 +
+ controller/ovn-controller.c | 27 +++++++++++-
+ 3 files changed, 131 insertions(+), 1 deletion(-)
+
+diff --git a/controller/lflow.c b/controller/lflow.c
+index 97457a8..fb8f130 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -1063,6 +1063,18 @@ put_load(const uint8_t *data, size_t len,
+ }
+
+ static void
++put_load64(uint64_t value, enum mf_field_id dst, int ofs, int n_bits,
++ struct ofpbuf *ofpacts)
++{
++ struct ofpact_set_field *sf = ofpact_put_set_field(ofpacts,
++ mf_from_id(dst), NULL,
++ NULL);
++ ovs_be64 n_value = htonll(value);
++ bitwise_copy(&n_value, 8, 0, sf->value, sf->field->n_bytes, ofs,
n_bits);
++ bitwise_one(ofpact_set_field_mask(sf), sf->field->n_bytes, ofs, n_bits);
++}
++
++static void
+ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
+ const struct hmap *local_datapaths,
+ const struct sbrec_mac_binding *b,
+@@ -1559,6 +1571,61 @@ lflow_handle_changed_neighbors(
+ }
+ }
+
++static void
++consider_fdb_flows(const struct sbrec_fdb *fdb,
++ const struct hmap *local_datapaths,
++ struct ovn_desired_flow_table *flow_table)
++{
++ if (!get_local_datapath(local_datapaths, fdb->dp_key)) {
++ return;
++ }
++
++ struct eth_addr mac;
++ if (!eth_addr_from_string(fdb->mac, &mac)) {
++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
++ VLOG_WARN_RL(&rl, "bad 'mac' %s", fdb->mac);
++ return;
++ }
++
++ struct match match = MATCH_CATCHALL_INITIALIZER;
++ match_set_metadata(&match, htonll(fdb->dp_key));
++ match_set_dl_dst(&match, mac);
++
++ uint64_t stub[1024 / 8];
++ struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
++ put_load64(fdb->port_key, MFF_LOG_OUTPORT, 0, 32, &ofpacts);
++ ofctrl_add_flow(flow_table, OFTABLE_GET_FDB, 100,
++ fdb->header_.uuid.parts[0], &match, &ofpacts,
++ &fdb->header_.uuid);
++ ofpbuf_clear(&ofpacts);
++
++ uint8_t value = 1;
++ put_load(&value, sizeof value, MFF_LOG_FLAGS,
++ MLF_LOOKUP_FDB_BIT, 1, &ofpacts);
++
++ struct match lookup_match = MATCH_CATCHALL_INITIALIZER;
++ match_set_metadata(&lookup_match, htonll(fdb->dp_key));
++ match_set_dl_src(&lookup_match, mac);
++ match_set_reg(&lookup_match, MFF_LOG_INPORT - MFF_REG0, fdb->port_key);
++ ofctrl_add_flow(flow_table, OFTABLE_LOOKUP_FDB, 100,
++ fdb->header_.uuid.parts[0], &lookup_match, &ofpacts,
++ &fdb->header_.uuid);
++ ofpbuf_uninit(&ofpacts);
++}
++
++/* Adds an OpenFlow flow to flow tables for each MAC binding in the OVN
++ * southbound database. */
++static void
++add_fdb_flows(const struct sbrec_fdb_table *fdb_table,
++ const struct hmap *local_datapaths,
++ struct ovn_desired_flow_table *flow_table)
++{
++ const struct sbrec_fdb *fdb;
++ SBREC_FDB_TABLE_FOR_EACH (fdb, fdb_table) {
++ consider_fdb_flows(fdb, local_datapaths, flow_table);
++ }
++}
++
+
+ /* Translates logical flows in the Logical_Flow table in the OVN_SB database
+ * into OpenFlow flows. See ovn-architecture(7) for more information. */
+@@ -1586,6 +1653,8 @@ lflow_run(struct lflow_ctx_in *l_ctx_in, struct lflow_ctx_out
*l_ctx_out)
+ l_ctx_out->flow_table);
+ add_lb_hairpin_flows(l_ctx_in->lb_table, l_ctx_in->local_datapaths,
+ l_ctx_out->flow_table);
++ add_fdb_flows(l_ctx_in->fdb_table, l_ctx_in->local_datapaths,
++ l_ctx_out->flow_table);
+ }
+
+ void
+@@ -1737,3 +1806,37 @@ lflow_handle_changed_lbs(struct lflow_ctx_in *l_ctx_in,
+
+ return true;
+ }
++
++bool
++lflow_handle_changed_fdbs(struct lflow_ctx_in *l_ctx_in,
++ struct lflow_ctx_out *l_ctx_out)
++{
++ const struct sbrec_fdb *fdb;
++
++ SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb, l_ctx_in->fdb_table) {
++ if (sbrec_fdb_is_deleted(fdb)) {
++ VLOG_DBG("Remove fdb flows for deleted fdb "UUID_FMT,
++ UUID_ARGS(&fdb->header_.uuid));
++ ofctrl_remove_flows(l_ctx_out->flow_table, &fdb->header_.uuid);
++ }
++ }
++
++ SBREC_FDB_TABLE_FOR_EACH_TRACKED (fdb, l_ctx_in->fdb_table) {
++ if (sbrec_fdb_is_deleted(fdb)) {
++ continue;
++ }
++
++ if (!sbrec_fdb_is_new(fdb)) {
++ VLOG_DBG("Remove fdb flows for updated fdb "UUID_FMT,
++ UUID_ARGS(&fdb->header_.uuid));
++ ofctrl_remove_flows(l_ctx_out->flow_table, &fdb->header_.uuid);
++ }
++
++ VLOG_DBG("Add fdb flows for fdb "UUID_FMT,
++ UUID_ARGS(&fdb->header_.uuid));
++ consider_fdb_flows(fdb, l_ctx_in->local_datapaths,
++ l_ctx_out->flow_table);
++ }
++
++ return true;
++}
+diff --git a/controller/lflow.h b/controller/lflow.h
+index d790d51..9f8bed4 100644
+--- a/controller/lflow.h
++++ b/controller/lflow.h
+@@ -138,6 +138,7 @@ struct lflow_ctx_in {
+ const struct sbrec_logical_flow_table *logical_flow_table;
+ const struct sbrec_logical_dp_group_table *logical_dp_group_table;
+ const struct sbrec_multicast_group_table *mc_group_table;
++ const struct sbrec_fdb_table *fdb_table;
+ const struct sbrec_chassis *chassis;
+ const struct sbrec_load_balancer_table *lb_table;
+ const struct hmap *local_datapaths;
+@@ -169,6 +170,7 @@ void lflow_handle_changed_neighbors(
+ const struct hmap *local_datapaths,
+ struct ovn_desired_flow_table *);
+ bool lflow_handle_changed_lbs(struct lflow_ctx_in *, struct lflow_ctx_out *);
++bool lflow_handle_changed_fdbs(struct lflow_ctx_in *, struct lflow_ctx_out *);
+ void lflow_destroy(void);
+
+ void lflow_cache_init(struct hmap *);
+diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
+index cc002db..288e2e1 100644
+--- a/controller/ovn-controller.c
++++ b/controller/ovn-controller.c
+@@ -932,7 +932,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+ SB_NODE(dhcp_options, "dhcp_options") \
+ SB_NODE(dhcpv6_options, "dhcpv6_options") \
+ SB_NODE(dns, "dns") \
+- SB_NODE(load_balancer, "load_balancer")
++ SB_NODE(load_balancer, "load_balancer") \
++ SB_NODE(fdb, "fdb")
+
+ enum sb_engine_node {
+ #define SB_NODE(NAME, NAME_STR) SB_##NAME,
+@@ -1863,6 +1864,10 @@ static void init_lflow_ctx(struct engine_node *node,
+ (struct sbrec_load_balancer_table *)EN_OVSDB_GET(
+ engine_get_input("SB_load_balancer", node));
+
++ struct sbrec_fdb_table *fdb_table =
++ (struct sbrec_fdb_table *)EN_OVSDB_GET(
++ engine_get_input("SB_fdb", node));
++
+ struct ovsrec_open_vswitch_table *ovs_table =
+ (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
+ engine_get_input("OVS_open_vswitch", node));
+@@ -1900,6 +1905,7 @@ static void init_lflow_ctx(struct engine_node *node,
+ l_ctx_in->logical_flow_table = logical_flow_table;
+ l_ctx_in->logical_dp_group_table = logical_dp_group_table;
+ l_ctx_in->mc_group_table = multicast_group_table;
++ l_ctx_in->fdb_table = fdb_table,
+ l_ctx_in->chassis = chassis;
+ l_ctx_in->lb_table = lb_table;
+ l_ctx_in->local_datapaths = &rt_data->local_datapaths;
+@@ -2340,6 +2346,23 @@ flow_output_sb_load_balancer_handler(struct engine_node *node,
void *data)
+ return handled;
+ }
+
++static bool
++flow_output_sb_fdb_handler(struct engine_node *node, void *data)
++{
++ struct ed_type_runtime_data *rt_data =
++ engine_get_input_data("runtime_data", node);
++
++ struct ed_type_flow_output *fo = data;
++ struct lflow_ctx_in l_ctx_in;
++ struct lflow_ctx_out l_ctx_out;
++ init_lflow_ctx(node, rt_data, fo, &l_ctx_in, &l_ctx_out);
++
++ bool handled = lflow_handle_changed_fdbs(&l_ctx_in, &l_ctx_out);
++
++ engine_set_node_state(node, EN_UPDATED);
++ return handled;
++}
++
+ struct ovn_controller_exit_args {
+ bool *exiting;
+ bool *restart;
+@@ -2601,6 +2624,8 @@ main(int argc, char *argv[])
+ engine_add_input(&en_flow_output, &en_sb_dns, NULL);
+ engine_add_input(&en_flow_output, &en_sb_load_balancer,
+ flow_output_sb_load_balancer_handler);
++ engine_add_input(&en_flow_output, &en_sb_fdb,
++ flow_output_sb_fdb_handler);
+
+ engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL);
+ engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL);
+--
+1.8.3.1
+
diff --git a/0010-northd-MAC-learning-Add-logical-flows-for-fdb.patch
b/0010-northd-MAC-learning-Add-logical-flows-for-fdb.patch
new file mode 100644
index 0000000..c8b9f1c
--- /dev/null
+++ b/0010-northd-MAC-learning-Add-logical-flows-for-fdb.patch
@@ -0,0 +1,1396 @@
+From da4e67e3ebaf75701f7018f893d2b3285a5da394 Mon Sep 17 00:00:00 2001
+From: Dumitru Ceara <dceara(a)redhat.com>
+Date: Thu, 25 Feb 2021 15:27:43 +0100
+Subject: [PATCH 10/16] northd: MAC learning: Add logical flows for fdb.
+
+This patch adds the logical flows to do mac learning
+for the logical ports whose port security is disabled
+with 'unknown' addresses set.
+
+Until now, OVN delivers the unknown destination packets to all
+the logical ports with 'unknown' addresses set. With this
+mac learning feature, such flooding of packets is avoided once
+OVN has learnt the port-to-mac bindings.
+
+Note that we don't learn macs seen on the localnet logical ports.
+
+Acked-by: Mark Michelson <mmichels(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit dd94f1266ca4f3c750bc59c474ea342ef3ff9983)
+
+Change-Id: Iff8c7789400a681bfe5c913855a4467b1805d2d9
+---
+ controller/lflow.c | 5 +
+ controller/lflow.h | 9 +-
+ lib/ovn-util.h | 3 +
+ northd/ovn-northd.8.xml | 162 ++++++++++++---
+ northd/ovn-northd.c | 96 ++++++---
+ ovn-sb.ovsschema | 6 +-
+ tests/ovn-northd.at | 32 +--
+ tests/ovn.at | 517 ++++++++++++++++++++++++++++++++++++++++++++----
+ utilities/ovn-trace.c | 7 +-
+ 9 files changed, 722 insertions(+), 115 deletions(-)
+
+diff --git a/controller/lflow.c b/controller/lflow.c
+index fb8f130..8f25877 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -88,6 +88,11 @@ static void lflow_resource_destroy_lflow(struct lflow_resource_ref *,
+ static bool
+ lookup_port_cb(const void *aux_, const char *port_name, unsigned int *portp)
+ {
++ if (!strcmp(port_name, "none")) {
++ *portp = 0;
++ return true;
++ }
++
+ const struct lookup_port_aux *aux = aux_;
+
+ const struct sbrec_port_binding *pb
+diff --git a/controller/lflow.h b/controller/lflow.h
+index 9f8bed4..2eb2cb1 100644
+--- a/controller/lflow.h
++++ b/controller/lflow.h
+@@ -60,9 +60,9 @@ struct uuid;
+ * you make any changes. */
+ #define OFTABLE_PHY_TO_LOG 0
+ #define OFTABLE_LOG_INGRESS_PIPELINE 8 /* First of LOG_PIPELINE_LEN tables. */
+-#define OFTABLE_REMOTE_OUTPUT 32
+-#define OFTABLE_LOCAL_OUTPUT 33
+-#define OFTABLE_CHECK_LOOPBACK 34
++#define OFTABLE_REMOTE_OUTPUT 37
++#define OFTABLE_LOCAL_OUTPUT 38
++#define OFTABLE_CHECK_LOOPBACK 39
+ #define OFTABLE_LOG_EGRESS_PIPELINE 40 /* First of LOG_PIPELINE_LEN tables. */
+ #define OFTABLE_SAVE_INPORT 64
+ #define OFTABLE_LOG_TO_PHY 65
+@@ -74,9 +74,6 @@ struct uuid;
+ #define OFTABLE_GET_FDB 71
+ #define OFTABLE_LOOKUP_FDB 72
+
+-/* The number of tables for the ingress and egress pipelines. */
+-#define LOG_PIPELINE_LEN 24
+-
+ enum ref_type {
+ REF_TYPE_ADDRSET,
+ REF_TYPE_PORTGROUP,
+diff --git a/lib/ovn-util.h b/lib/ovn-util.h
+index 0823461..df4b0bc 100644
+--- a/lib/ovn-util.h
++++ b/lib/ovn-util.h
+@@ -261,4 +261,7 @@ BUILD_ASSERT_DECL(SCTP_INIT_CHUNK_LEN == sizeof(struct
sctp_init_chunk));
+ /* See RFC 4960 Sections 3.3.7 and 8.5.1 for information on this flag. */
+ #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0)
+
++/* The number of tables for the ingress and egress pipelines. */
++#define LOG_PIPELINE_LEN 29
++
+ #endif
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index 9488d8a..a964db9 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -307,7 +307,73 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 3: <code>from-lport</code> Pre-ACLs</h3>
++ <h3>Ingress Table 3: Lookup MAC address learning table</h3>
++
++ <p>
++ This table looks up the MAC learning table of the logical switch
++ datapath to check if the <code>port-mac</code> pair is present
++ or not. MAC is learnt only for logical switch VIF ports whose
++ port security is disabled and 'unknown' address set.
++ </p>
++
++ <ul>
++ <li>
++ <p>
++ For each such logical port <var>p</var> whose port security
++ is disabled and 'unknown' address set following flow
++ is added.
++ </p>
++
++ <ul>
++ <li>
++ Priority 100 flow with the match
++ <code>inport == <var>p</var></code> and action
++ <code>reg0[11] = lookup_fdb(inport, eth.src); next;</code>
++ </li>
++ </ul>
++ </li>
++
++ <li>
++ One priority-0 fallback flow that matches all packets and advances to
++ the next table.
++ </li>
++ </ul>
++
++ <h3>Ingress Table 4: Learn MAC of 'unknown' ports.</h3>
++
++ <p>
++ This table learns the MAC addresses seen on the logical ports
++ whose port security is disabled and 'unknown' address set
++ if the <code>lookup_fdb</code> action returned false in the
++ previous table.
++ </p>
++
++ <ul>
++ <li>
++ <p>
++ For each such logical port <var>p</var> whose port security
++ is disabled and 'unknown' address set following flow
++ is added.
++ </p>
++
++ <ul>
++ <li>
++ Priority 100 flow with the match
++ <code>inport == <var>p</var> && reg0[11]
== 0</code> and
++ action <code>put_fdb(inport, eth.src); next;</code> which
stores
++ the <code>port-mac</code> in the mac learning table of the
++ logical switch datapath and advances the packet to the next table.
++ </li>
++ </ul>
++ </li>
++
++ <li>
++ One priority-0 fallback flow that matches all packets and advances to
++ the next table.
++ </li>
++ </ul>
++
++ <h3>Ingress Table 5: <code>from-lport</code> Pre-ACLs</h3>
+
+ <p>
+ This table prepares flows for possible stateful ACL processing in
+@@ -332,7 +398,7 @@
+ db="OVN_Northbound"/> table.
+ </p>
+
+- <h3>Ingress Table 4: Pre-LB</h3>
++ <h3>Ingress Table 6: Pre-LB</h3>
+
+ <p>
+ This table prepares flows for possible stateful load balancing processing
+@@ -399,7 +465,7 @@
+ logical router datapath to logical switch datapath.
+ </p>
+
+- <h3>Ingress Table 5: Pre-stateful</h3>
++ <h3>Ingress Table 7: Pre-stateful</h3>
+
+ <p>
+ This table prepares flows for all possible stateful processing
+@@ -410,7 +476,7 @@
+ <code>ct_next;</code> action.
+ </p>
+
+- <h3>Ingress Table 6: <code>from-lport</code> ACL hints</h3>
++ <h3>Ingress Table 8: <code>from-lport</code> ACL hints</h3>
+
+ <p>
+ This table consists of logical flows that set hints
+@@ -490,7 +556,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress table 7: <code>from-lport</code> ACLs</h3>
++ <h3>Ingress table 9: <code>from-lport</code> ACLs</h3>
+
+ <p>
+ Logical flows in this table closely reproduce those in the
+@@ -599,7 +665,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 8: <code>from-lport</code> QoS
Marking</h3>
++ <h3>Ingress Table 10: <code>from-lport</code> QoS
Marking</h3>
+
+ <p>
+ Logical flows in this table closely reproduce those in the
+@@ -621,7 +687,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 9: <code>from-lport</code> QoS Meter</h3>
++ <h3>Ingress Table 11: <code>from-lport</code> QoS
Meter</h3>
+
+ <p>
+ Logical flows in this table closely reproduce those in the
+@@ -643,7 +709,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 10: LB</h3>
++ <h3>Ingress Table 12: LB</h3>
+
+ <p>
+ It contains a priority-0 flow that simply moves traffic to the next
+@@ -669,7 +735,7 @@
+ connection.)
+ </p>
+
+- <h3>Ingress Table 11: Stateful</h3>
++ <h3>Ingress Table 13: Stateful</h3>
+
+ <ul>
+ <li>
+@@ -727,7 +793,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 12: Pre-Hairpin</h3>
++ <h3>Ingress Table 14: Pre-Hairpin</h3>
+ <ul>
+ <li>
+ If the logical switch has load balancer(s) configured, then a
+@@ -752,7 +818,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 13: Nat-Hairpin</h3>
++ <h3>Ingress Table 15: Nat-Hairpin</h3>
+ <ul>
+ <li>
+ If the logical switch has load balancer(s) configured, then a
+@@ -778,7 +844,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 14: Hairpin</h3>
++ <h3>Ingress Table 16: Hairpin</h3>
+ <ul>
+ <li>
+ A priority-1 flow that hairpins traffic matched by non-default
+@@ -791,7 +857,7 @@
+ </li>
+ </ul>
+
+- <h3>Ingress Table 15: ARP/ND responder</h3>
++ <h3>Ingress Table 17: ARP/ND responder</h3>
+
+ <p>
+ This table implements ARP/ND responder in a logical switch for known
+@@ -1081,7 +1147,7 @@ output;
+ </li>
+ </ul>
+
+- <h3>Ingress Table 16: DHCP option processing</h3>
++ <h3>Ingress Table 18: DHCP option processing</h3>
+
+ <p>
+ This table adds the DHCPv4 options to a DHCPv4 packet from the
+@@ -1142,7 +1208,7 @@ next;
+ </li>
+ </ul>
+
+- <h3>Ingress Table 17: DHCP responses</h3>
++ <h3>Ingress Table 19: DHCP responses</h3>
+
+ <p>
+ This table implements DHCP responder for the DHCP replies generated by
+@@ -1223,7 +1289,7 @@ output;
+ </li>
+ </ul>
+
+- <h3>Ingress Table 18 DNS Lookup</h3>
++ <h3>Ingress Table 20 DNS Lookup</h3>
+
+ <p>
+ This table looks up and resolves the DNS names to the corresponding
+@@ -1252,7 +1318,7 @@ reg0[4] = dns_lookup(); next;
+ </li>
+ </ul>
+
+- <h3>Ingress Table 19 DNS Responses</h3>
++ <h3>Ingress Table 21 DNS Responses</h3>
+
+ <p>
+ This table implements DNS responder for the DNS replies generated by
+@@ -1287,7 +1353,7 @@ output;
+ </li>
+ </ul>
+
+- <h3>Ingress table 20 External ports</h3>
++ <h3>Ingress table 22 External ports</h3>
+
+ <p>
+ Traffic from the <code>external</code> logical ports enter the
ingress
+@@ -1330,7 +1396,7 @@ output;
+ </li>
+ </ul>
+
+- <h3>Ingress Table 21 Destination Lookup</h3>
++ <h3>Ingress Table 23 Destination Lookup</h3>
+
+ <p>
+ This table implements switching behavior. It contains these logical
+@@ -1493,12 +1559,58 @@ output;
+ </li>
+
+ <li>
+- One priority-0 fallback flow that matches all packets and outputs them
+- to the <code>MC_UNKNOWN</code> multicast group, which
+- <code>ovn-northd</code> populates with all enabled logical ports
that
+- accept unknown destination packets. As a small optimization, if no
+- logical ports accept unknown destination packets,
+- <code>ovn-northd</code> omits this multicast group and logical
flow.
++ One priority-0 fallback flow that matches all packets with the
++ action <code>outport = get_fdb(eth.dst); next;</code>. The action
++ <code>get_fdb</code> gets the port for the
<code>eth.dst</code>
++ in the MAC learning table of the logical switch datapath. If there
++ is no entry for <code>eth.dst</code> in the MAC learning table,
++ then it stores <code>none</code> in the
<code>outport</code>.
++ </li>
++ </ul>
++
++ <h3>Ingress Table 23 Destination unknown</h3>
++
++ <p>
++ This table handles the packets whose destination was not found or
++ and looked up in the MAC learning table of the logical switch
++ datapath. It contains the following flows.
++ </p>
++
++ <ul>
++ <li>
++ <p>
++ If the logical switch has logical ports with 'unknown' addresses set,
++ then the below logical flow is added
++ </p>
++
++ <ul>
++ <li>
++ Priority 50 flow with the match <code>outport == none</code>
then
++ outputs them to the <code>MC_UNKNOWN</code> multicast group,
which
++ <code>ovn-northd</code> populates with all enabled logical
ports
++ that accept unknown destination packets. As a small optimization,
++ if no logical ports accept unknown destination packets,
++ <code>ovn-northd</code> omits this multicast group and logical
++ flow.
++ </li>
++ </ul>
++
++ <p>
++ If the logical switch has no logical ports with 'unknown' address
++ set, then the below logical flow is added
++ </p>
++
++ <ul>
++ <li>
++ Priority 50 flow with the match <code>outport == none</code>
++ and drops the packets.
++ </li>
++ </ul>
++ </li>
++
++ <li>
++ One priority-0 fallback flow that outputs the packet to the egress
++ stage with the outport learnt from <code>get_fdb</code> action.
+ </li>
+ </ul>
+
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index a9bc061..d3c8f93 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -142,25 +142,28 @@ enum ovn_stage {
+ PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \
+ PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \
+ PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \
+- PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \
+- PIPELINE_STAGE(SWITCH, IN, PRE_LB, 4, "ls_in_pre_lb") \
+- PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 5, "ls_in_pre_stateful") \
+- PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 6, "ls_in_acl_hint") \
+- PIPELINE_STAGE(SWITCH, IN, ACL, 7, "ls_in_acl") \
+- PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 8, "ls_in_qos_mark") \
+- PIPELINE_STAGE(SWITCH, IN, QOS_METER, 9, "ls_in_qos_meter") \
+- PIPELINE_STAGE(SWITCH, IN, LB, 10, "ls_in_lb") \
+- PIPELINE_STAGE(SWITCH, IN, STATEFUL, 11, "ls_in_stateful") \
+- PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 12, "ls_in_pre_hairpin") \
+- PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 13, "ls_in_nat_hairpin")
\
+- PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 14, "ls_in_hairpin") \
+- PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 15, "ls_in_arp_rsp") \
+- PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 16, "ls_in_dhcp_options") \
+- PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 17, "ls_in_dhcp_response") \
+- PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 18, "ls_in_dns_lookup") \
+- PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 19, "ls_in_dns_response") \
+- PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 20, "ls_in_external_port") \
+- PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 21, "ls_in_l2_lkup") \
++ PIPELINE_STAGE(SWITCH, IN, LOOKUP_FDB , 3, "ls_in_lookup_fdb") \
++ PIPELINE_STAGE(SWITCH, IN, PUT_FDB, 4, "ls_in_put_fdb") \
++ PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 5, "ls_in_pre_acl") \
++ PIPELINE_STAGE(SWITCH, IN, PRE_LB, 6, "ls_in_pre_lb") \
++ PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 7, "ls_in_pre_stateful") \
++ PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 8, "ls_in_acl_hint") \
++ PIPELINE_STAGE(SWITCH, IN, ACL, 9, "ls_in_acl") \
++ PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 10, "ls_in_qos_mark") \
++ PIPELINE_STAGE(SWITCH, IN, QOS_METER, 11, "ls_in_qos_meter") \
++ PIPELINE_STAGE(SWITCH, IN, LB, 12, "ls_in_lb") \
++ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 13, "ls_in_stateful") \
++ PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 14, "ls_in_pre_hairpin") \
++ PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 15, "ls_in_nat_hairpin") \
++ PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 16, "ls_in_hairpin") \
++ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 17, "ls_in_arp_rsp") \
++ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 18, "ls_in_dhcp_options") \
++ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 19, "ls_in_dhcp_response") \
++ PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 20, "ls_in_dns_lookup") \
++ PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 21, "ls_in_dns_response") \
++ PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 22, "ls_in_external_port") \
++ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 23, "ls_in_l2_lkup") \
++ PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 24, "ls_in_l2_unknown") \
+ \
+ /* Logical switch egress stages. */ \
+ PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \
+@@ -227,6 +230,7 @@ enum ovn_stage {
+ #define REGBIT_ACL_HINT_ALLOW "reg0[8]"
+ #define REGBIT_ACL_HINT_DROP "reg0[9]"
+ #define REGBIT_ACL_HINT_BLOCK "reg0[10]"
++#define REGBIT_LKUP_FDB "reg0[11]"
+
+ /* Register definitions for switches and routers. */
+
+@@ -4967,6 +4971,42 @@ build_lswitch_input_port_sec_od(
+ }
+ }
+
++static void
++build_lswitch_learn_fdb_op(
++ struct ovn_port *op, struct hmap *lflows,
++ struct ds *actions, struct ds *match)
++{
++ if (op->nbsp && !op->n_ps_addrs &&
!strcmp(op->nbsp->type, "") &&
++ op->has_unknown) {
++ ds_clear(match);
++ ds_clear(actions);
++ ds_put_format(match, "inport == %s", op->json_key);
++ ds_put_format(actions, REGBIT_LKUP_FDB
++ " = lookup_fdb(inport, eth.src); next;");
++ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 100,
++ ds_cstr(match), ds_cstr(actions),
++ &op->nbsp->header_);
++
++ ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
++ ds_clear(actions);
++ ds_put_cstr(actions, "put_fdb(inport, eth.src); next;");
++ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_PUT_FDB, 100,
++ ds_cstr(match), ds_cstr(actions),
++ &op->nbsp->header_);
++ }
++}
++
++static void
++build_lswitch_learn_fdb_od(
++ struct ovn_datapath *od, struct hmap *lflows)
++{
++
++ if (od->nbs) {
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_LOOKUP_FDB, 0, "1",
"next;");
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_PUT_FDB, 0, "1",
"next;");
++ }
++}
++
+ /* Egress table 8: Egress port security - IP (priorities 90 and 80)
+ * if port security enabled.
+ *
+@@ -6824,16 +6864,25 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows)
+ struct ds actions = DS_EMPTY_INITIALIZER;
+ struct ovn_datapath *od;
+
+- /* Ingress table 19: Destination lookup for unknown MACs (priority 0). */
++ /* Ingress table 24: Destination lookup for unknown MACs (priority 0). */
+ HMAP_FOR_EACH (od, key_node, datapaths) {
+ if (!od->nbs) {
+ continue;
+ }
+
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
++ "outport = get_fdb(eth.dst); next;");
++
+ if (od->has_unknown) {
+- ovn_lflow_add_unique(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1",
+- "outport = \""MC_UNKNOWN"\";
output;");
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
++ "outport == \"none\"",
++ "outport = \""MC_UNKNOWN"\";
output;");
++ } else {
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50,
++ "outport == \"none\"",
"drop;");
+ }
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1",
++ "output;");
+ }
+
+ ds_destroy(&match);
+@@ -11734,6 +11783,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od,
+ build_fwd_group_lflows(od, lsi->lflows);
+ build_lswitch_lflows_admission_control(od, lsi->lflows);
+ build_lswitch_input_port_sec_od(od, lsi->lflows);
++ build_lswitch_learn_fdb_od(od, lsi->lflows);
+ build_lswitch_arp_nd_responder_default(od, lsi->lflows);
+ build_lswitch_dns_lookup_and_response(od, lsi->lflows);
+ build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows);
+@@ -11773,6 +11823,8 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op,
+ /* Build Logical Switch Flows. */
+ build_lswitch_input_port_sec_op(op, lsi->lflows, &lsi->actions,
+ &lsi->match);
++ build_lswitch_learn_fdb_op(op, lsi->lflows, &lsi->actions,
++ &lsi->match);
+ build_lswitch_arp_nd_responder_skip_local(op, lsi->lflows,
+ &lsi->match);
+ build_lswitch_arp_nd_responder_known_ips(op, lsi->lflows,
+diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
+index 246e390..b5d3338 100644
+--- a/ovn-sb.ovsschema
++++ b/ovn-sb.ovsschema
+@@ -1,7 +1,7 @@
+ {
+ "name": "OVN_Southbound",
+- "version": "20.16.0",
+- "cksum": "1219580357 26536",
++ "version": "20.16.1",
++ "cksum": "4243908307 26536",
+ "tables": {
+ "SB_Global": {
+ "columns": {
+@@ -103,7 +103,7 @@
+ "egress"]]}}},
+ "table_id": {"type": {"key":
{"type": "integer",
+ "minInteger": 0,
+- "maxInteger": 23}}},
++ "maxInteger": 32}}},
+ "priority": {"type": {"key":
{"type": "integer",
+ "minInteger": 0,
+ "maxInteger": 65535}}},
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index e4831ae..ce8b654 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -1753,10 +1753,10 @@ AT_CAPTURE_FILE([sw1flows])
+
+ AT_CHECK(
+ [grep -E 'ls_(in|out)_acl' sw0flows sw1flows | grep pg0 | sort], [0], [dnl
+-sw0flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw0flows: table=7 (ls_in_acl ), priority=2002 , match=(inport == @pg0
&& ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=egress,table=6); };)
+-sw1flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw1flows: table=7 (ls_in_acl ), priority=2002 , match=(inport == @pg0
&& ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=egress,table=6); };)
++sw0flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw0flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0
&& ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=egress,table=6); };)
++sw1flows: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw1flows: table=9 (ls_in_acl ), priority=2002 , match=(inport == @pg0
&& ip4 && tcp && tcp.dst == 80), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=egress,table=6); };)
+ ])
+
+ AS_BOX([2])
+@@ -1769,10 +1769,10 @@ ovn-sbctl dump-flows sw1 > sw1flows2
+ AT_CAPTURE_FILE([sw1flows2])
+
+ AT_CHECK([grep "ls_out_acl" sw0flows2 sw1flows2 | grep pg0 | sort], [0], [dnl
+-sw0flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0
&& ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw0flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw1flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0
&& ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw1flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
++sw0flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0
&& ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw0flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw1flows2: table=5 (ls_out_acl ), priority=2002 , match=(outport == @pg0
&& ip4 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw1flows2: table=5 (ls_out_acl ), priority=2003 , match=(outport == @pg0
&& ip6 && udp), action=(reg0 = 0; reject { /* eth.dst <-> eth.src;
ip.dst <-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
+ ])
+
+ AS_BOX([3])
+@@ -1787,16 +1787,16 @@ AT_CAPTURE_FILE([sw1flows3])
+ AT_CHECK([grep "ls_out_acl" sw0flows3 sw1flows3 | grep pg0 | sort], [0], [dnl
+ sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1
&& (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
+ sw0flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1
&& (outport == @pg0 && ip)), action=(next;)
+-sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip4 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
+-sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip6 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
++sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip4 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw0flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=23); };)
++sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip6 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw0flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=23); };)
+ sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1
&& (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;)
+ sw1flows3: table=5 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1
&& (outport == @pg0 && ip)), action=(next;)
+-sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip4 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
+-sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip6 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=21); };)
+-sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=21); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip4 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2002 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip4 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=23); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[10]] == 1)
&& outport == @pg0 && ip6 && udp), action=(ct_commit {
ct_label.blocked = 1; }; reg0 = 0; reject { /* eth.dst <-> eth.src; ip.dst
<-> ip.src; is implicit. */ outport <-> inport;
next(pipeline=ingress,table=23); };)
++sw1flows3: table=5 (ls_out_acl ), priority=2003 , match=((reg0[[9]] == 1)
&& outport == @pg0 && ip6 && udp), action=(reg0 = 0; reject { /*
eth.dst <-> eth.src; ip.dst <-> ip.src; is implicit. */ outport <->
inport; next(pipeline=ingress,table=23); };)
+ ])
+
+ AT_CLEANUP
+diff --git a/tests/ovn.at b/tests/ovn.at
+index c13f5d6..ea155d2 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -8241,18 +8241,18 @@ as hv1
+ AT_CHECK([ovs-vsctl add-port br-int localvif1 -- set Interface localvif1
external_ids:iface-id=localvif1])
+
+ # On hv1, check that there are no flows outputting bcast to tunnel
+-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output |
wc -l` -eq 0])
++OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output |
wc -l` -eq 0])
+
+ # On hv2, check that no flow outputs bcast to tunnel to hv1.
+ as hv2
+-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output |
wc -l` -eq 0])
++OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output |
wc -l` -eq 0])
+
+ # Now bind vif2 on hv2.
+ AT_CHECK([ovs-vsctl add-port br-int localvif2 -- set Interface localvif2
external_ids:iface-id=localvif2])
+
+ # At this point, the broadcast flow on vif2 should be deleted.
+-# because, there is now a localnet vif bound (table=32 programming logic)
+-OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=32 | ofctl_strip | grep output |
wc -l` -eq 0])
++# because, there is now a localnet vif bound (table=37 programming logic)
++OVS_WAIT_UNTIL([test `ovs-ofctl dump-flows br-int table=37 | ofctl_strip | grep output |
wc -l` -eq 0])
+
+ # Verify that the local net patch port exists on hv2.
+ OVS_WAIT_UNTIL([test `ovs-vsctl show | grep "Port patch-br-int-to-ln_port" |
wc -l` -eq 1])
+@@ -10383,12 +10383,12 @@ AT_CAPTURE_FILE([hv2flows])
+
+ AT_CHECK(
+ [# Check that redirect mapping is programmed only on hv2
+- grep table=33 hv1flows | grep =0x3,metadata=0x1 | wc -l
+- grep table=33 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
++ grep table=38 hv1flows | grep =0x3,metadata=0x1 | wc -l
++ grep table=38 hv2flows | grep =0x3,metadata=0x1 | grep load:0x2- | wc -l
+
+ # Check that hv1 sends chassisredirect port traffic to hv2
+- grep table=32 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
+- grep table=32 hv2flows | grep =0x3,metadata=0x1 | wc -l
++ grep table=37 hv1flows | grep =0x3,metadata=0x1 | grep output | wc -l
++ grep table=37 hv2flows | grep =0x3,metadata=0x1 | wc -l
+
+ # Check that arp reply on distributed gateway port is only programmed on hv2
+ grep arp hv1flows | grep load:0x2- | grep =0x2,metadata=0x1 | wc -l
+@@ -10938,8 +10938,8 @@ as hv3 reset_pcap_file hv3-vif1 hv3/vif1
+ as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet
+ sleep 2
+
+-# On hv1, table 32 check that no packet goes via the tunnel port
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=32 \
++# On hv1, table 37 check that no packet goes via the tunnel port
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=37 \
+ | grep "NXM_NX_TUN_ID" | grep -v n_packets=0 | wc -l], [0], [[0
+ ]])
+
+@@ -11629,20 +11629,20 @@ echo $hv2_gw1_ofport
+ echo $hv2_gw2_ofport
+
+ echo "--- hv1 ---"
+-as hv1 ovs-ofctl dump-flows br-int table=32
++as hv1 ovs-ofctl dump-flows br-int table=37
+
+ echo "--- hv2 ---"
+-as hv2 ovs-ofctl dump-flows br-int table=32
++as hv2 ovs-ofctl dump-flows br-int table=37
+
+ gw1_chassis=$(fetch_column Chassis _uuid name=gw1)
+ gw2_chassis=$(fetch_column Chassis _uuid name=gw2)
+
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
+ | wc -l], [0], [1
+ ])
+
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
+ | wc -l], [0], [1
+ ])
+@@ -11683,12 +11683,12 @@ wait_for_ports_up
+ check ovn-nbctl --wait=hv sync
+
+ # we make sure that the hypervisors noticed, and inverted the slave ports
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+@@ -11841,12 +11841,12 @@ ovn-nbctl set Logical_Router_Port outside
ha_chassis_group=$hagrp1_uuid
+ wait_row_count HA_Chassis_Group 1
+ wait_row_count HA_Chassis 2
+
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv1_gw1_ofport,$hv1_gw2_ofport \
+ | wc -l], [0], [1
+ ])
+
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \
+ | wc -l], [0], [1
+ ])
+@@ -11898,12 +11898,12 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group
ref_chassis
+ # Increase the priority of gw2
+ ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40
+
+-OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv1 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv1_gw2_ofport,$hv1_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+
+-OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=32 | \
++OVS_WAIT_UNTIL([as hv2 ovs-ofctl dump-flows br-int table=37 | \
+ grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \
+ | wc -l], [0], [1
+ ])
+@@ -15370,7 +15370,7 @@ wait_for_ports_up ls1-lp_ext1
+ # There should be a flow in hv2 to drop traffic from ls1-lp_ext1 destined
+ # to router mac.
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int \
+-table=28,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
++table=30,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \
+ grep -c "actions=drop"], [0], [1
+ ])
+
+@@ -15968,6 +15968,7 @@ ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \
+
+ ovn-nbctl lsp-add sw0 sw0-p0 \
+ -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.1.2 2001::2"
++
+ ovn-nbctl lsp-add sw0 sw0-p1 \
+ -- lsp-set-addresses sw0-p1 "f0:00:00:11:02:03 192.168.1.3 2001::3"
+
+@@ -16033,7 +16034,7 @@ policy=$(fetch_column nb:Logical_Router_Policy _uuid
priority=2000)
+ check ovn-nbctl set logical_router_policy $policy options:pkt_mark=100
+ as hv2
+ # add a flow in egress pipeline to check pkt marking
+-ovs-ofctl --protocols=OpenFlow13 add-flow br-int
"table=32,priority=200,ip,nw_src=172.16.1.2,pkt_mark=0x64
actions=resubmit(,33)"
++ovs-ofctl --protocols=OpenFlow13 add-flow br-int
"table=37,priority=200,ip,nw_src=172.16.1.2,pkt_mark=0x64
actions=resubmit(,38)"
+
+ dst_ip=$(ip_to_hex 172 16 2 10)
+ fip_ip=$(ip_to_hex 172 16 1 2)
+@@ -16045,7 +16046,7 @@ echo $(get_arp_req f00000010204 $fip_ip $gw_router_ip) >>
expected
+ send_arp_reply 2 1 $gw_router_mac f00000010204 $gw_router_ip $fip_ip
+ echo
"${gw_router_mac}f0000001020408004500001c00004000fe0121b4${fip_ip}${dst_ip}${data}"
>> expected
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=32 | grep pkt_mark=0x64 | grep -q
n_packets=1],[0])
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=37 | grep pkt_mark=0x64 | grep -q
n_packets=1],[0])
+
+ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
+
+@@ -19034,7 +19035,7 @@ m4_define([DVR_N_S_PING],
+ OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected])
+
+ # Confirm that packets did not go out via tunnel port.
+- AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=33 | grep
NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
++ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=38 | grep
NXM_NX_TUN_METADATA0 | grep n_packets=0 | wc -l], [0], [[0
+ ]])
+
+ # Confirm that packet went out via localnet port
+@@ -19164,7 +19165,8 @@ list mac_binding], [0], [lr0-sw0
+ 50:54:00:00:00:03
+ ])
+
+-AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+ AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
+ grep controller | grep -v n_packets=0 | wc -l`])
+
+@@ -19181,7 +19183,8 @@ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int
table=67 | grep n_p
+
+ # The packet should not be sent to ovn-controller. The packet
+ # count should be 1 only.
+-AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+ AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
+ grep controller | grep -v n_packets=0 | wc -l`])
+
+@@ -19194,7 +19197,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
+
+ # The garp packet should be sent to ovn-controller and the mac_binding entry
+ # should be updated.
+-OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+
+ check_row_count MAC_Binding 1
+
+@@ -19219,7 +19223,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa
+
+ # The garp packet should be sent to ovn-controller and the mac_binding entry
+ # should be updated.
+-OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+
+ OVS_WAIT_UNTIL(
+ [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep
dl_src=50:54:00:00:00:33 \
+@@ -19240,7 +19245,8 @@ OVS_WAIT_UNTIL(
+ | grep n_packets=1 | wc -l`]
+ )
+
+-AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+
+ # Now send ARP reply packet with IP - 10.0.0.40 and mac 505400000023
+ eth_src=505400000023
+@@ -19257,7 +19263,8 @@ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
+
+ # The garp packet should be sent to ovn-controller and the mac_binding entry
+ # should be updated.
+-OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+
+ # Wait for an entry in table=67 for the learnt mac_binding entry.
+
+@@ -19273,7 +19280,8 @@ OVS_WAIT_UNTIL(
+ | grep n_packets=1 | wc -l`]
+ )
+
+-AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+
+ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
+ OVS_WAIT_UNTIL(
+@@ -19281,7 +19289,8 @@ OVS_WAIT_UNTIL(
+ | grep n_packets=2 | wc -l`]
+ )
+
+-AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
++AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | \
++grep table_id=10 | wc -l`])
+
+ OVN_CLEANUP([hv1], [hv2])
+ AT_CLEANUP
+@@ -21988,22 +21997,22 @@ AT_CHECK([test ! -z $p1_zoneid])
+ p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed
's/"//g')
+ AT_CHECK([test ! -z $p2_zoneid])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq
1])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
+ reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 1])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw1_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw1_dpkey},\
+ reg15=0x${p2_dpkey} | grep "load:0x${p2_zoneid}->NXM_NX_REG13" | wc -l) -eq
1])
+
+ ovs-vsctl set interface hv1-vif1 external_ids:iface-id=foo
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xdown])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
+
+ p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed
's/"//g')
+@@ -22015,16 +22024,16 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup])
+ p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed
's/"//g')
+ AT_CHECK([test ! -z $p1_zoneid])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 1])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep "load:0x${p1_zoneid}->NXM_NX_REG13" | wc -l) -eq
1])
+
+ ovs-vsctl del-port hv1-vif2
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
+
+-AT_CHECK([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
++AT_CHECK([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
+ reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 0])
+
+ p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed
's/"//g')
+@@ -22032,7 +22041,7 @@ AT_CHECK([test -z $p2_zoneid])
+
+ ovn-nbctl lsp-del sw0-p1
+
+-OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=33,metadata=${sw0_dpkey},\
++OVS_WAIT_UNTIL([test $(ovs-ofctl dump-flows br-int table=38,metadata=${sw0_dpkey},\
+ reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0])
+
+ p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed
's/"//g')
+@@ -22616,7 +22625,8 @@ ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port
lsp-s1-r1 type=router
+
+ # Create logical port p1 in s1
+ ovn-nbctl lsp-add s1 p1 \
+--- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2"
++-- lsp-set-addresses p1 "f0:00:00:00:01:02 10.0.1.2" \
++-- lsp-set-port-security p1 "f0:00:00:00:01:02 10.0.1.2"
+
+ # Create two hypervisor and create OVS ports corresponding to logical ports.
+ net_add n1
+@@ -24221,3 +24231,426 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 |
ofctl_strip_all | \
+
+ OVN_CLEANUP([hv1])
+ AT_CLEANUP
++
++AT_SETUP([ovn -- OVN FDB (MAC learning) - 2 HVs, 2 LS, 1 LR ])
++ovn_start
++
++# Create the first logical switch with one port
++check ovn-nbctl ls-add sw0
++check ovn-nbctl lsp-add sw0 sw0-p1
++check ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" unknown
++
++check ovn-nbctl lsp-add sw0 sw0-p2
++check ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4"
++# Port security is set for sw0-p2
++check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4"
++
++# sw0-p1 and sw0-p3 have unknown address and no port security.
++# FDB should be enabled for these lports.
++check ovn-nbctl lsp-add sw0 sw0-p3
++check ovn-nbctl lsp-set-addresses sw0-p3 unknown
++
++# Create the second logical switch with one port
++check ovn-nbctl ls-add sw1
++check ovn-nbctl lsp-add sw1 sw1-p1
++check ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 11.0.0.3" unknown
++
++check ovn-nbctl lsp-add sw1 sw1-p2
++check ovn-nbctl lsp-set-addresses sw1-p2 "40:54:00:00:00:04 11.0.0.4"
++check ovn-nbctl lsp-set-port-security sw1-p2 "40:54:00:00:00:04 11.0.0.4"
++
++# Create a logical router and attach both logical switches
++check ovn-nbctl lr-add lr0
++check ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
++check ovn-nbctl lsp-add sw0 sw0-lr0
++check ovn-nbctl lsp-set-type sw0-lr0 router
++check ovn-nbctl lsp-set-addresses sw0-lr0 router
++check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
++
++check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 11.0.0.1/24
++check ovn-nbctl lsp-add sw1 sw1-lr0
++check ovn-nbctl lsp-set-type sw1-lr0 router
++check ovn-nbctl lsp-set-addresses sw1-lr0 router
++check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
++ovn-nbctl --wait=hv sync
++
++net_add n1
++
++sim_add hv1
++as hv1
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.1
++ovs-vsctl -- add-port br-int hv1-vif1 -- \
++ set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
++ options:tx_pcap=hv1/vif1-tx.pcap \
++ options:rxq_pcap=hv1/vif1-rx.pcap \
++ ofport-request=1
++ovs-vsctl -- add-port br-int hv1-vif2 -- \
++ set interface hv1-vif2 external-ids:iface-id=sw1-p2 \
++ options:tx_pcap=hv1/vif2-tx.pcap \
++ options:rxq_pcap=hv1/vif2-rx.pcap \
++ ofport-request=2
++ovs-vsctl -- add-port br-int hv1-vif3 -- \
++ set interface hv1-vif3 external-ids:iface-id=sw0-p3 \
++ options:tx_pcap=hv1/vif3-tx.pcap \
++ options:rxq_pcap=hv1/vif3-rx.pcap \
++ ofport-request=3
++
++sim_add hv2
++as hv2
++ovs-vsctl add-br br-phys
++ovn_attach n1 br-phys 192.168.0.2
++ovs-vsctl -- add-port br-int hv2-vif1 -- \
++ set interface hv2-vif1 external-ids:iface-id=sw0-p2 \
++ options:tx_pcap=hv2/vif1-tx.pcap \
++ options:rxq_pcap=hv2/vif1-rx.pcap \
++ ofport-request=1
++ovs-vsctl -- add-port br-int hv2-vif2 -- \
++ set interface hv2-vif2 external-ids:iface-id=sw1-p1 \
++ options:tx_pcap=hv2/vif2-tx.pcap \
++ options:rxq_pcap=hv2/vif2-rx.pcap \
++ ofport-request=2
++
++OVN_POPULATE_ARP
++
++ip_to_hex() {
++ printf "%02x%02x%02x%02x" "$@"
++}
++
++send_icmp_packet() {
++ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7
data=$8
++ shift 8
++
++ local ip_ttl=ff
++ local ip_len=001c
++ local
packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data}
++ echo $packet > expected
++ as hv$hv ovs-appctl netdev-dummy/receive hv$hv-vif$inport $packet
++}
++
++reset_pcap_file() {
++ local iface=$1
++ local pcap_file=$2
++ ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
++options:rxq_pcap=dummy-rx.pcap
++ rm -f ${pcap_file}*.pcap
++ ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
++options:rxq_pcap=${pcap_file}-rx.pcap
++}
++
++trim_zeros() {
++ sed 's/\(00\)\{1,\}$//'
++}
++
++AS_BOX([Wait for all ports to be up])
++wait_for_ports_up
++
++# Check that there is put_fdb() flow added by ovn-northd for sw0-p1
++ovn-sbctl dump-flows sw0 > sw0flows
++AT_CAPTURE_FILE([sw0flows])
++
++AT_CHECK([grep "ls_in_lookup_fdb" sw0flows | sort], [0], [dnl
++ table=3 (ls_in_lookup_fdb ), priority=0 , dnl
++match=(1), action=(next;)
++ table=3 (ls_in_lookup_fdb ), priority=100 , dnl
++match=(inport == "sw0-p1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
next;)
++ table=3 (ls_in_lookup_fdb ), priority=100 , dnl
++match=(inport == "sw0-p3"), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
next;)
++])
++
++AT_CHECK([grep "ls_in_put_fdb" sw0flows | sort], [0], [dnl
++ table=4 (ls_in_put_fdb ), priority=0 , dnl
++match=(1), action=(next;)
++ table=4 (ls_in_put_fdb ), priority=100 , dnl
++match=(inport == "sw0-p1" && reg0[[11]] == 0), action=(put_fdb(inport,
eth.src); next;)
++ table=4 (ls_in_put_fdb ), priority=100 , dnl
++match=(inport == "sw0-p3" && reg0[[11]] == 0), action=(put_fdb(inport,
eth.src); next;)
++])
++
++# Send a packet from sw0-p1 with a different mac not present
++# in it's addresses.
++AS_BOX([Send a pkt from sw0-p1 with a different mac address])
++
++# Use the src mac 50:54:00:00:00:13 instead of 50:54:00:00:00:03
++src_mac=505400000013
++src_ip=$(ip_to_hex 10 0 0 13)
++
++# send the packet to sw0-p2
++dst_mac=505400000004
++dst_ip=$(ip_to_hex 10 0 0 4)
++
++data=0800bee4391a0001
++send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
++
++# There should be one row in fdb
++AS_BOX([Check that the FDB entry is created])
++wait_row_count FDB 1
++
++sw0_dpkey=$(fetch_column datapath_binding tunnel_key external_ids:name=sw0)
++sw0p1_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p1)
++sw0p3_dpkey=$(fetch_column port_binding tunnel_key logical_port=sw0-p3)
++
++check_column '50:54:00:00:00:13' fdb mac
++check_column $sw0_dpkey fdb dp_key
++check_column $sw0p1_dpkey fdb port_key
++
++# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2.
++AS_BOX([Check that ovn-controller programs the flows for FDB])
++as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
++as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
++
++AT_CAPTURE_FILE([hv1_offlows_table71.txt])
++AT_CAPTURE_FILE([hv2_offlows_table71.txt])
++AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG15[[]]
++])
++
++AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG15[[]]
++])
++
++as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
++as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
++
++AT_CAPTURE_FILE([hv1_offlows_table72.txt])
++AT_CAPTURE_FILE([hv2_offlows_table72.txt])
++AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG10[[8]]
++])
++
++AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG10[[8]]
++])
++
++# Use the src mac 50:54:00:00:00:14 instead of 50:54:00:00:00:03
++src_mac=505400000014
++src_ip=$(ip_to_hex 10 0 0 14)
++
++as hv2 reset_pcap_file hv2-vif1 hv2/vif1
++
++send_icmp_packet 1 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
++
++# There should be two rows in fdb
++wait_row_count FDB 2
++
++check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac
++check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key
++check_column "$sw0p1_dpkey $sw0p1_dpkey" fdb port_key
++
++# Make sure that OVS tables 71 and 72 are populated on both hv1 and hv2.
++as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
++as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
++
++AT_CAPTURE_FILE([hv1_offlows_table71.txt])
++AT_CAPTURE_FILE([hv2_offlows_table71.txt])
++AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG15[[]]
++priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14
actions=load:0x1->NXM_NX_REG15[[]]
++])
++
++AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,metadata=0x1,dl_dst=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG15[[]]
++priority=100,metadata=0x1,dl_dst=50:54:00:00:00:14
actions=load:0x1->NXM_NX_REG15[[]]
++])
++
++as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
++as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
++
++AT_CAPTURE_FILE([hv1_offlows_table72.txt])
++AT_CAPTURE_FILE([hv2_offlows_table72.txt])
++AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG10[[8]]
++priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14
actions=load:0x1->NXM_NX_REG10[[8]]
++])
++
++AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST | cut -d ' ' -f8- | sort],
[0], [dnl
++priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:13
actions=load:0x1->NXM_NX_REG10[[8]]
++priority=100,reg14=0x1,metadata=0x1,dl_src=50:54:00:00:00:14
actions=load:0x1->NXM_NX_REG10[[8]]
++])
++
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++as hv2 reset_pcap_file hv2-vif1 hv2/vif1
++
++# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:13
++src_mac=505400000004
++src_ip=$(ip_to_hex 10 0 0 4)
++
++dst_mac=505400000013
++dst_ip=$(ip_to_hex 10 0 0 13)
++
++send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
++
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++dst_mac=505400000014
++dst_ip=$(ip_to_hex 10 0 0 14)
++
++send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
++
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++as hv1 reset_pcap_file hv1-vif3 hv1/vif3
++
++# Send a packet from sw0-p2 to an unknown mac. Should be received
++# by both sw0-p1 and sw0-p3 (as unknown is set).
++AS_BOX([Send pkt from sw0-p2 to an unknown mac])
++
++src_mac=505400000004
++src_ip=$(ip_to_hex 10 0 0 4)
++
++dst_mac=505400000023
++dst_ip=$(ip_to_hex 10 0 0 23)
++
++send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
++OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
++
++AS_BOX([Flip the mac - 50:54:00:00:00:13 from sw0-p1 to sw0-p3])
++
++# Use the src mac 50:54:00:00:00:13
++src_mac=505400000013
++src_ip=$(ip_to_hex 10 0 0 23)
++
++# send the packet to sw0-p2
++dst_mac=505400000004
++dst_ip=$(ip_to_hex 10 0 0 4)
++
++data=0800bee4391a0001
++
++as hv2 reset_pcap_file hv2-vif1 hv2/vif1
++as hv1 reset_pcap_file hv1-vif3 hv1/vif3
++
++# Send the pkt from sw0-p3 to sw0-p2.
++send_icmp_packet 3 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
++
++# fdb row count should be still 2. But the mac 50:54:00:00:00:13
++# should be learnt on sw0-p3.
++
++wait_row_count FDB 2
++
++check_column "50:54:00:00:00:13 50:54:00:00:00:14" fdb mac
++check_column "$sw0_dpkey $sw0_dpkey" fdb dp_key
++check_column "$sw0p1_dpkey $sw0p3_dpkey" fdb port_key
++
++check_column "$sw0p3_dpkey" fdb port_key
mac="50\:54\:00\:00\:00\:13"
++
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++as hv1 reset_pcap_file hv1-vif3 hv1/vif3
++
++# Send the packet from sw0-p2 to sw0-p3 with the dst mac 50:54:00:00:00:13
++src_mac=505400000004
++src_ip=$(ip_to_hex 10 0 0 4)
++
++dst_mac=505400000013
++dst_ip=$(ip_to_hex 10 0 0 13)
++
++send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
++
++# sw0-p1 should not receive the packet.
++: > expected
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
++
++AS_BOX([Test routing])
++
++# Test the routing.
++# Send the packet from sw1-p2 (hv1) to sw0-p1 (hv1) with dst ip 10.0.0.14
++# The packet should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14
++# Before sending add mac_binding entry for 10.0.0.14
++
++lr0_dp_uuid=$(fetch_column datapath_binding _uuid external_ids:name=lr0)
++
++ovn-sbctl create mac_binding ip=10.0.0.14 logical_port=lr0-sw0 \
++mac="50\:54\:00\:00\:00\:14" datapath=$lr0_dp_uuid
++
++# Wait till the mac_binding flows appear in hv1
++OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=66 \
++| grep -c reg0=0xa00000e)])
++
++src_mac=405400000004
++src_ip=$(ip_to_hex 11 0 0 4)
++
++dst_mac=00000000ff02 # lr0-sw1 mac
++dst_ip=$(ip_to_hex 10 0 0 14)
++
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++as hv1 reset_pcap_file hv1-vif3 hv1/vif3
++
++send_icmp_packet 2 1 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++
++exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data}
++echo $exp_packet > expected
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
++
++# sw0-p3 should not receive the packet.
++: > expected
++OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
++
++# Now the send the packet from sw1-p1 (hv2) to sw0-p1 (hv1) with dst ip 10.0.0.14
++# The acket should be delivered to sw0-p1 with dst mac 50:54:00:00:00:14
++
++src_mac=405400000003
++src_ip=$(ip_to_hex 11 0 0 3)
++
++dst_mac=00000000ff02 # lr0-sw1 mac
++dst_ip=$(ip_to_hex 10 0 0 14)
++
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++send_icmp_packet 2 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++
++exp_packet=50540000001400000000ff0108004500001c00004000fe010100${src_ip}${dst_ip}${data}
++echo $exp_packet > expected
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
++
++AS_BOX([Clear the FDB rows])
++
++# Clear the fdb rows.
++check ovn-sbctl --all destroy fdb
++ovn-sbctl list fdb
++
++as hv1 reset_pcap_file hv1-vif1 hv1/vif1
++as hv1 reset_pcap_file hv1-vif3 hv1/vif3
++
++# Send the packet from sw0-p2 to sw0-p1 with the dst mac 50:54:00:00:00:14
++# It should be delivered to both sw0-p1 and sw0-p3 since we have cleared the
++# FDB table.
++src_mac=505400000004
++src_ip=$(ip_to_hex 10 0 0 4)
++
++dst_mac=505400000014
++dst_ip=$(ip_to_hex 10 0 0 13)
++
++send_icmp_packet 1 2 $src_mac $dst_mac $src_ip $dst_ip 0000 $data
++
++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
++OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected])
++
++# Make sure that OVS tables 71 and 72 are empty.
++as hv1 ovs-ofctl dump-flows br-int table=71 > hv1_offlows_table71.txt
++as hv2 ovs-ofctl dump-flows br-int table=71 > hv2_offlows_table71.txt
++
++AT_CAPTURE_FILE([hv1_offlows_table71.txt])
++AT_CAPTURE_FILE([hv2_offlows_table71.txt])
++AT_CHECK([cat hv1_offlows_table71.txt | grep -v NXST], [1], [dnl
++])
++
++AT_CHECK([cat hv2_offlows_table71.txt | grep -v NXST], [1], [dnl
++])
++
++as hv1 ovs-ofctl dump-flows br-int table=72 > hv1_offlows_table72.txt
++as hv2 ovs-ofctl dump-flows br-int table=72 > hv2_offlows_table72.txt
++
++AT_CAPTURE_FILE([hv1_offlows_table72.txt])
++AT_CAPTURE_FILE([hv2_offlows_table72.txt])
++AT_CHECK([cat hv1_offlows_table72.txt | grep -v NXST], [1], [dnl
++])
++
++AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl
++])
++
++OVN_CLEANUP([hv1], [hv2])
++AT_CLEANUP
+diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
+index 8376309..fb88bc0 100644
+--- a/utilities/ovn-trace.c
++++ b/utilities/ovn-trace.c
+@@ -945,7 +945,7 @@ parse_lflow_for_datapath(const struct sbrec_logical_flow *sblf,
+ .pipeline = (!strcmp(sblf->pipeline, "ingress")
+ ? OVNACT_P_INGRESS
+ : OVNACT_P_EGRESS),
+- .n_tables = 24,
++ .n_tables = LOG_PIPELINE_LEN,
+ .cur_ltable = sblf->table_id,
+ };
+ uint64_t stub[1024 / 8];
+@@ -1184,6 +1184,11 @@ ovntrace_lookup_port(const void *dp_, const char *port_name,
+ return true;
+ }
+
++ if (!strcmp(port_name, "none")) {
++ *portp = 0;
++ return true;
++ }
++
+ const struct ovntrace_port *port = ovntrace_port_lookup_by_name(port_name);
+ if (port) {
+ if (port->dp == dp) {
+--
+1.8.3.1
+
diff --git a/0011-Fix-the-failing-test-case-ovn-ACL-skip-hints-for-sta.patch
b/0011-Fix-the-failing-test-case-ovn-ACL-skip-hints-for-sta.patch
new file mode 100644
index 0000000..91e40a6
--- /dev/null
+++ b/0011-Fix-the-failing-test-case-ovn-ACL-skip-hints-for-sta.patch
@@ -0,0 +1,84 @@
+From f69f2d79d50efce77831f04464b47a3a58cbc3f4 Mon Sep 17 00:00:00 2001
+From: Numan Siddique <numans(a)ovn.org>
+Date: Sat, 20 Feb 2021 16:08:13 +0530
+Subject: [PATCH 11/16] Fix the failing test case - ovn -- ACL skip hints for
+ stateless config.
+
+The commit dd94f1266ca added a couple of stages in logical switch
+pipeline. The commit should have corrected the proper tables for
+this test.
+
+Fixes: dd94f1266ca("northd: MAC learning: Add logical flows for fdb.")
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit 57b2c0afba8a7302a9c58d8618b33e2e48521ccc)
+
+Change-Id: Ic44891e3247bc04e155986460f6114652f78556f
+---
+ tests/ovn-northd.at | 44 ++++++++++++++++++++++----------------------
+ 1 file changed, 22 insertions(+), 22 deletions(-)
+
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index ce8b654..ec64e92 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -1942,17 +1942,17 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e
ls_out_acl_hint -e
+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel
&& !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel
&& !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(next;)
+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est &&
ct.rpl && ct_label.blocked == 1)), action=(drop;)
+- table=6 (ls_in_acl_hint ), priority=1 , match=(ct.est &&
ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
+- table=6 (ls_in_acl_hint ), priority=2 , match=(ct.est &&
ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
+- table=6 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1;
next;)
+- table=6 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] =
1; next;)
+- table=6 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1;
reg0[[9]] = 1; next;)
+- table=6 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] =
1; next;)
+- table=6 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est),
action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
+- table=7 (ls_in_acl ), priority=1 , match=(ip && (!ct.est ||
(ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
+- table=7 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel
&& !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
+- table=7 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel
&& !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(next;)
+- table=7 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est &&
ct.rpl && ct_label.blocked == 1)), action=(drop;)
++ table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est &&
ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
++ table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est &&
ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
++ table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1;
next;)
++ table=8 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] =
1; next;)
++ table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1;
reg0[[9]] = 1; next;)
++ table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] =
1; next;)
++ table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est),
action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
++ table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est ||
(ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
++ table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel
&& !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
++ table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel
&& !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(next;)
++ table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est &&
ct.rpl && ct_label.blocked == 1)), action=(drop;)
+ ])
+
+ AS_BOX([Check match ct_state with load balancer])
+@@ -1974,17 +1974,17 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e
ls_out_acl_hint -e
+ table=5 (ls_out_acl ), priority=65535, match=(!ct.est && ct.rel
&& !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
+ table=5 (ls_out_acl ), priority=65535, match=(ct.est && !ct.rel
&& !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(next;)
+ table=5 (ls_out_acl ), priority=65535, match=(ct.inv || (ct.est &&
ct.rpl && ct_label.blocked == 1)), action=(drop;)
+- table=6 (ls_in_acl_hint ), priority=1 , match=(ct.est &&
ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
+- table=6 (ls_in_acl_hint ), priority=2 , match=(ct.est &&
ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
+- table=6 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1;
next;)
+- table=6 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] =
1; next;)
+- table=6 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1;
reg0[[9]] = 1; next;)
+- table=6 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] =
1; next;)
+- table=6 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est),
action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
+- table=7 (ls_in_acl ), priority=1 , match=(ip && (!ct.est ||
(ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
+- table=7 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel
&& !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
+- table=7 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel
&& !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(next;)
+- table=7 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est &&
ct.rpl && ct_label.blocked == 1)), action=(drop;)
++ table=8 (ls_in_acl_hint ), priority=1 , match=(ct.est &&
ct_label.blocked == 0), action=(reg0[[10]] = 1; next;)
++ table=8 (ls_in_acl_hint ), priority=2 , match=(ct.est &&
ct_label.blocked == 1), action=(reg0[[9]] = 1; next;)
++ table=8 (ls_in_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1;
next;)
++ table=8 (ls_in_acl_hint ), priority=4 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] =
1; next;)
++ table=8 (ls_in_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1;
reg0[[9]] = 1; next;)
++ table=8 (ls_in_acl_hint ), priority=6 , match=(!ct.new && ct.est
&& !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] =
1; next;)
++ table=8 (ls_in_acl_hint ), priority=7 , match=(ct.new && !ct.est),
action=(reg0[[7]] = 1; reg0[[9]] = 1; next;)
++ table=9 (ls_in_acl ), priority=1 , match=(ip && (!ct.est ||
(ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;)
++ table=9 (ls_in_acl ), priority=65535, match=(!ct.est && ct.rel
&& !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;)
++ table=9 (ls_in_acl ), priority=65535, match=(ct.est && !ct.rel
&& !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0),
action=(next;)
++ table=9 (ls_in_acl ), priority=65535, match=(ct.inv || (ct.est &&
ct.rpl && ct_label.blocked == 1)), action=(drop;)
+ ])
+
+ AT_CLEANUP
+--
+1.8.3.1
+
diff --git a/0012-northd-Cleanup-stale-FDB-entries.patch
b/0012-northd-Cleanup-stale-FDB-entries.patch
new file mode 100644
index 0000000..a8a396f
--- /dev/null
+++ b/0012-northd-Cleanup-stale-FDB-entries.patch
@@ -0,0 +1,211 @@
+From 33fe76837b1da9f8f9f1306a78b74d72a5449537 Mon Sep 17 00:00:00 2001
+From: Numan Siddique <numans(a)ovn.org>
+Date: Fri, 5 Feb 2021 00:36:33 +0530
+Subject: [PATCH 12/16] northd: Cleanup stale FDB entries.
+
+Acked-by: Mark Michelson <mmichels(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit 679d3550303ad1e5998e56bbcc63e9c4948522fc)
+
+Change-Id: Ib40b0273f61118365ff460b2dc0f944dd467d6b0
+---
+ lib/ovn-util.c | 20 ++++++++++++++----
+ lib/ovn-util.h | 1 +
+ northd/ovn-northd.c | 40 +++++++++++++++++++++++++++++++++++
+ tests/ovn-northd.at | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 117 insertions(+), 4 deletions(-)
+
+diff --git a/lib/ovn-util.c b/lib/ovn-util.c
+index b647106..8f67194 100644
+--- a/lib/ovn-util.c
++++ b/lib/ovn-util.c
+@@ -580,18 +580,30 @@ ovn_destroy_tnlids(struct hmap *tnlids)
+ hmap_destroy(tnlids);
+ }
+
++/* Returns true if 'tnlid' is present in the hmap 'tnlids'. */
+ bool
+-ovn_add_tnlid(struct hmap *set, uint32_t tnlid)
++ovn_tnlid_present(struct hmap *tnlids, uint32_t tnlid)
+ {
+ uint32_t hash = hash_int(tnlid, 0);
+ struct tnlid_node *node;
+- HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash, set) {
++ HMAP_FOR_EACH_IN_BUCKET (node, hmap_node, hash, tnlids) {
+ if (node->tnlid == tnlid) {
+- return false;
++ return true;
+ }
+ }
+
+- node = xmalloc(sizeof *node);
++ return false;
++}
++
++bool
++ovn_add_tnlid(struct hmap *set, uint32_t tnlid)
++{
++ if (ovn_tnlid_present(set, tnlid)) {
++ return false;
++ }
++
++ uint32_t hash = hash_int(tnlid, 0);
++ struct tnlid_node *node = xmalloc(sizeof *node);
+ hmap_insert(set, &node->hmap_node, hash);
+ node->tnlid = tnlid;
+ return true;
+diff --git a/lib/ovn-util.h b/lib/ovn-util.h
+index df4b0bc..40ecafe 100644
+--- a/lib/ovn-util.h
++++ b/lib/ovn-util.h
+@@ -126,6 +126,7 @@ void ovn_conn_show(struct unixctl_conn *conn, int argc OVS_UNUSED,
+ struct hmap;
+ void ovn_destroy_tnlids(struct hmap *tnlids);
+ bool ovn_add_tnlid(struct hmap *set, uint32_t tnlid);
++bool ovn_tnlid_present(struct hmap *tnlids, uint32_t tnlid);
+ uint32_t ovn_allocate_tnlid(struct hmap *set, const char *name, uint32_t min,
+ uint32_t max, uint32_t *hint);
+
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index d3c8f93..7b6a781 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -898,6 +898,20 @@ ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid)
+ return NULL;
+ }
+
++static struct ovn_datapath *
++ovn_datapath_find_by_key(struct hmap *datapaths, uint32_t dp_key)
++{
++ struct ovn_datapath *od;
++
++ HMAP_FOR_EACH (od, key_node, datapaths) {
++ if (od->tunnel_key == dp_key) {
++ return od;
++ }
++ }
++
++ return NULL;
++}
++
+ static bool
+ ovn_datapath_is_stale(const struct ovn_datapath *od)
+ {
+@@ -3394,6 +3408,26 @@ cleanup_sb_ha_chassis_groups(struct northd_context *ctx,
+ }
+ }
+
++static void
++cleanup_stale_fdp_entries(struct northd_context *ctx, struct hmap *datapaths)
++{
++ const struct sbrec_fdb *fdb_e, *next;
++ SBREC_FDB_FOR_EACH_SAFE (fdb_e, next, ctx->ovnsb_idl) {
++ bool delete = true;
++ struct ovn_datapath *od
++ = ovn_datapath_find_by_key(datapaths, fdb_e->dp_key);
++ if (od) {
++ if (ovn_tnlid_present(&od->port_tnlids, fdb_e->port_key)) {
++ delete = false;
++ }
++ }
++
++ if (delete) {
++ sbrec_fdb_delete(fdb_e);
++ }
++ }
++}
++
+ struct service_monitor_info {
+ struct hmap_node hmap_node;
+ const struct sbrec_service_monitor *sbrec_mon;
+@@ -13045,6 +13079,7 @@ ovnnb_db_run(struct northd_context *ctx,
+ sync_port_groups(ctx, &port_groups);
+ sync_meters(ctx, datapaths, &meter_groups, &port_groups);
+ sync_dns_entries(ctx, datapaths);
++ cleanup_stale_fdp_entries(ctx, datapaths);
+
+ struct ovn_northd_lb *lb;
+ HMAP_FOR_EACH_POP (lb, hmap_node, &lbs) {
+@@ -14023,6 +14058,11 @@ main(int argc, char *argv[])
+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_disc);
+ ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_bfd_col_src_port);
+
++ ovsdb_idl_add_table(ovnsb_idl_loop.idl, &sbrec_table_fdb);
++ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_mac);
++ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_dp_key);
++ add_column_noalert(ovnsb_idl_loop.idl, &sbrec_fdb_col_port_key);
++
+ struct ovsdb_idl_index *sbrec_chassis_by_name
+ = chassis_index_create(ovnsb_idl_loop.idl);
+
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index ec64e92..a2c1212 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -2599,3 +2599,63 @@ AT_CHECK([grep "lr_out_snat" lr0flows | grep
force_snat_for_lb | sort], [0], [dn
+ ])
+
+ AT_CLEANUP
++
++AT_SETUP([ovn -- FDB cleanup])
++
++ovn_start
++
++ovn-nbctl ls-add sw0
++ovn-nbctl lsp-add sw0 sw0-p1
++ovn-nbctl lsp-add sw0 sw0-p2
++ovn-nbctl lsp-add sw0 sw0-p3
++
++ovn-nbctl ls-add sw1
++ovn-nbctl lsp-add sw1 sw1-p1
++ovn-nbctl lsp-add sw1 sw1-p2
++ovn-nbctl --wait=sb lsp-add sw1 sw1-p3
++
++sw0_key=$(fetch_column datapath_binding tunnel_key external_ids:name=sw0)
++sw1_key=$(fetch_column datapath_binding tunnel_key external_ids:name=sw1)
++sw0p1_key=$(fetch_column port_binding tunnel_key logical_port=sw0-p1)
++sw0p2_key=$(fetch_column port_binding tunnel_key logical_port=sw0-p2)
++sw1p1_key=$(fetch_column port_binding tunnel_key logical_port=sw1-p1)
++
++ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:01" dp_key=$sw0_key
port_key=$sw0p1_key
++ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:02" dp_key=$sw0_key
port_key=$sw0p1_key
++ovn-sbctl create FDB mac="00\:00\:00\:00\:00\:03" dp_key=$sw0_key
port_key=$sw0p2_key
++ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:01" dp_key=$sw1_key
port_key=$sw1p1_key
++ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:02" dp_key=$sw1_key
port_key=$sw1p1_key
++ovn-sbctl create FDB mac="00\:00\:00\:00\:01\:03" dp_key=$sw1_key
port_key=$sw1p1_key
++
++wait_row_count FDB 6
++
++ovn-sbctl create fdb mac="00\:00\:00\:00\:01\:03" dp_key=$sw1_key port_key=10
++wait_row_count FDB 6
++ovn-sbctl create fdb mac="00\:00\:00\:00\:01\:03" dp_key=4 port_key=10
++wait_row_count FDB 6
++
++ovn-nbctl --wait=sb ls-del sw1
++wait_row_count FDB 3
++
++ovn-nbctl lsp-del sw0-p3
++wait_row_count FDB 3
++
++ovn-nbctl lsp-del sw0-p1
++wait_row_count FDB 1
++
++check_column '00:00:00:00:00:03' FDB mac
++ovn-sbctl list fdb
++
++check_column $sw0_key FDB dp_key
++check_column $sw0p2_key FDB port_key
++
++ovn-nbctl --wait=sb lsp-add sw0-p1
++wait_row_count FDB 1
++
++ovn-nbctl lsp-del sw0-p2
++ovn-nbctl lsp-add sw0-p2
++wait_row_count FDB 0
++
++ovn-sbctl list FDB
++
++AT_CLEANUP
+--
+1.8.3.1
+
diff --git a/0013-mac-learn-Fix-build-due-to-missing-newline-at-EOF.patch
b/0013-mac-learn-Fix-build-due-to-missing-newline-at-EOF.patch
new file mode 100644
index 0000000..a7a305d
--- /dev/null
+++ b/0013-mac-learn-Fix-build-due-to-missing-newline-at-EOF.patch
@@ -0,0 +1,29 @@
+From 492f5b3ce7c76af4197ded8540913ea52d272136 Mon Sep 17 00:00:00 2001
+From: Dumitru Ceara <dceara(a)redhat.com>
+Date: Mon, 22 Feb 2021 10:56:37 +0100
+Subject: [PATCH 13/16] mac-learn: Fix build due to missing newline at EOF.
+
+Fixes: 2c5e546f3486 ("controller: Split mac learning code to a separate
file.")
+Signed-off-by: Dumitru Ceara <dceara(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry-picked from upstream commit 3a517b06c293996118d277a9878e4d07bc055013)
+
+Change-Id: I0b9be13f49aca839b9c14535fada39e4113666b7
+---
+ controller/mac-learn.h | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/controller/mac-learn.h b/controller/mac-learn.h
+index 7a7897e..e7e8ba2 100644
+--- a/controller/mac-learn.h
++++ b/controller/mac-learn.h
+@@ -63,4 +63,4 @@ struct fdb_entry *ovn_fdb_add(struct hmap *fdbs,
+ uint32_t dp_key, struct eth_addr mac,
+ uint32_t port_key);
+
+-#endif /* OVN_MAC_LEARN_H */
+\ No newline at end of file
++#endif /* OVN_MAC_LEARN_H */
+--
+1.8.3.1
+
diff --git a/0014-Properly-handle-hairpin-traffic-for-VIPs-with-shared.patch
b/0014-Properly-handle-hairpin-traffic-for-VIPs-with-shared.patch
new file mode 100644
index 0000000..62dbae0
--- /dev/null
+++ b/0014-Properly-handle-hairpin-traffic-for-VIPs-with-shared.patch
@@ -0,0 +1,1370 @@
+From 811c575effb27cbc416986fafd2e8c815daa4ac6 Mon Sep 17 00:00:00 2001
+From: Dumitru Ceara <dceara(a)redhat.com>
+Date: Wed, 24 Feb 2021 17:09:44 +0100
+Subject: [PATCH 14/16] Properly handle hairpin traffic for VIPs with shared
+ backends.
+
+If two load balancer VIPs share the same backend, both sets of hairpin
+reply learn() flows should be generated. In order to ensure that,
+also match on the original destination IP and port tuple. These are
+now stored in OVS registers by ovn-northd in stage ls-in-stateful.
+
+An alternative solution would be to add an additional match on
+ct_nw_dst() and ct_tp_dst() in the hairpin detection flows but it's
+better to avoid that because these matches are usually not offloadable
+to hardware.
+
+To ensure backwards compatibility though, if ovn-controller detects
+that ovn-northd doesn't store the original destination tuple
+information in OVS registers, ovn-controller falls back to using
+ct_nw_dst() and ct_tp_dst().
+
+Reported-by: Tim Rozet <trozet(a)redhat.com>
+Reported-at:
https://bugzilla.redhat.com/1931599
+Fixes: 022ea339c8e2 ("lflow: Use learn() action to generate LB hairpin reply
flows.")
+Acked-by: Numan Siddique <numans(a)ovn.org>
+Signed-off-by: Dumitru Ceara <dceara(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit ce0ef8d59850255795ce5eb972821b8eb18945e5)
+
+Change-Id: I49a31e6e8ce7c73a17913d38dfc73f000dc67f2c
+---
+ controller/lflow.c | 57 ++++++-
+ include/ovn/logical-fields.h | 8 +-
+ lib/lb.c | 3 +
+ lib/lb.h | 3 +
+ northd/ovn-northd.8.xml | 21 ++-
+ northd/ovn-northd.c | 93 +++++++++--
+ ovn-sb.xml | 6 +
+ tests/ofproto-macros.at | 2 +-
+ tests/ovn-northd.at | 28 ++--
+ tests/ovn.at | 377 +++++++++++++++++++++++++++++++------------
+ tests/system-ovn.at | 40 +++--
+ 11 files changed, 489 insertions(+), 149 deletions(-)
+
+diff --git a/controller/lflow.c b/controller/lflow.c
+index 8f25877..5bc6f1e 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -1344,6 +1344,12 @@ add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32
vip,
+ ofpact_finish_LEARN(ofpacts, &ol);
+ }
+
++/* Adds flows to detect hairpin sessions.
++ *
++ * For backwards compatibilty with older ovn-northd versions, uses
++ * ct_nw_dst(), ct_ipv6_dst(), ct_tp_dst(), otherwise uses the
++ * original destination tuple stored by ovn-northd.
++ */
+ static void
+ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
+ struct ovn_lb_vip *lb_vip,
+@@ -1359,30 +1365,59 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
+ put_load(&value, sizeof value, MFF_LOG_FLAGS,
+ MLF_LOOKUP_LB_HAIRPIN_BIT, 1, &ofpacts);
+
++ /* Matching on ct_nw_dst()/ct_ipv6_dst()/ct_tp_dst() requires matching
++ * on ct_state first.
++ */
++ if (!lb->hairpin_orig_tuple) {
++ uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
++ match_set_ct_state_masked(&hairpin_match, ct_state, ct_state);
++ }
++
+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
+ ovs_be32 bip4 = in6_addr_get_mapped_ipv4(&lb_backend->ip);
+- ovs_be32 vip4 = lb->hairpin_snat_ips.n_ipv4_addrs
++ ovs_be32 vip4 = in6_addr_get_mapped_ipv4(&lb_vip->vip);
++ ovs_be32 snat_vip4 = lb->hairpin_snat_ips.n_ipv4_addrs
+ ? lb->hairpin_snat_ips.ipv4_addrs[0].addr
+- : in6_addr_get_mapped_ipv4(&lb_vip->vip);
++ : vip4;
+
+ match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IP));
+ match_set_nw_src(&hairpin_match, bip4);
+ match_set_nw_dst(&hairpin_match, bip4);
+
+- add_lb_vip_hairpin_reply_action(NULL, vip4, lb_proto,
++ if (!lb->hairpin_orig_tuple) {
++ match_set_ct_nw_dst(&hairpin_match, vip4);
++ } else {
++ match_set_reg(&hairpin_match,
++ MFF_LOG_LB_ORIG_DIP_IPV4 - MFF_LOG_REG0,
++ ntohl(vip4));
++ }
++
++ add_lb_vip_hairpin_reply_action(NULL, snat_vip4, lb_proto,
+ lb_backend->port,
+ lb->slb->header_.uuid.parts[0],
+ &ofpacts);
+ } else {
+ struct in6_addr *bip6 = &lb_backend->ip;
+- struct in6_addr *vip6 = lb->hairpin_snat_ips.n_ipv6_addrs
+- ? &lb->hairpin_snat_ips.ipv6_addrs[0].addr
+- : &lb_vip->vip;
++ struct in6_addr *snat_vip6 =
++ lb->hairpin_snat_ips.n_ipv6_addrs
++ ? &lb->hairpin_snat_ips.ipv6_addrs[0].addr
++ : &lb_vip->vip;
+ match_set_dl_type(&hairpin_match, htons(ETH_TYPE_IPV6));
+ match_set_ipv6_src(&hairpin_match, bip6);
+ match_set_ipv6_dst(&hairpin_match, bip6);
+
+- add_lb_vip_hairpin_reply_action(vip6, 0, lb_proto,
++ if (!lb->hairpin_orig_tuple) {
++ match_set_ct_ipv6_dst(&hairpin_match, &lb_vip->vip);
++ } else {
++ ovs_be128 vip6_value;
++
++ memcpy(&vip6_value, &lb_vip->vip, sizeof vip6_value);
++ match_set_xxreg(&hairpin_match,
++ MFF_LOG_LB_ORIG_DIP_IPV6 - MFF_LOG_XXREG0,
++ ntoh128(vip6_value));
++ }
++
++ add_lb_vip_hairpin_reply_action(snat_vip6, 0, lb_proto,
+ lb_backend->port,
+ lb->slb->header_.uuid.parts[0],
+ &ofpacts);
+@@ -1391,6 +1426,14 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
+ if (lb_backend->port) {
+ match_set_nw_proto(&hairpin_match, lb_proto);
+ match_set_tp_dst(&hairpin_match, htons(lb_backend->port));
++ if (!lb->hairpin_orig_tuple) {
++ match_set_ct_nw_proto(&hairpin_match, lb_proto);
++ match_set_ct_tp_dst(&hairpin_match, htons(lb_vip->vip_port));
++ } else {
++ match_set_reg_masked(&hairpin_match,
++ MFF_LOG_LB_ORIG_TP_DPORT - MFF_REG0,
++ lb_vip->vip_port, UINT16_MAX);
++ }
+ }
+
+ /* In the original direction, only match on traffic that was already
+diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
+index 9d8d07f..017176f 100644
+--- a/include/ovn/logical-fields.h
++++ b/include/ovn/logical-fields.h
+@@ -44,7 +44,13 @@ enum ovn_controller_event {
+ /* Logical registers.
+ *
+ * Make sure these don't overlap with the logical fields! */
+-#define MFF_LOG_REG0 MFF_REG0
++#define MFF_LOG_REG0 MFF_REG0
++#define MFF_LOG_LB_ORIG_DIP_IPV4 MFF_REG1
++#define MFF_LOG_LB_ORIG_TP_DPORT MFF_REG2
++
++#define MFF_LOG_XXREG0 MFF_XXREG0
++#define MFF_LOG_LB_ORIG_DIP_IPV6 MFF_XXREG1
++
+ #define MFF_N_LOG_REGS 10
+
+ void ovn_init_symtab(struct shash *symtab);
+diff --git a/lib/lb.c b/lib/lb.c
+index e11ac00..f305e9a 100644
+--- a/lib/lb.c
++++ b/lib/lb.c
+@@ -312,6 +312,9 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb)
+ */
+ lb->n_vips = n_vips;
+
++ lb->hairpin_orig_tuple = smap_get_bool(&sbrec_lb->options,
++ "hairpin_orig_tuple",
++ false);
+ ovn_lb_get_hairpin_snat_ip(&sbrec_lb->header_.uuid,
&sbrec_lb->options,
+ &lb->hairpin_snat_ips);
+ return lb;
+diff --git a/lib/lb.h b/lib/lb.h
+index dfce51c..9a78c72 100644
+--- a/lib/lb.h
++++ b/lib/lb.h
+@@ -95,6 +95,9 @@ struct ovn_controller_lb {
+
+ struct ovn_lb_vip *vips;
+ size_t n_vips;
++ bool hairpin_orig_tuple; /* True if ovn-northd stores the original
++ * destination tuple in registers.
++ */
+
+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used
+ * as source for hairpinned
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index a964db9..c2de5ba 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -755,7 +755,11 @@
+ of <var>VIP</var>. If health check is enabled, then
<var>args</var>
+ will only contain those endpoints whose service monitor status entry
+ in <code>OVN_Southbound</code> db is either
<code>online</code> or
+- empty.
++ empty. For IPv4 traffic the flow also loads the original destination
++ IP and transport port in registers <code>reg1</code> and
++ <code>reg2</code>. For IPv6 traffic the flow also loads the
original
++ destination IP and transport port in registers <code>xxreg1</code>
and
++ <code>reg2</code>.
+ </li>
+ <li>
+ For all the configured load balancing rules for a switch in
+@@ -767,6 +771,11 @@
+ VIP</var></code>. The action on this flow is <code>
+ ct_lb(<var>args</var>)</code>, where
<var>args</var> contains comma
+ separated IP addresses of the same address family as
<var>VIP</var>.
++ For IPv4 traffic the flow also loads the original destination
++ IP and transport port in registers <code>reg1</code> and
++ <code>reg2</code>. For IPv6 traffic the flow also loads the
original
++ destination IP and transport port in registers <code>xxreg1</code>
and
++ <code>reg2</code>.
+ </li>
+
+ <li>
+@@ -784,9 +793,15 @@
+ the previous tables (with a match for <code>reg0[1] == 1</code>).
+ </li>
+ <li>
+- A priority-100 flow sends the packets to connection tracker using
++ Priority-100 flows that send the packets to connection tracker using
+ <code>ct_lb;</code> as the action based on a hint provided by the
+- previous tables (with a match for <code>reg0[2] == 1</code>).
++ previous tables (with a match for <code>reg0[2] == 1</code> and
++ on supported load balancer protocols and address families).
++ For IPv4 traffic the flows also load the original destination
++ IP and transport port in registers <code>reg1</code> and
++ <code>reg2</code>. For IPv6 traffic the flows also load the
original
++ destination IP and transport port in registers <code>xxreg1</code>
and
++ <code>reg2</code>.
+ </li>
+ <li>
+ A priority-0 flow that simply moves traffic to the next table.
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index 7b6a781..9209c13 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -232,6 +232,10 @@ enum ovn_stage {
+ #define REGBIT_ACL_HINT_BLOCK "reg0[10]"
+ #define REGBIT_LKUP_FDB "reg0[11]"
+
++#define REG_ORIG_DIP_IPV4 "reg1"
++#define REG_ORIG_DIP_IPV6 "xxreg1"
++#define REG_ORIG_TP_DPORT "reg2[0..15]"
++
+ /* Register definitions for switches and routers. */
+
+ /* Indicate that this packet has been recirculated using egress
+@@ -265,12 +269,28 @@ enum ovn_stage {
+ * OVS register usage:
+ *
+ * Logical Switch pipeline:
+- * +---------+----------------------------------------------+
+- * | R0 | REGBIT_{CONNTRACK/DHCP/DNS/HAIRPIN} |
+- * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} |
+- * +---------+----------------------------------------------+
+- * | R1 - R9 | UNUSED |
+- * +---------+----------------------------------------------+
++ * +----+----------------------------------------------+---+------------------+
++ * | R0 | REGBIT_{CONNTRACK/DHCP/DNS/HAIRPIN} | | |
++ * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} | X | |
++ * +----+----------------------------------------------+ X | |
++ * | R1 | ORIG_DIP_IPV4 (>= IN_STATEFUL) | R | |
++ * +----+----------------------------------------------+ E | |
++ * | R2 | ORIG_TP_DPORT (>= IN_STATEFUL) | G | |
++ * +----+----------------------------------------------+ 0 | |
++ * | R3 | UNUSED | | |
++ * +----+----------------------------------------------+---+------------------+
++ * | R4 | UNUSED | | |
++ * +----+----------------------------------------------+ X | ORIG_DIP_IPV6 |
++ * | R5 | UNUSED | X | (>= IN_STATEFUL) |
++ * +----+----------------------------------------------+ R | |
++ * | R6 | UNUSED | E | |
++ * +----+----------------------------------------------+ G | |
++ * | R7 | UNUSED | 1 | |
++ * +----+----------------------------------------------+---+------------------+
++ * | R8 | UNUSED |
++ * +----+----------------------------------------------+
++ * | R9 | UNUSED |
++ * +----+----------------------------------------------+
+ *
+ * Logical Router pipeline:
+ * +-----+--------------------------+---+-----------------+---+---------------+
+@@ -3647,10 +3667,18 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap
*datapaths,
+ /* Create SB Load balancer records if not present and sync
+ * the SB load balancer columns. */
+ HMAP_FOR_EACH (lb, hmap_node, lbs) {
++
+ if (!lb->n_dps) {
+ continue;
+ }
+
++ /* Store the fact that northd provides the original (destination IP +
++ * transport port) tuple.
++ */
++ struct smap options;
++ smap_clone(&options, &lb->nlb->options);
++ smap_replace(&options, "hairpin_orig_tuple", "true");
++
+ if (!lb->slb) {
+ sbrec_lb = sbrec_load_balancer_insert(ctx->ovnsb_txn);
+ lb->slb = sbrec_lb;
+@@ -3664,10 +3692,11 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap
*datapaths,
+ sbrec_load_balancer_set_name(lb->slb, lb->nlb->name);
+ sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips);
+ sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol);
+- sbrec_load_balancer_set_options(lb->slb, &lb->nlb->options);
++ sbrec_load_balancer_set_options(lb->slb, &options);
+ sbrec_load_balancer_set_datapaths(
+ lb->slb, (struct sbrec_datapath_binding **)lb->dps,
+ lb->n_dps);
++ smap_destroy(&options);
+ }
+
+ /* Set the list of associated load balanacers to a logical switch
+@@ -6088,11 +6117,20 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
+ struct ovn_lb_vip *lb_vip = &lb->vips[i];
+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i];
+
++ struct ds action = DS_EMPTY_INITIALIZER;
+ const char *ip_match = NULL;
++
++ /* Store the original destination IP to be used when generating
++ * hairpin flows.
++ */
+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
+ ip_match = "ip4";
++ ds_put_format(&action, REG_ORIG_DIP_IPV4 " = %s; ",
++ lb_vip->vip_str);
+ } else {
+ ip_match = "ip6";
++ ds_put_format(&action, REG_ORIG_DIP_IPV6 " = %s; ",
++ lb_vip->vip_str);
+ }
+
+ const char *proto = NULL;
+@@ -6105,10 +6143,15 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows,
+ proto = "sctp";
+ }
+ }
++
++ /* Store the original destination port to be used when generating
++ * hairpin flows.
++ */
++ ds_put_format(&action, REG_ORIG_TP_DPORT " = %"PRIu16";
",
++ lb_vip->vip_port);
+ }
+
+ /* New connections in Ingress table. */
+- struct ds action = DS_EMPTY_INITIALIZER;
+ build_lb_vip_actions(lb_vip, lb_vip_nb, &action,
+ lb->selection_fields, true);
+
+@@ -6156,9 +6199,39 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows,
struct hmap *lbs)
+ * REGBIT_CONNTRACK_COMMIT is set for new connections and
+ * REGBIT_CONNTRACK_NAT is set for established connections. So they
+ * don't overlap.
++ *
++ * In the ingress pipeline, also store the original destination IP and
++ * transport port to be used when detecting hairpin packets.
+ */
+- ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
+- REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
++ const char *lb_protocols[] = {"tcp", "udp", "sctp"};
++ struct ds actions = DS_EMPTY_INITIALIZER;
++ struct ds match = DS_EMPTY_INITIALIZER;
++
++ for (size_t i = 0; i < ARRAY_SIZE(lb_protocols); i++) {
++ ds_clear(&match);
++ ds_clear(&actions);
++ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip4
&& %s",
++ lb_protocols[i]);
++ ds_put_format(&actions, REG_ORIG_DIP_IPV4 " = ip4.dst; "
++ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
++ lb_protocols[i]);
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
++ ds_cstr(&match), ds_cstr(&actions));
++
++ ds_clear(&match);
++ ds_clear(&actions);
++ ds_put_format(&match, REGBIT_CONNTRACK_NAT" == 1 && ip6
&& %s",
++ lb_protocols[i]);
++ ds_put_format(&actions, REG_ORIG_DIP_IPV6 " = ip6.dst; "
++ REG_ORIG_TP_DPORT " = %s.dst; ct_lb;",
++ lb_protocols[i]);
++ ovn_lflow_add(lflows, od, S_SWITCH_IN_STATEFUL, 100,
++ ds_cstr(&match), ds_cstr(&actions));
++ }
++
++ ds_destroy(&actions);
++ ds_destroy(&match);
++
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_STATEFUL, 100,
+ REGBIT_CONNTRACK_NAT" == 1", "ct_lb;");
+
+diff --git a/ovn-sb.xml b/ovn-sb.xml
+index 35000ed..258a12b 100644
+--- a/ovn-sb.xml
++++ b/ovn-sb.xml
+@@ -4306,6 +4306,12 @@ tcp.flags = RST;
+ load balancing. This value is automatically populated by
+ <code>ovn-northd</code>.
+ </column>
++ <column name="options" key="hairpin_orig_tuple"
type='{"type": "boolean"}'>
++ This value is automatically set to <code>true</code> by
++ <code>ovn-northd</code> when original destination IP and transport
port
++ of the load balanced packets are stored in registers
++ <code>reg1, reg2, xxreg1</code>.
++ </column>
+ </group>
+
+ <group title="Common Columns">
+diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at
+index ff65d60..3d7ac08 100644
+--- a/tests/ofproto-macros.at
++++ b/tests/ofproto-macros.at
+@@ -40,7 +40,7 @@ s/dir\/[0-9]*\/br0.mgmt/dir\/XXXX\/br0.mgmt/
+ # Strips out uninteresting parts of ovs-ofctl output, including n_packets=..
+ # n_bytes=..
+ ofctl_strip_all () {
+- ofctl_strip | strip_n_packets | strip_n_bytes | strip_cookie
++ ofctl_strip | strip_n_packets | strip_n_bytes | strip_cookie | sort
+ }
+
+ # Filter (multiline) vconn debug messages from ovs-vswitchd.log.
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index a2c1212..e252fc0 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -1077,7 +1077,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
+ AT_CAPTURE_FILE([sbflows])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*ct_lb' | sed
's/table=..//'], 0, [dnl
+- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
++ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+ AS_BOX([Delete the Load_Balancer_Health_Check])
+@@ -1087,7 +1087,7 @@ wait_row_count Service_Monitor 0
+ AT_CAPTURE_FILE([sbflows2])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*ct_lb' | sed
's/table=..//'], [0],
+-[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
++[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+ AS_BOX([Create the Load_Balancer_Health_Check again.])
+@@ -1099,7 +1099,7 @@ check ovn-nbctl --wait=sb sync
+
+ ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 > lflows.txt
+ AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
+- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
++ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+ AS_BOX([Get the uuid of both the service_monitor])
+@@ -1109,7 +1109,7 @@ sm_sw1_p1=$(fetch_column Service_Monitor _uuid
logical_port=sw1-p1)
+ AT_CAPTURE_FILE([sbflows3])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*ct_lb' | sed
's/table=..//'], [0],
+-[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
++[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+ AS_BOX([Set the service monitor for sw1-p1 to offline])
+@@ -1120,7 +1120,7 @@ check ovn-nbctl --wait=sb sync
+ AT_CAPTURE_FILE([sbflows4])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*ct_lb' | sed
's/table=..//'], [0],
+-[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
++[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80);)
+ ])
+
+ AS_BOX([Set the service monitor for sw0-p1 to offline])
+@@ -1149,7 +1149,7 @@ check ovn-nbctl --wait=sb sync
+ AT_CAPTURE_FILE([sbflows7])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep ct_lb | grep priority=120 | sed
's/table=..//'], 0,
+-[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
++[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+ ])
+
+ AS_BOX([Set the service monitor for sw1-p1 to error])
+@@ -1160,7 +1160,7 @@ check ovn-nbctl --wait=sb sync
+ ovn-sbctl dump-flows sw0 | grep "ip4.dst == 10.0.0.10 && tcp.dst ==
80" \
+ | grep priority=120 > lflows.txt
+ AT_CHECK([cat lflows.txt | sed 's/table=..//'], [0], [dnl
+- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
++ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80);)
+ ])
+
+ AS_BOX([Add one more vip to lb1])
+@@ -1186,8 +1186,8 @@ AT_CAPTURE_FILE([sbflows9])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep ct_lb | grep priority=120 | sed
's/table=..//' | sort],
+ 0,
+-[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80);)
+- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000);)
++[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80);)
++ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000;
ct_lb(backends=10.0.0.3:1000);)
+ ])
+
+ AS_BOX([Set the service monitor for sw1-p1 to online])
+@@ -1200,8 +1200,8 @@ AT_CAPTURE_FILE([sbflows10])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep ct_lb | grep priority=120 | sed
's/table=..//' | sort],
+ 0,
+-[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
++[ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
++ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000;
ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
+ ])
+
+ AS_BOX([Associate lb1 to sw1])
+@@ -1210,8 +1210,8 @@ AT_CAPTURE_FILE([sbflows11])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep ct_lb | grep priority=120 | sed
's/table=..//' | sort],
+ 0, [dnl
+- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
+- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
++ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10
&& tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80);)
++ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.40
&& tcp.dst == 1000), action=(reg1 = 10.0.0.40; reg2[[0..15]] = 1000;
ct_lb(backends=10.0.0.3:1000,20.0.0.3:80);)
+ ])
+
+ AS_BOX([Now create lb2 same as lb1 but udp protocol.])
+@@ -2175,7 +2175,7 @@ check_column "$lb0_uuid $lb1_uuid" sb:datapath_binding
load_balancers external_i
+ echo
+ echo "__file__:__line__: Set hairpin_snat_ip on lb1 and check that SB DB is
updated."
+ check ovn-nbctl --wait=sb set Load_Balancer lb1
options:hairpin_snat_ip="42.42.42.42 4242::4242"
+-check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1
options='{hairpin_snat_ip="42.42.42.42 4242::4242"}'
++check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1
options='{hairpin_orig_tuple="true", hairpin_snat_ip="42.42.42.42
4242::4242"}'
+
+ echo
+ echo "__file__:__line__: Delete load balancer lb1 an check that datapath sw1's
load_balancers are updated accordingly."
+diff --git a/tests/ovn.at b/tests/ovn.at
+index ea155d2..2515b5b 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -19771,7 +19771,7 @@ AT_CAPTURE_FILE([sbflows])
+ OVS_WAIT_FOR_OUTPUT(
+ [ovn-sbctl dump-flows > sbflows
+ ovn-sbctl dump-flows sw0 | grep ct_lb | grep priority=120 | sed
's/table=..//'], 0,
+- [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80;
hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
++ [ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst ==
10.0.0.10 && tcp.dst == 80), action=(reg1 = 10.0.0.10; reg2[[0..15]] = 80;
ct_lb(backends=10.0.0.3:80,20.0.0.3:80;
hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+ ])
+
+ AT_CAPTURE_FILE([sbflows2])
+@@ -20962,16 +20962,26 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \
+
+ # One logical switch with IPv4 and IPv6 load balancers that hairpin the
+ # traffic.
++# Also create "duplicate" load balancers, i.e., different VIPs using the same
++# backends.
+ ovn-nbctl ls-add sw
+ ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
+-ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
+-ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
+-ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
+-ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
++ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
++ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp
++ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
++ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp
++ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
++ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp
++ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
++ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp
+ ovn-nbctl ls-lb-add sw lb-ipv4-tcp
++ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup
+ ovn-nbctl ls-lb-add sw lb-ipv4-udp
++ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup
+ ovn-nbctl ls-lb-add sw lb-ipv6-tcp
++ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup
+ ovn-nbctl ls-lb-add sw lb-ipv6-udp
++ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup
+
+ ovn-nbctl lr-add rtr
+ ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24 4200::00ff/64
+@@ -20989,7 +20999,7 @@ AT_CAPTURE_FILE([sbflows])
+
+ AS_BOX([IPv4 TCP Hairpin])
+
+-# Inject IPv4 TCP packet from lsp.
++# Inject IPv4 TCP packets from lsp.
+ tcp_payload=$(build_tcp_syn 84d0 1f90 05a7)
+ hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156e)
+ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+@@ -20999,12 +21009,22 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ $(ip_to_hex 88 88 88 88) ${hp_tcp_payload} \
+ expected
+
++tcp_payload=$(build_tcp_syn 84d1 1f90 05a5)
++hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 156c)
++send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
++ 06 0028 \
++ ${tcp_payload} \
++ $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \
++ expected
++
+ # Check that traffic is hairpinned.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ # Change LB Hairpin SNAT IP.
+@@ -21012,7 +21032,8 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69
| ofctl_strip_a
+ as hv1 ovs-appctl dpctl/flush-conntrack
+
+ ovn-nbctl --wait=hv set load_balancer lb-ipv4-tcp
options:hairpin_snat_ip="88.88.88.87"
+-# Inject IPv4 TCP packet from lsp.
++# Inject IPv4 TCP packets from lsp.
++tcp_payload=$(build_tcp_syn 84d0 1f90 05a7)
+ hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156f)
+ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
+@@ -21021,17 +21042,27 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ $(ip_to_hex 88 88 88 87) ${hp_tcp_payload} \
+ expected
+
++tcp_payload=$(build_tcp_syn 84d1 1f90 05a5)
++hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 156c)
++send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
++ 06 0028 \
++ ${tcp_payload} \
++ $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \
++ expected
++
+ # Check that traffic is hairpinned.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ AS_BOX([IPv4 UDP Hairpin])
+
+-# Inject IPv4 UDP packet from lsp.
++# Inject IPv4 UDP packets from lsp.
+ udp_payload=$(build_udp 84d0 0fc8 6666)
+ hp_udp_payload=$(build_udp 84d0 07e5 6e49)
+ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+@@ -21041,20 +21072,32 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ $(ip_to_hex 88 88 88 88) ${hp_udp_payload} \
+ expected
+
++udp_payload=$(build_udp 84d1 0fc8 6664)
++hp_udp_payload=$(build_udp 84d1 07e5 6e47)
++send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
++ 11 001e \
++ ${udp_payload} \
++ $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \
++ expected
++
+ # Check that traffic is hairpinned.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ # Change LB Hairpin SNAT IP.
+ # Also flush conntrack to avoid reusing an existing entry.
+ as hv1 ovs-appctl dpctl/flush-conntrack
+ ovn-nbctl --wait=hv set load_balancer lb-ipv4-udp
options:hairpin_snat_ip="88.88.88.87"
+-# Inject IPv4 UDP packet from lsp.
++# Inject IPv4 UDP packets from lsp.
++udp_payload=$(build_udp 84d0 0fc8 6666)
+ hp_udp_payload=$(build_udp 84d0 07e5 6e4a)
+ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \
+@@ -21063,18 +21106,29 @@ send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ $(ip_to_hex 88 88 88 87) ${hp_udp_payload} \
+ expected
+
++udp_payload=$(build_udp 84d1 0fc8 6664)
++hp_udp_payload=$(build_udp 84d1 07e5 6e47)
++send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 89) \
++ 11 001e \
++ ${udp_payload} \
++ $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \
++ expected
++
+ # Check that traffic is hairpinned.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ AS_BOX([IPv6 TCP Hairpin])
+
+-# Inject IPv6 TCP packet from lsp.
++# Inject IPv6 TCP packets from lsp.
+ tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9)
+ hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0)
+ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+@@ -21084,14 +21138,26 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ 88000000000000000000000000000088 ${hp_tcp_payload} \
+ expected
+
++tcp_payload=$(build_tcp_syn 84d1 1f90 3ff7)
++hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 4fbe)
++send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ 42000000000000000000000000000001 88000000000000000000000000000089 \
++ 06 0014 \
++ ${tcp_payload} \
++ 88000000000000000000000000000089 ${hp_tcp_payload} \
++ expected
++
+ # Check that traffic is hairpinned.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+- table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ # Change LB Hairpin SNAT IP.
+@@ -21099,7 +21165,8 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69
| ofctl_strip_a
+ as hv1 ovs-appctl dpctl/flush-conntrack
+ ovn-nbctl --wait=hv set load_balancer lb-ipv6-tcp
options:hairpin_snat_ip="8800::0087"
+
+-# Inject IPv6 TCP packet from lsp.
++# Inject IPv6 TCP packets from lsp.
++tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9)
+ hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc1)
+ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ 42000000000000000000000000000001 88000000000000000000000000000088 \
+@@ -21108,19 +21175,31 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ 88000000000000000000000000000087 ${hp_tcp_payload} \
+ expected
+
++tcp_payload=$(build_tcp_syn 84d1 1f90 3ff7)
++hp_tcp_payload=$(build_tcp_syn 84d1 0fc9 4fbe)
++send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ 42000000000000000000000000000001 88000000000000000000000000000089 \
++ 06 0014 \
++ ${tcp_payload} \
++ 88000000000000000000000000000089 ${hp_tcp_payload} \
++ expected
++
+ # Check that traffic is hairpinned.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+- table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ AS_BOX([IPv6 UDP Hairpin])
+
+-# Inject IPv6 UDP packet from lsp.
++# Inject IPv6 UDP packets from lsp.
+ udp_payload=$(build_udp 84d0 0fc8 a0b8)
+ hp_udp_payload=$(build_udp 84d0 07e5 a89b)
+ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+@@ -21130,15 +21209,28 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ 88000000000000000000000000000088 ${hp_udp_payload} \
+ expected
+
++udp_payload=$(build_udp 84d1 0fc8 a0b6)
++hp_udp_payload=$(build_udp 84d1 07e5 a899)
++send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ 42000000000000000000000000000001 88000000000000000000000000000089 \
++ 11 000a \
++ ${udp_payload} \
++ 88000000000000000000000000000089 ${hp_udp_payload} \
++ expected
++
+ # Check that traffic is hairpinned.
+ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected])
+
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+- table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::88,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ # Change LB Hairpin SNAT IP.
+@@ -21146,7 +21238,8 @@ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69
| ofctl_strip_a
+ as hv1 ovs-appctl dpctl/flush-conntrack
+ ovn-nbctl --wait=hv set load_balancer lb-ipv6-udp
options:hairpin_snat_ip="8800::0087"
+
+-# Inject IPv6 UDP packet from lsp.
++# Inject IPv6 UDP packets from lsp.
++udp_payload=$(build_udp 84d0 0fc8 a0b8)
+ hp_udp_payload=$(build_udp 84d0 07e5 a89b)
+ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ 42000000000000000000000000000001 88000000000000000000000000000088 \
+@@ -21155,38 +21248,64 @@ send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
+ 88000000000000000000000000000087 ${hp_udp_payload} \
+ expected
+
++udp_payload=$(build_udp 84d1 0fc8 a0b6)
++hp_udp_payload=$(build_udp 84d1 07e5 a899)
++send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \
++ 42000000000000000000000000000001 88000000000000000000000000000089 \
++ 11 000a \
++ ${udp_payload} \
++ 88000000000000000000000000000089 ${hp_udp_payload} \
++ expected
++
+ # Check learned hairpin reply flows.
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+- table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ AS_BOX([Delete VIP])
+ check ovn-nbctl --wait=hv set Load_Balancer lb-ipv4-tcp
vips='"88.88.88.88:8080"=""'
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+- table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+ AS_BOX([Delete LB])
+-check ovn-nbctl --wait=hv \
+- -- lb-del lb-ipv4-tcp \
+- -- lb-del lb-ipv4-udp
++check ovn-nbctl --wait=hv \
++ -- lb-del lb-ipv4-tcp \
++ -- lb-del lb-ipv4-tcp-dup \
++ -- lb-del lb-ipv4-udp \
++ -- lb-del lb-ipv4-udp-dup
+
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, tcp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=4041
actions=load:0x1->NXM_NX_REG10[[7]]
+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+-check ovn-nbctl --wait=hv lb-del lb-ipv6-tcp
++check ovn-nbctl --wait=hv \
++ -- lb-del lb-ipv6-tcp \
++ -- lb-del lb-ipv6-tcp-dup
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::87,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
++ table=69, udp6,metadata=0x1,ipv6_src=4200::1,ipv6_dst=8800::89,tp_src=2021
actions=load:0x1->NXM_NX_REG10[[7]]
+ ])
+
+-check ovn-nbctl --wait=hv lb-del lb-ipv6-udp
++check ovn-nbctl --wait=hv \
++ -- lb-del lb-ipv6-udp \
++ -- lb-del lb-ipv6-udp-dup
+ OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all |
grep -v NXST], [1], [dnl
+ ])
+
+@@ -23487,15 +23606,15 @@ OVS_WAIT_UNTIL(
+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 1]
+ )
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
+ NXST_FLOW reply (xid=0x8):
+ ])
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ ])
+
+@@ -23514,17 +23633,17 @@ OVS_WAIT_UNTIL(
+ [test $(as hv1 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 3]
+ )
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
+ NXST_FLOW reply (xid=0x8):
+ ])
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+@@ -23546,16 +23665,16 @@ OVS_WAIT_UNTIL(
+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 3]
+ )
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+@@ -23570,33 +23689,33 @@ OVS_WAIT_UNTIL(
+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 4]
+ )
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+@@ -23612,36 +23731,36 @@ OVS_WAIT_UNTIL(
+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 5]
+ )
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+@@ -23658,19 +23777,19 @@ OVS_WAIT_UNTIL(
+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6]
+ )
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+@@ -23678,19 +23797,19 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 |
ofctl_strip_all | grep -
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+@@ -23710,19 +23829,40 @@ OVS_WAIT_UNTIL(
+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 6]
+ )
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
++])
++
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+@@ -23731,19 +23871,53 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 |
ofctl_strip_all | grep -
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++# Check backwards compatibility with ovn-northd versions that don't store the
++# original destination tuple.
++#
++# ovn-controller should fall back to matching on ct_nw_dst()/ct_tp_dst().
++as northd-backup ovn-appctl -t ovn-northd pause
++as northd ovn-appctl -t ovn-northd pause
++
++check ovn-sbctl \
++ -- remove load_balancer lb-ipv4-tcp options hairpin_orig_tuple \
++ -- remove load_balancer lb-ipv6-tcp options hairpin_orig_tuple \
++ -- remove load_balancer lb-ipv4-udp options hairpin_orig_tuple \
++ -- remove load_balancer lb-ipv6-udp options hairpin_orig_tuple
++
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all |
grep -v NXST], [0], [dnl
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++])
++
++AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
++])
++
++OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all |
grep -v NXST], [0], [dnl
++ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++])
++
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all |
grep -v NXST], [0], [dnl
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=42.42.42.42,nw_dst=42.42.42.42,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_state=+trk+dnat,ct_label=0x2/0x2,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,nw_src=52.52.52.52,nw_dst=52.52.52.52,tp_dst=4042
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.90,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v NXST], [1], [dnl
+ ])
+
+-AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++OVS_WAIT_FOR_OUTPUT([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all |
grep -v NXST], [0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+@@ -23752,6 +23926,11 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 |
ofctl_strip_all | grep -
+ table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+
++# Resume ovn-northd.
++as northd ovn-appctl -t ovn-northd resume
++as northd-backup ovn-appctl -t ovn-northd resume
++check ovn-nbctl --wait=hv sync
++
+ as hv2 ovs-vsctl del-port hv2-vif1
+ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown])
+
+@@ -23784,17 +23963,17 @@ OVS_WAIT_UNTIL(
+ [test $(as hv2 ovs-ofctl dump-flows br-int table=68 | grep -c -v NXST) -eq 0]
+ )
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
+- table=68,
priority=100,ct_label=0x2/0x2,tcp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+- table=68,
priority=100,ct_label=0x2/0x2,udp6,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
++ table=68,
priority=100,ct_label=0x2/0x2,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=4041
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=6,NXM_OF_TCP_SRC[[]]=NXM_OF_TCP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp,reg1=0x58585858,reg2=0xfc8/0xffff,nw_src=42.42.42.1,nw_dst=42.42.42.1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x800,NXM_OF_IP_SRC[[]],ip_dst=88.88.88.88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
++ table=68,
priority=100,ct_label=0x2/0x2,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,ipv6_src=4200::1,ipv6_dst=4200::1,tp_dst=2021
actions=load:0x1->NXM_NX_REG10[[7]],learn(table=69,delete_learned,OXM_OF_METADATA[[]],eth_type=0x86dd,NXM_NX_IPV6_SRC[[]],ipv6_dst=8800::88,nw_proto=17,NXM_OF_UDP_SRC[[]]=NXM_OF_UDP_DST[[]],load:0x1->NXM_NX_REG10[[7]])
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69], [0], [dnl
+ NXST_FLOW reply (xid=0x8):
+ ])
+
+-AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST |
sort], [0], [dnl
++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+diff --git a/tests/system-ovn.at b/tests/system-ovn.at
+index 05ccd86..f292344 100644
+--- a/tests/system-ovn.at
++++ b/tests/system-ovn.at
+@@ -4335,10 +4335,14 @@ start_daemon ovn-controller
+ # One logical switch with IPv4 load balancers that hairpin the traffic.
+ ovn-nbctl ls-add sw
+ ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
+-ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
+-ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
++ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp
++ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp
++ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp
++ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp
+ ovn-nbctl ls-lb-add sw lb-ipv4-tcp
++ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup
+ ovn-nbctl ls-lb-add sw lb-ipv4-udp
++ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup
+
+ ovn-nbctl lr-add rtr
+ ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24
+@@ -4354,24 +4358,26 @@ ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24",
"00:00:00:00:00:01", \
+ ovn-nbctl --wait=hv -t 3 sync
+
+ # Start IPv4 TCP server on lsp.
+-NS_CHECK_EXEC([lsp], [timeout 2s nc -l 42.42.42.1 4041 &], [0])
++NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 42.42.42.1 4041 &], [0])
+
+-# Check that IPv4 TCP hairpin connection succeeds.
++# Check that IPv4 TCP hairpin connection succeeds on both VIPs.
+ NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0])
++NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0])
+
+ # Capture IPv4 UDP hairpinned packets.
+-filter="src 88.88.88.88 and dst 42.42.42.1 and dst port 2021 and udp"
+-NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp ${filter} > lsp.pcap &])
++filter="dst 42.42.42.1 and dst port 2021 and udp"
++NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp ${filter} > lsp.pcap &])
+
+ sleep 1
+
+ # Generate IPv4 UDP hairpin traffic.
+ NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -z &], [0])
++NS_CHECK_EXEC([lsp], [nc -u 88.88.88.89 4040 -z &], [0])
+
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+ total_pkts=$(cat lsp.pcap | wc -l)
+- test "${total_pkts}" = "1"
++ test "${total_pkts}" = "2"
+ ])
+
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+@@ -4414,10 +4420,14 @@ start_daemon ovn-controller
+ # One logical switch with IPv6 load balancers that hairpin the traffic.
+ ovn-nbctl ls-add sw
+ ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01
+-ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
+-ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
++ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp
++ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp
++ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp
++ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp
+ ovn-nbctl ls-lb-add sw lb-ipv6-tcp
++ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup
+ ovn-nbctl ls-lb-add sw lb-ipv6-udp
++ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup
+
+ ovn-nbctl lr-add rtr
+ ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 4200::00ff/64
+@@ -4432,24 +4442,26 @@ OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep
4200::1 | grep tentative)"
+ ovn-nbctl --wait=hv -t 3 sync
+
+ # Start IPv6 TCP server on lsp.
+-NS_CHECK_EXEC([lsp], [timeout 2s nc -l 4200::1 4041 &], [0])
++NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 4200::1 4041 &], [0])
+
+-# Check that IPv6 TCP hairpin connection succeeds.
++# Check that IPv6 TCP hairpin connection succeeds on both VIPs.
+ NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0])
++NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0])
+
+ # Capture IPv4 UDP hairpinned packets.
+-filter="src 8800::0088 and dst 4200::1 and dst port 2021 and udp"
+-NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp $filter > lsp.pcap &])
++filter="dst 4200::1 and dst port 2021 and udp"
++NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp $filter > lsp.pcap &])
+
+ sleep 1
+
+ # Generate IPv6 UDP hairpin traffic.
+ NS_CHECK_EXEC([lsp], [nc -u 8800::0088 4040 -z &], [0])
++NS_CHECK_EXEC([lsp], [nc -u 8800::0089 4040 -z &], [0])
+
+ # Check hairpin traffic.
+ OVS_WAIT_UNTIL([
+ total_pkts=$(cat lsp.pcap | wc -l)
+- test "${total_pkts}" = "1"
++ test "${total_pkts}" = "2"
+ ])
+
+ OVS_APP_EXIT_AND_WAIT([ovn-controller])
+--
+1.8.3.1
+
diff --git a/0015-lflow-Avoid-matching-on-conntrack-original-tuple-if-.patch
b/0015-lflow-Avoid-matching-on-conntrack-original-tuple-if-.patch
new file mode 100644
index 0000000..dfe3215
--- /dev/null
+++ b/0015-lflow-Avoid-matching-on-conntrack-original-tuple-if-.patch
@@ -0,0 +1,281 @@
+From 35a6507db36c23724bc319d56dc6b05dd6d60a99 Mon Sep 17 00:00:00 2001
+From: Dumitru Ceara <dceara(a)redhat.com>
+Date: Wed, 24 Feb 2021 17:09:54 +0100
+Subject: [PATCH 15/16] lflow: Avoid matching on conntrack original tuple if
+ possible.
+
+Matching on ct_nw_dst()/ct_ipv6_dst()/ct_tp_dst() creates openflows
+that often are not offloadable to hardware. This was used for
+detecting load balancer hairpin sessions.
+
+However, it can be avoided if ovn-northd stores the original
+destination tuple in OVS registers. For backwards compatibility,
+during upgrade, fall back to matching on the conntrack original tuple.
+
+Acked-by: Numan Siddique <numans(a)ovn.org>
+Signed-off-by: Dumitru Ceara <dceara(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit e1f896fb8e1b505f09160052fc98e6a403fc27e5)
+
+Change-Id: I0a9c9c83050a6922c1c017414103742513d18ae7
+---
+ controller/lflow.c | 50 +++++++++++++++++++++++++-----
+ tests/ovn.at | 90 +++++++++++++++++++++++++++---------------------------
+ 2 files changed, 88 insertions(+), 52 deletions(-)
+
+diff --git a/controller/lflow.c b/controller/lflow.c
+index 5bc6f1e..76a4dea 100644
+--- a/controller/lflow.c
++++ b/controller/lflow.c
+@@ -1457,6 +1457,12 @@ add_lb_vip_hairpin_flows(struct ovn_controller_lb *lb,
+ ofpbuf_uninit(&ofpacts);
+ }
+
++/* Adds flows to perform SNAT for hairpin sessions.
++ *
++ * For backwards compatibilty with older ovn-northd versions, uses
++ * ct_nw_dst(), ct_ipv6_dst(), ct_tp_dst(), otherwise uses the
++ * original destination tuple stored by ovn-northd.
++ */
+ static void
+ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
+ struct ovn_lb_vip *lb_vip,
+@@ -1499,20 +1505,50 @@ add_lb_ct_snat_vip_flows(struct ovn_controller_lb *lb,
+ ofpact_finish(&ofpacts, &ct->ofpact);
+
+ struct match match = MATCH_CATCHALL_INITIALIZER;
++
++ /* Matching on ct_nw_dst()/ct_ipv6_dst()/ct_tp_dst() requires matching
++ * on ct_state first.
++ */
++ if (!lb->hairpin_orig_tuple) {
++ uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
++ match_set_ct_state_masked(&match, ct_state, ct_state);
++ }
++
+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
++ ovs_be32 vip4 = in6_addr_get_mapped_ipv4(&lb_vip->vip);
++
+ match_set_dl_type(&match, htons(ETH_TYPE_IP));
+- match_set_ct_nw_dst(&match, in6_addr_get_mapped_ipv4(&lb_vip->vip));
++
++ if (!lb->hairpin_orig_tuple) {
++ match_set_ct_nw_dst(&match, vip4);
++ } else {
++ match_set_reg(&match, MFF_LOG_LB_ORIG_DIP_IPV4 - MFF_LOG_REG0,
++ ntohl(vip4));
++ }
+ } else {
+ match_set_dl_type(&match, htons(ETH_TYPE_IPV6));
+- match_set_ct_ipv6_dst(&match, &lb_vip->vip);
++
++ if (!lb->hairpin_orig_tuple) {
++ match_set_ct_ipv6_dst(&match, &lb_vip->vip);
++ } else {
++ ovs_be128 vip6_value;
++
++ memcpy(&vip6_value, &lb_vip->vip, sizeof vip6_value);
++ match_set_xxreg(&match, MFF_LOG_LB_ORIG_DIP_IPV6 - MFF_LOG_XXREG0,
++ ntoh128(vip6_value));
++ }
+ }
+
+ match_set_nw_proto(&match, lb_proto);
+- match_set_ct_nw_proto(&match, lb_proto);
+- match_set_ct_tp_dst(&match, htons(lb_vip->vip_port));
+-
+- uint32_t ct_state = OVS_CS_F_TRACKED | OVS_CS_F_DST_NAT;
+- match_set_ct_state_masked(&match, ct_state, ct_state);
++ if (lb_vip->vip_port) {
++ if (!lb->hairpin_orig_tuple) {
++ match_set_ct_nw_proto(&match, lb_proto);
++ match_set_ct_tp_dst(&match, htons(lb_vip->vip_port));
++ } else {
++ match_set_reg_masked(&match, MFF_LOG_LB_ORIG_TP_DPORT - MFF_REG0,
++ lb_vip->vip_port, UINT16_MAX);
++ }
++ }
+
+ for (size_t i = 0; i < lb->slb->n_datapaths; i++) {
+ match_set_metadata(&match,
+diff --git a/tests/ovn.at b/tests/ovn.at
+index 2515b5b..bd59c0a 100644
+--- a/tests/ovn.at
++++ b/tests/ovn.at
+@@ -23615,7 +23615,7 @@ NXST_FLOW reply (xid=0x8):
+ ])
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
+@@ -23644,8 +23644,8 @@ NXST_FLOW reply (xid=0x8):
+ ])
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | grep -v NXST], [1], [dnl
+@@ -23675,8 +23675,8 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
+ ])
+
+ check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv4-udp
+@@ -23700,9 +23700,9 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+@@ -23716,9 +23716,9 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ ])
+
+ check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-tcp
+@@ -23743,10 +23743,10 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70,
priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+@@ -23761,10 +23761,10 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70,
priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+ ])
+
+ check ovn-nbctl --wait=hv ls-lb-add sw0 lb-ipv6-udp
+@@ -23790,11 +23790,11 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70,
priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+@@ -23810,11 +23810,11 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70,
priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ ])
+
+ check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp
+@@ -23842,12 +23842,12 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70,
priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=68 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+@@ -23863,12 +23863,12 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=69 | grep -v
NXST], [1], [dnl
+ ])
+
+ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.90,ct_nw_proto=6,ct_tp_dst=8080,tcp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70, priority=100,tcp,reg1=0x58585858,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70, priority=100,tcp,reg1=0x5858585a,reg2=0x1f90/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.90))
++ table=70,
priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ ])
+
+ # Check backwards compatibility with ovn-northd versions that don't store the
+@@ -23974,10 +23974,10 @@ NXST_FLOW reply (xid=0x8):
+ ])
+
+ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=70 | ofctl_strip_all | grep -v NXST],
[0], [dnl
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=17,ct_tp_dst=4040,udp6,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_ipv6_dst=8800::88,ct_nw_proto=6,ct_tp_dst=8080,tcp6,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+- table=70,
priority=100,ct_state=+trk+dnat,ct_nw_dst=88.88.88.88,ct_nw_proto=17,ct_tp_dst=4040,udp,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,tcp6,reg2=0x1f90/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70, priority=100,udp,reg1=0x58585858,reg2=0xfc8/0xffff,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=88.88.88.88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x1
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
++ table=70,
priority=100,udp6,reg2=0xfc8/0xffff,reg4=0x88000000,reg5=0,reg6=0,reg7=0x88,metadata=0x2
actions=ct(commit,zone=NXM_NX_REG12[[0..15]],nat(src=8800::88))
+ ])
+
+ check ovn-nbctl --wait=hv ls-del sw0
+--
+1.8.3.1
+
diff --git a/0016-northd-Avoid-matching-on-ct.dnat-flags-for-load-bala.patch
b/0016-northd-Avoid-matching-on-ct.dnat-flags-for-load-bala.patch
new file mode 100644
index 0000000..4c5e0a4
--- /dev/null
+++ b/0016-northd-Avoid-matching-on-ct.dnat-flags-for-load-bala.patch
@@ -0,0 +1,220 @@
+From 2baa1b74fb8e93e158d4800d9e42e24b70b3970a Mon Sep 17 00:00:00 2001
+From: Dumitru Ceara <dceara(a)redhat.com>
+Date: Wed, 24 Feb 2021 17:10:07 +0100
+Subject: [PATCH 16/16] northd: Avoid matching on ct.dnat flags for load
+ balancers.
+
+Matching on ct.dnat creates openflows that often are not offloadable
+to hardware. ovn-northd uses ct.dnat only for load balancer hairpin
+traffic handling and it turns out we don't really need to match on
+ct.dnat.
+
+Acked-by: Numan Siddique <numans(a)ovn.org>
+Signed-off-by: Dumitru Ceara <dceara(a)redhat.com>
+Signed-off-by: Numan Siddique <numans(a)ovn.org>
+(cherry picked from upstream commit 110e670462a9ae580d40d14181c388fc2f0b1e1c)
+
+Change-Id: Icea5badee2d4a83ac33f45557e39ebd47bf6192b
+---
+ northd/ovn-northd.8.xml | 38 +++++++++++++++++++-----------------
+ northd/ovn-northd.c | 52 ++++++++++++++++++++++++++++++-------------------
+ tests/ovn-northd.at | 29 +++++++++++++++++++++++++++
+ 3 files changed, 81 insertions(+), 38 deletions(-)
+
+diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
+index c2de5ba..0a22023 100644
+--- a/northd/ovn-northd.8.xml
++++ b/northd/ovn-northd.8.xml
+@@ -812,20 +812,13 @@
+ <ul>
+ <li>
+ If the logical switch has load balancer(s) configured, then a
+- priorirty-100 flow is added with the match
+- <code>ip && ct.trk&& ct.dnat</code>
to check if the
++ priority-100 flow is added with the match
++ <code>ip && ct.trk</code> to check if the
+ packet needs to be hairpinned (if after load balancing the destination
+- IP matches the source IP) or not by executing the action
+- <code>reg0[6] = chk_lb_hairpin();</code> and advances the packet to
+- the next table.
+- </li>
+-
+- <li>
+- If the logical switch has load balancer(s) configured, then a
+- priorirty-90 flow is added with the match <code>ip</code> to check
if
+- the packet is a reply for a hairpinned connection or not by executing
+- the action <code>reg0[6] = chk_lb_hairpin_reply();</code> and
advances
+- the packet to the next table.
++ IP matches the source IP) or not by executing the actions
++ <code>reg0[6] = chk_lb_hairpin();</code> and
++ <code>reg0[12] = chk_lb_hairpin_reply();</code> and advances the
packet
++ to the next table.
+ </li>
+
+ <li>
+@@ -837,17 +830,26 @@
+ <ul>
+ <li>
+ If the logical switch has load balancer(s) configured, then a
+- priorirty-100 flow is added with the match
+- <code>ip && (ct.new || ct.est) && ct.trk
&&
+- ct.dnat && reg0[6] == 1</code> which hairpins the traffic
by
++ priority-100 flow is added with the match
++ <code>ip && ct.new && ct.trk
&&
++ reg0[6] == 1</code> which hairpins the traffic by
+ NATting source IP to the load balancer VIP by executing the action
+ <code>ct_snat_to_vip</code> and advances the packet to the next
table.
+ </li>
+
+ <li>
+ If the logical switch has load balancer(s) configured, then a
+- priorirty-90 flow is added with the match
+- <code>ip && reg0[6] == 1</code> which matches on
the replies
++ priority-100 flow is added with the match
++ <code>ip && ct.est && ct.trk
&&
++ reg0[6] == 1</code> which hairpins the traffic by
++ NATting source IP to the load balancer VIP by executing the action
++ <code>ct_snat</code> and advances the packet to the next table.
++ </li>
++
++ <li>
++ If the logical switch has load balancer(s) configured, then a
++ priority-90 flow is added with the match
++ <code>ip && reg0[12] == 1</code> which matches on
the replies
+ of hairpinned traffic (i.e., destination IP is VIP,
+ source IP is the backend IP and source L4 port is backend port for L4
+ load balancers) and executes <code>ct_snat</code> and advances the
+diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
+index 9209c13..c1614d8 100644
+--- a/northd/ovn-northd.c
++++ b/northd/ovn-northd.c
+@@ -231,6 +231,7 @@ enum ovn_stage {
+ #define REGBIT_ACL_HINT_DROP "reg0[9]"
+ #define REGBIT_ACL_HINT_BLOCK "reg0[10]"
+ #define REGBIT_LKUP_FDB "reg0[11]"
++#define REGBIT_HAIRPIN_REPLY "reg0[12]"
+
+ #define REG_ORIG_DIP_IPV4 "reg1"
+ #define REG_ORIG_DIP_IPV6 "xxreg1"
+@@ -270,7 +271,8 @@ enum ovn_stage {
+ *
+ * Logical Switch pipeline:
+ * +----+----------------------------------------------+---+------------------+
+- * | R0 | REGBIT_{CONNTRACK/DHCP/DNS/HAIRPIN} | | |
++ * | R0 | REGBIT_{CONNTRACK/DHCP/DNS} | | |
++ * | | REGBIT_{HAIRPIN/HAIRPIN_REPLY} | X | |
+ * | | REGBIT_ACL_HINT_{ALLOW_NEW/ALLOW/DROP/BLOCK} | X | |
+ * +----+----------------------------------------------+ X | |
+ * | R1 | ORIG_DIP_IPV4 (>= IN_STATEFUL) | R | |
+@@ -6260,39 +6262,49 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows)
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1",
"next;");
+
+ if (od->has_lb_vip) {
+- /* Check if the packet needs to be hairpinned. */
+- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100,
+- "ip && ct.trk && ct.dnat",
+- REGBIT_HAIRPIN " = chk_lb_hairpin(); next;",
++ /* Check if the packet needs to be hairpinned.
++ * Set REGBIT_HAIRPIN in the original direction and
++ * REGBIT_HAIRPIN_REPLY in the reply direction.
++ */
++ ovn_lflow_add_with_hint(
++ lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 100, "ip && ct.trk",
++ REGBIT_HAIRPIN " = chk_lb_hairpin(); "
++ REGBIT_HAIRPIN_REPLY " = chk_lb_hairpin_reply(); "
++ "next;",
++ &od->nbs->header_);
++
++ /* If packet needs to be hairpinned, snat the src ip with the VIP
++ * for new sessions. */
++ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100,
++ "ip && ct.new && ct.trk"
++ " && "REGBIT_HAIRPIN " ==
1",
++ "ct_snat_to_vip; next;",
+ &od->nbs->header_);
+
+- /* Check if the packet is a reply of hairpinned traffic. */
+- ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_HAIRPIN, 90,
"ip",
+- REGBIT_HAIRPIN " = chk_lb_hairpin_reply(); "
+- "next;", &od->nbs->header_);
+-
+- /* If packet needs to be hairpinned, snat the src ip with the VIP. */
++ /* If packet needs to be hairpinned, for established sessions there
++ * should already be an SNAT conntrack entry.
++ */
+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 100,
+- "ip && (ct.new || ct.est) && ct.trk
&& ct.dnat"
++ "ip && ct.est && ct.trk"
+ " && "REGBIT_HAIRPIN " ==
1",
+- "ct_snat_to_vip; next;",
++ "ct_snat;",
+ &od->nbs->header_);
+
+ /* For the reply of hairpinned traffic, snat the src ip to the VIP. */
+ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 90,
+- "ip && "REGBIT_HAIRPIN " ==
1", "ct_snat;",
++ "ip && "REGBIT_HAIRPIN_REPLY " ==
1",
++ "ct_snat;",
+ &od->nbs->header_);
+
+ /* Ingress Hairpin table.
+ * - Priority 1: Packets that were SNAT-ed for hairpinning should be
+ * looped back (i.e., swap ETH addresses and send back on inport).
+ */
+- ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 1,
+- REGBIT_HAIRPIN " == 1",
+- "eth.dst <-> eth.src;"
+- "outport = inport;"
+- "flags.loopback = 1;"
+- "output;");
++ ovn_lflow_add(
++ lflows, od, S_SWITCH_IN_HAIRPIN, 1,
++ "("REGBIT_HAIRPIN " == 1 || " REGBIT_HAIRPIN_REPLY
" == 1)",
++ "eth.dst <-> eth.src; outport = inport; flags.loopback = 1;
"
++ "output;");
+ }
+ }
+
+diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
+index e252fc0..3592c04 100644
+--- a/tests/ovn-northd.at
++++ b/tests/ovn-northd.at
+@@ -2185,6 +2185,35 @@ check_column "$lb0_uuid" sb:datapath_binding
load_balancers external_ids:name=sw
+
+ AT_CLEANUP
+
++AT_SETUP([ovn -- LS load balancer hairpin logical flows])
++ovn_start
++
++check ovn-nbctl \
++ -- ls-add sw0 \
++ -- lb-add lb0 10.0.0.10:80 10.0.0.4:8080 \
++ -- ls-lb-add sw0 lb0
++
++check ovn-nbctl --wait=sb sync
++
++AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_pre_hairpin | sort], [0], [dnl
++ table=14(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;)
++ table=14(ls_in_pre_hairpin ), priority=100 , match=(ip && ct.trk),
action=(reg0[[6]] = chk_lb_hairpin(); reg0[[12]] = chk_lb_hairpin_reply(); next;)
++])
++
++AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_nat_hairpin | sort], [0], [dnl
++ table=15(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;)
++ table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est &&
ct.trk && reg0[[6]] == 1), action=(ct_snat;)
++ table=15(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new &&
ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;)
++ table=15(ls_in_nat_hairpin ), priority=90 , match=(ip && reg0[[12]] == 1),
action=(ct_snat;)
++])
++
++AT_CHECK([ovn-sbctl lflow-list sw0 | grep ls_in_hairpin | sort], [0], [dnl
++ table=16(ls_in_hairpin ), priority=0 , match=(1), action=(next;)
++ table=16(ls_in_hairpin ), priority=1 , match=((reg0[[6]] == 1 || reg0[[12]] ==
1)), action=(eth.dst <-> eth.src; outport = inport; flags.loopback = 1; output;)
++])
++
++AT_CLEANUP
++
+ AT_SETUP([ovn -- logical gatapath groups])
+ AT_KEYWORDS([use_logical_dp_groups])
+ ovn_start
+--
+1.8.3.1
+
diff --git a/ovn.spec b/ovn.spec
index a65feb1..d55ffef 100644
--- a/ovn.spec
+++ b/ovn.spec
@@ -43,7 +43,7 @@ Name: ovn
Summary: Open Virtual Network support
URL:
http://www.openvswitch.org/
Version: 20.12.0
-Release: 23%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
+Release: 24%{?commit0:.%{date}git%{shortcommit0}}%{?dist}
Obsoletes: openvswitch-ovn-common < %{?epoch_ovs:%{epoch_ovs}:}2.11.0-8
Provides: openvswitch-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release}
@@ -178,6 +178,26 @@ Patch231: 0002-ofctrl-Do-not-link-a-desired-flow-twice.patch
# Bug 1929978
Patch240: 0001-ofctrl-Fix-assert-seen-when-flood-removing-flows-conj.patch
+# Bug 1841009
+# Bug 1672625
+# Bug 1931599
+Patch250: 0001-tests-Eliminate-most-sleep-calls.patch
+Patch251: 0002-tests-Add-more-checking-to-3-HVs-1-LS-3-lports-HV-te.patch
+Patch252: 0003-tests-Improve-debuggability-of-tests.patch
+Patch253: 0004-Implement-SCTP-specific-reject-action.patch
+Patch254: 0005-Add-sctp_abort-logical-flow-action.patch
+Patch255: 0006-controller-Split-mac-learning-code-to-a-separate-fil.patch
+Patch256: 0007-MAC-learning-Add-a-new-FDB-table-in-southbound-db.patch
+Patch257: 0008-MAC-learning-Add-new-actions-put_fdb-get_fdb-and-loo.patch
+Patch258: 0009-controller-MAC-learning-Add-OF-rules-for-the-FDB-ent.patch
+Patch259: 0010-northd-MAC-learning-Add-logical-flows-for-fdb.patch
+Patch261: 0011-Fix-the-failing-test-case-ovn-ACL-skip-hints-for-sta.patch
+Patch262: 0012-northd-Cleanup-stale-FDB-entries.patch
+Patch263: 0013-mac-learn-Fix-build-due-to-missing-newline-at-EOF.patch
+Patch264: 0014-Properly-handle-hairpin-traffic-for-VIPs-with-shared.patch
+Patch265: 0015-lflow-Avoid-matching-on-conntrack-original-tuple-if-.patch
+Patch266: 0016-northd-Avoid-matching-on-ct.dnat-flags-for-load-bala.patch
+
# OpenvSwitch backports (400-) if required.
# Address crpto policy for fedora
%if 0%{?fedora}
@@ -543,6 +563,24 @@ fi
%{_unitdir}/ovn-controller-vtep.service
%changelog
+* Mon Mar 01 2021 Numan Siddique <numans(a)ovn.org> - 20.12.0-24
+- Backport "tests: Eliminate most "sleep" calls."
+- Backport "tests: Add more checking to "3 HVs, 1 LS, 3 lports/HV"
test."
+- Backport "tests: Improve debuggability of tests."
+- Backport "Implement SCTP-specific reject() action." (#1841009)
+- Backport "Add sctp_abort logical flow action." (#1841009)
+- Backport "controller: Split mac learning code to a separate file."
(#1672625)
+- Backport "MAC learning: Add a new FDB table in southbound db." (#1672625)
+- Backport "MAC learning: Add new actions - put_fdb, get_fdb and lookup_fdb."
(#1672625)
+- Backport "controller: MAC learning: Add OF rules for the FDB entries."
(#1672625)
+- Backport "northd: MAC learning: Add logical flows for fdb." (#1672625)
+- Backport "Fix the failing test case - ovn -- ACL skip hints for stateless
config." (#1672625)
+- Backport "northd: Cleanup stale FDB entries." (#1672625)
+- Backport "mac-learn: Fix build due to missing newline at EOF." (#1672625)
+- Backport "Properly handle hairpin traffic for VIPs with shared backends."
(#1931599)
+- Backport "lflow: Avoid matching on conntrack original tuple if possible."
+- Backport "northd: Avoid matching on ct.dnat flags for load balancers."
+
* Mon Mar 01 2021 Numan Siddique <numans(a)ovn.org> - 20.12.0-23
- Backport "ofctrl: Fix the assert seen when flood removing flows with conj
actions." (#1929978)
https://src.fedoraproject.org/rpms/ovn/c/a102681dd6f669b3ec3a9e1c20074996...