ebb439
From 9ac0a75fd1995dee44f80ba4d7856cbf7b0e456d Mon Sep 17 00:00:00 2001
ebb439
From: Han Zhou <hzhou@ovn.org>
ebb439
Date: Thu, 1 Oct 2020 22:49:04 -0700
ebb439
Subject: [PATCH 1/7] ofctrl.c: Fix duplicated flow handling in I-P while
ebb439
 merging opposite changes.
ebb439
ebb439
In a scenario in I-P when a desired flow is removed and an exactly same flow is
ebb439
added back in the same iteration, the merge_tracked_flows() function will merge
ebb439
the operation without firing any OpenFlow action. However, if there are
ebb439
multiple desired flows (e.g. F1 and F2) matching the same installed flow, and
ebb439
if the one being re-added (say, F1) is the one currently installed in OVS, the
ebb439
current implementation will update the currently installed flow to F2 in the
ebb439
data structure while the actual OVS flow is not updated (still F1). So far
ebb439
there is still no problem, but the member desired_flow of the installed flow
ebb439
is pointing to the wrong desired flow.
ebb439
ebb439
Now there is only one place where the desired_flow member is consulted, in
ebb439
update_installed_flows_by_track() for handling a tracked *modified* flow. In
ebb439
reality flow modification happens only when conjunction flows need to be
ebb439
combined, which would never happen together with the above scenario of
ebb439
merge_tracked_flows(). So this wrong desired_flow pointer doesn't cause any
ebb439
real problem yet.
ebb439
ebb439
However, there is a place that can already utilize this active desired_flow
ebb439
information, which is when handling a tracked flow deletion in
ebb439
update_installed_flows_by_track(): we can check if the flow being deleted is
ebb439
the currently active one installed in OVS. If not, we don't need to send and
ebb439
OpenFlow modify to OVS. This optimization is added in this patch, apart from
ebb439
fixing the problem of merge_tracked_flows(). Without the fix, this optimization
ebb439
would cause the test case added in this patch fail.
ebb439
ebb439
Fixes: f4e508dd7 ("ofctrl.c: Merge opposite changes of tracked flows before installing.")
ebb439
Acked-by: Dumitru Ceara <dceara@redhat.com>
ebb439
Signed-off-by: Han Zhou <hzhou@ovn.org>
ebb439
(cherry picked from upstream commit 9d2e8d32fb9865513b70408a665184a67564390d)
ebb439
ebb439
Change-Id: Ieb30dd8f4d07aae472c222920fcb773a1deace4d
ebb439
---
ebb439
 controller/ofctrl.c |  28 +++++++++++++-
ebb439
 tests/ovn.at        | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++
ebb439
 2 files changed, 134 insertions(+), 2 deletions(-)
ebb439
ebb439
diff --git a/controller/ofctrl.c b/controller/ofctrl.c
ebb439
index 81a00c8..e725c00 100644
ebb439
--- a/controller/ofctrl.c
ebb439
+++ b/controller/ofctrl.c
ebb439
@@ -788,6 +788,24 @@ ofctrl_recv(const struct ofp_header *oh, enum ofptype type)
ebb439
     }
ebb439
 }
ebb439
 
ebb439
+/* Returns true if a desired flow is active (the one currently installed
ebb439
+ * among the list of desired flows that are linked to the same installed
ebb439
+ * flow). */
ebb439
+static inline bool
ebb439
+desired_flow_is_active(struct desired_flow *d)
ebb439
+{
ebb439
+    return (d->installed_flow && d->installed_flow->desired_flow == d);
ebb439
+}
ebb439
+
ebb439
+/* Set a desired flow as the active one among the list of desired flows
ebb439
+ * that are linked to the same installed flow. */
ebb439
+static inline void
ebb439
+desired_flow_set_active(struct desired_flow *d)
ebb439
+{
ebb439
+    ovs_assert(d->installed_flow);
ebb439
+    d->installed_flow->desired_flow = d;
ebb439
+}
ebb439
+
ebb439
 static void
ebb439
 link_installed_to_desired(struct installed_flow *i, struct desired_flow *d)
ebb439
 {
ebb439
@@ -1740,6 +1758,8 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table)
ebb439
             /* del_f must have been installed, otherwise it should have been
ebb439
              * removed during track_flow_add_or_modify. */
ebb439
             ovs_assert(del_f->installed_flow);
