diff --git a/.gitignore b/.gitignore index 3279fa0..ea11613 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ /ovn-20.12.0.tar.gz /ovn-21.03.0.tar.gz /openvswitch-2.15.90.tar.gz +/ovn-21.06.0.tar.gz diff --git a/ovn-21.03.0.patch b/ovn-21.03.0.patch deleted file mode 100644 index 6b2d1c8..0000000 --- a/ovn-21.03.0.patch +++ /dev/null @@ -1,7237 +0,0 @@ -diff --git a/.ci/linux-prepare.sh b/.ci/linux-prepare.sh -index 0bb0ff096..83ad3958b 100755 ---- a/.ci/linux-prepare.sh -+++ b/.ci/linux-prepare.sh -@@ -12,5 +12,5 @@ set -ev - git clone git://git.kernel.org/pub/scm/devel/sparse/sparse.git - cd sparse && make -j4 HAVE_LLVM= HAVE_SQLITE= install && cd .. - --pip install --disable-pip-version-check --user six flake8 hacking --pip install --user --upgrade docutils -+pip3 install --disable-pip-version-check --user flake8 hacking sphinx pyOpenSSL -+pip3 install --upgrade --user docutils -diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml -index f3a53a8b6..91bd1e538 100644 ---- a/.github/workflows/test.yml -+++ b/.github/workflows/test.yml -@@ -13,7 +13,6 @@ jobs: - dependencies: | - automake libtool gcc bc libjemalloc1 libjemalloc-dev \ - libssl-dev llvm-dev libelf-dev libnuma-dev libpcap-dev \ -- python3-openssl python3-pip python3-sphinx \ - selinux-policy-dev - m32_dependecies: gcc-multilib - CC: ${{ matrix.compiler }} -@@ -88,11 +87,21 @@ jobs: - if: matrix.m32 != '' - run: sudo apt install -y ${{ env.m32_dependecies }} - -+ - name: update PATH -+ run: | -+ echo "$HOME/bin" >> $GITHUB_PATH -+ echo "$HOME/.local/bin" >> $GITHUB_PATH -+ -+ - name: set up python -+ uses: actions/setup-python@v2 -+ with: -+ python-version: '3.x' -+ - - name: prepare - run: ./.ci/linux-prepare.sh - - - name: build -- run: PATH="$PATH:$HOME/bin" ./.ci/linux-build.sh -+ run: ./.ci/linux-build.sh - - - name: copy logs on failure - if: failure() || cancelled() -@@ -145,10 +154,18 @@ jobs: - ref: 'master' - - name: install dependencies - run: brew install automake libtool -+ - name: update PATH -+ run: | -+ echo "$HOME/bin" >> $GITHUB_PATH -+ echo "$HOME/.local/bin" >> $GITHUB_PATH -+ - name: set up python -+ uses: actions/setup-python@v2 -+ with: -+ python-version: '3.x' - - name: prepare - run: ./.ci/osx-prepare.sh - - name: build -- run: PATH="$PATH:$HOME/bin" ./.ci/osx-build.sh -+ run: ./.ci/osx-build.sh - - name: upload logs on failure - if: failure() - uses: actions/upload-artifact@v2 -diff --git a/Makefile.am b/Makefile.am -index 80247b62d..1fe730dc4 100644 ---- a/Makefile.am -+++ b/Makefile.am -@@ -221,6 +221,7 @@ dist-hook-git: distfiles - grep -v '\.gitattributes$$' | \ - grep -v '\.gitmodules$$' | \ - grep -v "$(submodules)" | \ -+ grep -v 'redhat' | \ - LC_ALL=C sort -u > all-gitfiles; \ - LC_ALL=C comm -1 -3 distfiles all-gitfiles > missing-distfiles; \ - if test -s missing-distfiles; then \ -@@ -332,7 +333,7 @@ check-tabs: - @cd $(srcdir); \ - if test -e .git && (git --version) >/dev/null 2>&1 && \ - grep -ln "^ " \ -- `git ls-files | grep -v $(submodules) \ -+ `git ls-files | grep -v $(submodules) | grep -v redhat \ - | grep -v -f build-aux/initial-tab-whitelist` /dev/null \ - | $(EGREP) -v ':[ ]*/?\*'; \ - then \ -diff --git a/NEWS b/NEWS -index 5372668bf..530c5d42f 100644 ---- a/NEWS -+++ b/NEWS -@@ -1,3 +1,13 @@ -+Post-v21.03.0 -+------------------------- -+ - ovn-northd-ddlog: New implementation of northd, based on DDlog. This -+ implementation is incremental, meaning that it only recalculates what is -+ needed for the southbound database when northbound changes occur. It is -+ expected to scale better than the C implementation, for large deployments. -+ (This may take testing and tuning to be effective.) This version of OVN -+ requires DDLog 0.36. -+ - Introduce ovn-controller incremetal processing engine statistics -+ - OVN v21.03.0 - 12 Mar 2021 - ------------------------- - - Support ECMP multiple nexthops for reroute router policies. -diff --git a/configure.ac b/configure.ac -index 37b476d53..f3de6fef2 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -13,7 +13,7 @@ - # limitations under the License. - - AC_PREREQ(2.63) --AC_INIT(ovn, 21.03.0, bugs@openvswitch.org) -+AC_INIT(ovn, 21.03.1, bugs@openvswitch.org) - AC_CONFIG_MACRO_DIR([m4]) - AC_CONFIG_AUX_DIR([build-aux]) - AC_CONFIG_HEADERS([config.h]) -diff --git a/controller/binding.c b/controller/binding.c -index 4e6c75696..451f00e34 100644 ---- a/controller/binding.c -+++ b/controller/binding.c -@@ -597,6 +597,23 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, - } - } - -+/* Corresponds to each Port_Binding.type. */ -+enum en_lport_type { -+ LP_UNKNOWN, -+ LP_VIF, -+ LP_CONTAINER, -+ LP_PATCH, -+ LP_L3GATEWAY, -+ LP_LOCALNET, -+ LP_LOCALPORT, -+ LP_L2GATEWAY, -+ LP_VTEP, -+ LP_CHASSISREDIRECT, -+ LP_VIRTUAL, -+ LP_EXTERNAL, -+ LP_REMOTE -+}; -+ - /* Local bindings. binding.c module binds the logical port (represented by - * Port_Binding rows) and sets the 'chassis' column when it sees the - * OVS interface row (of type "" or "internal") with the -@@ -608,134 +625,180 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, - * 'struct local_binding' is used. A shash of these local bindings is - * maintained with the 'external_ids:iface-id' as the key to the shash. - * -- * struct local_binding (defined in binding.h) has 3 main fields: -- * - type -- * - OVS interface row object -- * - Port_Binding row object -- * -- * An instance of 'struct local_binding' can be one of 3 types. -- * -- * BT_VIF: Represent a local binding for an OVS interface of -- * type "" or "internal" with the external_ids:iface-id -- * set. -- * -- * This can be a -- * * probable local binding - external_ids:iface-id is -- * set, but the corresponding Port_Binding row is not -- * created or is not visible to the local ovn-controller -- * instance. -- * -- * * a local binding - external_ids:iface-id is set and -- * which is already bound to the corresponding Port_Binding -- * row. -- * -- * It maintains a list of children -- * (of type BT_CONTAINER/BT_VIRTUAL) if any. -- * -- * BT_CONTAINER: Represents a local binding which has a parent of type -- * BT_VIF. Its Port_Binding row's 'parent' column is set to -- * its parent's Port_Binding. It shares the OVS interface row -- * with the parent. -- * Each ovn-controller when it sees a container Port_Binding, -- * it creates 'struct local_binding' for the parent -- * Port_Binding and for its even if the OVS interface row for -- * the parent is not present. -- * -- * BT_VIRTUAL: Represents a local binding which has a parent of type BT_VIF. -- * Its Port_Binding type is "virtual" and it shares the OVS -- * interface row with the parent. -- * Port_Binding of type "virtual" is claimed by pinctrl module -- * when it sees the ARP packet from the parent's VIF. -- * -+ * struct local_binding has 3 main fields: -+ * - name : 'external_ids:iface-id' of the OVS interface (key). -+ * - OVS interface row object. -+ * - List of 'binding_lport' objects with the primary lport -+ * in the front of the list (if present). - * - * An object of 'struct local_binding' is created: -- * - For each interface that has iface-id configured with the type - BT_VIF. -- * -- * - For each container Port Binding (of type BT_CONTAINER) and its -- * parent Port_Binding (of type BT_VIF), no matter if -- * they are bound to this chassis i.e even if OVS interface row for the -- * parent is not present. -+ * - For each interface that has external_ids:iface-id configured. - * -- * - For each 'virtual' Port Binding (of type BT_VIRTUAL) provided its parent -- * is bound to this chassis. -+ * - For each port binding (also referred as lport) of type 'LP_VIF' -+ * if it is a parent lport of container lports even if there is no -+ * corresponding OVS interface. - */ -+struct local_binding { -+ char *name; -+ const struct ovsrec_interface *iface; -+ struct ovs_list binding_lports; -+}; - --static struct local_binding * --local_binding_create(const char *name, const struct ovsrec_interface *iface, -- const struct sbrec_port_binding *pb, -- enum local_binding_type type) --{ -- struct local_binding *lbinding = xzalloc(sizeof *lbinding); -- lbinding->name = xstrdup(name); -- lbinding->type = type; -- lbinding->pb = pb; -- lbinding->iface = iface; -- shash_init(&lbinding->children); -- return lbinding; --} -- --static void --local_binding_add(struct shash *local_bindings, struct local_binding *lbinding) --{ -- shash_add(local_bindings, lbinding->name, lbinding); --} -+/* This structure represents a logical port (or port binding) -+ * which is associated with 'struct local_binding'. -+ * -+ * An instance of 'struct binding_lport' is created for a logical port -+ * - If the OVS interface's iface-id corresponds to the logical port. -+ * - If it is a container or virtual logical port and its parent -+ * has a 'local binding'. -+ * -+ */ -+struct binding_lport { -+ struct ovs_list list_node; /* Node in local_binding.binding_lports. */ - --static void --local_binding_destroy(struct local_binding *lbinding) --{ -- local_bindings_destroy(&lbinding->children); -+ char *name; -+ const struct sbrec_port_binding *pb; -+ struct local_binding *lbinding; -+ enum en_lport_type type; -+}; - -- free(lbinding->name); -- free(lbinding); --} -+static struct local_binding *local_binding_create( -+ const char *name, const struct ovsrec_interface *); -+static void local_binding_add(struct shash *local_bindings, -+ struct local_binding *); -+static struct local_binding *local_binding_find( -+ struct shash *local_bindings, const char *name); -+static void local_binding_destroy(struct local_binding *, -+ struct shash *binding_lports); -+static void local_binding_delete(struct local_binding *, -+ struct shash *local_bindings, -+ struct shash *binding_lports); -+static struct binding_lport *local_binding_add_lport( -+ struct shash *binding_lports, -+ struct local_binding *, -+ const struct sbrec_port_binding *, -+ enum en_lport_type); -+static struct binding_lport *local_binding_get_primary_lport( -+ struct local_binding *); -+static bool local_binding_handle_stale_binding_lports( -+ struct local_binding *lbinding, struct binding_ctx_in *b_ctx_in, -+ struct binding_ctx_out *b_ctx_out, struct hmap *qos_map); -+ -+static struct binding_lport *binding_lport_create( -+ const struct sbrec_port_binding *, -+ struct local_binding *, enum en_lport_type); -+static void binding_lport_destroy(struct binding_lport *); -+static void binding_lport_delete(struct shash *binding_lports, -+ struct binding_lport *); -+static void binding_lport_add(struct shash *binding_lports, -+ struct binding_lport *); -+static struct binding_lport *binding_lport_find( -+ struct shash *binding_lports, const char *lport_name); -+static const struct sbrec_port_binding *binding_lport_get_parent_pb( -+ struct binding_lport *b_lprt); -+static struct binding_lport *binding_lport_check_and_cleanup( -+ struct binding_lport *, struct shash *b_lports); -+ -+static char *get_lport_type_str(enum en_lport_type lport_type); - - void --local_bindings_init(struct shash *local_bindings) -+local_binding_data_init(struct local_binding_data *lbinding_data) - { -- shash_init(local_bindings); -+ shash_init(&lbinding_data->bindings); -+ shash_init(&lbinding_data->lports); - } - - void --local_bindings_destroy(struct shash *local_bindings) -+local_binding_data_destroy(struct local_binding_data *lbinding_data) - { - struct shash_node *node, *next; -- SHASH_FOR_EACH_SAFE (node, next, local_bindings) { -+ -+ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->lports) { -+ struct binding_lport *b_lport = node->data; -+ binding_lport_destroy(b_lport); -+ shash_delete(&lbinding_data->lports, node); -+ } -+ -+ SHASH_FOR_EACH_SAFE (node, next, &lbinding_data->bindings) { - struct local_binding *lbinding = node->data; -- local_binding_destroy(lbinding); -- shash_delete(local_bindings, node); -+ local_binding_destroy(lbinding, &lbinding_data->lports); -+ shash_delete(&lbinding_data->bindings, node); - } - -- shash_destroy(local_bindings); -+ shash_destroy(&lbinding_data->lports); -+ shash_destroy(&lbinding_data->bindings); - } - --static --void local_binding_delete(struct shash *local_bindings, -- struct local_binding *lbinding) -+const struct sbrec_port_binding * -+local_binding_get_primary_pb(struct shash *local_bindings, const char *pb_name) - { -- shash_find_and_delete(local_bindings, lbinding->name); -- local_binding_destroy(lbinding); --} -+ struct local_binding *lbinding = -+ local_binding_find(local_bindings, pb_name); -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); - --static void --local_binding_add_child(struct local_binding *lbinding, -- struct local_binding *child) --{ -- local_binding_add(&lbinding->children, child); -- child->parent = lbinding; -+ return b_lport ? b_lport->pb : NULL; - } - --static struct local_binding * --local_binding_find_child(struct local_binding *lbinding, -- const char *child_name) -+void -+binding_dump_local_bindings(struct local_binding_data *lbinding_data, -+ struct ds *out_data) - { -- return local_binding_find(&lbinding->children, child_name); --} -+ const struct shash_node **nodes; - --static void --local_binding_delete_child(struct local_binding *lbinding, -- struct local_binding *child) --{ -- shash_find_and_delete(&lbinding->children, child->name); -+ nodes = shash_sort(&lbinding_data->bindings); -+ size_t n = shash_count(&lbinding_data->bindings); -+ -+ ds_put_cstr(out_data, "Local bindings:\n"); -+ for (size_t i = 0; i < n; i++) { -+ const struct shash_node *node = nodes[i]; -+ struct local_binding *lbinding = node->data; -+ size_t num_lports = ovs_list_size(&lbinding->binding_lports); -+ ds_put_format(out_data, "name: [%s], OVS interface name : [%s], " -+ "num binding lports : [%"PRIuSIZE"]\n", -+ lbinding->name, -+ lbinding->iface ? lbinding->iface->name : "NULL", -+ num_lports); -+ -+ if (num_lports) { -+ struct shash child_lports = SHASH_INITIALIZER(&child_lports); -+ struct binding_lport *primary_lport = NULL; -+ struct binding_lport *b_lport; -+ bool first_elem = true; -+ -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (first_elem && b_lport->type == LP_VIF) { -+ primary_lport = b_lport; -+ } else { -+ shash_add(&child_lports, b_lport->name, b_lport); -+ } -+ first_elem = false; -+ } -+ -+ if (primary_lport) { -+ ds_put_format(out_data, "primary lport : [%s]\n", -+ primary_lport->name); -+ } else { -+ ds_put_format(out_data, "no primary lport\n"); -+ } -+ -+ if (!shash_is_empty(&child_lports)) { -+ const struct shash_node **c_nodes = -+ shash_sort(&child_lports); -+ for (size_t j = 0; j < shash_count(&child_lports); j++) { -+ b_lport = c_nodes[j]->data; -+ ds_put_format(out_data, "child lport[%"PRIuSIZE"] : [%s], " -+ "type : [%s]\n", j + 1, b_lport->name, -+ get_lport_type_str(b_lport->type)); -+ } -+ free(c_nodes); -+ } -+ shash_destroy(&child_lports); -+ } -+ -+ ds_put_cstr(out_data, "----------------------------------------\n"); -+ } -+ -+ free(nodes); - } - - static bool -@@ -744,12 +807,6 @@ is_lport_vif(const struct sbrec_port_binding *pb) - return !pb->type[0]; - } - --static bool --is_lport_container(const struct sbrec_port_binding *pb) --{ -- return is_lport_vif(pb) && pb->parent_port && pb->parent_port[0]; --} -- - static struct tracked_binding_datapath * - tracked_binding_datapath_create(const struct sbrec_datapath_binding *dp, - bool is_new, -@@ -818,26 +875,13 @@ binding_tracked_dp_destroy(struct hmap *tracked_datapaths) - hmap_destroy(tracked_datapaths); - } - --/* Corresponds to each Port_Binding.type. */ --enum en_lport_type { -- LP_UNKNOWN, -- LP_VIF, -- LP_PATCH, -- LP_L3GATEWAY, -- LP_LOCALNET, -- LP_LOCALPORT, -- LP_L2GATEWAY, -- LP_VTEP, -- LP_CHASSISREDIRECT, -- LP_VIRTUAL, -- LP_EXTERNAL, -- LP_REMOTE --}; -- - static enum en_lport_type - get_lport_type(const struct sbrec_port_binding *pb) - { - if (is_lport_vif(pb)) { -+ if (pb->parent_port && pb->parent_port[0]) { -+ return LP_CONTAINER; -+ } - return LP_VIF; - } else if (!strcmp(pb->type, "patch")) { - return LP_PATCH; -@@ -864,6 +908,41 @@ get_lport_type(const struct sbrec_port_binding *pb) - return LP_UNKNOWN; - } - -+static char * -+get_lport_type_str(enum en_lport_type lport_type) -+{ -+ switch (lport_type) { -+ case LP_VIF: -+ return "VIF"; -+ case LP_CONTAINER: -+ return "CONTAINER"; -+ case LP_VIRTUAL: -+ return "VIRTUAL"; -+ case LP_PATCH: -+ return "PATCH"; -+ case LP_CHASSISREDIRECT: -+ return "CHASSISREDIRECT"; -+ case LP_L3GATEWAY: -+ return "L3GATEWAT"; -+ case LP_LOCALNET: -+ return "PATCH"; -+ case LP_LOCALPORT: -+ return "LOCALPORT"; -+ case LP_L2GATEWAY: -+ return "L2GATEWAY"; -+ case LP_EXTERNAL: -+ return "EXTERNAL"; -+ case LP_REMOTE: -+ return "REMOTE"; -+ case LP_VTEP: -+ return "VTEP"; -+ case LP_UNKNOWN: -+ return "UNKNOWN"; -+ } -+ -+ OVS_NOT_REACHED(); -+} -+ - /* For newly claimed ports, if 'notify_up' is 'false': - * - set the 'pb.up' field to true if 'pb' has no 'parent_pb'. - * - set the 'pb.up' field to true if 'parent_pb.up' is 'true' (e.g., for -@@ -991,14 +1070,15 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, - static bool - is_lbinding_set(struct local_binding *lbinding) - { -- return lbinding && lbinding->pb && lbinding->iface; -+ return lbinding && lbinding->iface; - } - - static bool --is_lbinding_this_chassis(struct local_binding *lbinding, -- const struct sbrec_chassis *chassis) -+is_binding_lport_this_chassis(struct binding_lport *b_lport, -+ const struct sbrec_chassis *chassis) - { -- return lbinding && lbinding->pb && lbinding->pb->chassis == chassis; -+ return (b_lport && b_lport->pb && chassis && -+ b_lport->pb->chassis == chassis); - } - - static bool -@@ -1010,15 +1090,14 @@ can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec, - || !strcmp(requested_chassis, chassis_rec->hostname); - } - --/* Returns 'true' if the 'lbinding' has children of type BT_CONTAINER, -+/* Returns 'true' if the 'lbinding' has binding lports of type LP_CONTAINER, - * 'false' otherwise. */ - static bool - is_lbinding_container_parent(struct local_binding *lbinding) - { -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *l = node->data; -- if (l->type == BT_CONTAINER) { -+ struct binding_lport *b_lport; -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (b_lport->type == LP_CONTAINER) { - return true; - } - } -@@ -1027,66 +1106,41 @@ is_lbinding_container_parent(struct local_binding *lbinding) - } - - static bool --release_local_binding_children(const struct sbrec_chassis *chassis_rec, -- struct local_binding *lbinding, -- bool sb_readonly, -- struct hmap *tracked_dp_bindings) --{ -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *l = node->data; -- if (is_lbinding_this_chassis(l, chassis_rec)) { -- if (!release_lport(l->pb, sb_readonly, tracked_dp_bindings)) { -- return false; -- } -+release_binding_lport(const struct sbrec_chassis *chassis_rec, -+ struct binding_lport *b_lport, bool sb_readonly, -+ struct binding_ctx_out *b_ctx_out) -+{ -+ if (is_binding_lport_this_chassis(b_lport, chassis_rec)) { -+ remove_local_lport_ids(b_lport->pb, b_ctx_out); -+ if (!release_lport(b_lport->pb, sb_readonly, -+ b_ctx_out->tracked_dp_bindings)) { -+ return false; - } -- -- /* Clear the local bindings' 'iface'. */ -- l->iface = NULL; - } - - return true; - } - --static bool --release_local_binding(const struct sbrec_chassis *chassis_rec, -- struct local_binding *lbinding, bool sb_readonly, -- struct hmap *tracked_dp_bindings) --{ -- if (!release_local_binding_children(chassis_rec, lbinding, -- sb_readonly, tracked_dp_bindings)) { -- return false; -- } -- -- bool retval = true; -- if (is_lbinding_this_chassis(lbinding, chassis_rec)) { -- retval = release_lport(lbinding->pb, sb_readonly, tracked_dp_bindings); -- } -- -- lbinding->pb = NULL; -- lbinding->iface = NULL; -- return retval; --} -- - static bool - consider_vif_lport_(const struct sbrec_port_binding *pb, - bool can_bind, const char *vif_chassis, - struct binding_ctx_in *b_ctx_in, - struct binding_ctx_out *b_ctx_out, -- struct local_binding *lbinding, -+ struct binding_lport *b_lport, - struct hmap *qos_map) - { -- bool lbinding_set = is_lbinding_set(lbinding); -+ bool lbinding_set = b_lport && is_lbinding_set(b_lport->lbinding); -+ - if (lbinding_set) { - if (can_bind) { - /* We can claim the lport. */ - const struct sbrec_port_binding *parent_pb = -- lbinding->parent ? lbinding->parent->pb : NULL; -+ binding_lport_get_parent_pb(b_lport); - - if (!claim_lport(pb, parent_pb, b_ctx_in->chassis_rec, -- lbinding->iface, !b_ctx_in->ovnsb_idl_txn, -- !lbinding->parent, -- b_ctx_out->tracked_dp_bindings)){ -+ b_lport->lbinding->iface, -+ !b_ctx_in->ovnsb_idl_txn, -+ !parent_pb, b_ctx_out->tracked_dp_bindings)){ - return false; - } - -@@ -1098,7 +1152,7 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, - b_ctx_out->tracked_dp_bindings); - update_local_lport_ids(pb, b_ctx_out); - update_local_lports(pb->logical_port, b_ctx_out); -- if (lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) { -+ if (b_lport->lbinding->iface && qos_map && b_ctx_in->ovs_idl_txn) { - get_qos_params(pb, qos_map); - } - } else { -@@ -1136,16 +1190,19 @@ consider_vif_lport(const struct sbrec_port_binding *pb, - vif_chassis); - - if (!lbinding) { -- lbinding = local_binding_find(b_ctx_out->local_bindings, -+ lbinding = local_binding_find(&b_ctx_out->lbinding_data->bindings, - pb->logical_port); - } - -+ struct binding_lport *b_lport = NULL; - if (lbinding) { -- lbinding->pb = pb; -+ struct shash *binding_lports = -+ &b_ctx_out->lbinding_data->lports; -+ b_lport = local_binding_add_lport(binding_lports, lbinding, pb, LP_VIF); - } - - return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, -- b_ctx_out, lbinding, qos_map); -+ b_ctx_out, b_lport, qos_map); - } - - static bool -@@ -1154,9 +1211,9 @@ consider_container_lport(const struct sbrec_port_binding *pb, - struct binding_ctx_out *b_ctx_out, - struct hmap *qos_map) - { -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; - struct local_binding *parent_lbinding; -- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, -- pb->parent_port); -+ parent_lbinding = local_binding_find(local_bindings, pb->parent_port); - - if (!parent_lbinding) { - /* There is no local_binding for parent port. Create it -@@ -1171,54 +1228,61 @@ consider_container_lport(const struct sbrec_port_binding *pb, - * we want the these container ports also be claimed by the - * chassis. - * */ -- parent_lbinding = local_binding_create(pb->parent_port, NULL, NULL, -- BT_VIF); -- local_binding_add(b_ctx_out->local_bindings, parent_lbinding); -+ parent_lbinding = local_binding_create(pb->parent_port, NULL); -+ local_binding_add(local_bindings, parent_lbinding); - } - -- struct local_binding *container_lbinding = -- local_binding_find_child(parent_lbinding, pb->logical_port); -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ struct binding_lport *container_b_lport = -+ local_binding_add_lport(binding_lports, parent_lbinding, pb, -+ LP_CONTAINER); - -- if (!container_lbinding) { -- container_lbinding = local_binding_create(pb->logical_port, -- parent_lbinding->iface, -- pb, BT_CONTAINER); -- local_binding_add_child(parent_lbinding, container_lbinding); -- } else { -- ovs_assert(container_lbinding->type == BT_CONTAINER); -- container_lbinding->pb = pb; -- container_lbinding->iface = parent_lbinding->iface; -- } -+ struct binding_lport *parent_b_lport = -+ binding_lport_find(binding_lports, pb->parent_port); - -- if (!parent_lbinding->pb) { -- parent_lbinding->pb = lport_lookup_by_name( -+ bool can_consider_c_lport = true; -+ if (!parent_b_lport || !parent_b_lport->pb) { -+ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name( - b_ctx_in->sbrec_port_binding_by_name, pb->parent_port); - -- if (parent_lbinding->pb) { -+ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) { - /* Its possible that the parent lport is not considered yet. - * So call consider_vif_lport() to process it first. */ -- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out, -+ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out, - parent_lbinding, qos_map); -+ parent_b_lport = binding_lport_find(binding_lports, -+ pb->parent_port); - } else { -- /* The parent lport doesn't exist. Call release_lport() to -- * release the container lport, if it was bound earlier. */ -- if (is_lbinding_this_chassis(container_lbinding, -- b_ctx_in->chassis_rec)) { -- return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings); -- } -+ /* The parent lport doesn't exist. Cannot consider the container -+ * lport for binding. */ -+ can_consider_c_lport = false; -+ } -+ } - -- return true; -+ if (parent_b_lport && parent_b_lport->type != LP_VIF) { -+ can_consider_c_lport = false; -+ } -+ -+ if (!can_consider_c_lport) { -+ /* Call release_lport() to release the container lport, -+ * if it was bound earlier. */ -+ if (is_binding_lport_this_chassis(container_b_lport, -+ b_ctx_in->chassis_rec)) { -+ return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out->tracked_dp_bindings); - } -+ -+ return true; - } - -- const char *vif_chassis = smap_get(&parent_lbinding->pb->options, -+ ovs_assert(parent_b_lport && parent_b_lport->pb); -+ const char *vif_chassis = smap_get(&parent_b_lport->pb->options, - "requested-chassis"); - bool can_bind = can_bind_on_this_chassis(b_ctx_in->chassis_rec, - vif_chassis); - - return consider_vif_lport_(pb, can_bind, vif_chassis, b_ctx_in, b_ctx_out, -- container_lbinding, qos_map); -+ container_b_lport, qos_map); - } - - static bool -@@ -1227,46 +1291,58 @@ consider_virtual_lport(const struct sbrec_port_binding *pb, - struct binding_ctx_out *b_ctx_out, - struct hmap *qos_map) - { -- struct local_binding * parent_lbinding = -- pb->virtual_parent ? local_binding_find(b_ctx_out->local_bindings, -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct local_binding *parent_lbinding = -+ pb->virtual_parent ? local_binding_find(local_bindings, - pb->virtual_parent) - : NULL; - -- if (parent_lbinding && !parent_lbinding->pb) { -- parent_lbinding->pb = lport_lookup_by_name( -- b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent); -- -- if (parent_lbinding->pb) { -- /* Its possible that the parent lport is not considered yet. -- * So call consider_vif_lport() to process it first. */ -- consider_vif_lport(parent_lbinding->pb, b_ctx_in, b_ctx_out, -- parent_lbinding, qos_map); -- } -- } -- -+ struct binding_lport *virtual_b_lport = NULL; - /* Unlike container lports, we don't have to create parent_lbinding if - * it is NULL. This is because, if parent_lbinding is not present, it - * means the virtual port can't bind in this chassis. - * Note: pinctrl module binds the virtual lport when it sees ARP - * packet from the parent lport. */ -- struct local_binding *virtual_lbinding = NULL; -- if (is_lbinding_this_chassis(parent_lbinding, b_ctx_in->chassis_rec)) { -- virtual_lbinding = -- local_binding_find_child(parent_lbinding, pb->logical_port); -- if (!virtual_lbinding) { -- virtual_lbinding = local_binding_create(pb->logical_port, -- parent_lbinding->iface, -- pb, BT_VIRTUAL); -- local_binding_add_child(parent_lbinding, virtual_lbinding); -- } else { -- ovs_assert(virtual_lbinding->type == BT_VIRTUAL); -- virtual_lbinding->pb = pb; -- virtual_lbinding->iface = parent_lbinding->iface; -+ if (parent_lbinding) { -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ -+ struct binding_lport *parent_b_lport = -+ binding_lport_find(binding_lports, pb->virtual_parent); -+ -+ if (!parent_b_lport || !parent_b_lport->pb) { -+ const struct sbrec_port_binding *parent_pb = lport_lookup_by_name( -+ b_ctx_in->sbrec_port_binding_by_name, pb->virtual_parent); -+ -+ if (parent_pb && get_lport_type(parent_pb) == LP_VIF) { -+ /* Its possible that the parent lport is not considered yet. -+ * So call consider_vif_lport() to process it first. */ -+ consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out, -+ parent_lbinding, qos_map); -+ } -+ } -+ -+ parent_b_lport = local_binding_get_primary_lport(parent_lbinding); -+ if (is_binding_lport_this_chassis(parent_b_lport, -+ b_ctx_in->chassis_rec)) { -+ virtual_b_lport = -+ local_binding_add_lport(binding_lports, parent_lbinding, pb, -+ LP_VIRTUAL); - } - } - -- return consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out, -- virtual_lbinding, qos_map); -+ if (!consider_vif_lport_(pb, true, NULL, b_ctx_in, b_ctx_out, -+ virtual_b_lport, qos_map)) { -+ return false; -+ } -+ -+ /* If the virtual lport is not bound to this chassis, then remove -+ * its entry from the local_lport_ids if present. This is required -+ * when a virtual port moves from one chassis to other.*/ -+ if (!virtual_b_lport) { -+ remove_local_lport_ids(pb, b_ctx_out); -+ } -+ -+ return true; - } - - /* Considers either claiming the lport or releasing the lport -@@ -1407,6 +1483,8 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, - continue; - } - -+ struct shash *local_bindings = -+ &b_ctx_out->lbinding_data->bindings; - for (j = 0; j < port_rec->n_interfaces; j++) { - const struct ovsrec_interface *iface_rec; - -@@ -1416,11 +1494,10 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, - - if (iface_id && ofport > 0) { - struct local_binding *lbinding = -- local_binding_find(b_ctx_out->local_bindings, iface_id); -+ local_binding_find(local_bindings, iface_id); - if (!lbinding) { -- lbinding = local_binding_create(iface_id, iface_rec, NULL, -- BT_VIF); -- local_binding_add(b_ctx_out->local_bindings, lbinding); -+ lbinding = local_binding_create(iface_id, iface_rec); -+ local_binding_add(local_bindings, lbinding); - } else { - static struct vlog_rate_limit rl = - VLOG_RATE_LIMIT_INIT(1, 5); -@@ -1431,7 +1508,6 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, - "configuration on interface [%s]", - lbinding->iface->name, iface_rec->name, - iface_rec->name); -- ovs_assert(lbinding->type == BT_VIF); - } - - update_local_lports(iface_id, b_ctx_out); -@@ -1494,11 +1570,11 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) - break; - - case LP_VIF: -- if (is_lport_container(pb)) { -- consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr); -- } else { -- consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr); -- } -+ consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map_ptr); -+ break; -+ -+ case LP_CONTAINER: -+ consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map_ptr); - break; - - case LP_VIRTUAL: -@@ -1799,39 +1875,44 @@ consider_iface_claim(const struct ovsrec_interface *iface_rec, - update_local_lports(iface_id, b_ctx_out); - smap_replace(b_ctx_out->local_iface_ids, iface_rec->name, iface_id); - -- struct local_binding *lbinding = -- local_binding_find(b_ctx_out->local_bindings, iface_id); -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ struct local_binding *lbinding = local_binding_find(local_bindings, -+ iface_id); - - if (!lbinding) { -- lbinding = local_binding_create(iface_id, iface_rec, NULL, BT_VIF); -- local_binding_add(b_ctx_out->local_bindings, lbinding); -+ lbinding = local_binding_create(iface_id, iface_rec); -+ local_binding_add(local_bindings, lbinding); - } else { - lbinding->iface = iface_rec; - } - -- if (!lbinding->pb || strcmp(lbinding->name, lbinding->pb->logical_port)) { -- lbinding->pb = lport_lookup_by_name( -- b_ctx_in->sbrec_port_binding_by_name, lbinding->name); -- if (lbinding->pb && !strcmp(lbinding->pb->type, "virtual")) { -- lbinding->pb = NULL; -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ const struct sbrec_port_binding *pb = NULL; -+ if (!b_lport) { -+ pb = lport_lookup_by_name(b_ctx_in->sbrec_port_binding_by_name, -+ lbinding->name); -+ if (pb && get_lport_type(pb) == LP_VIF) { -+ b_lport = local_binding_add_lport(binding_lports, lbinding, pb, -+ LP_VIF); - } - } - -- if (lbinding->pb) { -- if (!consider_vif_lport(lbinding->pb, b_ctx_in, b_ctx_out, -- lbinding, qos_map)) { -- return false; -- } -+ if (!b_lport) { -+ /* There is no binding lport for this local binding. */ -+ return true; -+ } -+ -+ if (!consider_vif_lport(b_lport->pb, b_ctx_in, b_ctx_out, -+ lbinding, qos_map)) { -+ return false; - } - - /* Update the child local_binding's iface (if any children) and try to - * claim the container lbindings. */ -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *child = node->data; -- child->iface = iface_rec; -- if (child->type == BT_CONTAINER) { -- if (!consider_container_lport(child->pb, b_ctx_in, b_ctx_out, -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (b_lport->type == LP_CONTAINER) { -+ if (!consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out, - qos_map)) { - return false; - } -@@ -1862,32 +1943,42 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, - struct binding_ctx_out *b_ctx_out) - { - struct local_binding *lbinding; -- lbinding = local_binding_find(b_ctx_out->local_bindings, -- iface_id); -- if (is_lbinding_this_chassis(lbinding, b_ctx_in->chassis_rec)) { -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ -+ lbinding = local_binding_find(local_bindings, iface_id); -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lbinding); -+ if (is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec)) { - struct local_datapath *ld = - get_local_datapath(b_ctx_out->local_datapaths, -- lbinding->pb->datapath->tunnel_key); -+ b_lport->pb->datapath->tunnel_key); - if (ld) { -- remove_pb_from_local_datapath(lbinding->pb, -- b_ctx_in->chassis_rec, -- b_ctx_out, ld); -+ remove_pb_from_local_datapath(b_lport->pb, -+ b_ctx_in->chassis_rec, -+ b_ctx_out, ld); - } - -- /* Note: release_local_binding() resets lbinding->pb and -- * lbinding->iface. -- * Cannot access these members of lbinding after this call. */ -- if (!release_local_binding(b_ctx_in->chassis_rec, lbinding, -- !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings)) { -- return false; -+ /* Release the primary binding lport and other children lports if -+ * any. */ -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (!release_binding_lport(b_ctx_in->chassis_rec, b_lport, -+ !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out)) { -+ return false; -+ } - } -+ -+ } -+ -+ if (lbinding) { -+ /* Clear the iface of the local binding. */ -+ lbinding->iface = NULL; - } - - /* Check if the lbinding has children of type PB_CONTAINER. - * If so, don't delete the local_binding. */ - if (lbinding && !is_lbinding_container_parent(lbinding)) { -- local_binding_delete(b_ctx_out->local_bindings, lbinding); -+ local_binding_delete(lbinding, local_bindings, binding_lports); - } - - remove_local_lports(iface_id, b_ctx_out); -@@ -2088,56 +2179,35 @@ handle_deleted_lport(const struct sbrec_port_binding *pb, - } - } - --static struct local_binding * --get_lbinding_for_lport(const struct sbrec_port_binding *pb, -- enum en_lport_type lport_type, -- struct binding_ctx_out *b_ctx_out) --{ -- ovs_assert(lport_type == LP_VIF || lport_type == LP_VIRTUAL); -- -- if (lport_type == LP_VIF && !is_lport_container(pb)) { -- return local_binding_find(b_ctx_out->local_bindings, pb->logical_port); -- } -- -- struct local_binding *parent_lbinding = NULL; -- -- if (lport_type == LP_VIRTUAL) { -- if (pb->virtual_parent) { -- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, -- pb->virtual_parent); -- } -- } else { -- if (pb->parent_port) { -- parent_lbinding = local_binding_find(b_ctx_out->local_bindings, -- pb->parent_port); -- } -- } -- -- return parent_lbinding -- ? local_binding_find(&parent_lbinding->children, pb->logical_port) -- : NULL; --} -- - static bool - handle_deleted_vif_lport(const struct sbrec_port_binding *pb, - enum en_lport_type lport_type, - struct binding_ctx_in *b_ctx_in, - struct binding_ctx_out *b_ctx_out) - { -- struct local_binding *lbinding = -- get_lbinding_for_lport(pb, lport_type, b_ctx_out); -+ struct local_binding *lbinding = NULL; -+ bool bound = false; - -- if (lbinding) { -- lbinding->pb = NULL; -- /* The port_binding 'pb' is deleted. So there is no need to -- * clear the 'chassis' column of 'pb'. But we need to do -- * for the local_binding's children. */ -- if (lbinding->type == BT_VIF && -- !release_local_binding_children( -- b_ctx_in->chassis_rec, lbinding, -- !b_ctx_in->ovnsb_idl_txn, -- b_ctx_out->tracked_dp_bindings)) { -- return false; -+ struct shash *binding_lports = &b_ctx_out->lbinding_data->lports; -+ struct binding_lport *b_lport = binding_lport_find(binding_lports, pb->logical_port); -+ if (b_lport) { -+ lbinding = b_lport->lbinding; -+ bound = is_binding_lport_this_chassis(b_lport, b_ctx_in->chassis_rec); -+ -+ /* Remove b_lport from local_binding. */ -+ binding_lport_delete(binding_lports, b_lport); -+ } -+ -+ if (bound && lbinding && lport_type == LP_VIF) { -+ /* We need to release the container/virtual binding lports (if any) if -+ * deleted 'pb' type is LP_VIF. */ -+ struct binding_lport *c_lport; -+ LIST_FOR_EACH (c_lport, list_node, &lbinding->binding_lports) { -+ if (!release_binding_lport(b_ctx_in->chassis_rec, c_lport, -+ !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out)) { -+ return false; -+ } - } - } - -@@ -2147,18 +2217,8 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb, - * it from local_lports if there is a VIF entry. - * consider_iface_release() takes care of removing from the local_lports - * when the interface change happens. */ -- if (is_lport_container(pb)) { -+ if (lport_type == LP_CONTAINER) { - remove_local_lports(pb->logical_port, b_ctx_out); -- -- /* If the container port is removed we should also remove it from -- * its parent's children set. -- */ -- if (lbinding) { -- if (lbinding->parent) { -- local_binding_delete_child(lbinding->parent, lbinding); -- } -- local_binding_destroy(lbinding); -- } - } - - handle_deleted_lport(pb, b_ctx_in, b_ctx_out); -@@ -2177,7 +2237,7 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, - - if (lport_type == LP_VIRTUAL) { - handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map); -- } else if (lport_type == LP_VIF && is_lport_container(pb)) { -+ } else if (lport_type == LP_CONTAINER) { - handled = consider_container_lport(pb, b_ctx_in, b_ctx_out, qos_map); - } else { - handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL, qos_map); -@@ -2189,14 +2249,14 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, - - bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec); - -- if (lport_type == LP_VIRTUAL || -- (lport_type == LP_VIF && is_lport_container(pb)) || -+ if (lport_type == LP_VIRTUAL || lport_type == LP_CONTAINER || - claimed == now_claimed) { - return true; - } - -- struct local_binding *lbinding = -- local_binding_find(b_ctx_out->local_bindings, pb->logical_port); -+ struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; -+ struct local_binding *lbinding = local_binding_find(local_bindings, -+ pb->logical_port); - - /* If the ovs port backing this binding previously was removed in the - * meantime, we won't have a local_binding for it. -@@ -2206,12 +2266,11 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, - return true; - } - -- struct shash_node *node; -- SHASH_FOR_EACH (node, &lbinding->children) { -- struct local_binding *child = node->data; -- if (child->type == BT_CONTAINER) { -- handled = consider_container_lport(child->pb, b_ctx_in, b_ctx_out, -- qos_map); -+ struct binding_lport *b_lport; -+ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { -+ if (b_lport->type == LP_CONTAINER) { -+ handled = consider_container_lport(b_lport->pb, b_ctx_in, -+ b_ctx_out, qos_map); - if (!handled) { - return false; - } -@@ -2256,12 +2315,25 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - - enum en_lport_type lport_type = get_lport_type(pb); - -- if (lport_type == LP_VIF) { -- if (is_lport_container(pb)) { -- shash_add(&deleted_container_pbs, pb->logical_port, pb); -- } else { -- shash_add(&deleted_vif_pbs, pb->logical_port, pb); -+ struct binding_lport *b_lport = -+ binding_lport_find(&b_ctx_out->lbinding_data->lports, -+ pb->logical_port); -+ if (b_lport) { -+ /* If the 'b_lport->type' and 'lport_type' don't match, then update -+ * the b_lport->type to the updated 'lport_type'. The function -+ * binding_lport_check_and_cleanup() will cleanup the 'b_lport' -+ * if required. */ -+ if (b_lport->type != lport_type) { -+ b_lport->type = lport_type; - } -+ b_lport = binding_lport_check_and_cleanup( -+ b_lport, &b_ctx_out->lbinding_data->lports); -+ } -+ -+ if (lport_type == LP_VIF) { -+ shash_add(&deleted_vif_pbs, pb->logical_port, pb); -+ } else if (lport_type == LP_CONTAINER) { -+ shash_add(&deleted_container_pbs, pb->logical_port, pb); - } else if (lport_type == LP_VIRTUAL) { - shash_add(&deleted_virtual_pbs, pb->logical_port, pb); - } else { -@@ -2272,7 +2344,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, - struct shash_node *node; - struct shash_node *node_next; - SHASH_FOR_EACH_SAFE (node, node_next, &deleted_container_pbs) { -- handled = handle_deleted_vif_lport(node->data, LP_VIF, b_ctx_in, -+ handled = handle_deleted_vif_lport(node->data, LP_CONTAINER, b_ctx_in, - b_ctx_out); - shash_delete(&deleted_container_pbs, node); - if (!handled) { -@@ -2326,12 +2398,33 @@ delete_done: - - enum en_lport_type lport_type = get_lport_type(pb); - -+ struct binding_lport *b_lport = -+ binding_lport_find(&b_ctx_out->lbinding_data->lports, -+ pb->logical_port); -+ if (b_lport) { -+ ovs_assert(b_lport->pb == pb); -+ -+ if (b_lport->type != lport_type) { -+ b_lport->type = lport_type; -+ } -+ -+ if (b_lport->lbinding) { -+ handled = local_binding_handle_stale_binding_lports( -+ b_lport->lbinding, b_ctx_in, b_ctx_out, qos_map_ptr); -+ if (!handled) { -+ /* Backout from the handling. */ -+ break; -+ } -+ } -+ } -+ - struct local_datapath *ld = - get_local_datapath(b_ctx_out->local_datapaths, - pb->datapath->tunnel_key); - - switch (lport_type) { - case LP_VIF: -+ case LP_CONTAINER: - case LP_VIRTUAL: - handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in, - b_ctx_out, qos_map_ptr); -@@ -2468,11 +2561,11 @@ binding_init(void) - * available. - */ - void --binding_seqno_run(struct shash *local_bindings) -+binding_seqno_run(struct local_binding_data *lbinding_data) - { - const char *iface_id; - const char *iface_id_next; -- -+ struct shash *local_bindings = &lbinding_data->bindings; - SSET_FOR_EACH_SAFE (iface_id, iface_id_next, &binding_iface_released_set) { - struct shash_node *lb_node = shash_find(local_bindings, iface_id); - -@@ -2508,16 +2601,18 @@ binding_seqno_run(struct shash *local_bindings) - * If so, then this is a newly bound interface, make sure we reset the - * Port_Binding 'up' field and the OVS Interface 'external-id'. - */ -- if (lb && lb->pb && lb->iface) { -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lb); -+ if (lb && b_lport && lb->iface -+ && !simap_contains(&binding_iface_seqno_map, lb->name)) { - new_ifaces = true; - - if (smap_get(&lb->iface->external_ids, OVN_INSTALLED_EXT_ID)) { - ovsrec_interface_update_external_ids_delkey( - lb->iface, OVN_INSTALLED_EXT_ID); - } -- if (lb->pb->n_up) { -+ if (b_lport->pb->n_up) { - bool up = false; -- sbrec_port_binding_set_up(lb->pb, &up, 1); -+ sbrec_port_binding_set_up(b_lport->pb, &up, 1); - } - simap_put(&binding_iface_seqno_map, lb->name, new_seqno); - } -@@ -2542,12 +2637,13 @@ binding_seqno_run(struct shash *local_bindings) - * available. - */ - void --binding_seqno_install(struct shash *local_bindings) -+binding_seqno_install(struct local_binding_data *lbinding_data) - { - struct ofctrl_acked_seqnos *acked_seqnos = - ofctrl_acked_seqnos_get(binding_seq_type_pb_cfg); - struct simap_node *node; - struct simap_node *node_next; -+ struct shash *local_bindings = &lbinding_data->bindings; - - SIMAP_FOR_EACH_SAFE (node, node_next, &binding_iface_seqno_map) { - struct shash_node *lb_node = shash_find(local_bindings, node->name); -@@ -2557,7 +2653,8 @@ binding_seqno_install(struct shash *local_bindings) - } - - struct local_binding *lb = lb_node->data; -- if (!lb->pb || !lb->iface) { -+ struct binding_lport *b_lport = local_binding_get_primary_lport(lb); -+ if (!b_lport || !lb->iface) { - goto del_seqno; - } - -@@ -2568,14 +2665,12 @@ binding_seqno_install(struct shash *local_bindings) - ovsrec_interface_update_external_ids_setkey(lb->iface, - OVN_INSTALLED_EXT_ID, - "true"); -- if (lb->pb->n_up) { -+ if (b_lport->pb->n_up) { - bool up = true; - -- sbrec_port_binding_set_up(lb->pb, &up, 1); -- struct shash_node *child_node; -- SHASH_FOR_EACH (child_node, &lb->children) { -- struct local_binding *lb_child = child_node->data; -- sbrec_port_binding_set_up(lb_child->pb, &up, 1); -+ sbrec_port_binding_set_up(b_lport->pb, &up, 1); -+ LIST_FOR_EACH (b_lport, list_node, &lb->binding_lports) { -+ sbrec_port_binding_set_up(b_lport->pb, &up, 1); - } - } - -@@ -2591,3 +2686,305 @@ binding_seqno_flush(void) - { - simap_clear(&binding_iface_seqno_map); - } -+ -+/* Static functions for local_lbindind and binding_lport. */ -+static struct local_binding * -+local_binding_create(const char *name, const struct ovsrec_interface *iface) -+{ -+ struct local_binding *lbinding = xzalloc(sizeof *lbinding); -+ lbinding->name = xstrdup(name); -+ lbinding->iface = iface; -+ ovs_list_init(&lbinding->binding_lports); -+ -+ return lbinding; -+} -+ -+static struct local_binding * -+local_binding_find(struct shash *local_bindings, const char *name) -+{ -+ return shash_find_data(local_bindings, name); -+} -+ -+static void -+local_binding_add(struct shash *local_bindings, struct local_binding *lbinding) -+{ -+ shash_add(local_bindings, lbinding->name, lbinding); -+} -+ -+static void -+local_binding_destroy(struct local_binding *lbinding, -+ struct shash *binding_lports) -+{ -+ struct binding_lport *b_lport; -+ LIST_FOR_EACH_POP (b_lport, list_node, &lbinding->binding_lports) { -+ b_lport->lbinding = NULL; -+ binding_lport_delete(binding_lports, b_lport); -+ } -+ -+ free(lbinding->name); -+ free(lbinding); -+} -+ -+static void -+local_binding_delete(struct local_binding *lbinding, -+ struct shash *local_bindings, -+ struct shash *binding_lports) -+{ -+ shash_find_and_delete(local_bindings, lbinding->name); -+ local_binding_destroy(lbinding, binding_lports); -+} -+ -+/* Returns the primary binding lport if present in lbinding's -+ * binding lports list. A binding lport is considered primary -+ * if binding lport's type is LP_VIF and the name matches -+ * with the 'lbinding'. -+ */ -+static struct binding_lport * -+local_binding_get_primary_lport(struct local_binding *lbinding) -+{ -+ if (!lbinding) { -+ return NULL; -+ } -+ -+ if (!ovs_list_is_empty(&lbinding->binding_lports)) { -+ struct binding_lport *b_lport = NULL; -+ b_lport = CONTAINER_OF(ovs_list_front(&lbinding->binding_lports), -+ struct binding_lport, list_node); -+ -+ if (b_lport->type == LP_VIF && -+ !strcmp(lbinding->name, b_lport->name)) { -+ return b_lport; -+ } -+ } -+ -+ return NULL; -+} -+ -+static struct binding_lport * -+local_binding_add_lport(struct shash *binding_lports, -+ struct local_binding *lbinding, -+ const struct sbrec_port_binding *pb, -+ enum en_lport_type b_type) -+{ -+ struct binding_lport *b_lport = -+ binding_lport_find(binding_lports, pb->logical_port); -+ bool add_to_lport_list = false; -+ if (!b_lport) { -+ b_lport = binding_lport_create(pb, lbinding, b_type); -+ binding_lport_add(binding_lports, b_lport); -+ add_to_lport_list = true; -+ } else if (b_lport->lbinding != lbinding) { -+ add_to_lport_list = true; -+ if (!ovs_list_is_empty(&b_lport->list_node)) { -+ ovs_list_remove(&b_lport->list_node); -+ } -+ b_lport->lbinding = lbinding; -+ b_lport->type = b_type; -+ } -+ -+ if (add_to_lport_list) { -+ if (b_type == LP_VIF) { -+ ovs_list_push_front(&lbinding->binding_lports, &b_lport->list_node); -+ } else { -+ ovs_list_push_back(&lbinding->binding_lports, &b_lport->list_node); -+ } -+ } -+ -+ return b_lport; -+} -+ -+/* This function handles the stale binding lports of 'lbinding' if 'lbinding' -+ * doesn't have a primary binding lport. -+ */ -+static bool -+local_binding_handle_stale_binding_lports(struct local_binding *lbinding, -+ struct binding_ctx_in *b_ctx_in, -+ struct binding_ctx_out *b_ctx_out, -+ struct hmap *qos_map) -+{ -+ /* Check if this lbinding has a primary binding_lport or not. */ -+ struct binding_lport *p_lport = local_binding_get_primary_lport(lbinding); -+ if (p_lport) { -+ /* Nothing to be done. */ -+ return true; -+ } -+ -+ bool handled = true; -+ struct binding_lport *b_lport, *next; -+ const struct sbrec_port_binding *pb; -+ LIST_FOR_EACH_SAFE (b_lport, next, list_node, &lbinding->binding_lports) { -+ /* Get the lport type again from the pb. Its possible that the -+ * pb type has changed. */ -+ enum en_lport_type pb_lport_type = get_lport_type(b_lport->pb); -+ if (b_lport->type == LP_VIRTUAL && pb_lport_type == LP_VIRTUAL) { -+ pb = b_lport->pb; -+ binding_lport_delete(&b_ctx_out->lbinding_data->lports, -+ b_lport); -+ handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out, qos_map); -+ } else if (b_lport->type == LP_CONTAINER && -+ pb_lport_type == LP_CONTAINER) { -+ /* For container lport, binding_lport is preserved so that when -+ * the parent port is created, it can be considered. -+ * consider_container_lport() creates the binding_lport for the parent -+ * port (with iface set to NULL). */ -+ handled = consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out, qos_map); -+ } else { -+ /* This can happen when the lport type changes from one type -+ * to another. Eg. from normal lport to external. Release the -+ * lport if it was claimed earlier and delete the b_lport. */ -+ handled = release_binding_lport(b_ctx_in->chassis_rec, b_lport, -+ !b_ctx_in->ovnsb_idl_txn, -+ b_ctx_out); -+ binding_lport_delete(&b_ctx_out->lbinding_data->lports, -+ b_lport); -+ } -+ -+ if (!handled) { -+ return false; -+ } -+ } -+ -+ return handled; -+} -+ -+static struct binding_lport * -+binding_lport_create(const struct sbrec_port_binding *pb, -+ struct local_binding *lbinding, -+ enum en_lport_type type) -+{ -+ struct binding_lport *b_lport = xzalloc(sizeof *b_lport); -+ b_lport->name = xstrdup(pb->logical_port); -+ b_lport->pb = pb; -+ b_lport->type = type; -+ b_lport->lbinding = lbinding; -+ ovs_list_init(&b_lport->list_node); -+ -+ return b_lport; -+} -+ -+static void -+binding_lport_add(struct shash *binding_lports, struct binding_lport *b_lport) -+{ -+ shash_add(binding_lports, b_lport->pb->logical_port, b_lport); -+} -+ -+static struct binding_lport * -+binding_lport_find(struct shash *binding_lports, const char *lport_name) -+{ -+ if (!lport_name) { -+ return NULL; -+ } -+ -+ return shash_find_data(binding_lports, lport_name); -+} -+ -+static void -+binding_lport_destroy(struct binding_lport *b_lport) -+{ -+ if (!ovs_list_is_empty(&b_lport->list_node)) { -+ ovs_list_remove(&b_lport->list_node); -+ } -+ -+ free(b_lport->name); -+ free(b_lport); -+} -+ -+static void -+binding_lport_delete(struct shash *binding_lports, -+ struct binding_lport *b_lport) -+{ -+ shash_find_and_delete(binding_lports, b_lport->name); -+ binding_lport_destroy(b_lport); -+} -+ -+ -+static const struct sbrec_port_binding * -+binding_lport_get_parent_pb(struct binding_lport *b_lport) -+{ -+ if (!b_lport) { -+ return NULL; -+ } -+ -+ if (b_lport->type == LP_VIF) { -+ return NULL; -+ } -+ -+ struct local_binding *lbinding = b_lport->lbinding; -+ ovs_assert(lbinding); -+ -+ struct binding_lport *parent_b_lport = -+ local_binding_get_primary_lport(lbinding); -+ -+ return parent_b_lport ? parent_b_lport->pb : NULL; -+} -+ -+/* This function checks and cleans up the 'b_lport' if it is -+ * not in the correct state. -+ * -+ * If the 'b_lport' type is LP_VIF, then its name and its lbinding->name -+ * should match. Otherwise this should be cleaned up. -+ * -+ * If the 'b_lport' type is LP_CONTAINER, then its parent_port name should -+ * be the same as its lbinding's name. Otherwise this should be -+ * cleaned up. -+ * -+ * If the 'b_lport' type is LP_VIRTUAL, then its virtual parent name -+ * should be the same as its lbinding's name. Otherwise this -+ * should be cleaned up. -+ * -+ * If the 'b_lport' type is not LP_VIF, LP_CONTAINER or LP_VIRTUAL, it -+ * should be cleaned up. This can happen if the CMS changes -+ * the port binding type. -+ */ -+static struct binding_lport * -+binding_lport_check_and_cleanup(struct binding_lport *b_lport, -+ struct shash *binding_lports) -+{ -+ bool cleanup_blport = false; -+ -+ if (!b_lport->lbinding) { -+ cleanup_blport = true; -+ goto cleanup; -+ } -+ -+ switch (b_lport->type) { -+ case LP_VIF: -+ if (strcmp(b_lport->name, b_lport->lbinding->name)) { -+ cleanup_blport = true; -+ } -+ break; -+ -+ case LP_CONTAINER: -+ if (strcmp(b_lport->pb->parent_port, b_lport->lbinding->name)) { -+ cleanup_blport = true; -+ } -+ break; -+ -+ case LP_VIRTUAL: -+ if (!b_lport->pb->virtual_parent || -+ strcmp(b_lport->pb->virtual_parent, b_lport->lbinding->name)) { -+ cleanup_blport = true; -+ } -+ break; -+ -+ case LP_PATCH: -+ case LP_LOCALPORT: -+ case LP_VTEP: -+ case LP_L2GATEWAY: -+ case LP_L3GATEWAY: -+ case LP_CHASSISREDIRECT: -+ case LP_EXTERNAL: -+ case LP_LOCALNET: -+ case LP_REMOTE: -+ case LP_UNKNOWN: -+ cleanup_blport = true; -+ } -+ -+cleanup: -+ if (cleanup_blport) { -+ binding_lport_delete(binding_lports, b_lport); -+ return NULL; -+ } -+ -+ return b_lport; -+} -diff --git a/controller/binding.h b/controller/binding.h -index c9ebef4b1..4fc9ef207 100644 ---- a/controller/binding.h -+++ b/controller/binding.h -@@ -36,6 +36,7 @@ struct sbrec_chassis; - struct sbrec_port_binding_table; - struct sset; - struct sbrec_port_binding; -+struct ds; - - struct binding_ctx_in { - struct ovsdb_idl_txn *ovnsb_idl_txn; -@@ -56,7 +57,7 @@ struct binding_ctx_in { - - struct binding_ctx_out { - struct hmap *local_datapaths; -- struct shash *local_bindings; -+ struct local_binding_data *lbinding_data; - - /* sset of (potential) local lports. */ - struct sset *local_lports; -@@ -86,28 +87,16 @@ struct binding_ctx_out { - struct hmap *tracked_dp_bindings; - }; - --enum local_binding_type { -- BT_VIF, -- BT_CONTAINER, -- BT_VIRTUAL -+struct local_binding_data { -+ struct shash bindings; -+ struct shash lports; - }; - --struct local_binding { -- char *name; -- enum local_binding_type type; -- const struct ovsrec_interface *iface; -- const struct sbrec_port_binding *pb; -- -- /* shash of 'struct local_binding' representing children. */ -- struct shash children; -- struct local_binding *parent; --}; -+void local_binding_data_init(struct local_binding_data *); -+void local_binding_data_destroy(struct local_binding_data *); - --static inline struct local_binding * --local_binding_find(struct shash *local_bindings, const char *name) --{ -- return shash_find_data(local_bindings, name); --} -+const struct sbrec_port_binding *local_binding_get_primary_pb( -+ struct shash *local_bindings, const char *pb_name); - - /* Represents a tracked binding logical port. */ - struct tracked_binding_lport { -@@ -128,8 +117,6 @@ bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, - const struct sbrec_port_binding_table *, - const struct sbrec_chassis *); - --void local_bindings_init(struct shash *local_bindings); --void local_bindings_destroy(struct shash *local_bindings); - bool binding_handle_ovs_interface_changes(struct binding_ctx_in *, - struct binding_ctx_out *); - bool binding_handle_port_binding_changes(struct binding_ctx_in *, -@@ -137,7 +124,8 @@ bool binding_handle_port_binding_changes(struct binding_ctx_in *, - void binding_tracked_dp_destroy(struct hmap *tracked_datapaths); - - void binding_init(void); --void binding_seqno_run(struct shash *local_bindings); --void binding_seqno_install(struct shash *local_bindings); -+void binding_seqno_run(struct local_binding_data *lbinding_data); -+void binding_seqno_install(struct local_binding_data *lbinding_data); - void binding_seqno_flush(void); -+void binding_dump_local_bindings(struct local_binding_data *, struct ds *); - #endif /* controller/binding.h */ -diff --git a/controller/ovn-controller.8.xml b/controller/ovn-controller.8.xml -index 51c0c372c..8886df568 100644 ---- a/controller/ovn-controller.8.xml -+++ b/controller/ovn-controller.8.xml -@@ -578,6 +578,28 @@ - Displays logical flow cache statistics: enabled/disabled, per cache - type entry counts. - -+ -+
inc-engine/show-stats
-+
-+ Display ovn-controller engine counters. For each engine -+ node the following counters have been added: -+ -+
-+ -+
inc-engine/clear-stats
-+
-+ Reset ovn-controller engine counters. -+
- -