ebb439
+
ebb439
+            bool del_f_was_active = desired_flow_is_active(del_f);
ebb439
             if (!f->installed_flow) {
ebb439
                 /* f is not installed yet. */
ebb439
                 struct installed_flow *i = del_f->installed_flow;
ebb439
@@ -1751,6 +1771,9 @@ merge_tracked_flows(struct ovn_desired_flow_table *flow_table)
ebb439
                 ovs_assert(f->installed_flow == del_f->installed_flow);
ebb439
                 unlink_installed_to_desired(del_f->installed_flow, del_f);
ebb439
             }
ebb439
+            if (del_f_was_active) {
ebb439
+                desired_flow_set_active(f);
ebb439
+            }
ebb439
             hmap_remove(&deleted_flows, &del_f->match_hmap_node);
ebb439
             ovs_list_remove(&del_f->track_list_node);
ebb439
             desired_flow_destroy(del_f);
ebb439
@@ -1778,6 +1801,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
ebb439
             /* The desired flow was deleted */
ebb439
             if (f->installed_flow) {
ebb439
                 struct installed_flow *i = f->installed_flow;
ebb439
+                bool was_active = desired_flow_is_active(f);
ebb439
                 unlink_installed_to_desired(i, f);
ebb439
 
ebb439
                 if (!i->desired_flow) {
ebb439
@@ -1786,7 +1810,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
ebb439
 
ebb439
                     hmap_remove(&installed_flows, &i->match_hmap_node);
ebb439
                     installed_flow_destroy(i);
ebb439
-                } else {
ebb439
+                } else if (was_active) {
ebb439
                     /* There are other desired flow(s) referencing this
ebb439
                      * installed flow, so update the OVS flow for the new
ebb439
                      * active flow (at least the cookie will be different,
ebb439
@@ -1810,7 +1834,7 @@ update_installed_flows_by_track(struct ovn_desired_flow_table *flow_table,
ebb439
                 hmap_insert(&installed_flows, &new_node->match_hmap_node,
ebb439
                             new_node->flow.hash);
ebb439
                 link_installed_to_desired(new_node, f);
ebb439
-            } else if (i->desired_flow == f) {
ebb439
+            } else if (desired_flow_is_active(f)) {
ebb439
                 /* The installed flow is installed for f, but f has change
ebb439
                  * tracked, so it must have been modified. */
ebb439
                 ovn_flow_log(&i->flow, "updating installed (tracked)");
ebb439
diff --git a/tests/ovn.at b/tests/ovn.at
ebb439
index e3f3153..6f1ab59 100644
ebb439
--- a/tests/ovn.at
ebb439
+++ b/tests/ovn.at
ebb439
@@ -22266,6 +22266,114 @@ OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected])
ebb439
 OVN_CLEANUP([hv1])
ebb439
 AT_CLEANUP
ebb439
 