- -diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 5dd643f52..41a64a4ca 100644 ---- a/controller/ovn-controller.c -+++ b/controller/ovn-controller.c -@@ -81,6 +81,7 @@ static unixctl_cb_func cluster_state_reset_cmd; - static unixctl_cb_func debug_pause_execution; - static unixctl_cb_func debug_resume_execution; - static unixctl_cb_func debug_status_execution; -+static unixctl_cb_func debug_dump_local_bindings; - static unixctl_cb_func lflow_cache_flush_cmd; - static unixctl_cb_func lflow_cache_show_stats_cmd; - static unixctl_cb_func debug_delay_nb_cfg_report; -@@ -258,23 +259,15 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, - uuid); - } - -- /* Updating conditions to receive logical flows that references -- * datapath groups containing local datapaths. */ -- const struct sbrec_logical_dp_group *group; -- SBREC_LOGICAL_DP_GROUP_FOR_EACH (group, ovnsb_idl) { -- struct uuid *uuid = CONST_CAST(struct uuid *, -- &group->header_.uuid); -- size_t i; -- -- for (i = 0; i < group->n_datapaths; i++) { -- if (get_local_datapath(local_datapaths, -- group->datapaths[i]->tunnel_key)) { -- sbrec_logical_flow_add_clause_logical_dp_group( -- &lf, OVSDB_F_EQ, uuid); -- break; -- } -- } -- } -+ /* Datapath groups are immutable, which means a new group record is -+ * created when a datapath is added to a group. The logical flows -+ * referencing a datapath group are also updated in such cases but the -+ * new group UUID is not known by ovn-controller until the SB update -+ * is received. To avoid unnecessarily removing and adding lflows -+ * that reference datapath groups, set the monitor condition to always -+ * request all of them. -+ */ -+ sbrec_logical_flow_add_clause_logical_dp_group(&lf, OVSDB_F_NE, NULL); - } - - out:; -@@ -420,6 +413,10 @@ process_br_int(struct ovsdb_idl_txn *ovs_idl_txn, - if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) { - ovsrec_bridge_set_datapath_type(br_int, datapath_type); - } -+ if (!br_int->fail_mode || strcmp(br_int->fail_mode, "secure")) { -+ ovsrec_bridge_set_fail_mode(br_int, "secure"); -+ VLOG_WARN("Integration bridge fail-mode changed to 'secure'."); -+ } - } - return br_int; - } -@@ -1182,8 +1179,7 @@ struct ed_type_runtime_data { - /* Contains "struct local_datapath" nodes. */ - struct hmap local_datapaths; - -- /* Contains "struct local_binding" nodes. */ -- struct shash local_bindings; -+ struct local_binding_data lbinding_data; - - /* Contains the name of each logical port resident on the local - * hypervisor. These logical ports include the VIFs (and their child -@@ -1222,9 +1218,9 @@ struct ed_type_runtime_data { - * | | Interface and Port Binding changes store the | - * | @tracked_dp_bindings | changed datapaths (datapaths added/removed from | - * | | local_datapaths) and changed port bindings | -- * | | (added/updated/deleted in 'local_bindings'). | -+ * | | (added/updated/deleted in 'lbinding_data'). | - * | | So any changes to the runtime data - | -- * | | local_datapaths and local_bindings is captured | -+ * | | local_datapaths and lbinding_data is captured | - * | | here. | - * ------------------------------------------------------------------------ - * | | This is a bool which represents if the runtime | -@@ -1251,7 +1247,7 @@ struct ed_type_runtime_data { - * - * --------------------------------------------------------------------- - * | local_datapaths | The changes to these runtime data is captured in | -- * | local_bindings | the @tracked_dp_bindings indirectly and hence it | -+ * | lbinding_data | the @tracked_dp_bindings indirectly and hence it | - * | local_lport_ids | is not tracked explicitly. | - * --------------------------------------------------------------------- - * | local_iface_ids | This is used internally within the runtime data | -@@ -1294,7 +1290,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED, - sset_init(&data->active_tunnels); - sset_init(&data->egress_ifaces); - smap_init(&data->local_iface_ids); -- local_bindings_init(&data->local_bindings); -+ local_binding_data_init(&data->lbinding_data); - - /* Init the tracked data. */ - hmap_init(&data->tracked_dp_bindings); -@@ -1322,7 +1318,7 @@ en_runtime_data_cleanup(void *data) - free(cur_node); - } - hmap_destroy(&rt_data->local_datapaths); -- local_bindings_destroy(&rt_data->local_bindings); -+ local_binding_data_destroy(&rt_data->lbinding_data); - hmapx_destroy(&rt_data->ct_updated_datapaths); - } - -@@ -1405,7 +1401,7 @@ init_binding_ctx(struct engine_node *node, - b_ctx_out->local_lport_ids_changed = false; - b_ctx_out->non_vif_ports_changed = false; - b_ctx_out->egress_ifaces = &rt_data->egress_ifaces; -- b_ctx_out->local_bindings = &rt_data->local_bindings; -+ b_ctx_out->lbinding_data = &rt_data->lbinding_data; - b_ctx_out->local_iface_ids = &rt_data->local_iface_ids; - b_ctx_out->tracked_dp_bindings = NULL; - b_ctx_out->local_lports_changed = NULL; -@@ -1449,7 +1445,7 @@ en_runtime_data_run(struct engine_node *node, void *data) - free(cur_node); - } - hmap_clear(local_datapaths); -- local_bindings_destroy(&rt_data->local_bindings); -+ local_binding_data_destroy(&rt_data->lbinding_data); - sset_destroy(local_lports); - sset_destroy(local_lport_ids); - sset_destroy(active_tunnels); -@@ -1460,7 +1456,7 @@ en_runtime_data_run(struct engine_node *node, void *data) - sset_init(active_tunnels); - sset_init(&rt_data->egress_ifaces); - smap_init(&rt_data->local_iface_ids); -- local_bindings_init(&rt_data->local_bindings); -+ local_binding_data_init(&rt_data->lbinding_data); - hmapx_clear(&rt_data->ct_updated_datapaths); - } - -@@ -1822,7 +1818,7 @@ static void init_physical_ctx(struct engine_node *node, - p_ctx->local_lports = &rt_data->local_lports; - p_ctx->ct_zones = ct_zones; - p_ctx->mff_ovn_geneve = ed_mff_ovn_geneve->mff_ovn_geneve; -- p_ctx->local_bindings = &rt_data->local_bindings; -+ p_ctx->local_bindings = &rt_data->lbinding_data.bindings; - p_ctx->ct_updated_datapaths = &rt_data->ct_updated_datapaths; - } - -@@ -2685,7 +2681,8 @@ main(int argc, char *argv[]) - engine_get_internal_data(&en_flow_output); - struct ed_type_ct_zones *ct_zones_data = - engine_get_internal_data(&en_ct_zones); -- struct ed_type_runtime_data *runtime_data = NULL; -+ struct ed_type_runtime_data *runtime_data = -+ engine_get_internal_data(&en_runtime_data); - - ofctrl_init(&flow_output_data->group_table, - &flow_output_data->meter_table, -@@ -2738,6 +2735,10 @@ main(int argc, char *argv[]) - unixctl_command_register("debug/delay-nb-cfg-report", "SECONDS", 1, 1, - debug_delay_nb_cfg_report, &delay_nb_cfg_report); - -+ unixctl_command_register("debug/dump-local-bindings", "", 0, 0, -+ debug_dump_local_bindings, -+ &runtime_data->lbinding_data); -+ - unsigned int ovs_cond_seqno = UINT_MAX; - unsigned int ovnsb_cond_seqno = UINT_MAX; - unsigned int ovnsb_expected_cond_seqno = UINT_MAX; -@@ -2955,7 +2956,7 @@ main(int argc, char *argv[]) - ovnsb_cond_seqno, - ovnsb_expected_cond_seqno)); - if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -- binding_seqno_run(&runtime_data->local_bindings); -+ binding_seqno_run(&runtime_data->lbinding_data); - } - - flow_output_data = engine_get_data(&en_flow_output); -@@ -2968,7 +2969,7 @@ main(int argc, char *argv[]) - } - ofctrl_seqno_run(ofctrl_get_cur_cfg()); - if (runtime_data && ovs_idl_txn && ovnsb_idl_txn) { -- binding_seqno_install(&runtime_data->local_bindings); -+ binding_seqno_install(&runtime_data->lbinding_data); - } - } - -@@ -3408,3 +3409,13 @@ debug_delay_nb_cfg_report(struct unixctl_conn *conn, int argc OVS_UNUSED, - unixctl_command_reply(conn, "no delay for nb_cfg report."); - } - } -+ -+static void -+debug_dump_local_bindings(struct unixctl_conn *conn, int argc OVS_UNUSED, -+ const char *argv[] OVS_UNUSED, void *local_bindings) -+{ -+ struct ds binding_data = DS_EMPTY_INITIALIZER; -+ binding_dump_local_bindings(local_bindings, &binding_data); -+ unixctl_command_reply(conn, ds_cstr(&binding_data)); -+ ds_destroy(&binding_data); -+} -diff --git a/controller/physical.c b/controller/physical.c -index fa5d0d692..874d1ee27 100644 ---- a/controller/physical.c -+++ b/controller/physical.c -@@ -1839,20 +1839,19 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, - continue; - } - -- const struct local_binding *lb = -- local_binding_find(p_ctx->local_bindings, iface_id); -- -- if (!lb || !lb->pb) { -+ const struct sbrec_port_binding *lb_pb = -+ local_binding_get_primary_pb(p_ctx->local_bindings, iface_id); -+ if (!lb_pb) { - continue; - } - - int64_t ofport = iface_rec->n_ofport ? *iface_rec->ofport : 0; - if (ovsrec_interface_is_deleted(iface_rec)) { -- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid); -+ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid); - simap_find_and_delete(&localvif_to_ofport, iface_id); - } else { - if (!ovsrec_interface_is_new(iface_rec)) { -- ofctrl_remove_flows(flow_table, &lb->pb->header_.uuid); -+ ofctrl_remove_flows(flow_table, &lb_pb->header_.uuid); - } - - simap_put(&localvif_to_ofport, iface_id, ofport); -@@ -1860,7 +1859,7 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, - p_ctx->mff_ovn_geneve, p_ctx->ct_zones, - p_ctx->active_tunnels, - p_ctx->local_datapaths, -- lb->pb, p_ctx->chassis, -+ lb_pb, p_ctx->chassis, - flow_table, &ofpacts); - } - } -diff --git a/controller/pinctrl.c b/controller/pinctrl.c -index b42288ea5..523a45b9a 100644 ---- a/controller/pinctrl.c -+++ b/controller/pinctrl.c -@@ -4240,6 +4240,12 @@ send_garp_rarp_update(struct ovsdb_idl_txn *ovnsb_idl_txn, - struct shash *nat_addresses) - { - volatile struct garp_rarp_data *garp_rarp = NULL; -+ -+ /* Skip localports as they don't need to be announced */ -+ if (!strcmp(binding_rec->type, "localport")) { -+ return; -+ } -+ - /* Update GARP for NAT IP if it exists. Consider port bindings with type - * "l3gateway" for logical switch ports attached to gateway routers, and - * port bindings with type "patch" for logical switch ports attached to -diff --git a/debian/changelog b/debian/changelog -index 51f9bcc91..25a04f8ae 100644 ---- a/debian/changelog -+++ b/debian/changelog -@@ -1,3 +1,9 @@ -+ovn (21.03.1-1) unstable; urgency=low -+ -+ * New upstream version -+ -+ -- OVN team Fri, 12 Mar 2021 12:00:00 -0500 -+ - ovn (21.03.0-1) unstable; urgency=low - - * New upstream version -diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h -index 017176f98..d44b30b30 100644 ---- a/include/ovn/logical-fields.h -+++ b/include/ovn/logical-fields.h -@@ -66,6 +66,7 @@ enum mff_log_flags_bits { - MLF_LOOKUP_MAC_BIT = 6, - MLF_LOOKUP_LB_HAIRPIN_BIT = 7, - MLF_LOOKUP_FDB_BIT = 8, -+ MLF_SKIP_SNAT_FOR_LB_BIT = 9, - }; - - /* MFF_LOG_FLAGS_REG flag assignments */ -@@ -102,6 +103,10 @@ enum mff_log_flags { - - /* Indicate that the lookup in the fdb table was successful. */ - MLF_LOOKUP_FDB = (1 << MLF_LOOKUP_FDB_BIT), -+ -+ /* Indicate that a packet must not SNAT in the gateway router when -+ * load-balancing has taken place. */ -+ MLF_SKIP_SNAT_FOR_LB = (1 << MLF_SKIP_SNAT_FOR_LB_BIT), - }; - - /* OVN logical fields -diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c -index 916dbbe39..a6337a1d9 100644 ---- a/lib/inc-proc-eng.c -+++ b/lib/inc-proc-eng.c -@@ -27,6 +27,7 @@ - #include "openvswitch/hmap.h" - #include "openvswitch/vlog.h" - #include "inc-proc-eng.h" -+#include "unixctl.h" - - VLOG_DEFINE_THIS_MODULE(inc_proc_eng); - -@@ -102,6 +103,40 @@ engine_get_nodes(struct engine_node *node, size_t *n_count) - return engine_topo_sort(node, NULL, n_count, &n_size); - } - -+static void -+engine_clear_stats(struct unixctl_conn *conn, int argc OVS_UNUSED, -+ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED) -+{ -+ for (size_t i = 0; i < engine_n_nodes; i++) { -+ struct engine_node *node = engine_nodes[i]; -+ -+ memset(&node->stats, 0, sizeof node->stats); -+ } -+ unixctl_command_reply(conn, NULL); -+} -+ -+static void -+engine_dump_stats(struct unixctl_conn *conn, int argc OVS_UNUSED, -+ const char *argv[] OVS_UNUSED, void *arg OVS_UNUSED) -+{ -+ struct ds dump = DS_EMPTY_INITIALIZER; -+ -+ for (size_t i = 0; i < engine_n_nodes; i++) { -+ struct engine_node *node = engine_nodes[i]; -+ -+ ds_put_format(&dump, -+ "Node: %s\n" -+ "- recompute: %12"PRIu64"\n" -+ "- compute: %12"PRIu64"\n" -+ "- abort: %12"PRIu64"\n", -+ node->name, node->stats.recompute, -+ node->stats.compute, node->stats.abort); -+ } -+ unixctl_command_reply(conn, ds_cstr(&dump)); -+ -+ ds_destroy(&dump); -+} -+ - void - engine_init(struct engine_node *node, struct engine_arg *arg) - { -@@ -115,6 +150,11 @@ engine_init(struct engine_node *node, struct engine_arg *arg) - engine_nodes[i]->data = NULL; - } - } -+ -+ unixctl_command_register("inc-engine/show-stats", "", 0, 0, -+ engine_dump_stats, NULL); -+ unixctl_command_register("inc-engine/clear-stats", "", 0, 0, -+ engine_clear_stats, NULL); - } - - void -@@ -288,6 +328,7 @@ engine_recompute(struct engine_node *node, bool forced, bool allowed) - - /* Run the node handler which might change state. */ - node->run(node, node->data); -+ node->stats.recompute++; - } - - /* Return true if the node could be computed, false otherwise. */ -@@ -312,6 +353,8 @@ engine_compute(struct engine_node *node, bool recompute_allowed) - } - } - } -+ node->stats.compute++; -+ - return true; - } - -@@ -321,6 +364,7 @@ engine_run_node(struct engine_node *node, bool recompute_allowed) - if (!node->n_inputs) { - /* Run the node handler which might change state. */ - node->run(node, node->data); -+ node->stats.recompute++; - return; - } - -@@ -377,6 +421,7 @@ engine_run(bool recompute_allowed) - engine_run_node(engine_nodes[i], recompute_allowed); - - if (engine_nodes[i]->state == EN_ABORTED) { -+ engine_nodes[i]->stats.abort++; - engine_run_aborted = true; - return; - } -@@ -393,6 +438,7 @@ engine_need_run(void) - } - - engine_nodes[i]->run(engine_nodes[i], engine_nodes[i]->data); -+ engine_nodes[i]->stats.recompute++; - VLOG_DBG("input node: %s, state: %s", engine_nodes[i]->name, - engine_node_state_name[engine_nodes[i]->state]); - if (engine_nodes[i]->state == EN_UPDATED) { -diff --git a/lib/inc-proc-eng.h b/lib/inc-proc-eng.h -index 857234677..7e9f5bb70 100644 ---- a/lib/inc-proc-eng.h -+++ b/lib/inc-proc-eng.h -@@ -107,6 +107,12 @@ enum engine_node_state { - EN_STATE_MAX, - }; - -+struct engine_stats { -+ uint64_t recompute; -+ uint64_t compute; -+ uint64_t abort; -+}; -+ - struct engine_node { - /* A unique name for each node. */ - char *name; -@@ -154,6 +160,9 @@ struct engine_node { - /* Method to clear up tracked data maintained by the engine node in the - * engine 'data'. It may be NULL. */ - void (*clear_tracked_data)(void *tracked_data); -+ -+ /* Engine stats. */ -+ struct engine_stats stats; - }; - - /* Initialize the data for the engine nodes. It calls each node's -diff --git a/lib/logical-fields.c b/lib/logical-fields.c -index 9d08b44c2..72853013e 100644 ---- a/lib/logical-fields.c -+++ b/lib/logical-fields.c -@@ -121,6 +121,10 @@ ovn_init_symtab(struct shash *symtab) - MLF_FORCE_SNAT_FOR_LB_BIT); - expr_symtab_add_subfield(symtab, "flags.force_snat_for_lb", NULL, - flags_str); -+ snprintf(flags_str, sizeof flags_str, "flags[%d]", -+ MLF_SKIP_SNAT_FOR_LB_BIT); -+ expr_symtab_add_subfield(symtab, "flags.skip_snat_for_lb", NULL, -+ flags_str); - - /* Connection tracking state. */ - expr_symtab_add_field_scoped(symtab, "ct_mark", MFF_CT_MARK, NULL, false, -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index c272cc922..137477f51 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml -@@ -407,12 +407,13 @@ - it contains a priority-110 flow to move IPv6 Neighbor Discovery and MLD - traffic to the next table. If load balancing rules with virtual IP - addresses (and ports) are configured in OVN_Northbound -- database for alogical switch datapath, a priority-100 flow is added -+ database for a logical switch datapath, a priority-100 flow is added - with the match ip to match on IP packets and sets the action -- reg0[0] = 1; next; to act as a hint for table -+ reg0[2] = 1; next; to act as a hint for table - Pre-stateful to send IP packets to the connection tracker -- for packet de-fragmentation before eventually advancing to ingress -- table LB. -+ for packet de-fragmentation (and to possibly do DNAT for already -+ established load balanced traffic) before eventually advancing to ingress -+ table Stateful. - If controller_event has been enabled and load balancing rules with - empty backends have been added in OVN_Northbound, a 130 flow - is added to trigger ovn-controller events whenever the chassis receives a -@@ -470,11 +471,38 @@ -

- This table prepares flows for all possible stateful processing - in next tables. It contains a priority-0 flow that simply moves -- traffic to the next table. A priority-100 flow sends the packets to -- connection tracker based on a hint provided by the previous tables -- (with a match for reg0[0] == 1) by using the -- ct_next; action. -+ traffic to the next table. -

-+ - -

Ingress Table 8: from-lport ACL hints

- -@@ -511,6 +539,14 @@ -

- The table contains the following flows: -

-+ -+ - - -

Ingress table 9: from-lport ACLs

-@@ -599,9 +632,14 @@ - - -

-- This table also contains a priority 0 flow with action -- next;, so that ACLs allow packets by default. If the -- logical datapath has a stateful ACL or a load balancer with VIP -+ This table contains a priority-65535 flow to advance to the next table -+ if the logical switch has no ACLs configured, otherwise a -+ priority-0 flow to advance to the next table so that ACLs allow -+ packets by default. -+

-+ -+

-+ If the logical datapath has a stateful ACL or a load balancer with VIP - configured, the following flows will also be added: -

- -@@ -615,7 +653,7 @@ - - -
  • -- A priority-65535 flow that allows any traffic in the reply -+ A priority-65532 flow that allows any traffic in the reply - direction for a connection that has been committed to the - connection tracker (i.e., established flows), as long as - the committed flow does not have ct_label.blocked set. -@@ -628,19 +666,19 @@ -
  • - -
  • -- A priority-65535 flow that allows any traffic that is considered -+ A priority-65532 flow that allows any traffic that is considered - related to a committed flow in the connection tracker (e.g., an - ICMP Port Unreachable from a non-listening UDP port), as long - as the committed flow does not have ct_label.blocked set. -
  • - -
  • -- A priority-65535 flow that drops all traffic marked by the -+ A priority-65532 flow that drops all traffic marked by the - connection tracker as invalid. -
  • - -
  • -- A priority-65535 flow that drops all traffic in the reply direction -+ A priority-65532 flow that drops all traffic in the reply direction - with ct_label.blocked set meaning that the connection - should no longer be allowed due to a policy change. Packets - in the request direction are skipped here to let a newly created -@@ -648,11 +686,18 @@ -
  • - -
  • -- A priority-65535 flow that allows IPv6 Neighbor solicitation, -+ A priority-65532 flow that allows IPv6 Neighbor solicitation, - Neighbor discover, Router solicitation, Router advertisement and MLD - packets. -
  • -+ - -+

    -+ If the logical datapath has any ACL or a load balancer with VIP -+ configured, the following flow will also be added: -+

    -+ -+ - --

    Ingress Table 12: LB

    -- --

    -- It contains a priority-0 flow that simply moves traffic to the next -- table. --

    -- --

    -- A priority-65535 flow with the match -- inport == I for all logical switch -- datapaths to move traffic to the next table. Where I -- is the peer of a logical router port. This flow is added to -- skip the connection tracking of packets which enter from -- logical router datapath to logical switch datapath. --

    -- --

    -- For established connections a priority 65534 flow matches on -- ct.est && !ct.rel && !ct.new && -- !ct.inv and sets an action reg0[2] = 1; next; to act -- as a hint for table Stateful to send packets through -- connection tracker to NAT the packets. (The packet will automatically -- get DNATed to the same IP address as the first packet in that -- connection.) --

    -- --

    Ingress Table 13: Stateful

    -+

    Ingress Table 12: Stateful

    - - - --

    Ingress Table 14: Pre-Hairpin

    -+

    Ingress Table 13: Pre-Hairpin

    - - --

    Ingress Table 15: Nat-Hairpin

    -+

    Ingress Table 14: Nat-Hairpin

    - - --

    Ingress Table 16: Hairpin

    -+

    Ingress Table 15: Hairpin

    - - --

    Ingress Table 17: ARP/ND responder

    -+

    Ingress Table 16: ARP/ND responder

    - -

    - This table implements ARP/ND responder in a logical switch for known -@@ -1164,7 +1172,7 @@ output; - - - --

    Ingress Table 18: DHCP option processing

    -+

    Ingress Table 17: DHCP option processing

    - -

    - This table adds the DHCPv4 options to a DHCPv4 packet from the -@@ -1225,7 +1233,7 @@ next; - - - --

    Ingress Table 19: DHCP responses

    -+

    Ingress Table 18: DHCP responses

    - -

    - This table implements DHCP responder for the DHCP replies generated by -@@ -1306,7 +1314,7 @@ output; - - - --

    Ingress Table 20 DNS Lookup

    -+

    Ingress Table 19 DNS Lookup

    - -

    - This table looks up and resolves the DNS names to the corresponding -@@ -1335,7 +1343,7 @@ reg0[4] = dns_lookup(); next; - - - --

    Ingress Table 21 DNS Responses

    -+

    Ingress Table 20 DNS Responses

    - -

    - This table implements DNS responder for the DNS replies generated by -@@ -1370,7 +1378,7 @@ output; - - - --

    Ingress table 22 External ports

    -+

    Ingress table 21 External ports

    - -

    - Traffic from the external logical ports enter the ingress -@@ -1413,7 +1421,7 @@ output; - - - --

    Ingress Table 23 Destination Lookup

    -+

    Ingress Table 22 Destination Lookup

    - -

    - This table implements switching behavior. It contains these logical -@@ -1639,9 +1647,11 @@ output; - Moreover it contains a priority-110 flow to move IPv6 Neighbor Discovery - traffic to the next table. If any load balancing rules exist for the - datapath, a priority-100 flow is added with a match of ip -- and action of reg0[0] = 1; next; to act as a hint for -+ and action of reg0[2] = 1; next; to act as a hint for - table Pre-stateful to send IP packets to the connection -- tracker for packet de-fragmentation. -+ tracker for packet de-fragmentation and possibly DNAT the destination -+ VIP to one of the selected backend for already commited load balanced -+ traffic. -

    - -

    -@@ -1683,20 +1693,39 @@ output; -

    Egress Table 2: Pre-stateful

    - -

    -- This is similar to ingress table Pre-stateful. -+ This is similar to ingress table Pre-stateful. This table -+ adds the below 3 logical flows. -

    - --

    Egress Table 3: LB

    --

    -- This is similar to ingress table LB. --

    -+ - --

    Egress Table 4: from-lport ACL hints

    -+

    Egress Table 3: from-lport ACL hints

    -

    - This is similar to ingress table ACL hints. -

    - --

    Egress Table 5: to-lport ACLs

    -+

    Egress Table 4: to-lport ACLs

    - -

    - This is similar to ingress table ACLs except for -@@ -1733,28 +1762,28 @@ output; - - - --

    Egress Table 6: to-lport QoS Marking

    -+

    Egress Table 5: to-lport QoS Marking

    - -

    - This is similar to ingress table QoS marking except - they apply to to-lport QoS rules. -

    - --

    Egress Table 7: to-lport QoS Meter

    -+

    Egress Table 6: to-lport QoS Meter

    - -

    - This is similar to ingress table QoS meter except - they apply to to-lport QoS rules. -

    - --

    Egress Table 8: Stateful

    -+

    Egress Table 7: Stateful

    - -

    - This is similar to ingress table Stateful except that - there are no rules added for load balancing new connections. -

    - --

    Egress Table 9: Egress Port Security - IP

    -+

    Egress Table 8: Egress Port Security - IP

    - -

    - This is similar to the port security logic in table -@@ -1764,7 +1793,7 @@ output; - ip4.src and ip6.src -

    - --

    Egress Table 10: Egress Port Security - L2

    -+

    Egress Table 9: Egress Port Security - L2

    - -

    - This is similar to the ingress port security logic in ingress table -@@ -2720,7 +2749,11 @@ icmp6 { - (and optional port numbers) to load balance to. If the router is - configured to force SNAT any load-balanced packets, the above action - will be replaced by flags.force_snat_for_lb = 1; -- ct_lb(args);. If health check is enabled, then -+ ct_lb(args);. -+ If the load balancing rule is configured with skip_snat -+ set to true, the above action will be replaced by -+ flags.skip_snat_for_lb = 1; ct_lb(args);. -+ If health check is enabled, then - args will only contain those endpoints whose service - monitor status entry in OVN_Southbound db is - either online or empty. -@@ -2737,6 +2770,9 @@ icmp6 { - with an action of ct_dnat;. If the router is - configured to force SNAT any load-balanced packets, the above action - will be replaced by flags.force_snat_for_lb = 1; ct_dnat;. -+ If the load balancing rule is configured with skip_snat -+ set to true, the above action will be replaced by -+ flags.skip_snat_for_lb = 1; ct_dnat;. - - -

  • -@@ -2751,6 +2787,9 @@ icmp6 { - to force SNAT any load-balanced packets, the above action will be - replaced by flags.force_snat_for_lb = 1; - ct_lb(args);. -+ If the load balancing rule is configured with skip_snat -+ set to true, the above action will be replaced by -+ flags.skip_snat_for_lb = 1; ct_lb(args);. -
  • - -
  • -@@ -2763,6 +2802,9 @@ icmp6 { - If the router is configured to force SNAT any load-balanced - packets, the above action will be replaced by - flags.force_snat_for_lb = 1; ct_dnat;. -+ If the load balancing rule is configured with skip_snat -+ set to true, the above action will be replaced by -+ flags.skip_snat_for_lb = 1; ct_dnat;. -
  • - -
  • -@@ -3795,6 +3837,15 @@ nd_ns { -

    -
  • - -+
  • -+

    -+ If a load balancer configured to skip snat has been applied to -+ the Gateway router pipeline, a priority-120 flow matches -+ flags.skip_snat_for_lb == 1 && ip with an -+ action next;. -+

    -+
  • -+ -
  • -

    - If the Gateway router in the OVN Northbound database has been -diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 5a2018c2e..ae58fda16 100644 ---- a/northd/ovn-northd.c -+++ b/northd/ovn-northd.c -@@ -97,6 +97,10 @@ static bool check_lsp_is_up; - static char svc_monitor_mac[ETH_ADDR_STRLEN + 1]; - static struct eth_addr svc_monitor_mac_ea; - -+/* If this option is 'true' northd will make use of ct.inv match fields. -+ * Otherwise, it will avoid using it. The default is true. */ -+static bool use_ct_inv_match = true; -+ - /* Default probe interval for NB and SB DB connections. */ - #define DEFAULT_PROBE_INTERVAL_MSEC 5000 - static int northd_probe_interval_nb = 0; -@@ -147,32 +151,30 @@ enum ovn_stage { - 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") \ -+ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 12, "ls_in_stateful") \ -+ PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 13, "ls_in_pre_hairpin") \ -+ PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 14, "ls_in_nat_hairpin") \ -+ PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 15, "ls_in_hairpin") \ -+ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 16, "ls_in_arp_rsp") \ -+ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 17, "ls_in_dhcp_options") \ -+ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 18, "ls_in_dhcp_response") \ -+ PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 19, "ls_in_dns_lookup") \ -+ PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 20, "ls_in_dns_response") \ -+ PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 21, "ls_in_external_port") \ -+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 22, "ls_in_l2_lkup") \ -+ PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 23, "ls_in_l2_unknown") \ - \ - /* Logical switch egress stages. */ \ - PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ - PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 1, "ls_out_pre_acl") \ - PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful") \ -- PIPELINE_STAGE(SWITCH, OUT, LB, 3, "ls_out_lb") \ -- PIPELINE_STAGE(SWITCH, OUT, ACL_HINT, 4, "ls_out_acl_hint") \ -- PIPELINE_STAGE(SWITCH, OUT, ACL, 5, "ls_out_acl") \ -- PIPELINE_STAGE(SWITCH, OUT, QOS_MARK, 6, "ls_out_qos_mark") \ -- PIPELINE_STAGE(SWITCH, OUT, QOS_METER, 7, "ls_out_qos_meter") \ -- PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 8, "ls_out_stateful") \ -- PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 9, "ls_out_port_sec_ip") \ -- PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 10, "ls_out_port_sec_l2") \ -+ PIPELINE_STAGE(SWITCH, OUT, ACL_HINT, 3, "ls_out_acl_hint") \ -+ PIPELINE_STAGE(SWITCH, OUT, ACL, 4, "ls_out_acl") \ -+ PIPELINE_STAGE(SWITCH, OUT, QOS_MARK, 5, "ls_out_qos_mark") \ -+ PIPELINE_STAGE(SWITCH, OUT, QOS_METER, 6, "ls_out_qos_meter") \ -+ PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 7, "ls_out_stateful") \ -+ PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 8, "ls_out_port_sec_ip") \ -+ PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 9, "ls_out_port_sec_l2") \ - \ - /* Logical router ingress stages. */ \ - PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ -@@ -626,6 +628,7 @@ struct ovn_datapath { - bool has_stateful_acl; - bool has_lb_vip; - bool has_unknown; -+ bool has_acls; - - /* IPAM data. */ - struct ipam_info ipam_info; -@@ -664,9 +667,6 @@ struct ovn_datapath { - struct hmap nb_pgs; - }; - --static bool ls_has_stateful_acl(struct ovn_datapath *od); --static bool ls_has_lb_vip(struct ovn_datapath *od); -- - /* Contains a NAT entry with the external addresses pre-parsed. */ - struct ovn_nat { - const struct nbrec_nat *nb; -@@ -4729,27 +4729,38 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs) - hmap_destroy(nb_pgs); - } - --static bool --ls_has_stateful_acl(struct ovn_datapath *od) -+static void -+ls_get_acl_flags(struct ovn_datapath *od) - { -- for (size_t i = 0; i < od->nbs->n_acls; i++) { -- struct nbrec_acl *acl = od->nbs->acls[i]; -- if (!strcmp(acl->action, "allow-related")) { -- return true; -+ od->has_acls = false; -+ od->has_stateful_acl = false; -+ -+ if (od->nbs->n_acls) { -+ od->has_acls = true; -+ -+ for (size_t i = 0; i < od->nbs->n_acls; i++) { -+ struct nbrec_acl *acl = od->nbs->acls[i]; -+ if (!strcmp(acl->action, "allow-related")) { -+ od->has_stateful_acl = true; -+ return; -+ } - } - } - - struct ovn_ls_port_group *ls_pg; - HMAP_FOR_EACH (ls_pg, key_node, &od->nb_pgs) { -- for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) { -- struct nbrec_acl *acl = ls_pg->nb_pg->acls[i]; -- if (!strcmp(acl->action, "allow-related")) { -- return true; -+ if (ls_pg->nb_pg->n_acls) { -+ od->has_acls = true; -+ -+ for (size_t i = 0; i < ls_pg->nb_pg->n_acls; i++) { -+ struct nbrec_acl *acl = ls_pg->nb_pg->acls[i]; -+ if (!strcmp(acl->action, "allow-related")) { -+ od->has_stateful_acl = true; -+ return; -+ } - } - } - } -- -- return false; - } - - /* Logical switch ingress table 0: Ingress port security - L2 -@@ -5128,8 +5139,8 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows, - vip_configured = (vip_configured || lb->n_vips); - } - -- /* 'REGBIT_CONNTRACK_DEFRAG' is set to let the pre-stateful table send -- * packet to conntrack for defragmentation. -+ /* 'REGBIT_CONNTRACK_NAT' is set to let the pre-stateful table send -+ * packet to conntrack for defragmentation and possibly for unNATting. - * - * Send all the packets to conntrack in the ingress pipeline if the - * logical switch has a load balancer with VIP configured. Earlier -@@ -5159,9 +5170,9 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows, - */ - if (vip_configured) { - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, -- 100, "ip", REGBIT_CONNTRACK_DEFRAG" = 1; next;"); -+ 100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_LB, -- 100, "ip", REGBIT_CONNTRACK_DEFRAG" = 1; next;"); -+ 100, "ip", REGBIT_CONNTRACK_NAT" = 1; next;"); - } - } - -@@ -5173,10 +5184,46 @@ build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows) - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 0, "1", "next;"); - -+ 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_PRE_STATEFUL, 120, -+ 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_PRE_STATEFUL, 120, -+ ds_cstr(&match), ds_cstr(&actions)); -+ } -+ -+ ds_destroy(&actions); -+ ds_destroy(&match); -+ -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 110, -+ REGBIT_CONNTRACK_NAT" == 1", "ct_lb;"); -+ -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 110, -+ REGBIT_CONNTRACK_NAT" == 1", "ct_lb;"); -+ - /* If REGBIT_CONNTRACK_DEFRAG is set as 1, then the packets should be - * sent to conntrack for tracking and defragmentation. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_STATEFUL, 100, - REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;"); -+ - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 100, - REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;"); - } -@@ -5206,7 +5253,11 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) - enum ovn_stage stage = stages[i]; - - /* In any case, advance to the next stage. */ -- ovn_lflow_add(lflows, od, stage, 0, "1", "next;"); -+ if (!od->has_acls && !od->has_lb_vip) { -+ ovn_lflow_add(lflows, od, stage, UINT16_MAX, "1", "next;"); -+ } else { -+ ovn_lflow_add(lflows, od, stage, 0, "1", "next;"); -+ } - - if (!od->has_stateful_acl && !od->has_lb_vip) { - continue; -@@ -5606,10 +5657,19 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, - bool has_stateful = od->has_stateful_acl || od->has_lb_vip; - - /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by -- * default. A related rule at priority 1 is added below if there -+ * default. If the logical switch has no ACLs or no load balancers, -+ * then add 65535-priority flow to advance the packet to next -+ * stage. -+ * -+ * A related rule at priority 1 is added below if there - * are any stateful ACLs in this datapath. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;"); -+ if (!od->has_acls && !od->has_lb_vip) { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, "1", "next;"); -+ } else { -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;"); -+ } - - if (has_stateful) { - /* Ingress and Egress ACL Table (Priority 1). -@@ -5640,21 +5700,23 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, - "ip && (!ct.est || (ct.est && ct_label.blocked == 1))", - REGBIT_CONNTRACK_COMMIT" = 1; next;"); - -- /* Ingress and Egress ACL Table (Priority 65535). -+ /* Ingress and Egress ACL Table (Priority 65532). - * - * Always drop traffic that's in an invalid state. Also drop - * reply direction packets for connections that have been marked - * for deletion (bit 0 of ct_label is set). - * - * This is enforced at a higher priority than ACLs can be defined. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, -- "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)", -- "drop;"); -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, -- "ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)", -- "drop;"); -+ char *match = -+ xasprintf("%s(ct.est && ct.rpl && ct_label.blocked == 1)", -+ use_ct_inv_match ? "ct.inv || " : ""); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3, -+ match, "drop;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3, -+ match, "drop;"); -+ free(match); - -- /* Ingress and Egress ACL Table (Priority 65535). -+ /* Ingress and Egress ACL Table (Priority 65535 - 3). - * - * Allow reply traffic that is part of an established - * conntrack entry that has not been marked for deletion -@@ -5663,14 +5725,15 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, - * direction to hit the currently defined policy from ACLs. - * - * This is enforced at a higher priority than ACLs can be defined. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, -- "ct.est && !ct.rel && !ct.new && !ct.inv " -- "&& ct.rpl && ct_label.blocked == 0", -- "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, -- "ct.est && !ct.rel && !ct.new && !ct.inv " -- "&& ct.rpl && ct_label.blocked == 0", -- "next;"); -+ match = xasprintf("ct.est && !ct.rel && !ct.new%s && " -+ "ct.rpl && ct_label.blocked == 0", -+ use_ct_inv_match ? " && !ct.inv" : ""); -+ -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3, -+ match, "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3, -+ match, "next;"); -+ free(match); - - /* Ingress and Egress ACL Table (Priority 65535). - * -@@ -5683,21 +5746,21 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, - * a dynamically negotiated FTP data channel), but will allow - * related traffic such as an ICMP Port Unreachable through - * that's generated from a non-listening UDP port. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, -- "!ct.est && ct.rel && !ct.new && !ct.inv " -- "&& ct_label.blocked == 0", -- "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, -- "!ct.est && ct.rel && !ct.new && !ct.inv " -- "&& ct_label.blocked == 0", -- "next;"); -+ match = xasprintf("!ct.est && ct.rel && !ct.new%s && " -+ "ct_label.blocked == 0", -+ use_ct_inv_match ? " && !ct.inv" : ""); -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3, -+ match, "next;"); -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3, -+ match, "next;"); -+ free(match); - -- /* Ingress and Egress ACL Table (Priority 65535). -+ /* Ingress and Egress ACL Table (Priority 65532). - * - * Not to do conntrack on ND packets. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX - 3, - "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX - 3, - "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;"); - } - -@@ -5784,15 +5847,18 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, - actions); - } - -- /* Add a 34000 priority flow to advance the service monitor reply -- * packets to skip applying ingress ACLs. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 34000, -- "eth.dst == $svc_monitor_mac", "next;"); - -- /* Add a 34000 priority flow to advance the service monitor packets -- * generated by ovn-controller to skip applying egress ACLs. */ -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 34000, -- "eth.src == $svc_monitor_mac", "next;"); -+ if (od->has_acls || od->has_lb_vip) { -+ /* Add a 34000 priority flow to advance the service monitor reply -+ * packets to skip applying ingress ACLs. */ -+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 34000, -+ "eth.dst == $svc_monitor_mac", "next;"); -+ -+ /* Add a 34000 priority flow to advance the service monitor packets -+ * generated by ovn-controller to skip applying egress ACLs. */ -+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 34000, -+ "eth.src == $svc_monitor_mac", "next;"); -+ } - } - - static void -@@ -5856,37 +5922,6 @@ build_qos(struct ovn_datapath *od, struct hmap *lflows) { - } - } - --static void --build_lb(struct ovn_datapath *od, struct hmap *lflows) --{ -- /* Ingress and Egress LB Table (Priority 0): Packets are allowed by -- * default. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, 0, "1", "next;"); -- -- if (od->nbs->n_load_balancer) { -- for (size_t i = 0; i < od->n_router_ports; i++) { -- skip_port_from_conntrack(od, od->router_ports[i], -- S_SWITCH_IN_LB, S_SWITCH_OUT_LB, -- UINT16_MAX, lflows); -- } -- } -- -- if (od->has_lb_vip) { -- /* Ingress and Egress LB Table (Priority 65534). -- * -- * Send established traffic through conntrack for just NAT. */ -- ovn_lflow_add(lflows, od, S_SWITCH_IN_LB, UINT16_MAX - 1, -- "ct.est && !ct.rel && !ct.new && !ct.inv && " -- "ct_label.natted == 1", -- REGBIT_CONNTRACK_NAT" = 1; next;"); -- ovn_lflow_add(lflows, od, S_SWITCH_OUT_LB, UINT16_MAX - 1, -- "ct.est && !ct.rel && !ct.new && !ct.inv && " -- "ct_label.natted == 1", -- REGBIT_CONNTRACK_NAT" = 1; next;"); -- } --} -- - static void - build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, - struct ovn_northd_lb *lb) -@@ -5971,48 +6006,6 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs) - REGBIT_CONNTRACK_COMMIT" == 1", - "ct_commit { ct_label.blocked = 0; }; next;"); - -- /* If REGBIT_CONNTRACK_NAT is set as 1, then packets should just be sent -- * through nat (without committing). -- * -- * 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. -- */ -- 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;"); -- - /* Load balancing rules for new connections get committed to conntrack - * table. So even if REGBIT_CONNTRACK_COMMIT is set in a previous table - * a higher priority rule for load balancing below also commits the -@@ -6759,7 +6752,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *lflows) - struct ds actions = DS_EMPTY_INITIALIZER; - struct ovn_datapath *od; - -- /* Ingress table 24: Destination lookup for unknown MACs (priority 0). */ -+ /* Ingress table 23: Destination lookup for unknown MACs (priority 0). */ - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbs) { - continue; -@@ -6794,8 +6787,8 @@ build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od, - struct hmap *lbs) - { - if (od->nbs) { -- od->has_stateful_acl = ls_has_stateful_acl(od); - od->has_lb_vip = ls_has_lb_vip(od); -+ ls_get_acl_flags(od); - - build_pre_acls(od, lflows); - build_pre_lb(od, lflows, meter_groups, lbs); -@@ -6803,7 +6796,6 @@ build_lswitch_lflows_pre_acl_and_acl(struct ovn_datapath *od, - build_acl_hints(od, lflows); - build_acls(od, lflows, port_groups, meter_groups); - build_qos(od, lflows); -- build_lb(od, lflows); - build_stateful(od, lflows, lbs); - build_lb_hairpin(od, lflows); - } -@@ -8573,10 +8565,16 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, - return true; - } - -+enum lb_snat_type { -+ NO_FORCE_SNAT, -+ FORCE_SNAT, -+ SKIP_SNAT, -+}; -+ - static void - add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - struct ds *match, struct ds *actions, int priority, -- bool force_snat_for_lb, struct ovn_lb_vip *lb_vip, -+ enum lb_snat_type snat_type, struct ovn_lb_vip *lb_vip, - const char *proto, struct nbrec_load_balancer *lb, - struct shash *meter_groups, struct sset *nat_entries) - { -@@ -8585,9 +8583,10 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - - /* A match and actions for new connections. */ - char *new_match = xasprintf("ct.new && %s", ds_cstr(match)); -- if (force_snat_for_lb) { -- char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s", -- ds_cstr(actions)); -+ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) { -+ char *new_actions = xasprintf("flags.%s_snat_for_lb = 1; %s", -+ snat_type == SKIP_SNAT ? "skip" : "force", -+ ds_cstr(actions)); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, - new_match, new_actions, &lb->header_); - free(new_actions); -@@ -8598,11 +8597,12 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - - /* A match and actions for established connections. */ - char *est_match = xasprintf("ct.est && %s", ds_cstr(match)); -- if (force_snat_for_lb) { -+ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) { -+ char *est_actions = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;", -+ snat_type == SKIP_SNAT ? "skip" : "force"); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, -- est_match, -- "flags.force_snat_for_lb = 1; ct_dnat;", -- &lb->header_); -+ est_match, est_actions, &lb->header_); -+ free(est_actions); - } else { - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, - est_match, "ct_dnat;", &lb->header_); -@@ -8675,11 +8675,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - ds_put_format(&undnat_match, ") && outport == %s && " - "is_chassis_resident(%s)", od->l3dgw_port->json_key, - od->l3redirect_port->json_key); -- if (force_snat_for_lb) { -+ if (snat_type == FORCE_SNAT || snat_type == SKIP_SNAT) { -+ char *action = xasprintf("flags.%s_snat_for_lb = 1; ct_dnat;", -+ snat_type == SKIP_SNAT ? "skip" : "force"); - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120, -- ds_cstr(&undnat_match), -- "flags.force_snat_for_lb = 1; ct_dnat;", -+ ds_cstr(&undnat_match), action, - &lb->header_); -+ free(action); - } else { - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120, - ds_cstr(&undnat_match), "ct_dnat;", -@@ -8689,6 +8691,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, - ds_destroy(&undnat_match); - } - -+static void -+build_lrouter_lb_flows(struct hmap *lflows, struct ovn_datapath *od, -+ struct hmap *lbs, struct shash *meter_groups, -+ struct sset *nat_entries, struct ds *match, -+ struct ds *actions) -+{ -+ /* A set to hold all ips that need defragmentation and tracking. */ -+ struct sset all_ips = SSET_INITIALIZER(&all_ips); -+ bool lb_force_snat_ip = -+ !lport_addresses_is_empty(&od->lb_force_snat_addrs); -+ -+ for (int i = 0; i < od->nbr->n_load_balancer; i++) { -+ struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; -+ struct ovn_northd_lb *lb = -+ ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); -+ ovs_assert(lb); -+ -+ bool lb_skip_snat = smap_get_bool(&nb_lb->options, "skip_snat", false); -+ if (lb_skip_snat) { -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, -+ "flags.skip_snat_for_lb == 1 && ip", "next;"); -+ } -+ -+ for (size_t j = 0; j < lb->n_vips; j++) { -+ struct ovn_lb_vip *lb_vip = &lb->vips[j]; -+ struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; -+ ds_clear(actions); -+ build_lb_vip_actions(lb_vip, lb_vip_nb, actions, -+ lb->selection_fields, false); -+ -+ if (!sset_contains(&all_ips, lb_vip->vip_str)) { -+ sset_add(&all_ips, lb_vip->vip_str); -+ /* If there are any load balancing rules, we should send -+ * the packet to conntrack for defragmentation and -+ * tracking. This helps with two things. -+ * -+ * 1. With tracking, we can send only new connections to -+ * pick a DNAT ip address from a group. -+ * 2. If there are L4 ports in load balancing rules, we -+ * need the defragmentation to match on L4 ports. */ -+ ds_clear(match); -+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -+ ds_put_format(match, "ip && ip4.dst == %s", -+ lb_vip->vip_str); -+ } else { -+ ds_put_format(match, "ip && ip6.dst == %s", -+ lb_vip->vip_str); -+ } -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, -+ 100, ds_cstr(match), "ct_next;", -+ &nb_lb->header_); -+ } -+ -+ /* Higher priority rules are added for load-balancing in DNAT -+ * table. For every match (on a VIP[:port]), we add two flows -+ * via add_router_lb_flow(). One flow is for specific matching -+ * on ct.new with an action of "ct_lb($targets);". The other -+ * flow is for ct.est with an action of "ct_dnat;". */ -+ ds_clear(match); -+ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -+ ds_put_format(match, "ip && ip4.dst == %s", -+ lb_vip->vip_str); -+ } else { -+ ds_put_format(match, "ip && ip6.dst == %s", -+ lb_vip->vip_str); -+ } -+ -+ int prio = 110; -+ bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); -+ bool is_sctp = nullable_string_is_equal(nb_lb->protocol, -+ "sctp"); -+ const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; -+ -+ if (lb_vip->vip_port) { -+ ds_put_format(match, " && %s && %s.dst == %d", proto, -+ proto, lb_vip->vip_port); -+ prio = 120; -+ } -+ -+ if (od->l3redirect_port && -+ (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ -+ enum lb_snat_type snat_type = NO_FORCE_SNAT; -+ if (lb_skip_snat) { -+ snat_type = SKIP_SNAT; -+ } else if (lb_force_snat_ip || od->lb_force_snat_router_ip) { -+ snat_type = FORCE_SNAT; -+ } -+ add_router_lb_flow(lflows, od, match, actions, prio, -+ snat_type, lb_vip, proto, nb_lb, -+ meter_groups, nat_entries); -+ } -+ } -+ sset_destroy(&all_ips); -+} -+ - #define ND_RA_MAX_INTERVAL_MAX 1800 - #define ND_RA_MAX_INTERVAL_MIN 4 - -@@ -11002,668 +11103,643 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op, - } - } - --/* NAT, Defrag and load balancing. */ - static void --build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, -- struct hmap *lflows, -- struct shash *meter_groups, -- struct hmap *lbs, -- struct ds *match, struct ds *actions) -+build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od, -+ const struct nbrec_nat *nat, struct ds *match, -+ struct ds *actions, bool distributed, bool is_v6) - { -- if (od->nbr) { -+ /* Ingress UNSNAT table: It is for already established connections' -+ * reverse traffic. i.e., SNAT has already been done in egress -+ * pipeline and now the packet has entered the ingress pipeline as -+ * part of a reply. We undo the SNAT here. -+ * -+ * Undoing SNAT has to happen before DNAT processing. This is -+ * because when the packet was DNATed in ingress pipeline, it did -+ * not know about the possibility of eventual additional SNAT in -+ * egress pipeline. */ -+ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) { -+ return; -+ } - -- /* Packets are allowed by default. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); -- -- /* Send the IPv6 NS packets to next table. When ovn-controller -- * generates IPv6 NS (for the action - nd_ns{}), the injected -- * packet would go through conntrack - which is not required. */ -- ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); -- -- /* NAT rules are only valid on Gateway routers and routers with -- * l3dgw_port (router has a port with gateway chassis -- * specified). */ -- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -- return; -+ bool stateless = lrouter_nat_is_stateless(nat); -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "ip && ip%s.dst == %s", -+ is_v6 ? "6" : "4", nat->external_ip); -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_cstr(actions, "ct_snat;"); - } - -- struct sset nat_entries = SSET_INITIALIZER(&nat_entries); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -+ 90, ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } else { -+ /* Distributed router. */ - -- bool dnat_force_snat_ip = -- !lport_addresses_is_empty(&od->dnat_force_snat_addrs); -- bool lb_force_snat_ip = -- !lport_addresses_is_empty(&od->lb_force_snat_addrs); -+ /* Traffic received on l3dgw_port is subject to NAT. */ -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s", -+ is_v6 ? "6" : "4", nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } - -- for (int i = 0; i < od->nbr->n_nat; i++) { -- const struct nbrec_nat *nat; -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_cstr(actions, "ct_snat;"); -+ } - -- nat = od->nbr->nat[i]; -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -+ 100, ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } -+} - -- ovs_be32 ip, mask; -- struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; -- bool is_v6 = false; -- bool stateless = lrouter_nat_is_stateless(nat); -- struct nbrec_address_set *allowed_ext_ips = -- nat->allowed_ext_ips; -- struct nbrec_address_set *exempted_ext_ips = -- nat->exempted_ext_ips; -+static void -+build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od, -+ const struct nbrec_nat *nat, struct ds *match, -+ struct ds *actions, bool distributed, -+ ovs_be32 mask, bool is_v6) -+{ -+ /* Ingress DNAT table: Packets enter the pipeline with destination -+ * IP address that needs to be DNATted from a external IP address -+ * to a logical IP address. */ -+ if (!strcmp(nat->type, "dnat") || !strcmp(nat->type, "dnat_and_snat")) { -+ bool stateless = lrouter_nat_is_stateless(nat); - -- if (allowed_ext_ips && exempted_ext_ips) { -- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -- VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " -- "both allowed and exempt external ips set", -- UUID_ARGS(&(nat->header_.uuid))); -- continue; -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ /* Packet when it goes from the initiator to destination. -+ * We need to set flags.loopback because the router can -+ * send the packet back through the same interface. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.dst == %s", -+ is_v6 ? "6" : "4", nat->external_ip); -+ ds_clear(actions); -+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, true, mask); - } - -- char *error = ip_parse_masked(nat->external_ip, &ip, &mask); -- if (error || mask != OVS_BE32_MAX) { -- free(error); -- error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); -- if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad external ip %s for nat", -- nat->external_ip); -- free(error); -- continue; -- } -- /* It was an invalid IPv4 address, but valid IPv6. -- * Treat the rest of the handling of this NAT rule -- * as IPv6. */ -- is_v6 = true; -- } -- -- /* Check the validity of nat->logical_ip. 'logical_ip' can -- * be a subnet when the type is "snat". */ -- int cidr_bits; -- if (is_v6) { -- error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); -- cidr_bits = ipv6_count_cidr_bits(&mask_v6); -- } else { -- error = ip_parse_masked(nat->logical_ip, &ip, &mask); -- cidr_bits = ip_count_cidr_bits(mask); -+ if (!lport_addresses_is_empty(&od->dnat_force_snat_addrs)) { -+ /* Indicate to the future tables that a DNAT has taken -+ * place and a force SNAT needs to be done in the -+ * Egress SNAT table. */ -+ ds_put_format(actions, "flags.force_snat_for_dnat = 1; "); - } -- if (!strcmp(nat->type, "snat")) { -- if (error) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " -- "in router "UUID_FMT"", -- nat->logical_ip, UUID_ARGS(&od->key)); -- free(error); -- continue; -- } -+ -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "flags.loopback = 1; " -+ "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); - } else { -- if (error || (!is_v6 && mask != OVS_BE32_MAX) -- || (is_v6 && memcmp(&mask_v6, &v6_exact, -- sizeof mask_v6))) { -- /* Invalid for both IPv4 and IPv6 */ -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " -- ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); -- free(error); -- continue; -+ ds_put_format(actions, "flags.loopback = 1; ct_dnat(%s", -+ nat->logical_ip); -+ -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", nat->external_port_range); - } -+ ds_put_format(actions, ");"); - } - -- /* For distributed router NAT, determine whether this NAT rule -- * satisfies the conditions for distributed NAT processing. */ -- bool distributed = false; -- struct eth_addr mac; -- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && -- nat->logical_port && nat->external_mac) { -- if (eth_addr_from_string(nat->external_mac, &mac)) { -- distributed = true; -- } else { -- static struct vlog_rate_limit rl = -- VLOG_RATE_LIMIT_INIT(5, 1); -- VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " -- ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); -- continue; -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } else { -+ /* Distributed router. */ -+ -+ /* Traffic received on l3dgw_port is subject to NAT. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.dst == %s && inport == %s", -+ is_v6 ? "6" : "4", nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); -+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, true, mask); -+ } -+ -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.dst=%s; next;", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ } else { -+ ds_put_format(actions, "ct_dnat(%s", nat->logical_ip); -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", nat->external_port_range); - } -+ ds_put_format(actions, ");"); - } - -- /* Ingress UNSNAT table: It is for already established connections' -- * reverse traffic. i.e., SNAT has already been done in egress -- * pipeline and now the packet has entered the ingress pipeline as -- * part of a reply. We undo the SNAT here. -- * -- * Undoing SNAT has to happen before DNAT processing. This is -- * because when the packet was DNATed in ingress pipeline, it did -- * not know about the possibility of eventual additional SNAT in -- * egress pipeline. */ -- if (!strcmp(nat->type, "snat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- ds_clear(match); -- ds_clear(actions); -- ds_put_format(match, "ip && ip%s.dst == %s", -- is_v6 ? "6" : "4", -- nat->external_ip); -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_cstr(actions, "ct_snat;"); -- } -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } -+ } -+} - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -- 90, ds_cstr(match), -- ds_cstr(actions), -- &nat->header_); -- } else { -- /* Distributed router. */ -+static void -+build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od, -+ const struct nbrec_nat *nat, struct ds *match, -+ struct ds *actions, bool distributed, -+ struct eth_addr mac, bool is_v6) -+{ -+ /* Egress UNDNAT table: It is for already established connections' -+ * reverse traffic. i.e., DNAT has already been done in ingress -+ * pipeline and now the packet has entered the egress pipeline as -+ * part of a reply. We undo the DNAT here. -+ * -+ * Note that this only applies for NAT on a distributed router. -+ * Undo DNAT on a gateway router is done in the ingress DNAT -+ * pipeline stage. */ -+ if (!od->l3dgw_port || -+ (strcmp(nat->type, "dnat") && strcmp(nat->type, "dnat_and_snat"))) { -+ return; -+ } - -- /* Traffic received on l3dgw_port is subject to NAT. */ -- ds_clear(match); -- ds_clear(actions); -- ds_put_format(match, "ip && ip%s.dst == %s" -- " && inport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s && outport == %s", -+ is_v6 ? "6" : "4", nat->logical_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); -+ if (distributed) { -+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -+ ETH_ADDR_ARGS(mac)); -+ } - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_cstr(actions, "ct_snat;"); -- } -+ if (!strcmp(nat->type, "dnat_and_snat") && -+ lrouter_nat_is_stateless(nat)) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_dnat;"); -+ } - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, -- 100, -- ds_cstr(match), ds_cstr(actions), -- &nat->header_); -- } -- } -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+} - -- /* Ingress DNAT table: Packets enter the pipeline with destination -- * IP address that needs to be DNATted from a external IP address -- * to a logical IP address. */ -- if (!strcmp(nat->type, "dnat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- /* Packet when it goes from the initiator to destination. -- * We need to set flags.loopback because the router can -- * send the packet back through the same interface. */ -- ds_clear(match); -- ds_put_format(match, "ip && ip%s.dst == %s", -- is_v6 ? "6" : "4", -- nat->external_ip); -- ds_clear(actions); -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -- is_v6, true, mask); -- } -+static void -+build_lrouter_out_snat_flow(struct hmap *lflows, struct ovn_datapath *od, -+ const struct nbrec_nat *nat, struct ds *match, -+ struct ds *actions, bool distributed, -+ struct eth_addr mac, ovs_be32 mask, -+ int cidr_bits, bool is_v6) -+{ -+ /* Egress SNAT table: Packets enter the egress pipeline with -+ * source ip address that needs to be SNATted to a external ip -+ * address. */ -+ if (strcmp(nat->type, "snat") && strcmp(nat->type, "dnat_and_snat")) { -+ return; -+ } - -- if (dnat_force_snat_ip) { -- /* Indicate to the future tables that a DNAT has taken -- * place and a force SNAT needs to be done in the -- * Egress SNAT table. */ -- ds_put_format(actions, -- "flags.force_snat_for_dnat = 1; "); -- } -+ bool stateless = lrouter_nat_is_stateless(nat); -+ if (!od->l3dgw_port) { -+ /* Gateway router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s", -+ is_v6 ? "6" : "4", nat->logical_ip); -+ ds_clear(actions); - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(actions, "flags.loopback = 1; " -- "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_format(actions, "flags.loopback = 1; " -- "ct_dnat(%s", nat->logical_ip); -+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, false, mask); -+ } - -- if (nat->external_port_range[0]) { -- ds_put_format(actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(actions, ");"); -- } -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_snat(%s", nat->external_ip); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -- ds_cstr(match), ds_cstr(actions), -- &nat->header_); -- } else { -- /* Distributed router. */ -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", -+ nat->external_port_range); -+ } -+ ds_put_format(actions, ");"); -+ } - -- /* Traffic received on l3dgw_port is subject to NAT. */ -- ds_clear(match); -- ds_put_format(match, "ip && ip%s.dst == %s" -- " && inport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(actions); -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -- is_v6, true, mask); -- } -+ /* The priority here is calculated such that the -+ * nat->logical_ip with the longest mask gets a higher -+ * priority. */ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -+ cidr_bits + 1, ds_cstr(match), -+ ds_cstr(actions), &nat->header_); -+ } else { -+ uint16_t priority = cidr_bits + 1; - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(actions, "ip%s.dst=%s; next;", -- is_v6 ? "6" : "4", nat->logical_ip); -- } else { -- ds_put_format(actions, "ct_dnat(%s", nat->logical_ip); -- if (nat->external_port_range[0]) { -- ds_put_format(actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(actions, ");"); -- } -+ /* Distributed router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip && ip%s.src == %s && outport == %s", -+ is_v6 ? "6" : "4", nat->logical_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed && od->l3redirect_port) { -+ /* Flows for NAT rules that are centralized are only -+ * programmed on the gateway chassis. */ -+ priority += 128; -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } -+ ds_clear(actions); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, -- ds_cstr(match), ds_cstr(actions), -- &nat->header_); -- } -- } -+ if (nat->allowed_ext_ips || nat->exempted_ext_ips) { -+ lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -+ is_v6, false, mask); -+ } - -- /* ARP resolve for NAT IPs. */ -- if (od->l3dgw_port) { -- if (!strcmp(nat->type, "snat")) { -- ds_clear(match); -- ds_put_format( -- match, "inport == %s && %s == %s", -- od->l3dgw_port->json_key, -- is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, -- 120, ds_cstr(match), "next;", -- &nat->header_); -- } -+ if (distributed) { -+ ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -+ ETH_ADDR_ARGS(mac)); -+ } - -- if (!sset_contains(&nat_entries, nat->external_ip)) { -- ds_clear(match); -- ds_put_format( -- match, "outport == %s && %s == %s", -- od->l3dgw_port->json_key, -- is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, -+ if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -+ ds_put_format(actions, "ip%s.src=%s; next;", -+ is_v6 ? "6" : "4", nat->external_ip); -+ } else { -+ ds_put_format(actions, "ct_snat(%s", - nat->external_ip); -- ds_clear(actions); -- ds_put_format( -- actions, "eth.dst = %s; next;", -- distributed ? nat->external_mac : -- od->l3dgw_port->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, od, -- S_ROUTER_IN_ARP_RESOLVE, -- 100, ds_cstr(match), -- ds_cstr(actions), -- &nat->header_); -- sset_add(&nat_entries, nat->external_ip); -- } -- } else { -- /* Add the NAT external_ip to the nat_entries even for -- * gateway routers. This is required for adding load balancer -- * flows.*/ -- sset_add(&nat_entries, nat->external_ip); -+ if (nat->external_port_range[0]) { -+ ds_put_format(actions, ",%s", nat->external_port_range); - } -+ ds_put_format(actions, ");"); -+ } - -- /* Egress UNDNAT table: It is for already established connections' -- * reverse traffic. i.e., DNAT has already been done in ingress -- * pipeline and now the packet has entered the egress pipeline as -- * part of a reply. We undo the DNAT here. -- * -- * Note that this only applies for NAT on a distributed router. -- * Undo DNAT on a gateway router is done in the ingress DNAT -- * pipeline stage. */ -- if (od->l3dgw_port && (!strcmp(nat->type, "dnat") -- || !strcmp(nat->type, "dnat_and_snat"))) { -- ds_clear(match); -- ds_put_format(match, "ip && ip%s.src == %s" -- " && outport == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- ds_put_format(match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(actions); -- if (distributed) { -- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -- ETH_ADDR_ARGS(mac)); -- } -+ /* The priority here is calculated such that the -+ * nat->logical_ip with the longest mask gets a higher -+ * priority. */ -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -+ priority, ds_cstr(match), -+ ds_cstr(actions), &nat->header_); -+ } -+} - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(actions, "ct_dnat;"); -- } -+static void -+build_lrouter_ingress_flow(struct hmap *lflows, struct ovn_datapath *od, -+ const struct nbrec_nat *nat, struct ds *match, -+ struct ds *actions, struct eth_addr mac, -+ bool distributed, bool is_v6) -+{ -+ if (od->l3dgw_port && !strcmp(nat->type, "snat")) { -+ ds_clear(match); -+ ds_put_format( -+ match, "inport == %s && %s == %s", -+ od->l3dgw_port->json_key, -+ is_v6 ? "ip6.src" : "ip4.src", nat->external_ip); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, -+ 120, ds_cstr(match), "next;", -+ &nat->header_); -+ } -+ /* Logical router ingress table 0: -+ * For NAT on a distributed router, add rules allowing -+ * ingress traffic with eth.dst matching nat->external_mac -+ * on the l3dgw_port instance where nat->logical_port is -+ * resident. */ -+ if (distributed) { -+ /* Store the ethernet address of the port receiving the packet. -+ * This will save us from having to match on inport further -+ * down in the pipeline. -+ */ -+ ds_clear(actions); -+ ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -+ od->l3dgw_port->lrp_networks.ea_s); - -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 100, -- ds_cstr(match), ds_cstr(actions), -- &nat->header_); -- } -+ ds_clear(match); -+ ds_put_format(match, -+ "eth.dst == "ETH_ADDR_FMT" && inport == %s" -+ " && is_chassis_resident(\"%s\")", -+ ETH_ADDR_ARGS(mac), -+ od->l3dgw_port->json_key, -+ nat->logical_port); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); -+ } -+} - -- /* Egress SNAT table: Packets enter the egress pipeline with -- * source ip address that needs to be SNATted to a external ip -- * address. */ -- if (!strcmp(nat->type, "snat") -- || !strcmp(nat->type, "dnat_and_snat")) { -- if (!od->l3dgw_port) { -- /* Gateway router. */ -- ds_clear(match); -- ds_put_format(match, "ip && ip%s.src == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip); -- ds_clear(actions); -+static int -+lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat, -+ ovs_be32 *mask, bool *is_v6, int *cidr_bits, -+ struct eth_addr *mac, bool *distributed) -+{ -+ struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT; -+ ovs_be32 ip; - -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -- is_v6, false, mask); -- } -+ if (nat->allowed_ext_ips && nat->exempted_ext_ips) { -+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); -+ VLOG_WARN_RL(&rl, "NAT rule: "UUID_FMT" not applied, since " -+ "both allowed and exempt external ips set", -+ UUID_ARGS(&(nat->header_.uuid))); -+ return -EINVAL; -+ } - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(actions, "ct_snat(%s", -- nat->external_ip); -+ char *error = ip_parse_masked(nat->external_ip, &ip, mask); -+ *is_v6 = false; - -- if (nat->external_port_range[0]) { -- ds_put_format(actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(actions, ");"); -- } -+ if (error || *mask != OVS_BE32_MAX) { -+ free(error); -+ error = ipv6_parse_masked(nat->external_ip, &ipv6, &mask_v6); -+ if (error || memcmp(&mask_v6, &v6_exact, sizeof(mask_v6))) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad external ip %s for nat", -+ nat->external_ip); -+ free(error); -+ return -EINVAL; -+ } -+ /* It was an invalid IPv4 address, but valid IPv6. -+ * Treat the rest of the handling of this NAT rule -+ * as IPv6. */ -+ *is_v6 = true; -+ } - -- /* The priority here is calculated such that the -- * nat->logical_ip with the longest mask gets a higher -- * priority. */ -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -- cidr_bits + 1, -- ds_cstr(match), ds_cstr(actions), -- &nat->header_); -- } else { -- uint16_t priority = cidr_bits + 1; -+ /* Check the validity of nat->logical_ip. 'logical_ip' can -+ * be a subnet when the type is "snat". */ -+ if (*is_v6) { -+ error = ipv6_parse_masked(nat->logical_ip, &ipv6, &mask_v6); -+ *cidr_bits = ipv6_count_cidr_bits(&mask_v6); -+ } else { -+ error = ip_parse_masked(nat->logical_ip, &ip, mask); -+ *cidr_bits = ip_count_cidr_bits(*mask); -+ } -+ if (!strcmp(nat->type, "snat")) { -+ if (error) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad ip network or ip %s for snat " -+ "in router "UUID_FMT"", -+ nat->logical_ip, UUID_ARGS(&od->key)); -+ free(error); -+ return -EINVAL; -+ } -+ } else { -+ if (error || (*is_v6 == false && *mask != OVS_BE32_MAX) -+ || (*is_v6 && memcmp(&mask_v6, &v6_exact, -+ sizeof mask_v6))) { -+ /* Invalid for both IPv4 and IPv6 */ -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad ip %s for dnat in router " -+ ""UUID_FMT"", nat->logical_ip, UUID_ARGS(&od->key)); -+ free(error); -+ return -EINVAL; -+ } -+ } - -- /* Distributed router. */ -- ds_clear(match); -- ds_put_format(match, "ip && ip%s.src == %s" -- " && outport == %s", -- is_v6 ? "6" : "4", -- nat->logical_ip, -- od->l3dgw_port->json_key); -- if (!distributed && od->l3redirect_port) { -- /* Flows for NAT rules that are centralized are only -- * programmed on the gateway chassis. */ -- priority += 128; -- ds_put_format(match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- ds_clear(actions); -+ /* For distributed router NAT, determine whether this NAT rule -+ * satisfies the conditions for distributed NAT processing. */ -+ *distributed = false; -+ if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") && -+ nat->logical_port && nat->external_mac) { -+ if (eth_addr_from_string(nat->external_mac, mac)) { -+ *distributed = true; -+ } else { -+ static struct vlog_rate_limit rl = -+ VLOG_RATE_LIMIT_INIT(5, 1); -+ VLOG_WARN_RL(&rl, "bad mac %s for dnat in router " -+ ""UUID_FMT"", nat->external_mac, UUID_ARGS(&od->key)); -+ return -EINVAL; -+ } -+ } - -- if (allowed_ext_ips || exempted_ext_ips) { -- lrouter_nat_add_ext_ip_match(od, lflows, match, nat, -- is_v6, false, mask); -- } -+ return 0; -+} - -- if (distributed) { -- ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", -- ETH_ADDR_ARGS(mac)); -- } -+/* NAT, Defrag and load balancing. */ -+static void -+build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, -+ struct hmap *lflows, -+ struct shash *meter_groups, -+ struct hmap *lbs, -+ struct ds *match, struct ds *actions) -+{ -+ if (!od->nbr) { -+ return; -+ } - -- if (!strcmp(nat->type, "dnat_and_snat") && stateless) { -- ds_put_format(actions, "ip%s.src=%s; next;", -- is_v6 ? "6" : "4", nat->external_ip); -- } else { -- ds_put_format(actions, "ct_snat(%s", -- nat->external_ip); -- if (nat->external_port_range[0]) { -- ds_put_format(actions, ",%s", -- nat->external_port_range); -- } -- ds_put_format(actions, ");"); -- } -+ /* Packets are allowed by default. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_EGR_LOOP, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ECMP_STATEFUL, 0, "1", "next;"); -+ -+ /* Send the IPv6 NS packets to next table. When ovn-controller -+ * generates IPv6 NS (for the action - nd_ns{}), the injected -+ * packet would go through conntrack - which is not required. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;"); -+ -+ /* NAT rules are only valid on Gateway routers and routers with -+ * l3dgw_port (router has a port with gateway chassis -+ * specified). */ -+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -+ return; -+ } - -- /* The priority here is calculated such that the -- * nat->logical_ip with the longest mask gets a higher -- * priority. */ -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_SNAT, -- priority, ds_cstr(match), -- ds_cstr(actions), -- &nat->header_); -- } -- } -+ struct sset nat_entries = SSET_INITIALIZER(&nat_entries); - -- /* Logical router ingress table 0: -- * For NAT on a distributed router, add rules allowing -- * ingress traffic with eth.dst matching nat->external_mac -- * on the l3dgw_port instance where nat->logical_port is -- * resident. */ -- if (distributed) { -- /* Store the ethernet address of the port receiving the packet. -- * This will save us from having to match on inport further -- * down in the pipeline. -- */ -- ds_clear(actions); -- ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", -- od->l3dgw_port->lrp_networks.ea_s); -+ bool dnat_force_snat_ip = -+ !lport_addresses_is_empty(&od->dnat_force_snat_addrs); -+ bool lb_force_snat_ip = -+ !lport_addresses_is_empty(&od->lb_force_snat_addrs); - -- ds_clear(match); -- ds_put_format(match, -- "eth.dst == "ETH_ADDR_FMT" && inport == %s" -- " && is_chassis_resident(\"%s\")", -- ETH_ADDR_ARGS(mac), -- od->l3dgw_port->json_key, -- nat->logical_port); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50, -- ds_cstr(match), ds_cstr(actions), -- &nat->header_); -- } -+ for (int i = 0; i < od->nbr->n_nat; i++) { -+ const struct nbrec_nat *nat = nat = od->nbr->nat[i]; -+ struct eth_addr mac = eth_addr_broadcast; -+ bool is_v6, distributed; -+ ovs_be32 mask; -+ int cidr_bits; - -- /* Ingress Gateway Redirect Table: For NAT on a distributed -- * router, add flows that are specific to a NAT rule. These -- * flows indicate the presence of an applicable NAT rule that -- * can be applied in a distributed manner. -- * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to -- * NAT external IP and NAT external mac so the ARP request -- * generated in the following stage is sent out with proper IP/MAC -- * src addresses. -- */ -- if (distributed) { -- ds_clear(match); -- ds_clear(actions); -- ds_put_format(match, -- "ip%s.src == %s && outport == %s && " -- "is_chassis_resident(\"%s\")", -- is_v6 ? "6" : "4", nat->logical_ip, -- od->l3dgw_port->json_key, nat->logical_port); -- ds_put_format(actions, "eth.src = %s; %s = %s; next;", -- nat->external_mac, -- is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, -- nat->external_ip); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, -- 100, ds_cstr(match), -- ds_cstr(actions), &nat->header_); -- } -+ if (lrouter_check_nat_entry(od, nat, &mask, &is_v6, &cidr_bits, -+ &mac, &distributed) < 0) { -+ continue; -+ } - -- /* Egress Loopback table: For NAT on a distributed router. -- * If packets in the egress pipeline on the distributed -- * gateway port have ip.dst matching a NAT external IP, then -- * loop a clone of the packet back to the beginning of the -- * ingress pipeline with inport = outport. */ -- if (od->l3dgw_port) { -- /* Distributed router. */ -- ds_clear(match); -- ds_put_format(match, "ip%s.dst == %s && outport == %s", -- is_v6 ? "6" : "4", -- nat->external_ip, -- od->l3dgw_port->json_key); -- if (!distributed) { -- ds_put_format(match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } else { -- ds_put_format(match, " && is_chassis_resident(\"%s\")", -- nat->logical_port); -- } -+ /* S_ROUTER_IN_UNSNAT */ -+ build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions, distributed, -+ is_v6); -+ /* S_ROUTER_IN_DNAT */ -+ build_lrouter_in_dnat_flow(lflows, od, nat, match, actions, distributed, -+ mask, is_v6); - -+ /* ARP resolve for NAT IPs. */ -+ if (od->l3dgw_port) { -+ if (!sset_contains(&nat_entries, nat->external_ip)) { -+ ds_clear(match); -+ ds_put_format( -+ match, "outport == %s && %s == %s", -+ od->l3dgw_port->json_key, -+ is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4, -+ nat->external_ip); - ds_clear(actions); -- ds_put_format(actions, -- "clone { ct_clear; " -- "inport = outport; outport = \"\"; " -- "flags = 0; flags.loopback = 1; "); -- for (int j = 0; j < MFF_N_LOG_REGS; j++) { -- ds_put_format(actions, "reg%d = 0; ", j); -- } -- ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; " -- "next(pipeline=ingress, table=%d); };", -- ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, -- ds_cstr(match), ds_cstr(actions), -+ ds_put_format( -+ actions, "eth.dst = %s; next;", -+ distributed ? nat->external_mac : -+ od->l3dgw_port->lrp_networks.ea_s); -+ ovn_lflow_add_with_hint(lflows, od, -+ S_ROUTER_IN_ARP_RESOLVE, -+ 100, ds_cstr(match), -+ ds_cstr(actions), - &nat->header_); -+ sset_add(&nat_entries, nat->external_ip); - } -- } -- -- /* Handle force SNAT options set in the gateway router. */ -- if (!od->l3dgw_port) { -- if (dnat_force_snat_ip) { -- if (od->dnat_force_snat_addrs.n_ipv4_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "4", -- od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, -- "dnat"); -- } -- if (od->dnat_force_snat_addrs.n_ipv6_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "6", -- od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, -- "dnat"); -- } -- } -- if (lb_force_snat_ip) { -- if (od->lb_force_snat_addrs.n_ipv4_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "4", -- od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); -- } -- if (od->lb_force_snat_addrs.n_ipv6_addrs) { -- build_lrouter_force_snat_flows(lflows, od, "6", -- od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); -- } -+ } else { -+ /* Add the NAT external_ip to the nat_entries even for -+ * gateway routers. This is required for adding load balancer -+ * flows.*/ -+ sset_add(&nat_entries, nat->external_ip); -+ } -+ -+ /* S_ROUTER_OUT_UNDNAT */ -+ build_lrouter_out_undnat_flow(lflows, od, nat, match, actions, distributed, -+ mac, is_v6); -+ /* S_ROUTER_OUT_SNAT */ -+ build_lrouter_out_snat_flow(lflows, od, nat, match, actions, distributed, -+ mac, mask, cidr_bits, is_v6); -+ -+ /* S_ROUTER_IN_ADMISSION - S_ROUTER_IN_IP_INPUT */ -+ build_lrouter_ingress_flow(lflows, od, nat, match, actions, -+ mac, distributed, is_v6); -+ -+ /* Ingress Gateway Redirect Table: For NAT on a distributed -+ * router, add flows that are specific to a NAT rule. These -+ * flows indicate the presence of an applicable NAT rule that -+ * can be applied in a distributed manner. -+ * In particulr REG_SRC_IPV4/REG_SRC_IPV6 and eth.src are set to -+ * NAT external IP and NAT external mac so the ARP request -+ * generated in the following stage is sent out with proper IP/MAC -+ * src addresses. -+ */ -+ if (distributed) { -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, -+ "ip%s.src == %s && outport == %s && " -+ "is_chassis_resident(\"%s\")", -+ is_v6 ? "6" : "4", nat->logical_ip, -+ od->l3dgw_port->json_key, nat->logical_port); -+ ds_put_format(actions, "eth.src = %s; %s = %s; next;", -+ nat->external_mac, -+ is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, -+ nat->external_ip); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, -+ 100, ds_cstr(match), -+ ds_cstr(actions), &nat->header_); -+ } -+ -+ /* Egress Loopback table: For NAT on a distributed router. -+ * If packets in the egress pipeline on the distributed -+ * gateway port have ip.dst matching a NAT external IP, then -+ * loop a clone of the packet back to the beginning of the -+ * ingress pipeline with inport = outport. */ -+ if (od->l3dgw_port) { -+ /* Distributed router. */ -+ ds_clear(match); -+ ds_put_format(match, "ip%s.dst == %s && outport == %s", -+ is_v6 ? "6" : "4", -+ nat->external_ip, -+ od->l3dgw_port->json_key); -+ if (!distributed) { -+ ds_put_format(match, " && is_chassis_resident(%s)", -+ od->l3redirect_port->json_key); -+ } else { -+ ds_put_format(match, " && is_chassis_resident(\"%s\")", -+ nat->logical_port); - } - -- /* For gateway router, re-circulate every packet through -- * the DNAT zone. This helps with the following. -- * -- * Any packet that needs to be unDNATed in the reverse -- * direction gets unDNATed. Ideally this could be done in -- * the egress pipeline. But since the gateway router -- * does not have any feature that depends on the source -- * ip address being external IP address for IP routing, -- * we can do it here, saving a future re-circulation. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, -- "ip", "flags.loopback = 1; ct_dnat;"); -+ ds_clear(actions); -+ ds_put_format(actions, -+ "clone { ct_clear; " -+ "inport = outport; outport = \"\"; " -+ "flags = 0; flags.loopback = 1; "); -+ for (int j = 0; j < MFF_N_LOG_REGS; j++) { -+ ds_put_format(actions, "reg%d = 0; ", j); -+ } -+ ds_put_format(actions, REGBIT_EGRESS_LOOPBACK" = 1; " -+ "next(pipeline=ingress, table=%d); };", -+ ovn_stage_get_table(S_ROUTER_IN_ADMISSION)); -+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_EGR_LOOP, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &nat->header_); - } -+ } - -- /* Load balancing and packet defrag are only valid on -- * Gateway routers or router with gateway port. */ -- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { -- sset_destroy(&nat_entries); -- return; -+ /* Handle force SNAT options set in the gateway router. */ -+ if (!od->l3dgw_port) { -+ if (dnat_force_snat_ip) { -+ if (od->dnat_force_snat_addrs.n_ipv4_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "4", -+ od->dnat_force_snat_addrs.ipv4_addrs[0].addr_s, -+ "dnat"); -+ } -+ if (od->dnat_force_snat_addrs.n_ipv6_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "6", -+ od->dnat_force_snat_addrs.ipv6_addrs[0].addr_s, -+ "dnat"); -+ } - } -- -- /* A set to hold all ips that need defragmentation and tracking. */ -- struct sset all_ips = SSET_INITIALIZER(&all_ips); -- -- for (int i = 0; i < od->nbr->n_load_balancer; i++) { -- struct nbrec_load_balancer *nb_lb = od->nbr->load_balancer[i]; -- struct ovn_northd_lb *lb = -- ovn_northd_lb_find(lbs, &nb_lb->header_.uuid); -- ovs_assert(lb); -- -- for (size_t j = 0; j < lb->n_vips; j++) { -- struct ovn_lb_vip *lb_vip = &lb->vips[j]; -- struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[j]; -- ds_clear(actions); -- build_lb_vip_actions(lb_vip, lb_vip_nb, actions, -- lb->selection_fields, false); -- -- if (!sset_contains(&all_ips, lb_vip->vip_str)) { -- sset_add(&all_ips, lb_vip->vip_str); -- /* If there are any load balancing rules, we should send -- * the packet to conntrack for defragmentation and -- * tracking. This helps with two things. -- * -- * 1. With tracking, we can send only new connections to -- * pick a DNAT ip address from a group. -- * 2. If there are L4 ports in load balancing rules, we -- * need the defragmentation to match on L4 ports. */ -- ds_clear(match); -- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -- ds_put_format(match, "ip && ip4.dst == %s", -- lb_vip->vip_str); -- } else { -- ds_put_format(match, "ip && ip6.dst == %s", -- lb_vip->vip_str); -- } -- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DEFRAG, -- 100, ds_cstr(match), "ct_next;", -- &nb_lb->header_); -- } -- -- /* Higher priority rules are added for load-balancing in DNAT -- * table. For every match (on a VIP[:port]), we add two flows -- * via add_router_lb_flow(). One flow is for specific matching -- * on ct.new with an action of "ct_lb($targets);". The other -- * flow is for ct.est with an action of "ct_dnat;". */ -- ds_clear(match); -- if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { -- ds_put_format(match, "ip && ip4.dst == %s", -- lb_vip->vip_str); -- } else { -- ds_put_format(match, "ip && ip6.dst == %s", -- lb_vip->vip_str); -- } -- -- int prio = 110; -- bool is_udp = nullable_string_is_equal(nb_lb->protocol, "udp"); -- bool is_sctp = nullable_string_is_equal(nb_lb->protocol, -- "sctp"); -- const char *proto = is_udp ? "udp" : is_sctp ? "sctp" : "tcp"; -- -- if (lb_vip->vip_port) { -- ds_put_format(match, " && %s && %s.dst == %d", proto, -- proto, lb_vip->vip_port); -- prio = 120; -- } -- -- if (od->l3redirect_port && -- (lb_vip->n_backends || !lb_vip->empty_backend_rej)) { -- ds_put_format(match, " && is_chassis_resident(%s)", -- od->l3redirect_port->json_key); -- } -- bool force_snat_for_lb = -- lb_force_snat_ip || od->lb_force_snat_router_ip; -- add_router_lb_flow(lflows, od, match, actions, prio, -- force_snat_for_lb, lb_vip, proto, -- nb_lb, meter_groups, &nat_entries); -+ if (lb_force_snat_ip) { -+ if (od->lb_force_snat_addrs.n_ipv4_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "4", -+ od->lb_force_snat_addrs.ipv4_addrs[0].addr_s, "lb"); -+ } -+ if (od->lb_force_snat_addrs.n_ipv6_addrs) { -+ build_lrouter_force_snat_flows(lflows, od, "6", -+ od->lb_force_snat_addrs.ipv6_addrs[0].addr_s, "lb"); - } - } -- sset_destroy(&all_ips); -+ -+ /* For gateway router, re-circulate every packet through -+ * the DNAT zone. This helps with the following. -+ * -+ * Any packet that needs to be unDNATed in the reverse -+ * direction gets unDNATed. Ideally this could be done in -+ * the egress pipeline. But since the gateway router -+ * does not have any feature that depends on the source -+ * ip address being external IP address for IP routing, -+ * we can do it here, saving a future re-circulation. */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 50, -+ "ip", "flags.loopback = 1; ct_dnat;"); -+ } -+ -+ /* Load balancing and packet defrag are only valid on -+ * Gateway routers or router with gateway port. */ -+ if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { - sset_destroy(&nat_entries); -+ return; - } -+ -+ build_lrouter_lb_flows(lflows, od, lbs, meter_groups, &nat_entries, -+ match, actions); -+ -+ sset_destroy(&nat_entries); - } - - -@@ -12909,6 +12985,9 @@ ovnnb_db_run(struct northd_context *ctx, - - use_logical_dp_groups = smap_get_bool(&nb->options, - "use_logical_dp_groups", false); -+ use_ct_inv_match = smap_get_bool(&nb->options, -+ "use_ct_inv_match", true); -+ - /* deprecated, use --event instead */ - controller_event_en = smap_get_bool(&nb->options, - "controller_event", false); -diff --git a/ovn-nb.xml b/ovn-nb.xml -index b0a4adffe..046d053e9 100644 ---- a/ovn-nb.xml -+++ b/ovn-nb.xml -@@ -227,6 +227,21 @@ -

    - - -+ -+

    -+ If set to false, ovn-northd will not use the -+ ct.inv field in any of the logical flow matches. -+ The default value is true. If the NIC supports offloading -+ OVS datapath flows but doesn't support offloading ct_state -+ inv flag, then the datapath flows matching on this flag -+ (either +inv or -inv) will not be -+ offloaded. CMS should consider setting use_ct_inv_match -+ to false in such cases. This results in a side effect -+ of the invalid packets getting delivered to the destination VIF, -+ which otherwise would have been dropped by OVN. -+

    -+
    -+ - -

    - These options control how routes are advertised between OVN -@@ -1653,6 +1668,12 @@ - exactly one IPv4 and/or one IPv6 address on it, separated by a space - character. - -+ -+ -+ If the load balancing rule is configured with skip_snat -+ option, the force_snat_for_lb option configured for the router -+ pipeline will not be applied for this load balancer. -+ - - - -diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at -index 2cd3e261f..5c64fff12 100644 ---- a/tests/ovn-controller.at -+++ b/tests/ovn-controller.at -@@ -431,3 +431,83 @@ OVS_WAIT_UNTIL([ - - OVN_CLEANUP([hv1]) - AT_CLEANUP -+ -+# Test that changes of a port binding from one type to another doesn'that -+# result in any ovn-controller asserts or crashes. -+AT_SETUP([ovn-controller - port binding type change handling]) -+AT_KEYWORDS([ovn]) -+ovn_start -+ -+net_add n1 -+sim_add hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ -+check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1 -+ -+as hv1 -+check ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 -+ -+# ovn-controller should bind the interface. -+wait_for_ports_up -+hv_uuid=$(fetch_column Chassis _uuid name=hv1) -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]] -+primary lport : [[lsp1]] -+---------------------------------------- -+]) -+ -+# pause ovn-northd -+check as northd ovn-appctl -t ovn-northd pause -+check as northd-backup ovn-appctl -t ovn-northd pause -+ -+as northd ovn-appctl -t ovn-northd status -+as northd-backup ovn-appctl -t ovn-northd status -+ -+pb_types=(patch chassisredirect l3gateway localnet localport l2gateway -+ virtual external remote vtep) -+for type in ${pb_types[[@]]} -+do -+ for update_type in ${pb_types[[@]]} -+ do -+ check ovn-sbctl set port_binding lsp1 type=$type -+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$type -+ OVS_WAIT_UNTIL([test $type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) -+ -+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+ echo "Updating to $update_type from $type" -+ check ovn-sbctl set port_binding lsp1 type=$update_type -+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=$update_type -+ OVS_WAIT_UNTIL([test $update_type = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) -+ -+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ # Set the port binding type back to VIF. -+ check ovn-sbctl set port_binding lsp1 type=\"\" -+ check as hv1 ovs-vsctl set open . external_ids:ovn-cms-options=foo -+ OVS_WAIT_UNTIL([test foo = $(ovn-sbctl get chassis . other_config:ovn-cms-options)]) -+ -+ AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[lsp1]], OVS interface name : [[vif1]], num binding lports : [[1]] -+primary lport : [[lsp1]] -+---------------------------------------- -+]) -+ done -+done -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at -index 2ba29a960..4cf14b1f2 100644 ---- a/tests/ovn-macros.at -+++ b/tests/ovn-macros.at -@@ -433,6 +433,24 @@ wait_for_ports_up() { - done - fi - } -+ -+# reset_pcap_file iface pcap_file -+# Resets the pcap file associates with OVS interface. should be used -+# with dummy datapath. -+reset_iface_pcap_file() { -+ local iface=$1 -+ local pcap_file=$2 -+ check rm -f dummy-*.pcap -+ check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ -+options:rxq_pcap=dummy-rx.pcap -+ OVS_WAIT_WHILE([test 24 = $(wc -c dummy-tx.pcap | cut -d " " -f1)]) -+ check rm -f ${pcap_file}*.pcap -+ check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ -+options:rxq_pcap=${pcap_file}-rx.pcap -+ -+ OVS_WAIT_WHILE([test 24 = $(wc -c ${pcap_file}-tx.pcap | cut -d " " -f1)]) -+} -+ - 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 b78baa708..b941ee86b 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 -+ [ovn-sbctl dump-flows sw0 | tee sbflows | grep 'priority=120.*backends' | sed 's/table=..//'], 0, [dnl - (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);) - ]) - -@@ -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], -+ [ovn-sbctl dump-flows sw0 | tee sbflows2 | grep 'priority=120.*backends' | sed 's/table=..//'], [0], - [ (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);) - ]) - -@@ -1098,7 +1098,7 @@ 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 -+ovn-sbctl dump-flows sw0 | grep backends | 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=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) -@@ -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], -+ [ovn-sbctl dump-flows sw0 | tee sbflows 3 | grep 'priority=120.*backends' | sed 's/table=..//'], [0], - [ (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);) - ]) - -@@ -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], -+ [ovn-sbctl dump-flows sw0 | tee sbflows4 | grep 'priority=120.*backends' | sed 's/table=..//'], [0], - [ (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);) - ]) - -@@ -1132,7 +1132,7 @@ check ovn-nbctl --wait=sb sync - - AT_CAPTURE_FILE([sbflows5]) - OVS_WAIT_FOR_OUTPUT( -- [ovn-sbctl dump-flows sw0 | tee sbflows5 | grep 'priority=120.*ct_lb'], 1) -+ [ovn-sbctl dump-flows sw0 | tee sbflows5 | grep 'priority=120.*backends'], 1) - - AT_CAPTURE_FILE([sbflows6]) - OVS_WAIT_FOR_OUTPUT( -@@ -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, -+ [ovn-sbctl dump-flows sw0 | tee sbflows7 | grep backends | 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=(reg1 = 10.0.0.10; reg2[[0..15]] = 80; ct_lb(backends=10.0.0.3:80,20.0.0.3:80);) - ]) - -@@ -1185,7 +1185,7 @@ wait_row_count Service_Monitor 1 port=1000 - - 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], -+ [ovn-sbctl dump-flows sw0 | tee sbflows9 | grep backends | 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=(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);) -@@ -1199,7 +1199,7 @@ check ovn-nbctl --wait=sb sync - - 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], -+ [ovn-sbctl dump-flows sw0 | tee sbflows10 | grep backends | 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=(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);) -@@ -1209,7 +1209,7 @@ AS_BOX([Associate lb1 to sw1]) - check ovn-nbctl --wait=sb ls-lb-add sw1 lb1 - 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], -+ [ovn-sbctl dump-flows sw1 | tee sbflows11 | grep backends | 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=(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);) -@@ -1269,7 +1269,7 @@ ovn-sbctl set service_monitor $sm_sw1_p1 status=offline - AT_CAPTURE_FILE([sbflows12]) - OVS_WAIT_FOR_OUTPUT( - [ovn-sbctl dump-flows sw0 | tee sbflows12 | grep "ip4.dst == 10.0.0.10 && tcp.dst == 80" | grep priority=120 | sed 's/table=..//'], [0], [dnl -- (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=6);};) -+ (ls_in_stateful ), priority=120 , match=(ct.new && ip4.dst == 10.0.0.10 && tcp.dst == 80), action=(reg0 = 0; reject { outport <-> inport; next(pipeline=egress,table=5);};) - ]) - - AT_CLEANUP -@@ -1671,13 +1671,13 @@ AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 - ovn-nbctl ls-lb-add sw0 lb1 - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - ovn-nbctl ls-lb-add sw0 lb2 - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - lb1_uuid=$(ovn-nbctl --bare --columns _uuid find load_balancer name=lb1) -@@ -1686,7 +1686,7 @@ lb2_uuid=$(ovn-nbctl --bare --columns _uuid find load_balancer name=lb2) - ovn-nbctl clear load_balancer $lb1_uuid vips - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - ovn-nbctl clear load_balancer $lb2_uuid vips -@@ -1699,14 +1699,14 @@ ovn-nbctl set load_balancer $lb2_uuid vips:"10.0.0.11"="10.0.0.4" - - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - # Now reverse the order of clearing the vip. - ovn-nbctl clear load_balancer $lb2_uuid vips - ovn-nbctl --wait=sb sync - AT_CHECK([ovn-sbctl lflow-list | grep "ls_out_pre_lb.*priority=100" | grep reg0 | sort], [0], [dnl -- table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[0]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) - ]) - - ovn-nbctl clear load_balancer $lb1_uuid vips -@@ -1754,10 +1754,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=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); };) -+sw0flows: table=4 (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=22); };) -+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=5); };) -+sw1flows: table=4 (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=22); };) -+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=5); };) - ]) - - AS_BOX([2]) -@@ -1770,10 +1770,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=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); };) -+sw0flows2: table=4 (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=22); };) -+sw0flows2: table=4 (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=22); };) -+sw1flows2: table=4 (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=22); };) -+sw1flows2: table=4 (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=22); };) - ]) - - AS_BOX([3]) -@@ -1786,18 +1786,18 @@ ovn-sbctl dump-flows sw1 > sw1flows3 - 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=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=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); };) -+sw0flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) -+sw0flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) -+sw0flows3: table=4 (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=22); };) -+sw0flows3: table=4 (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=22); };) -+sw0flows3: table=4 (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=22); };) -+sw0flows3: table=4 (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=22); };) -+sw1flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[7]] == 1 && (outport == @pg0 && ip)), action=(reg0[[1]] = 1; next;) -+sw1flows3: table=4 (ls_out_acl ), priority=2001 , match=(reg0[[8]] == 1 && (outport == @pg0 && ip)), action=(next;) -+sw1flows3: table=4 (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=22); };) -+sw1flows3: table=4 (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=22); };) -+sw1flows3: table=4 (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=22); };) -+sw1flows3: table=4 (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=22); };) - ]) - - AT_CLEANUP -@@ -1932,17 +1932,17 @@ check ovn-nbctl --wait=sb \ - -- acl-add ls from-lport 2 "udp" allow-related \ - -- acl-add ls to-lport 2 "udp" allow-related - AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -- table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -- 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=3 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, 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;) -@@ -1951,9 +1951,9 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e - 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;) -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) - ]) - - AS_BOX([Check match ct_state with load balancer]) -@@ -1963,18 +1963,25 @@ check ovn-nbctl --wait=sb \ - -- lb-add lb "10.0.0.1" "10.0.0.2" \ - -- ls-lb-add ls lb - --AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | grep 'ct\.' | sort], [0], [dnl -- table=4 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=4 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -- table=5 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -- 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;) -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl -+ table=3 (ls_out_acl_hint ), priority=0 , match=(1), action=(next;) -+ table=3 (ls_out_acl_hint ), priority=1 , match=(ct.est && ct_label.blocked == 0), action=(reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=2 , match=(ct.est && ct_label.blocked == 1), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=3 , match=(!ct.est), action=(reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=4 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 0), action=(reg0[[8]] = 1; reg0[[10]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=5 , match=(!ct.trk), action=(reg0[[8]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=6 , match=(!ct.new && ct.est && !ct.rpl && ct_label.blocked == 1), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=3 (ls_out_acl_hint ), priority=7 , match=(ct.new && !ct.est), action=(reg0[[7]] = 1; reg0[[9]] = 1; next;) -+ table=4 (ls_out_acl ), priority=0 , match=(1), action=(next;) -+ table=4 (ls_out_acl ), priority=1 , match=(ip && (!ct.est || (ct.est && ct_label.blocked == 1))), action=(reg0[[1]] = 1; next;) -+ table=4 (ls_out_acl ), priority=1001 , match=(reg0[[7]] == 1 && (ip)), action=(reg0[[1]] = 1; next;) -+ table=4 (ls_out_acl ), priority=1001 , match=(reg0[[8]] == 1 && (ip)), action=(next;) -+ table=4 (ls_out_acl ), priority=34000, match=(eth.src == $svc_monitor_mac), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+ table=8 (ls_in_acl_hint ), priority=0 , match=(1), action=(next;) - 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;) -@@ -1982,12 +1989,28 @@ AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e - 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=0 , match=(1), action=(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;) -+ table=9 (ls_in_acl ), priority=1001 , match=(reg0[[7]] == 1 && (ip)), action=(reg0[[1]] = 1; next;) -+ table=9 (ls_in_acl ), priority=1001 , match=(reg0[[8]] == 1 && (ip)), action=(next;) -+ table=9 (ls_in_acl ), priority=34000, match=(eth.dst == $svc_monitor_mac), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+ovn-nbctl --wait=sb clear logical_switch ls acls -+ovn-nbctl --wait=sb clear logical_switch ls load_balancer -+ -+AT_CHECK([ovn-sbctl lflow-list ls | grep -e ls_in_acl_hint -e ls_out_acl_hint -e ls_in_acl -e ls_out_acl | sort], [0], [dnl -+ table=3 (ls_out_acl_hint ), priority=65535, match=(1), action=(next;) -+ table=4 (ls_out_acl ), priority=65535, match=(1), action=(next;) -+ table=8 (ls_in_acl_hint ), priority=65535, match=(1), action=(next;) -+ table=9 (ls_in_acl ), priority=65535, match=(1), action=(next;) - ]) - -+ - AT_CLEANUP - - AT_SETUP([datapath requested-tnl-key]) -@@ -2197,20 +2220,20 @@ check ovn-nbctl \ - 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;) -+ table=13(ls_in_pre_hairpin ), priority=0 , match=(1), action=(next;) -+ table=13(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;) -+ table=14(ls_in_nat_hairpin ), priority=0 , match=(1), action=(next;) -+ table=14(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.est && ct.trk && reg0[[6]] == 1), action=(ct_snat;) -+ table=14(ls_in_nat_hairpin ), priority=100 , match=(ip && ct.new && ct.trk && reg0[[6]] == 1), action=(ct_snat_to_vip; next;) -+ table=14(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;) -+ table=15(ls_in_hairpin ), priority=0 , match=(1), action=(next;) -+ table=15(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 -@@ -2324,6 +2347,13 @@ check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public - - check ovn-nbctl --wait=sb lr-policy-add lr0 10 "ip4.src == 10.0.0.3" reroute 172.168.0.101,172.168.0.102 - -+ovn-nbctl lr-policy-list lr0 > policy-list -+AT_CAPTURE_FILE([policy-list]) -+AT_CHECK([cat policy-list], [0], [dnl -+Routing Policies -+ 10 ip4.src == 10.0.0.3 reroute 172.168.0.101, 172.168.0.102 -+]) -+ - ovn-sbctl dump-flows lr0 > lr0flows3 - AT_CAPTURE_FILE([lr0flows3]) - -@@ -2551,7 +2581,7 @@ wait_row_count nb:Logical_Switch_Port 1 up=false name=lsp1 - - AT_CLEANUP - --AT_SETUP([ovn -- lb_force_snat_ip for Gateway Routers]) -+AT_SETUP([ovn -- Load Balancers and lb_force_snat_ip for Gateway Routers]) - ovn_start - - check ovn-nbctl ls-add sw0 -@@ -2589,11 +2619,11 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl --]) -- -- --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_dnat;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - - check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="20.0.0.4 aef0::4" -@@ -2608,14 +2638,18 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=110 , match=(ip6 && ip6.dst == aef0::4), action=(ct_snat;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) - table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip4), action=(ct_snat(20.0.0.4);) - table=1 (lr_out_snat ), priority=100 , match=(flags.force_snat_for_lb == 1 && ip6), action=(ct_snat(aef0::4);) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) - ]) - - check ovn-nbctl --wait=sb set logical_router lr0 options:lb_force_snat_ip="router_ip" -@@ -2633,15 +2667,19 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) - ]) - - check ovn-nbctl --wait=sb remove logical_router lr0 options chassis -@@ -2653,7 +2691,9 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) - ]) - - check ovn-nbctl set logical_router lr0 options:chassis=ch1 -@@ -2670,16 +2710,43 @@ AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl - table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;) - ]) - --AT_CHECK([grep "lr_in_dnat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_in_dnat" lr0flows | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=0 , match=(1), action=(next;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_dnat;) - table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80), action=(flags.force_snat_for_lb = 1; ct_lb(backends=10.0.0.4:8080);) -+ table=6 (lr_in_dnat ), priority=50 , match=(ip), action=(flags.loopback = 1; ct_dnat;) - ]) - --AT_CHECK([grep "lr_out_snat" lr0flows | grep force_snat_for_lb | sort], [0], [dnl -+AT_CHECK([grep "lr_out_snat" lr0flows | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=0 , match=(1), action=(next;) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-public"), action=(ct_snat(172.168.0.100);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw0"), action=(ct_snat(10.0.0.1);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip4 && outport == "lr0-sw1"), action=(ct_snat(20.0.0.1);) - table=1 (lr_out_snat ), priority=110 , match=(flags.force_snat_for_lb == 1 && ip6 && outport == "lr0-sw1"), action=(ct_snat(bef0::1);) -+ table=1 (lr_out_snat ), priority=120 , match=(nd_ns), action=(next;) -+]) -+ -+check ovn-nbctl --wait=sb lb-add lb2 10.0.0.20:80 10.0.0.40:8080 -+check ovn-nbctl --wait=sb set load_balancer lb2 options:skip_snat=true -+check ovn-nbctl lr-lb-add lr0 lb2 -+check ovn-nbctl --wait=sb lb-del lb1 -+ovn-sbctl dump-flows lr0 > lr0flows -+ -+AT_CHECK([grep "lr_in_unsnat" lr0flows | sort], [0], [dnl -+ table=5 (lr_in_unsnat ), priority=0 , match=(1), action=(next;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-public" && ip4.dst == 172.168.0.100), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw0" && ip4.dst == 10.0.0.1), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip4.dst == 20.0.0.1), action=(ct_snat;) -+ table=5 (lr_in_unsnat ), priority=110 , match=(inport == "lr0-sw1" && ip6.dst == bef0::1), action=(ct_snat;) -+]) -+ -+AT_CHECK([grep "lr_in_dnat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_dnat;) -+ table=6 (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.20 && tcp && tcp.dst == 80), action=(flags.skip_snat_for_lb = 1; ct_lb(backends=10.0.0.40:8080);) -+]) -+ -+AT_CHECK([grep "lr_out_snat" lr0flows | grep skip_snat_for_lb | sort], [0], [dnl -+ table=1 (lr_out_snat ), priority=120 , match=(flags.skip_snat_for_lb == 1 && ip), action=(next;) - ]) - - AT_CLEANUP -@@ -2783,3 +2850,206 @@ wait_row_count FDB 0 - ovn-sbctl list FDB - - AT_CLEANUP -+ -+AT_SETUP([ovn -- LS load balancer 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 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 00:00:00:00:ff:01 -+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 -+ -+check ovn-nbctl --wait=sb sync -+ -+check_stateful_flows() { -+ ovn-sbctl dump-flows sw0 > sw0flows -+ AT_CAPTURE_FILE([sw0flows]) -+ -+ AT_CHECK([grep "ls_in_pre_lb" sw0flows | sort], [0], [dnl -+ table=6 (ls_in_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(ip && inport == "sw0-lr0"), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+ AT_CHECK([grep "ls_in_pre_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_in_pre_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_in_pre_stateful ), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=7 (ls_in_pre_stateful ), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+]) -+ -+ AT_CHECK([grep "ls_in_stateful" sw0flows | sort], [0], [dnl -+ table=12(ls_in_stateful ), priority=0 , match=(1), action=(next;) -+ table=12(ls_in_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+ table=12(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.4:8080);) -+]) -+ -+ AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl -+ table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=100 , match=(ip), action=(reg0[[2]] = 1; next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(ip && outport == "sw0-lr0"), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+ AT_CHECK([grep "ls_out_pre_stateful" sw0flows | sort], [0], [dnl -+ table=2 (ls_out_pre_stateful), priority=0 , match=(1), action=(next;) -+ table=2 (ls_out_pre_stateful), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=2 (ls_out_pre_stateful), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+]) -+ -+ AT_CHECK([grep "ls_out_lb" sw0flows | sort], [0], []) -+ -+ AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_out_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_out_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+]) -+} -+ -+check_stateful_flows -+ -+# Add few ACLs -+check ovn-nbctl --wait=sb acl-add sw0 from-lport 1002 "ip4 && tcp && tcp.dst == 80" allow-related -+check ovn-nbctl --wait=sb acl-add sw0 to-lport 1002 "ip4 && tcp && tcp.src == 80" drop -+ -+check_stateful_flows -+ -+# Remove load balancer from sw0 -+check ovn-nbctl --wait=sb ls-lb-del sw0 lb0 -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep "ls_in_pre_lb" sw0flows | sort], [0], [dnl -+ table=6 (ls_in_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(ip && inport == "sw0-lr0"), action=(next;) -+ table=6 (ls_in_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep "ls_in_pre_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_in_pre_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_in_pre_stateful ), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=7 (ls_in_pre_stateful ), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ table=7 (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+]) -+ -+AT_CHECK([grep "ls_in_stateful" sw0flows | sort], [0], [dnl -+ table=12(ls_in_stateful ), priority=0 , match=(1), action=(next;) -+ table=12(ls_in_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+]) -+ -+AT_CHECK([grep "ls_out_pre_lb" sw0flows | sort], [0], [dnl -+ table=0 (ls_out_pre_lb ), priority=0 , match=(1), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(ip && outport == "sw0-lr0"), action=(next;) -+ table=0 (ls_out_pre_lb ), priority=110 , match=(nd || nd_rs || nd_ra || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep "ls_out_pre_stateful" sw0flows | sort], [0], [dnl -+ table=2 (ls_out_pre_stateful), priority=0 , match=(1), action=(next;) -+ table=2 (ls_out_pre_stateful), priority=100 , match=(reg0[[0]] == 1), action=(ct_next;) -+ table=2 (ls_out_pre_stateful), priority=110 , match=(reg0[[2]] == 1), action=(ct_lb;) -+]) -+ -+AT_CHECK([grep "ls_out_stateful" sw0flows | sort], [0], [dnl -+ table=7 (ls_out_stateful ), priority=0 , match=(1), action=(next;) -+ table=7 (ls_out_stateful ), priority=100 , match=(reg0[[1]] == 1), action=(ct_commit { ct_label.blocked = 0; }; next;) -+]) -+ -+AT_CLEANUP -+]) -+ -+AT_SETUP([ovn -- ct.inv usage]) -+ovn_start -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0p1 -+ -+check ovn-nbctl --wait=sb acl-add sw0 to-lport 1002 ip allow-related -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+# Disable ct.inv usage. -+check ovn-nbctl --wait=sb set NB_Global . options:use_ct_inv_match=false -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=((ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=((ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -c "ct.inv" sw0flows], [1], [dnl -+0 -+]) -+ -+# Enable ct.inv usage. -+check ovn-nbctl --wait=sb set NB_Global . options:use_ct_inv_match=true -+ -+ovn-sbctl dump-flows sw0 > sw0flows -+AT_CAPTURE_FILE([sw0flows]) -+ -+AT_CHECK([grep -w "ls_in_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=9 (ls_in_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=9 (ls_in_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=9 (ls_in_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -w "ls_out_acl" sw0flows | grep 6553 | sort], [0], [dnl -+ table=4 (ls_out_acl ), priority=65532, match=(!ct.est && ct.rel && !ct.new && !ct.inv && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.est && !ct.rel && !ct.new && !ct.inv && ct.rpl && ct_label.blocked == 0), action=(next;) -+ table=4 (ls_out_acl ), priority=65532, match=(ct.inv || (ct.est && ct.rpl && ct_label.blocked == 1)), action=(drop;) -+ table=4 (ls_out_acl ), priority=65532, match=(nd || nd_ra || nd_rs || mldv1 || mldv2), action=(next;) -+]) -+ -+AT_CHECK([grep -c "ct.inv" sw0flows], [0], [dnl -+6 -+]) -+ -+AT_CLEANUP -diff --git a/tests/ovn.at b/tests/ovn.at -index b465784cd..f994088ed 100644 ---- a/tests/ovn.at -+++ b/tests/ovn.at -@@ -9878,15 +9878,12 @@ AT_CHECK([ovn-nbctl --wait=sb sync], [0], [ignore]) - ovn-sbctl dump-flows > sbflows - AT_CAPTURE_FILE([sbflows]) - --reset_pcap_file() { -- local iface=$1 -- local pcap_file=$2 -- check ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \ --options:rxq_pcap=dummy-rx.pcap -- rm -f ${pcap_file}*.pcap -- check ovs-vsctl -- set Interface $iface options:tx_pcap=${pcap_file}-tx.pcap \ --options:rxq_pcap=${pcap_file}-rx.pcap --} -+hv1_gw1_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw1-0) -+hv1_gw2_ofport=$(as hv1 ovs-vsctl --bare --columns ofport find Interface name=ovn-gw2-0) -+ -+OVS_WAIT_UNTIL([ -+ test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw1_ofport,$hv1_gw2_ofport") -+]) - - test_ip_packet() - { -@@ -9932,13 +9929,13 @@ test_ip_packet() - echo $expected > ext1-vif1.expected - exp_gw_ip_garp=ffffffffffff00000201020308060001080006040001000002010203ac100101000000000000ac100101 - echo $exp_gw_ip_garp >> ext1-vif1.expected -- as $active_gw reset_pcap_file br-phys_n1 $active_gw/br-phys_n1 -+ as $active_gw reset_iface_pcap_file br-phys_n1 $active_gw/br-phys_n1 - - if test $backup_vswitchd_dead != 1; then - # Reset the file only if vswitchd in backup gw is alive -- as $backup_gw reset_pcap_file br-phys_n1 $backup_gw/br-phys_n1 -+ as $backup_gw reset_iface_pcap_file br-phys_n1 $backup_gw/br-phys_n1 - fi -- as ext1 reset_pcap_file ext1-vif1 ext1/vif1 -+ as ext1 reset_iface_pcap_file ext1-vif1 ext1/vif1 - - # Resend packet from foo1 to outside1 - check as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet -@@ -9990,6 +9987,10 @@ AT_CHECK( - <1> - ]) - -+OVS_WAIT_UNTIL([ -+ test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=37 | grep -c "active_backup,ofport,members:$hv1_gw2_ofport,$hv1_gw1_ofport") -+]) -+ - test_ip_packet gw2 gw1 0 - - # Get the claim count of both gw1 and gw2. -@@ -10010,6 +10011,12 @@ OVS_WAIT_UNTIL([test $gw1_claim_ct = `cat gw1/ovn-controller.log \ - AT_CHECK([test $gw2_claim_ct = `cat gw2/ovn-controller.log | \ - grep -c "cr-alice: Claiming"`]) - -+OVS_WAIT_UNTIL([ -+ bfd_status=$(as hv1 ovs-vsctl get interface ovn-gw2-0 bfd_status:state) -+ echo "bfd status = $bfd_status" -+ test "$bfd_status" = "down" -+]) -+ - test_ip_packet gw1 gw2 1 - - as gw2 -@@ -11494,6 +11501,59 @@ OVN_CLEANUP([hv1],[hv2]) - - AT_CLEANUP - -+AT_SETUP([ovn -- localport suppress gARP]) -+ovn_start -+ -+net_add n1 -+sim_add hv1 -+as hv1 -+check ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.1 -+ -+check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys -+ -+check ovn-nbctl ls-add ls \ -+ -- lsp-add ls lp \ -+ -- lsp-set-type lp localport \ -+ -- lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1" \ -+ -- lsp-add ls ln \ -+ -- lsp-set-type ln localnet \ -+ -- lsp-set-options ln network_name=phys \ -+ -- lsp-add ls lsp \ -+ -- lsp-set-addresses lsp "00:00:00:00:00:02 10.0.0.2" -+ -+dnl First bind the localport. -+check ovs-vsctl add-port br-int vif1 \ -+ -- set Interface vif1 external-ids:iface-id=lp -+check ovn-nbctl --wait=hv sync -+ -+dnl Then bind the regular vif. -+check ovs-vsctl add-port br-int vif2 \ -+ -- set Interface vif2 external-ids:iface-id=lsp \ -+ options:tx_pcap=hv1/vif2-tx.pcap \ -+ options:rxq_pcap=hv1/vif2-rx.pcap -+ -+wait_for_ports_up lsp -+check ovn-nbctl --wait=hv sync -+ -+dnl Wait for at least two gARPs from lsp (10.0.0.2). -+lsp_garp=ffffffffffff000000000002080600010800060400010000000000020a0000020000000000000a000002 -+OVS_WAIT_UNTIL([ -+ garps=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep ${lsp_garp} -c` -+ test $garps -ge 2 -+]) -+ -+dnl At this point it's safe to assume that ovn-controller skipped sending gARP -+dnl for the localport. Check that there are no other packets than the gARPs -+dnl for the regular vif. -+AT_CHECK([ -+ pkts=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/br-phys-tx.pcap | grep -v ${lsp_garp} -c` -+ test 0 -eq $pkts -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ - AT_SETUP([ovn -- 1 LR with HA distributed router gateway port]) - ovn_start - -@@ -13901,16 +13961,16 @@ 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 | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. -@@ -13945,16 +14005,16 @@ 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 | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Remove the less restrictive allow ACL. -@@ -13962,16 +14022,16 @@ 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 | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. -@@ -14001,16 +14061,16 @@ 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 | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() - ]) - - # Add another ACL that overlaps with the existing less restrictive ones. -@@ -14021,19 +14081,19 @@ check ovn-nbctl --wait=hv sync - # with an additional conjunction action. - # - # New non-conjunctive flows should be added to match on 'udp'. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=1003" | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,46) -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction() -- table=45, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -- table=45, priority=1003,udp,metadata=0x1 actions=resubmit(,46) -- table=45, priority=1003,udp6,metadata=0x1 actions=resubmit(,46) -+ table=44, priority=1003,conj_id=2,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=3,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,conj_id=4,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.3 actions=conjunction(),conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_dst=10.0.0.4 actions=conjunction(),conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.1 actions=resubmit(,45) -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.2 actions=conjunction(),conjunction() -+ table=44, priority=1003,ip,metadata=0x1,nw_src=10.0.0.42 actions=conjunction() -+ table=44, priority=1003,udp,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=1003,udp6,metadata=0x1 actions=resubmit(,45) - ]) - - OVN_CLEANUP([hv1]) -@@ -15375,7 +15435,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=30,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ -+table=29,dl_src=f0:00:00:00:00:03,dl_dst=a0:10:00:00:00:01 | \ - grep -c "actions=drop"], [0], [1 - ]) - -@@ -16647,56 +16707,67 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \ - - ovn-nbctl ls-add sw0 - --ovn-nbctl lsp-add sw0 sw0-vir --ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" --ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" --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 -+check ovn-nbctl lsp-add sw0 sw0-vir -+check ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" -+check ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" -+check ovn-nbctl lsp-set-type sw0-vir virtual -+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-ip=10.0.0.10 -+check ovn-nbctl set logical_switch_port sw0-vir options:virtual-parents=sw0-p1,sw0-p2,sw0-p3 - --ovn-nbctl lsp-add sw0 sw0-p1 --ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03 10.0.0.3" --ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10" -+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" -+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03 10.0.0.3 10.0.0.10" - --ovn-nbctl lsp-add sw0 sw0-p2 --ovn-nbctl lsp-set-addresses sw0-p2 "50:54:00:00:00:04 10.0.0.4" --ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10" -+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" -+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4 10.0.0.10" - --ovn-nbctl lsp-add sw0 sw0-p3 --ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5" --ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10" -+check ovn-nbctl lsp-add sw0 sw0-p3 -+check ovn-nbctl lsp-set-addresses sw0-p3 "50:54:00:00:00:05 10.0.0.5" -+check ovn-nbctl lsp-set-port-security sw0-p3 "50:54:00:00:00:05 10.0.0.5 10.0.0.10" - - # Create the second logical switch with one port --ovn-nbctl ls-add sw1 --ovn-nbctl lsp-add sw1 sw1-p1 --ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3" --ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" -+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 20.0.0.3" -+check ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3" - - # Create a logical router and attach both logical switches --ovn-nbctl lr-add lr0 --ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24 --ovn-nbctl lsp-add sw0 sw0-lr0 --ovn-nbctl lsp-set-type sw0-lr0 router --ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01 --ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 -+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 00:00:00:00:ff:01 -+check ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0 - --ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24 --ovn-nbctl lsp-add sw1 sw1-lr0 --ovn-nbctl lsp-set-type sw1-lr0 router --ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 --ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 -+check ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.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 00:00:00:00:ff:02 -+check ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 - --OVN_POPULATE_ARP -+# Add an ACL that matches on sw0-vir being bound locally. -+check ovn-nbctl acl-add sw0 to-lport 1000 'is_chassis_resident("sw0-vir") && ip' allow - --# Delete sw0-vir and add again. --ovn-nbctl lsp-del sw0-vir -+check ovn-nbctl ls-add public -+check ovn-nbctl lrp-add lr0 lr0-public 00:00:20:20:12:13 172.168.0.100/24 -+check ovn-nbctl lsp-add public public-lr0 -+check ovn-nbctl lsp-set-type public-lr0 router -+check ovn-nbctl lsp-set-addresses public-lr0 router -+check ovn-nbctl lsp-set-options public-lr0 router-port=lr0-public - --ovn-nbctl lsp-add sw0 sw0-vir --ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" --ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" --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 -+# localnet port -+check ovn-nbctl lsp-add public ln-public -+check ovn-nbctl lsp-set-type ln-public localnet -+check ovn-nbctl lsp-set-addresses ln-public unknown -+check ovn-nbctl lsp-set-options ln-public network_name=public -+ -+# schedule the gw router port to a chassis. Change the name of the chassis -+check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 -+ -+check ovn-nbctl lr-nat-add lr0 dnat_and_snat 172.168.0.50 10.0.0.10 sw0-vir 10:54:00:00:00:10 -+ -+OVN_POPULATE_ARP - - wait_for_ports_up - ovn-nbctl --wait=hv sync -@@ -16746,6 +16817,30 @@ ovs-vsctl del-port hv1-vif3 - AT_CHECK([test x$(ovn-sbctl --bare --columns chassis find port_binding \ - logical_port=sw0-vir) = x], [0], []) - -+check_virtual_offlows_present() { -+ hv=$1 -+ -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | grep "priority=2000"], [0], [dnl -+ table=44, priority=2000,ip,metadata=0x1 actions=resubmit(,45) -+ table=44, priority=2000,ipv6,metadata=0x1 actions=resubmit(,45) -+]) -+ -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ -+ grep "priority=92" | grep 172.168.0.50], [0], [dnl -+ table=11, priority=92,arp,reg14=0x3,metadata=0x3,arp_tpa=172.168.0.50,arp_op=1 actions=move:NXM_OF_ETH_SRC[[]]->NXM_OF_ETH_DST[[]],mod_dl_src:10:54:00:00:00:10,load:0x2->NXM_OF_ARP_OP[[]],move:NXM_NX_ARP_SHA[[]]->NXM_NX_ARP_THA[[]],load:0x105400000010->NXM_NX_ARP_SHA[[]],move:NXM_OF_ARP_SPA[[]]->NXM_OF_ARP_TPA[[]],load:0xaca80032->NXM_OF_ARP_SPA[[]],move:NXM_NX_REG14[[]]->NXM_NX_REG15[[]],load:0x1->NXM_NX_REG10[[0]],resubmit(,37) -+]) -+} -+ -+check_virtual_offlows_not_present() { -+ hv=$1 -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | grep "priority=2000"], [1], [dnl -+]) -+ -+ AT_CHECK([as $hv ovs-ofctl dump-flows br-int table=11 | ofctl_strip_all | \ -+ grep "priority=92" | grep 172.168.0.50], [1], [dnl -+]) -+} -+ - # From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir - # and sw0-p1 should be its virtual_parent. - eth_src=505400000003 -@@ -16767,6 +16862,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) - ]) - -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # Forcibly clear virtual_parent. ovn-controller should release the binding - # gracefully. - pb_uuid=$(ovn-sbctl --bare --columns _uuid find port_binding logical_port=sw0-vir) -@@ -16777,6 +16879,13 @@ logical_port=sw0-vir) = x]) - - wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir - -+check ovn-nbctl --wait=hv sync -+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the flow for ACL. -+check_virtual_offlows_not_present hv2 -+ - # From sw0-p0 resend GARP for 10.0.0.10. hv1 should reclaim sw0-vir - # and sw0-p1 should be its virtual_parent. - send_garp 1 1 $eth_src $eth_dst $spa $tpa -@@ -16789,6 +16898,58 @@ logical_port=sw0-vir) = xsw0-p1]) - - wait_for_ports_up sw0-vir - -+check ovn-nbctl --wait=hv sync -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+# Release sw0-p1. -+as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-px -+wait_column "false" nb:Logical_Switch_Port up name=sw0-p1 -+wait_column "false" nb:Logical_Switch_Port up name=sw0-vir -+ -+check ovn-nbctl --wait=hv sync -+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+# Claim sw0-p1 again. -+as hv1 ovs-vsctl set interface hv1-vif1 external-ids:iface-id=sw0-p1 -+wait_for_ports_up sw0-p1 -+ -+# hv1 should not have the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+# From sw0-p0 send GARP for 10.0.0.10. hv1 should claim sw0-vir -+# and sw0-p1 should be its virtual_parent. -+eth_src=505400000003 -+eth_dst=ffffffffffff -+spa=$(ip_to_hex 10 0 0 10) -+tpa=$(ip_to_hex 10 0 0 10) -+send_garp 1 1 $eth_src $eth_dst $spa $tpa -+ -+wait_row_count Port_Binding 1 logical_port=sw0-vir chassis=$hv1_ch_uuid -+check_row_count Port_Binding 1 logical_port=sw0-vir virtual_parent=sw0-p1 -+wait_for_ports_up sw0-vir -+check ovn-nbctl --wait=hv sync -+ -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # From sw0-p3 send GARP for 10.0.0.10. hv1 should claim sw0-vir - # and sw0-p3 should be its virtual_parent. - eth_src=505400000005 -@@ -16806,8 +16967,8 @@ logical_port=sw0-vir) = xsw0-p3]) - wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the --# sw0-p2's MAC. --sleep 1 -+# sw0-p3's MAC. -+check ovn-nbctl --wait=hv sync - ovn-sbctl dump-flows lr0 > lr0-flows3 - AT_CAPTURE_FILE([lr0-flows3]) - cp ovn-sb/ovn-sb.db lr0-flows3.db -@@ -16815,6 +16976,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:05; next;) - ]) - -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir - # and sw0-p2 shpuld be its virtual_parent. - eth_src=505400000004 -@@ -16832,14 +17000,21 @@ logical_port=sw0-vir) = xsw0-p2]) - wait_for_ports_up sw0-vir - - # There should be an arp resolve flow to resolve the virtual_ip with the --# sw0-p3's MAC. --sleep 1 -+# sw0-p2's MAC. -+check ovn-nbctl --wait=hv sync - ovn-sbctl dump-flows lr0 > lr0-flows4 - AT_CAPTURE_FILE([lr0-flows4]) - AT_CHECK([grep lr_in_arp_resolve lr0-flows4 | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) - ]) - -+# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv2 -+ -+# hv1 should not have the above flows. -+check_virtual_offlows_not_present hv1 -+ - # Now send arp reply from sw0-p1. hv1 should claim sw0-vir - # and sw0-p1 shpuld be its virtual_parent. - eth_src=505400000003 -@@ -16863,6 +17038,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows5 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv1 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - # Delete hv1-vif1 port. hv1 should release sw0-vir - as hv1 ovs-vsctl del-port hv1-vif1 - -@@ -16883,6 +17066,15 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv1 -+ -+# hv2 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ -+ - # Now send arp reply from sw0-p2. hv2 should claim sw0-vir - # and sw0-p2 should be its virtual_parent. - eth_src=505400000004 -@@ -16906,6 +17098,14 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows7 | grep "reg0 == 10.0.0.10" | sed 's/ - table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv2 should add the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_present hv2 -+ -+# hv1 should not have the above flows. -+check_virtual_offlows_not_present hv1 -+ - # Delete sw0-p2 logical port - ovn-nbctl lsp-del sw0-p2 - -@@ -16933,6 +17133,14 @@ AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../tabl - table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) - ]) - -+check ovn-nbctl --wait=hv sync -+# hv2 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir and -+# arp responder flow in lr0 pipeline. -+check_virtual_offlows_not_present hv2 -+ -+# hv1 should not have the above flows. -+check_virtual_offlows_not_present hv2 -+ - ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-parents - ovn-sbctl dump-flows sw0 > sw0-flows4 - AT_CAPTURE_FILE([sw0-flows4]) -@@ -16942,6 +17150,38 @@ ovn-sbctl dump-flows lr0 > lr0-flows8 - AT_CAPTURE_FILE([lr0-flows8]) - AT_CHECK([grep lr_in_arp_resolve lr0-flows8 | grep "reg0 == 10.0.0.10"], [1]) - -+# Delete sw0-vir and add again. -+ovn-nbctl lsp-del sw0-vir -+ -+ovn-nbctl lsp-add sw0 sw0-vir -+ovn-nbctl lsp-set-addresses sw0-vir "50:54:00:00:00:10 10.0.0.10" -+ovn-nbctl lsp-set-port-security sw0-vir "50:54:00:00:00:10 10.0.0.10" -+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 -+ -+ovn-nbctl --wait=hv sync -+ -+# Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline -+# with bind_vport action. -+ -+ovn-sbctl dump-flows sw0 > sw0-flows -+AT_CAPTURE_FILE([sw0-flows]) -+ -+AT_CHECK([grep ls_in_arp_rsp sw0-flows | grep bind_vport | sed 's/table=../table=??/' | sort], [0], [dnl -+ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p1" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) -+ table=??(ls_in_arp_rsp ), priority=100 , match=(inport == "sw0-p3" && ((arp.op == 1 && arp.spa == 10.0.0.10 && arp.tpa == 10.0.0.10) || (arp.op == 2 && arp.spa == 10.0.0.10))), action=(bind_vport("sw0-vir", inport); next;) -+]) -+ -+ovn-sbctl dump-flows lr0 > lr0-flows -+AT_CAPTURE_FILE([lr0-flows]) -+ -+# Since the sw0-vir is not claimed by any chassis, eth.dst should be set to -+# zero if the ip4.dst is the virtual ip in the router pipeline. -+AT_CHECK([grep lr_in_arp_resolve lr0-flows | grep "reg0 == 10.0.0.10" | sed 's/table=../table=??/'], [0], [dnl -+ table=??(lr_in_arp_resolve ), priority=100 , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;) -+]) -+ - OVN_CLEANUP([hv1], [hv2]) - AT_CLEANUP - -@@ -17321,6 +17561,27 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \ - ofport-request=1 - ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys - -+AT_CAPTURE_FILE([exp]) -+AT_CAPTURE_FILE([rcv]) -+check_packets() { -+ > exp -+ > rcv -+ if test "$1" = --uniq; then -+ sort="sort -u"; shift -+ else -+ sort=sort -+ fi -+ for tuple in "$@"; do -+ set $tuple; pcap=$1 type=$2 -+ echo "--- $pcap" | tee -a exp >> rcv -+ $sort "$type" >> exp -+ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | $sort >> rcv -+ echo | tee -a exp >> rcv -+ done -+ -+ $at_diff exp rcv >/dev/null -+} -+ - OVN_POPULATE_ARP - - # Enable IGMP snooping on sw1. -@@ -17337,21 +17598,16 @@ ovn-sbctl dump-flows > sbflows - AT_CAPTURE_FILE([expected]) - AT_CAPTURE_FILE([received]) - > expected --> received --for i in 1 2; do -- for j in 1 2; do -- pcap=hv$i/vif$j-tx.pcap -- echo "--- $pcap" | tee -a expected >> received -- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> received -- echo | tee -a expected >> received -- done --done --check $at_diff -F'^---' expected received -+OVS_WAIT_UNTIL( -+ [check_packets 'hv1/vif1-tx.pcap expected' \ -+ 'hv1/vif2-tx.pcap expected' \ -+ 'hv2/vif1-tx.pcap expected' \ -+ 'hv2/vif2-tx.pcap expected'], -+ [$at_diff -F'^---' exp rcv]) - - check ovn-nbctl --wait=hv sync - - AT_CAPTURE_FILE([sbflows2]) --cp ovn-sb/ovn-sb.db ovn-sb2.db - ovn-sbctl dump-flows > sbflows2 - - # Inject IGMP Join for 239.0.1.68 on sw1-p11. -@@ -17369,7 +17625,6 @@ wait_row_count IGMP_Group 2 address=239.0.1.68 - check ovn-nbctl --wait=hv sync - - AT_CAPTURE_FILE([sbflows3]) --cp ovn-sb/ovn-sb.db ovn-sb3.db - ovn-sbctl dump-flows > sbflows3 - - AS_BOX([IGMP traffic test 1]) -@@ -17386,22 +17641,6 @@ store_ip_multicast_pkt \ - $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \ - e518e518000a3b3a0000 expected - --AT_CAPTURE_FILE([exp]) --AT_CAPTURE_FILE([rcv]) --check_packets() { -- > exp -- > rcv -- for tuple in "$@"; do -- set $tuple; pcap=$1 type=$2 -- echo "--- $pcap" | tee -a exp >> rcv -- sort "$type" >> exp -- $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" $pcap | sort >> rcv -- echo | tee -a exp >> rcv -- done -- -- $at_diff exp rcv >/dev/null --} -- - OVS_WAIT_UNTIL( - [check_packets 'hv1/vif1-tx.pcap expected' \ - 'hv2/vif1-tx.pcap expected' \ -@@ -17492,15 +17731,26 @@ check ovn-nbctl set Logical_Switch sw2 \ - 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. -+# Check that multiple queries are generated over time. - > expected - store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected - store_igmp_v3_query 0000000002fe $(ip_to_hex 20 0 0 254) 84dd expected - --OVS_WAIT_UNTIL( -- [check_packets 'hv1/vif3-tx.pcap expected' \ -- 'hv2/vif3-tx.pcap expected'], -- [$at_diff -F'^---' exp rcv]) -+for count in 1 2 3; do -+ as hv1 reset_pcap_file hv1-vif1 hv1/vif1 -+ as hv1 reset_pcap_file hv1-vif2 hv1/vif2 -+ as hv1 reset_pcap_file hv1-vif3 hv1/vif3 -+ as hv1 reset_pcap_file hv1-vif4 hv1/vif4 -+ as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -+ as hv2 reset_pcap_file hv2-vif2 hv2/vif2 -+ as hv2 reset_pcap_file hv2-vif3 hv2/vif3 -+ as hv2 reset_pcap_file hv2-vif4 hv2/vif4 -+ OVS_WAIT_UNTIL( -+ [check_packets --uniq \ -+ 'hv1/vif3-tx.pcap expected' \ -+ 'hv2/vif3-tx.pcap expected'], -+ [$at_diff -F'^---' exp rcv]) -+done - - # Disable IGMP querier on sw2. - check ovn-nbctl set Logical_Switch sw2 \ -@@ -19776,7 +20026,14 @@ 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=(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");) -+ [dnl -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && sctp), action=(reg1 = ip4.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && tcp), action=(reg1 = ip4.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip4 && udp), action=(reg1 = ip4.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && sctp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = sctp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && tcp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = tcp.dst; ct_lb;) -+ (ls_in_pre_stateful ), priority=120 , match=(reg0[[2]] == 1 && ip6 && udp), action=(xxreg1 = ip6.dst; reg2[[0..15]] = udp.dst; ct_lb;) -+ (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]) -@@ -22463,7 +22720,7 @@ check ovn-nbctl --wait=hv sync - # 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 -+# table 44 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", -@@ -22480,7 +22737,7 @@ wait_conj_id_count() { - 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], -+ grep table=44 hv1flows | grep -c conj_id], - [$retval], [$1 - ]) - -@@ -22489,7 +22746,7 @@ wait_conj_id_count() { - 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], -+ [grep table=44 hv1flows | grep "$match" | grep -c conj_id=$id], - [0], [$count - ]) - done -@@ -22514,8 +22771,8 @@ wait_conj_id_count 1 "3 1 udp" - 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")]) -+AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep udp | grep -c "conj_id=3")]) -+AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=44 | grep tcp | grep -c "conj_id=4")]) - - 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 -@@ -24454,43 +24711,43 @@ AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) - check ovn-nbctl --wait=hv sync - - # Check OVS flows are installed properly. --AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=45 | ofctl_strip_all | \ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=44 | ofctl_strip_all | \ - grep "priority=2002" | grep conjunction | \ - sed 's/conjunction([[^)]]*)/conjunction()/g' | sort], [0], [dnl -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -- table=45, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x100/0x100,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x10/0xfff0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x100/0xff00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x1000/0xf000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2/0xfffe actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x20/0xffe0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x200/0xfe00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x2000/0xe000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4/0xfffc actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x40/0xffc0 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x400/0xfc00 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x4000/0xc000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8/0xfff8 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x80/0xff80 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x800/0xf800 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=0x8000/0x8000 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,metadata=0x1,nw_src=192.168.47.3,tp_dst=1 actions=conjunction() -+ table=44, priority=2002,udp,reg0=0x80/0x80,reg15=0x3,metadata=0x1,nw_src=192.168.47.3 actions=conjunction() - ]) - - OVN_CLEANUP([hv1]) -@@ -24918,3 +25175,633 @@ AT_CHECK([cat hv2_offlows_table72.txt | grep -v NXST], [1], [dnl - - OVN_CLEANUP([hv1], [hv2]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- container port changed to normal port and then deleted]) -+ovn_start -+ -+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 vm1 -+ -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont vm1 1 -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont parent_name -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+check ovn-nbctl lsp-del vm-cont -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+ -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+check ovn-nbctl --wait=sb lsp-del vm1 -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl clear logical_switch_port vm-cont2 parent_name -+ -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not crashed. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1 -+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1 -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl --wait=sb lsp-del vm1 -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name -+check ovn-nbctl lsp-del vm-cont1 -+check ovn-nbctl --wait=sb lsp-del vm-cont2 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not crashed. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name -+check ovn-nbctl lsp-del vm-cont1 -+check ovn-nbctl lsp-del vm-cont2 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not crashed. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check ovn-nbctl --wait=sb clear logical_switch_port vm-cont2 parent_name -+ -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+ -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 -+ -+check ovn-nbctl set logical_switch_port vm-cont1 parent_name=vm1 -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm1 -+ -+wait_for_ports_up -+ -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont1 parent_name -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm-cont1 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+wait_column "true" nb:Logical_Switch_Port up name=vm-cont1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 -+ -+check ovn-nbctl --wait=sb set logical_switch_port vm-cont2 parent_name=vm-cont1 -+check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=vm-cont1 -+ -+wait_for_ports_up -+ -+# Delete vm1, vm-cont1 and vm-cont2 and recreate again. -+check ovn-nbctl lsp-del vm1 -+check ovn-nbctl lsp-del vm-cont1 -+check ovn-nbctl --wait=hv lsp-del vm-cont2 -+ -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont1 vm1 1 -+check ovn-nbctl lsp-add ls vm-cont2 vm1 2 -+ -+wait_for_ports_up -+ -+# Make vm1 as a child port of some non existent lport - foo. vm1, vm1-cont1 and -+# vm1-cont2 should be released. -+check ovn-nbctl --wait=sb set logical_switch_port vm1 parent_name=bar -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont1 -+wait_column "false" nb:Logical_Switch_Port up name=vm-cont2 -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- container port changed from one parent to another]) -+ovn_start -+ -+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 vm1 -- set interface vm1 ofport-request=1 -+ovs-vsctl -- add-port br-int vm2 -- set interface vm1 ofport-request=2 -+ -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm1-cont vm1 1 -+check ovn-nbctl lsp-add ls vm2 -+check ovn-nbctl lsp-add ls vm2-cont vm2 2 -+ -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check as hv1 ovs-vsctl set Interface vm2 external_ids:iface-id=vm2 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [0], [dnl -+1 -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl -+1 -+]) -+ -+# change the parent of vm1-cont to vm2. -+as hv1 ovn-appctl -t ovn-controller vlog/set dbg -+check ovn-nbctl --wait=sb set logical_switch_port vm1-cont parent_name=vm2 \ -+-- set logical_switch_port vm1-cont tag_request=3 -+ -+wait_for_ports_up -+ -+check ovn-nbctl --wait=hv sync -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=1], [1], [dnl -+0 -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=2], [0], [dnl -+1 -+]) -+ -+AT_CHECK([as hv1 ovs-ofctl dump-flows br-int table=0 | grep -c dl_vlan=3], [0], [dnl -+1 -+]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+AT_SETUP([ovn -- container port use-after-free test]) -+ovn_start -+ -+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 vm1 -+ -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont vm1 1 -+check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check ovn-nbctl clear logical_switch_port vm-cont parent_name -+check ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+check ovn-nbctl lsp-del vm-cont -+check ovn-nbctl ls-del ls -+check ovn-nbctl ls-add ls -+check ovn-nbctl lsp-add ls vm1 -+check ovn-nbctl lsp-add ls vm-cont vm1 1 -+check ovs-vsctl set Interface vm1 external_ids:iface-id=vm1 -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl clear logical_switch_port vm-cont parent_name -+check ovn-nbctl lsp-del vm-cont -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+check as hv1 ovs-vsctl set Interface vm1 external_ids:iface-id=foo -+ -+ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+wait_column "false" nb:Logical_Switch_Port up name=vm1 -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+# Test that OVS.external_ids:iface-id doesn't affect non-VIF port bindings. -+AT_SETUP([ovn -- Non-VIF ports incremental processing]) -+ovn_start -+ -+net_add n1 -+sim_add hv1 -+as hv1 -+ovs-vsctl add-br br-phys -+ovn_attach n1 br-phys 192.168.0.10 -+ -+check ovn-nbctl ls-add ls1 -- lsp-add ls1 lsp1 -+ -+as hv1 -+check ovs-vsctl \ -+ -- add-port br-int vif1 \ -+ -- set Interface vif1 external_ids:iface-id=lsp1 -+ -+# ovn-controller should bind the interface. -+wait_for_ports_up -+hv_uuid=$(fetch_column Chassis _uuid name=hv1) -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to router, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 router -+check_column "" Port_Binding chassis logical_port=lsp1 -+ -+# Clear port type, ovn-controller should rebind it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 '' -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to localnet, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 localnet -+check_column "" Port_Binding chassis logical_port=lsp1 -+ -+# Clear port type, ovn-controller should rebind it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 '' -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to localport, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 localport -+check_column "" Port_Binding chassis logical_port=lsp1 -+ -+# Clear port type, ovn-controller should rebind it. -+check ovn-nbctl --wait=hv lsp-set-type lsp1 '' -+check_column "$hv_uuid" Port_Binding chassis logical_port=lsp1 -+ -+# Change the port type to localnet and then delete it. -+# ovn-controller should handle this properly. -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl --wait=sb lsp-set-type lsp1 localport -+check ovn-nbctl --wait=sb lsp-del lsp1 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+check ovn-nbctl lsp-add ls1 lsp1 -+wait_for_ports_up -+ -+# Change the port type to virtual and then delete it. -+# ovn-controller should handle this properly. -+check as hv1 ovn-appctl -t ovn-controller debug/pause -+check ovn-nbctl --wait=sb lsp-set-type lsp1 virtual -+check ovn-nbctl --wait=sb lsp-del lsp1 -+check as hv1 ovn-appctl -t ovn-controller debug/resume -+ -+check ovn-nbctl --wait=hv sync -+ -+# Make sure that ovn-controller has not asserted. -+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)]) -+ -+OVN_CLEANUP([hv1]) -+AT_CLEANUP -+ -+# Tests that ovn-controller creates local bindings correctly by running -+# ovn-appctl -t ovn-controller debug/dump-local-bindings. -+# Ideally this test case should have been a unit test case. -+AT_SETUP([ovn -- ovn-controller local bindings]) -+ovn_start -+ -+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-vm1 -+ -+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-vm1 -+ -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl lsp-add sw0 sw0p1 -+check ovn-nbctl lsp-add sw0 sw0p2 -+ -+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1 -+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+# Create an ovs interface in hv1 -+check as hv1 ovs-vsctl add-port br-int hv1-vm2 -- set interface hv1-vm2 external_ids:iface-id=sw1p1 -+check ovn-nbctl --wait=hv sync -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# Create lport sw1p1 -+check ovn-nbctl ls-add sw1 -- lsp-add sw1 sw1p1 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+# Swap sw0p1 and sw0p2. -+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p2 -+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p1 -+ -+check ovn-nbctl --wait=hv sync -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p1]] -+---------------------------------------- -+]) -+ -+# Create child port for sw0p1 -+check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c1 sw0p1 1 -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[2]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+---------------------------------------- -+]) -+ -+# Create another child port for sw0p1 -+check ovn-nbctl --wait=hv lsp-add sw0 sw0p1-c2 sw0p1 2 -+ -+wait_for_ports_up -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv1-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv2-vm1]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+]) -+ -+# Swap sw0p1 and sw0p2 again. -+check as hv1 ovs-vsctl set interface hv1-vm1 external_ids:iface-id=sw0p1 -+check as hv2 ovs-vsctl set interface hv2-vm1 external_ids:iface-id=sw0p2 -+ -+check ovn-nbctl --wait=hv sync -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[3]] -+primary lport : [[sw0p1]] -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+# Make sw0p1 as child port of non existent lport - foo -+check ovn-nbctl --wait=hv set logical_switch_port sw0p1 parent_name=foo -+ -+AT_CHECK([as hv1 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[hv1-vm1]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw1p1]], OVS interface name : [[hv1-vm2]], num binding lports : [[1]] -+primary lport : [[sw1p1]] -+---------------------------------------- -+]) -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+# Change the lport type of sw0p2 to different types and make sure that -+# local bindings are correct. -+ -+hv2_uuid=$(fetch_column Chassis _uuid name=hv2) -+check_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2 -+ -+# Change the port type to router, ovn-controller should release it. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 router -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type to external from router. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 external -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type to localnet from external. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type to localport from localnet. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 localnet -+check_column "" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[0]] -+---------------------------------------- -+]) -+ -+# change the port type back to vif. -+check ovn-nbctl --wait=hv lsp-set-type sw0p2 "" -+wait_column "$hv2_uuid" Port_Binding chassis logical_port=sw0p2 -+ -+AT_CHECK([as hv2 ovn-appctl -t ovn-controller debug/dump-local-bindings], [0], [dnl -+Local bindings: -+name: [[foo]], OVS interface name : [[NULL]], num binding lports : [[1]] -+no primary lport -+child lport[[1]] : [[sw0p1]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p1]], OVS interface name : [[NULL]], num binding lports : [[2]] -+no primary lport -+child lport[[1]] : [[sw0p1-c1]], type : [[CONTAINER]] -+child lport[[2]] : [[sw0p1-c2]], type : [[CONTAINER]] -+---------------------------------------- -+name: [[sw0p2]], OVS interface name : [[hv2-vm1]], num binding lports : [[1]] -+primary lport : [[sw0p2]] -+---------------------------------------- -+]) -+ -+OVN_CLEANUP([hv1], [hv2]) -+AT_CLEANUP -diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index 9819573bb..bd27b01a0 100644 ---- a/tests/system-ovn.at -+++ b/tests/system-ovn.at -@@ -4722,7 +4722,7 @@ OVS_WAIT_UNTIL([ - ]) - - OVS_WAIT_UNTIL([ -- n_pkt=$(ovs-ofctl dump-flows br-int table=45 | grep -v n_packets=0 | \ -+ n_pkt=$(ovs-ofctl dump-flows br-int table=44 | grep -v n_packets=0 | \ - grep controller | grep tp_dst=84 -c) - test $n_pkt -eq 1 - ]) -@@ -5831,3 +5831,131 @@ as - OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d - /.*terminating with signal 15.*/d"]) - AT_CLEANUP -+ -+AT_SETUP([ovn -- No ct_state matches in dp flows when no ACLs in an LS]) -+AT_KEYWORDS([no ct_state match]) -+ovn_start -+ -+OVS_TRAFFIC_VSWITCHD_START() -+ADD_BR([br-int]) -+ -+# Set external-ids in br-int needed for ovn-controller -+ovs-vsctl \ -+ -- set Open_vSwitch . external-ids:system-id=hv1 \ -+ -- set Open_vSwitch . external-ids:ovn-remote=unix:$ovs_base/ovn-sb/ovn-sb.sock \ -+ -- set Open_vSwitch . external-ids:ovn-encap-type=geneve \ -+ -- set Open_vSwitch . external-ids:ovn-encap-ip=169.0.0.1 \ -+ -- set bridge br-int fail-mode=secure other-config:disable-in-band=true -+ -+# Start ovn-controller -+start_daemon ovn-controller -+ -+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" -+check ovn-nbctl lsp-set-port-security sw0-p1 "50:54:00:00:00:03" -+ -+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" -+check ovn-nbctl lsp-set-port-security sw0-p2 "50:54:00:00:00:04 10.0.0.4" -+ -+ -+# Create the second logical switch with one port and configure some ACLs. -+check ovn-nbctl ls-add sw1 -+check ovn-nbctl lsp-add sw1 sw1-p1 -+ -+# Create port group and ACLs for sw1 ports. -+check ovn-nbctl pg-add pg1 sw1-p1 -+check ovn-nbctl acl-add pg1 from-lport 1002 "ip" allow-related -+check ovn-nbctl acl-add pg1 to-lport 1002 "ip" allow-related -+ -+ -+OVN_POPULATE_ARP -+ovn-nbctl --wait=hv sync -+ -+ADD_NAMESPACES(sw0-p1) -+ADD_VETH(sw0-p1, sw0-p1, br-int, "10.0.0.3/24", "50:54:00:00:00:03", \ -+ "10.0.0.1") -+ -+ -+ADD_NAMESPACES(sw0-p2) -+ADD_VETH(sw0-p2, sw0-p2, br-int, "10.0.0.4/24", "50:54:00:00:00:04", \ -+ "10.0.0.1") -+ -+ADD_NAMESPACES(sw1-p1) -+ADD_VETH(sw1-p1, sw1-p1, br-int, "20.0.0.4/24", "30:54:00:00:00:04", \ -+ "20.0.0.1") -+ -+wait_for_ports_up -+ -+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \ -+[0], [dnl -+3 packets transmitted, 3 received, 0% packet loss, time 0ms -+]) -+ -+ovs-appctl dpctl/dump-flows -+ -+# sw1-p1 may send IPv6 traffic. So filter this out. Since sw1-p1 has -+# ACLs configured, the datapath flows for the packets from sw1-p1 will have -+# matches on ct_state and ct_label fields. -+# Since sw0 doesn't have any ACLs, there should be no match on ct fields. -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+# Add an ACL to sw0. -+check ovn-nbctl --wait=hv acl-add sw0 to-lport 1002 ip allow-related -+ -+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \ -+[0], [dnl -+3 packets transmitted, 3 received, 0% packet loss, time 0ms -+]) -+ -+ovs-appctl dpctl/dump-flows -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [0], [ignore]) -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [0], [ignore]) -+ -+# Clear ACL for sw0 -+check ovn-nbctl --wait=hv clear logical_switch sw0 acls -+ -+check ovs-appctl dpctl/del-flows -+ -+check ovn-nbctl --wait=hv sync -+ -+NS_CHECK_EXEC([sw0-p1], [ping -q -c 3 -i 0.3 -w 2 10.0.0.4 | FORMAT_PING], \ -+[0], [dnl -+3 packets transmitted, 3 received, 0% packet loss, time 0ms -+]) -+ -+ovs-appctl dpctl/dump-flows -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_state | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+AT_CHECK([ovs-appctl dpctl/dump-flows | grep ct_label | grep -v ipv6 -c], [1], [dnl -+0 -+]) -+ -+OVS_APP_EXIT_AND_WAIT([ovn-controller]) -+ -+as ovn-sb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as ovn-nb -+OVS_APP_EXIT_AND_WAIT([ovsdb-server]) -+ -+as northd -+OVS_APP_EXIT_AND_WAIT([NORTHD_TYPE]) -+ -+as -+OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -+/connection dropped.*/d"]) -+AT_CLEANUP -diff --git a/utilities/ovn-ctl b/utilities/ovn-ctl -index 967db6d6c..c52c17ee0 100755 ---- a/utilities/ovn-ctl -+++ b/utilities/ovn-ctl -@@ -45,18 +45,12 @@ pidfile_is_running () { - test -e "$pidfile" && [ -s "$pidfile" ] && pid=`cat "$pidfile"` && pid_exists "$pid" - } >/dev/null 2>&1 - --stop_xx_ovsdb() { -- if pidfile_is_running $1; then -- ovn-appctl -t $OVN_RUNDIR/$2 exit -- fi --} -- - stop_nb_ovsdb() { -- stop_xx_ovsdb $DB_NB_PID ovnnb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovnnb_db $DB_NB_PID $OVN_RUNDIR/ovnnb_db.ctl - } - - stop_sb_ovsdb() { -- stop_xx_ovsdb $DB_SB_PID ovnsb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovnsb_db $DB_SB_PID $OVN_RUNDIR/ovnsb_db.ctl - } - - stop_ovsdb () { -@@ -65,11 +59,11 @@ stop_ovsdb () { - } - - stop_ic_nb_ovsdb() { -- stop_xx_ovsdb $DB_IC_NB_PID ovn_ic_nb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn_ic_nb_db $DB_IC_NB_PID $OVN_RUNDIR/ovn_ic_nb_db.ctl - } - - stop_ic_sb_ovsdb() { -- stop_xx_ovsdb $DB_IC_SB_PID ovn_ic_sb_db.ctl -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn_ic_sb_db $DB_IC_SB_PID $OVN_RUNDIR/ovn_ic_sb_db.ctl - } - - stop_ic_ovsdb () { -@@ -590,7 +584,7 @@ stop_ic () { - } - - stop_controller () { -- OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller "$@" -+ OVS_RUNDIR=${OVS_RUNDIR} stop_ovn_daemon ovn-controller "" "" "$@" - } - - stop_controller_vtep () { -diff --git a/utilities/ovn-lib.in b/utilities/ovn-lib.in -index 016815626..301cc5712 100644 ---- a/utilities/ovn-lib.in -+++ b/utilities/ovn-lib.in -@@ -137,10 +137,22 @@ start_ovn_daemon () { - } - - stop_ovn_daemon () { -- if test -e "$ovn_rundir/$1.pid"; then -- if pid=`cat "$ovn_rundir/$1.pid"`; then -+ local pid_file=$2 -+ local ctl_file=$3 -+ local other_args=$4 -+ -+ if [ -z "$pid_file" ]; then -+ pid_file="$ovn_rundir/$1.pid" -+ fi -+ -+ if test -e "$pid_file"; then -+ if pid=`cat "$pid_file"`; then -+ if [ -z "$ctl_file" ]; then -+ ctl_file="$ovn_rundir/$1.$pid.ctl" -+ fi -+ - if pid_exists "$pid" >/dev/null 2>&1; then :; else -- rm -f $ovn_rundir/$1.$pid.ctl $ovn_rundir/$1.$pid -+ rm -f $ctl_file $pid_file - return 0 - fi - -@@ -148,7 +160,7 @@ stop_ovn_daemon () { - actions="TERM .1 .25 .65 1 1 1 1 \ - KILL 1 1 1 2 10 15 30 \ - FAIL" -- version=`ovs-appctl -T 1 -t $ovn_rundir/$1.$pid.ctl version \ -+ version=`ovs-appctl -T 1 -t $ctl_file version \ - | awk 'NR==1{print $NF}'` - - # Use `ovs-appctl exit` only if the running daemon version -@@ -159,20 +171,36 @@ stop_ovn_daemon () { - if version_geq "$version" "2.5.90"; then - actions="$graceful $actions" - fi -+ actiontype="" - for action in $actions; do - if pid_exists "$pid" >/dev/null 2>&1; then :; else -- return 0 -+ # pid does not exist. -+ if [ -n "$actiontype" ]; then -+ return 0 -+ fi -+ # But, does the file exist? We may have had a daemon -+ # segfault with `ovs-appctl exit`. Check one more time -+ # before deciding that the daemon is dead. -+ [ -e "$pid_file" ] && sleep 2 && pid=`cat "$pid_file"` 2>/dev/null -+ if pid_exists "$pid" >/dev/null 2>&1; then :; else -+ return 0 -+ fi - fi - case $action in - EXIT) - action "Exiting $1 ($pid)" \ -- ${bindir}/ovs-appctl -T 1 -t $ovn_rundir/$1.$pid.ctl exit $2 -+ ${bindir}/ovs-appctl -T 1 -t $ctl_file exit $other_args -+ # The above command could have resulted in delayed -+ # daemon segfault. And if a monitor is running, it -+ # would restart the daemon giving it a new pid. - ;; - TERM) - action "Killing $1 ($pid)" kill $pid -+ actiontype="force" - ;; - KILL) - action "Killing $1 ($pid) with SIGKILL" kill -9 $pid -+ actiontype="force" - ;; - FAIL) - log_failure_msg "Killing $1 ($pid) failed" -diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index 2c77f4ba7..6e4a3daf0 100644 ---- a/utilities/ovn-nbctl.c -+++ b/utilities/ovn-nbctl.c -@@ -3866,11 +3866,15 @@ static void - print_routing_policy(const struct nbrec_logical_router_policy *policy, - struct ds *s) - { -- if (policy->nexthop != NULL) { -- char *next_hop = normalize_prefix_str(policy->nexthop); -- ds_put_format(s, "%10"PRId64" %50s %15s %25s", policy->priority, -- policy->match, policy->action, next_hop); -- free(next_hop); -+ if (policy->n_nexthops) { -+ ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority, -+ policy->match, policy->action); -+ for (int i = 0; i < policy->n_nexthops; i++) { -+ char *next_hop = normalize_prefix_str(policy->nexthops[i]); -+ char *fmt = i ? ", %s" : " %25s"; -+ ds_put_format(s, fmt, next_hop); -+ free(next_hop); -+ } - } else { - ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority, - policy->match, policy->action); -diff --git a/utilities/ovndb-servers.ocf b/utilities/ovndb-servers.ocf -index 7351c7d64..eba9c97a1 100755 ---- a/utilities/ovndb-servers.ocf -+++ b/utilities/ovndb-servers.ocf -@@ -259,6 +259,9 @@ ovsdb_server_notify() { - ovn-nbctl -- --id=@conn_uuid create Connection \ - target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" \ - inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid -+ else -+ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn}) -+ ovn-nbctl set connection "${CONN_UID}" target="p${NB_MASTER_PROTO}\:${NB_MASTER_PORT}\:${LISTEN_ON_IP}" - fi - - conn=`ovn-sbctl get SB_global . connections` -@@ -267,6 +270,9 @@ inactivity_probe=$INACTIVE_PROBE -- set NB_Global . connections=@conn_uuid - ovn-sbctl -- --id=@conn_uuid create Connection \ - target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" \ - inactivity_probe=$INACTIVE_PROBE -- set SB_Global . connections=@conn_uuid -+ else -+ CONN_UID=$(sed -e 's/^\[//' -e 's/\]$//' <<< ${conn}) -+ ovn-sbctl set connection "${CONN_UID}" target="p${SB_MASTER_PROTO}\:${SB_MASTER_PORT}\:${LISTEN_ON_IP}" - fi - - else diff --git a/ovn.spec b/ovn.spec index dcb5825..b89f591 100644 --- a/ovn.spec +++ b/ovn.spec @@ -42,8 +42,8 @@ Name: ovn Summary: Open Virtual Network support URL: http://www.openvswitch.org/ -Version: 21.03.0 -Release: 32%{?commit0:.%{date}git%{shortcommit0}}%{?dist} +Version: 21.06.0 +Release: 1%{?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} @@ -73,8 +73,6 @@ Source10: https://openvswitch.org/releases/openvswitch-%{ovsver}.tar.gz # ovn-patches -Patch01: ovn-21.03.0.patch - # OpenvSwitch backports (400-) if required. # Address crpto policy for fedora %if 0%{?fedora} @@ -441,6 +439,9 @@ fi %{_unitdir}/ovn-controller-vtep.service %changelog +* Fri Jun 25 2021 Numan Siddique - 21.06.0-1 +- Synced with the upstream release v21.06.0 + * Wed May 12 2021 Numan Siddique - 21.03.0-32 - Synced with the RHEL FDP ovn-2021 - Backports since 21.03.0-2 are: diff --git a/sources b/sources index 3db51bc..adeff4a 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (ovn-21.03.0.tar.gz) = b704efc0d9a7b5ef6c83e7fcf88e79f4316bb54bb36a87cb5bb83f36e6970ca3ccf53ae0b67b87eb2f2fd473dac00c6083b7278b34c021f0a71bb9a216a07ffc -SHA512 (openvswitch-2.15.90.tar.gz) = 661c85570115d6d7e2c2cafee588706ee02ecd8afae3ca589eab3eb993522ae35b8a032bafef4da826b97e58c42daedbf9b33562552954e940b5b34d14bd58b0 +SHA512 (ovn-21.06.0.tar.gz) = 2ac0197abfff832c167baffb6971c9897b04d85f440a6ad1e09fc4b78a1dda70ec6638a9396ffb0c8dd44ffe55242de868950cbcd1b6a850f172b424e043ec81 +SHA512 (openvswitch-2.15.90.tar.gz) = 4c28207ac0388b2fa83ebe48725bb8c74dcc6e55c569445179946e6040e45265455930bd4ea35a718db19581e194d56a1542af1a4e189af98f47bbfb57b7c5c7