ebb439
+# This test cases tests a scenario of ACL confliction with address set update.
ebb439
+# It is to cover a corner case when flows are re-processed in the I-P
ebb439
+# iteration, combined with the scenario of conflicting ACLs.
ebb439
+AT_SETUP([ovn -- conflict ACLs with address set])
ebb439
+ovn_start
ebb439
+
ebb439
+ovn-nbctl ls-add ls1
ebb439
+
ebb439
+ovn-nbctl lsp-add ls1 lsp1 \
ebb439
+-- lsp-set-addresses lsp1 "f0:00:00:00:00:01 10.0.0.1"
ebb439
+
ebb439
+ovn-nbctl lsp-add ls1 lsp2 \
ebb439
+-- lsp-set-addresses lsp2 "f0:00:00:00:00:02 10.0.0.2"
ebb439
+
ebb439
+net_add n1
ebb439
+sim_add hv1
ebb439
+
ebb439
+as hv1
ebb439
+ovs-vsctl add-br br-phys
ebb439
+ovn_attach n1 br-phys 192.168.0.1
ebb439
+ovs-vsctl -- add-port br-int hv1-vif1 -- \
ebb439
+    set interface hv1-vif1 external-ids:iface-id=lsp1 \
ebb439
+    options:tx_pcap=hv1/vif1-tx.pcap \
ebb439
+    options:rxq_pcap=hv1/vif1-rx.pcap \
ebb439
+    ofport-request=1
ebb439
+
ebb439
+ovs-vsctl -- add-port br-int hv1-vif2 -- \
ebb439
+    set interface hv1-vif2 external-ids:iface-id=lsp2 \
ebb439
+    options:tx_pcap=hv1/vif2-tx.pcap \
ebb439
+    options:rxq_pcap=hv1/vif2-rx.pcap \
ebb439
+    ofport-request=2
ebb439
+
ebb439
+# Default drop
ebb439
+ovn-nbctl acl-add ls1 to-lport 1000 \
ebb439
+'outport == "lsp1" && ip4' drop
ebb439
+
ebb439
+# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT...
ebb439
+#
ebb439
+# This shell function causes an ip packet to be received on INPORT.
ebb439
+# The packet's content has Ethernet destination DST and source SRC
ebb439
+# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits).
ebb439
+# The OUTPORTs (zero or more) list the VIFs on which the packet should
ebb439
+# be received.  INPORT and the OUTPORTs are specified as logical switch
ebb439
+# port numbers, e.g. 11 for vif11.
ebb439
+test_ip() {
ebb439
+    # This packet has bad checksums but logical L3 routing doesn't check.
ebb439
+    local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5
ebb439
+    local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\
ebb439
+${dst_ip}0035111100080000
ebb439
+    shift; shift; shift; shift; shift
ebb439
+    as hv1 ovs-appctl netdev-dummy/receive hv1-vif$inport $packet
ebb439
+    for outport; do
ebb439
+        echo $packet >> $outport.expected
ebb439
+    done
ebb439
+}
ebb439
+
ebb439
+ip_to_hex() {
ebb439
+    printf "%02x%02x%02x%02x" "$@"
ebb439
+}
ebb439
+
ebb439
+reset_pcap_file() {
ebb439
+    local iface=$1
ebb439
+    local pcap_file=$2
ebb439
+    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
ebb439
+options:rxq_pcap=dummy-rx.pcap
ebb439
+    rm -f ${pcap_file}*.pcap
ebb439
+    ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \
ebb439
+options:rxq_pcap=${pcap_file}-rx.pcap
ebb439
+}
ebb439
+
ebb439
+# Create an address set
ebb439
+ovn-nbctl create Address_Set name=as1 \
ebb439
+addresses=\"10.0.0.2\",\"10.0.0.3\"
ebb439
+
ebb439
+# Create overlapping ACLs resulting in conflict desired OVS flows
ebb439
+# Add ACL1 uses the address set
ebb439
+ovn-nbctl --wait=hv acl-add ls1 to-lport 1001 \
ebb439
+'outport == "lsp1" && ip4 && ip4.src == $as1' allow
ebb439
+
ebb439
+# Add ACL2 which uses a single IP, which shouldn't take effect because
ebb439
+# when it is added incrementally there is already a conflict one installed.
ebb439
+ovn-nbctl --wait=hv acl-add ls1 to-lport 1001 \
ebb439
+'outport == "lsp1" && ip4 && ip4.src == 10.0.0.2' drop
ebb439
+
ebb439
+
ebb439
+sip=`ip_to_hex 10 0 0 2`
ebb439
+dip=`ip_to_hex 10 0 0 1`
ebb439
+test_ip 2 f00000000002 f00000000001 $sip $dip 1
ebb439
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected])
ebb439
+
ebb439
+# Update the address set which causes flow reprocessing but the OVS flow
ebb439
+# for allowing 10.0.0.2 should keep unchanged
ebb439
+ovn-nbctl --wait=hv set Address_Set as1 addresses=\"10.0.0.2\"
ebb439
+
ebb439
+test_ip 2 f00000000002 f00000000001 $sip $dip 1
ebb439
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected])
ebb439
+
ebb439
+# Delete the ACL1 that has "allow" action
ebb439
+ovn-nbctl acl-del ls1 to-lport 1001 \
ebb439
+'outport == "lsp1" && ip4 && ip4.src == $as1'
ebb439
+
ebb439
+# ACL2 should take effect and packet should be dropped
ebb439
+test_ip 2 f00000000002 f00000000001 $sip $dip
ebb439
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [1.expected])
ebb439
+
ebb439
+OVN_CLEANUP([hv1])
ebb439
+AT_CLEANUP
ebb439
+
ebb439
 AT_SETUP([ovn -- port bind/unbind change handling with conj flows - IPv6])
ebb439
 ovn_start
ebb439
 
ebb439
-- 
ebb439
1.8.3.1
ebb439