From 83e4bc25561e928f516fe4206f87b4f8daed2c73 Mon Sep 17 00:00:00 2001 From: yatinkarel Date: Aug 09 2021 06:19:47 +0000 Subject: Import ovn2.13-20.12.0-161 From Fast DataPath --- diff --git a/.ovn.metadata b/.ovn.metadata index 1121eb0..f7a0528 100644 --- a/.ovn.metadata +++ b/.ovn.metadata @@ -4,4 +4,4 @@ f56373e54eec629b9d6e88e8b1c0c880bd498809 SOURCES/ovn-20.12.0.tar.gz d34f96421a86004aa5d26ecf975edefd09f948b1 SOURCES/Pygments-1.4.tar.gz 6beb30f18ffac3de7689b7fd63e9a8a7d9c8df3a SOURCES/Sphinx-1.1.3.tar.gz -b7cb5bddcefce929e60e4533da84d13dc8ce4fd0 SOURCES/openvswitch-ac85cdb.tar.gz +90c634ea30bd1f8c09eb1e65dea10b5ce4dbb3fb SOURCES/openvswitch-e6ad4d8.tar.gz diff --git a/SOURCES/ovn-20.12.0.patch b/SOURCES/ovn-20.12.0.patch index be57187..f4bdf0b 100644 --- a/SOURCES/ovn-20.12.0.patch +++ b/SOURCES/ovn-20.12.0.patch @@ -484,10 +484,10 @@ index 45e1bdd36..2f6c50890 100644 man_MANS += controller/ovn-controller.8 EXTRA_DIST += controller/ovn-controller.8.xml diff --git a/controller/binding.c b/controller/binding.c -index cb60c5d67..31f3a210f 100644 +index cb60c5d67..6857cde3a 100644 --- a/controller/binding.c +++ b/controller/binding.c -@@ -16,6 +16,7 @@ +@@ -16,11 +16,13 @@ #include #include "binding.h" #include "ha-chassis.h" @@ -495,7 +495,13 @@ index cb60c5d67..31f3a210f 100644 #include "lflow.h" #include "lport.h" #include "patch.h" -@@ -34,6 +35,12 @@ + + #include "lib/bitmap.h" ++#include "lib/hmapx.h" + #include "openvswitch/poll-loop.h" + #include "lib/sset.h" + #include "lib/util.h" +@@ -34,6 +36,12 @@ VLOG_DEFINE_THIS_MODULE(binding); @@ -508,7 +514,34 @@ index cb60c5d67..31f3a210f 100644 #define OVN_QOS_TYPE "linux-htb" struct qos_queue { -@@ -564,6 +571,23 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, +@@ -101,6 +109,7 @@ add_local_datapath__(struct ovsdb_idl_index *sbrec_datapath_binding_by_key, + hmap_insert(local_datapaths, &ld->hmap_node, dp_key); + ld->datapath = datapath; + ld->localnet_port = NULL; ++ shash_init(&ld->external_ports); + ld->has_local_l3gateway = has_local_l3gateway; + + if (tracked_datapaths) { +@@ -467,6 +476,18 @@ is_network_plugged(const struct sbrec_port_binding *binding_rec, + return network ? !!shash_find_data(bridge_mappings, network) : false; + } + ++static void ++update_ld_external_ports(const struct sbrec_port_binding *binding_rec, ++ struct hmap *local_datapaths) ++{ ++ struct local_datapath *ld = get_local_datapath( ++ local_datapaths, binding_rec->datapath->tunnel_key); ++ if (ld) { ++ shash_replace(&ld->external_ports, binding_rec->logical_port, ++ binding_rec); ++ } ++} ++ + static void + update_ld_localnet_port(const struct sbrec_port_binding *binding_rec, + struct shash *bridge_mappings, +@@ -564,6 +585,23 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, } } @@ -532,7 +565,7 @@ index cb60c5d67..31f3a210f 100644 /* 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 -@@ -575,126 +599,270 @@ remove_local_lport_ids(const struct sbrec_port_binding *pb, +@@ -575,126 +613,270 @@ 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. * @@ -584,11 +617,7 @@ index cb60c5d67..31f3a210f 100644 * An object of 'struct local_binding' is created: - * - For each interface that has iface-id configured with the type - BT_VIF. + * - For each interface that has external_ids:iface-id configured. - * -- * - 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 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. @@ -601,7 +630,11 @@ index cb60c5d67..31f3a210f 100644 + +/* This structure represents a logical port (or port binding) + * which is associated with 'struct local_binding'. -+ * + * +- * - 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. + * 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 @@ -754,14 +787,14 @@ index cb60c5d67..31f3a210f 100644 + + if (!lbinding) { + return true; - } - -- shash_destroy(local_bindings); ++ } ++ + if (lbinding->iface && smap_get_bool(&lbinding->iface->external_ids, + OVN_INSTALLED_EXT_ID, false)) { + return false; -+ } -+ + } + +- shash_destroy(local_bindings); + if (b_lport && b_lport->pb->n_up && b_lport->pb->up[0]) { + return false; + } @@ -892,7 +925,7 @@ index cb60c5d67..31f3a210f 100644 } static bool -@@ -703,12 +871,6 @@ is_lport_vif(const struct sbrec_port_binding *pb) +@@ -703,12 +885,6 @@ is_lport_vif(const struct sbrec_port_binding *pb) return !pb->type[0]; } @@ -905,7 +938,7 @@ index cb60c5d67..31f3a210f 100644 static struct tracked_binding_datapath * tracked_binding_datapath_create(const struct sbrec_datapath_binding *dp, bool is_new, -@@ -777,26 +939,13 @@ binding_tracked_dp_destroy(struct hmap *tracked_datapaths) +@@ -777,26 +953,13 @@ binding_tracked_dp_destroy(struct hmap *tracked_datapaths) hmap_destroy(tracked_datapaths); } @@ -935,7 +968,7 @@ index cb60c5d67..31f3a210f 100644 return LP_VIF; } else if (!strcmp(pb->type, "patch")) { return LP_PATCH; -@@ -823,15 +972,88 @@ get_lport_type(const struct sbrec_port_binding *pb) +@@ -823,15 +986,88 @@ get_lport_type(const struct sbrec_port_binding *pb) return LP_UNKNOWN; } @@ -1025,7 +1058,7 @@ index cb60c5d67..31f3a210f 100644 if (pb->chassis != chassis_rec) { if (sb_readonly) { return false; -@@ -877,7 +1099,7 @@ claim_lport(const struct sbrec_port_binding *pb, +@@ -877,7 +1113,7 @@ claim_lport(const struct sbrec_port_binding *pb, */ static bool release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, @@ -1034,7 +1067,7 @@ index cb60c5d67..31f3a210f 100644 { if (pb->encap) { if (sb_readonly) { -@@ -901,6 +1123,7 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, +@@ -901,6 +1137,7 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, } update_lport_tracking(pb, tracked_datapaths); @@ -1042,7 +1075,7 @@ index cb60c5d67..31f3a210f 100644 VLOG_INFO("Releasing lport %s from this chassis.", pb->logical_port); return true; } -@@ -908,14 +1131,15 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, +@@ -908,14 +1145,15 @@ release_lport(const struct sbrec_port_binding *pb, bool sb_readonly, static bool is_lbinding_set(struct local_binding *lbinding) { @@ -1062,7 +1095,7 @@ index cb60c5d67..31f3a210f 100644 } static bool -@@ -927,15 +1151,14 @@ can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec, +@@ -927,15 +1165,14 @@ can_bind_on_this_chassis(const struct sbrec_chassis *chassis_rec, || !strcmp(requested_chassis, chassis_rec->hostname); } @@ -1082,7 +1115,7 @@ index cb60c5d67..31f3a210f 100644 return true; } } -@@ -944,63 +1167,44 @@ is_lbinding_container_parent(struct local_binding *lbinding) +@@ -944,63 +1181,44 @@ is_lbinding_container_parent(struct local_binding *lbinding) } static bool @@ -1167,7 +1200,7 @@ index cb60c5d67..31f3a210f 100644 return false; } -@@ -1012,7 +1216,7 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, +@@ -1012,7 +1230,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); @@ -1176,7 +1209,7 @@ index cb60c5d67..31f3a210f 100644 get_qos_params(pb, qos_map); } } else { -@@ -1031,7 +1235,8 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, +@@ -1031,7 +1249,8 @@ consider_vif_lport_(const struct sbrec_port_binding *pb, /* Release the lport if there is no lbinding. */ if (!lbinding_set || !can_bind) { return release_lport(pb, !b_ctx_in->ovnsb_idl_txn, @@ -1186,7 +1219,7 @@ index cb60c5d67..31f3a210f 100644 } } -@@ -1050,16 +1255,19 @@ consider_vif_lport(const struct sbrec_port_binding *pb, +@@ -1050,16 +1269,19 @@ consider_vif_lport(const struct sbrec_port_binding *pb, vif_chassis); if (!lbinding) { @@ -1209,7 +1242,7 @@ index cb60c5d67..31f3a210f 100644 } static bool -@@ -1068,9 +1276,9 @@ consider_container_lport(const struct sbrec_port_binding *pb, +@@ -1068,9 +1290,9 @@ consider_container_lport(const struct sbrec_port_binding *pb, struct binding_ctx_out *b_ctx_out, struct hmap *qos_map) { @@ -1221,7 +1254,7 @@ index cb60c5d67..31f3a210f 100644 if (!parent_lbinding) { /* There is no local_binding for parent port. Create it -@@ -1085,54 +1293,62 @@ consider_container_lport(const struct sbrec_port_binding *pb, +@@ -1085,54 +1307,62 @@ consider_container_lport(const struct sbrec_port_binding *pb, * we want the these container ports also be claimed by the * chassis. * */ @@ -1313,7 +1346,7 @@ index cb60c5d67..31f3a210f 100644 } static bool -@@ -1141,46 +1357,58 @@ consider_virtual_lport(const struct sbrec_port_binding *pb, +@@ -1141,46 +1371,58 @@ consider_virtual_lport(const struct sbrec_port_binding *pb, struct binding_ctx_out *b_ctx_out, struct hmap *qos_map) { @@ -1401,7 +1434,7 @@ index cb60c5d67..31f3a210f 100644 } /* Considers either claiming the lport or releasing the lport -@@ -1203,12 +1431,14 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb, +@@ -1203,12 +1445,14 @@ consider_nonvif_lport_(const struct sbrec_port_binding *pb, b_ctx_out->tracked_dp_bindings); update_local_lport_ids(pb, b_ctx_out); @@ -1420,7 +1453,7 @@ index cb60c5d67..31f3a210f 100644 } return true; -@@ -1321,6 +1551,8 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, +@@ -1321,6 +1565,8 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, continue; } @@ -1429,7 +1462,7 @@ index cb60c5d67..31f3a210f 100644 for (j = 0; j < port_rec->n_interfaces; j++) { const struct ovsrec_interface *iface_rec; -@@ -1330,11 +1562,10 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, +@@ -1330,11 +1576,10 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, if (iface_id && ofport > 0) { struct local_binding *lbinding = @@ -1444,7 +1477,7 @@ index cb60c5d67..31f3a210f 100644 } else { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); -@@ -1345,7 +1576,6 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, +@@ -1345,7 +1590,6 @@ build_local_bindings(struct binding_ctx_in *b_ctx_in, "configuration on interface [%s]", lbinding->iface->name, iface_rec->name, iface_rec->name); @@ -1452,7 +1485,18 @@ index cb60c5d67..31f3a210f 100644 } update_local_lports(iface_id, b_ctx_out); -@@ -1408,11 +1638,11 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) +@@ -1384,8 +1628,9 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) + !sset_is_empty(b_ctx_out->egress_ifaces) ? &qos_map : NULL; + + struct ovs_list localnet_lports = OVS_LIST_INITIALIZER(&localnet_lports); ++ struct ovs_list external_lports = OVS_LIST_INITIALIZER(&external_lports); + +- struct localnet_lport { ++ struct lport { + struct ovs_list list_node; + const struct sbrec_port_binding *pb; + }; +@@ -1408,11 +1653,11 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) break; case LP_VIF: @@ -1469,7 +1513,57 @@ index cb60c5d67..31f3a210f 100644 break; case LP_VIRTUAL: -@@ -1713,39 +1943,44 @@ consider_iface_claim(const struct ovsrec_interface *iface_rec, +@@ -1433,11 +1678,14 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) + + case LP_EXTERNAL: + consider_external_lport(pb, b_ctx_in, b_ctx_out); ++ struct lport *ext_lport = xmalloc(sizeof *ext_lport); ++ ext_lport->pb = pb; ++ ovs_list_push_back(&external_lports, &ext_lport->list_node); + break; + + case LP_LOCALNET: { + consider_localnet_lport(pb, b_ctx_in, b_ctx_out, &qos_map); +- struct localnet_lport *lnet_lport = xmalloc(sizeof *lnet_lport); ++ struct lport *lnet_lport = xmalloc(sizeof *lnet_lport); + lnet_lport->pb = pb; + ovs_list_push_back(&localnet_lports, &lnet_lport->list_node); + break; +@@ -1464,7 +1712,7 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) + /* Run through each localnet lport list to see if it is a localnet port + * on local datapaths discovered from above loop, and update the + * corresponding local datapath accordingly. */ +- struct localnet_lport *lnet_lport; ++ struct lport *lnet_lport; + LIST_FOR_EACH_POP (lnet_lport, list_node, &localnet_lports) { + update_ld_localnet_port(lnet_lport->pb, &bridge_mappings, + b_ctx_out->egress_ifaces, +@@ -1472,6 +1720,15 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) + free(lnet_lport); + } + ++ /* Run through external lport list to see if these are external ports ++ * on local datapaths discovered from above loop, and update the ++ * corresponding local datapath accordingly. */ ++ struct lport *ext_lport; ++ LIST_FOR_EACH_POP (ext_lport, list_node, &external_lports) { ++ update_ld_external_ports(ext_lport->pb, b_ctx_out->local_datapaths); ++ free(ext_lport); ++ } ++ + shash_destroy(&bridge_mappings); + + if (!sset_is_empty(b_ctx_out->egress_ifaces) +@@ -1674,6 +1931,8 @@ remove_pb_from_local_datapath(const struct sbrec_port_binding *pb, + pb->logical_port)) { + ld->localnet_port = NULL; + } ++ } else if (!strcmp(pb->type, "external")) { ++ shash_find_and_delete(&ld->external_ports, pb->logical_port); + } + + if (!strcmp(pb->type, "l3gateway")) { +@@ -1713,39 +1972,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); @@ -1534,7 +1628,7 @@ index cb60c5d67..31f3a210f 100644 qos_map)) { return false; } -@@ -1776,32 +2011,43 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, +@@ -1776,32 +2040,43 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, struct binding_ctx_out *b_ctx_out) { struct local_binding *lbinding; @@ -1593,7 +1687,7 @@ index cb60c5d67..31f3a210f 100644 } remove_local_lports(iface_id, b_ctx_out); -@@ -2002,56 +2248,35 @@ handle_deleted_lport(const struct sbrec_port_binding *pb, +@@ -2002,56 +2277,35 @@ handle_deleted_lport(const struct sbrec_port_binding *pb, } } @@ -1672,7 +1766,7 @@ index cb60c5d67..31f3a210f 100644 } } -@@ -2061,7 +2286,7 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb, +@@ -2061,7 +2315,7 @@ 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. */ @@ -1681,7 +1775,7 @@ index cb60c5d67..31f3a210f 100644 remove_local_lports(pb->logical_port, b_ctx_out); } -@@ -2081,7 +2306,7 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, +@@ -2081,7 +2335,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); @@ -1690,7 +1784,7 @@ index cb60c5d67..31f3a210f 100644 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); -@@ -2093,14 +2318,14 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, +@@ -2093,14 +2347,14 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec); @@ -1709,7 +1803,7 @@ index cb60c5d67..31f3a210f 100644 /* If the ovs port backing this binding previously was removed in the * meantime, we won't have a local_binding for it. -@@ -2110,12 +2335,11 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, +@@ -2110,12 +2364,11 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, return true; } @@ -1727,7 +1821,7 @@ index cb60c5d67..31f3a210f 100644 if (!handled) { return false; } -@@ -2132,13 +2356,26 @@ bool +@@ -2132,13 +2385,26 @@ bool binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) { @@ -1759,7 +1853,7 @@ index cb60c5d67..31f3a210f 100644 SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, b_ctx_in->port_binding_table) { if (!sbrec_port_binding_is_deleted(pb)) { -@@ -2146,18 +2383,73 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, +@@ -2146,18 +2412,76 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, } enum en_lport_type lport_type = get_lport_type(pb); @@ -1791,6 +1885,9 @@ index cb60c5d67..31f3a210f 100644 } else { - handle_deleted_lport(pb, b_ctx_in, b_ctx_out); + shash_add(&deleted_other_pbs, pb->logical_port, pb); ++ if (lport_type == LP_EXTERNAL) { ++ hmapx_add(b_ctx_out->extport_updated_datapaths, pb->datapath); ++ } } + } @@ -1803,9 +1900,9 @@ index cb60c5d67..31f3a210f 100644 if (!handled) { - break; + goto delete_done; - } - } - ++ } ++ } ++ + SHASH_FOR_EACH_SAFE (node, node_next, &deleted_virtual_pbs) { + handled = handle_deleted_vif_lport(node->data, LP_VIRTUAL, b_ctx_in, + b_ctx_out); @@ -1821,9 +1918,9 @@ index cb60c5d67..31f3a210f 100644 + shash_delete(&deleted_vif_pbs, node); + if (!handled) { + goto delete_done; -+ } -+ } -+ + } + } + + SHASH_FOR_EACH_SAFE (node, node_next, &deleted_other_pbs) { + handle_deleted_lport(node->data, b_ctx_in, b_ctx_out); + shash_delete(&deleted_other_pbs, node); @@ -1838,7 +1935,7 @@ index cb60c5d67..31f3a210f 100644 if (!handled) { return false; } -@@ -2175,12 +2467,33 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, +@@ -2175,12 +2499,33 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, enum en_lport_type lport_type = get_lport_type(pb); @@ -1872,7 +1969,16 @@ index cb60c5d67..31f3a210f 100644 case LP_VIRTUAL: handled = handle_updated_vif_lport(pb, lport_type, b_ctx_in, b_ctx_out, qos_map_ptr); -@@ -2288,3 +2601,328 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, +@@ -2248,6 +2593,8 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, + + case LP_EXTERNAL: + handled = consider_external_lport(pb, b_ctx_in, b_ctx_out); ++ update_ld_external_ports(pb, b_ctx_out->local_datapaths); ++ hmapx_add(b_ctx_out->extport_updated_datapaths, pb->datapath); + break; + + case LP_LOCALNET: { +@@ -2288,3 +2635,328 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, destroy_qos_map(&qos_map); return handled; } @@ -2202,7 +2308,7 @@ index cb60c5d67..31f3a210f 100644 + return b_lport; +} diff --git a/controller/binding.h b/controller/binding.h -index c9740560f..7a6495320 100644 +index c9740560f..7b5e7e5d5 100644 --- a/controller/binding.h +++ b/controller/binding.h @@ -36,6 +36,8 @@ struct sbrec_chassis; @@ -2223,7 +2329,7 @@ index c9740560f..7a6495320 100644 /* sset of (potential) local lports. */ struct sset *local_lports; -@@ -84,29 +86,26 @@ struct binding_ctx_out { +@@ -84,29 +86,28 @@ struct binding_ctx_out { * binding_handle_port_binding_changes) fills in for * the changed datapaths and port bindings. */ struct hmap *tracked_dp_bindings; @@ -2233,15 +2339,17 @@ index c9740560f..7a6495320 100644 - BT_VIF, - BT_CONTAINER, - BT_VIRTUAL +-}; + struct if_status_mgr *if_mgr; - }; -struct local_binding { - char *name; - enum local_binding_type type; - const struct ovsrec_interface *iface; - const struct sbrec_port_binding *pb; -- ++ struct hmapx *extport_updated_datapaths; ++}; + - /* shash of 'struct local_binding' representing children. */ - struct shash children; +struct local_binding_data { @@ -2268,7 +2376,7 @@ index c9740560f..7a6495320 100644 /* Represents a tracked binding logical port. */ struct tracked_binding_lport { -@@ -127,11 +126,11 @@ bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, +@@ -127,11 +128,11 @@ bool binding_cleanup(struct ovsdb_idl_txn *ovnsb_idl_txn, const struct sbrec_port_binding_table *, const struct sbrec_chassis *); @@ -5366,10 +5474,10 @@ index 29833c7c7..c407f8984 100644

diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c -index 366fc9c06..b4eee4848 100644 +index 366fc9c06..110da4479 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c -@@ -33,12 +33,16 @@ +@@ -33,15 +33,20 @@ #include "openvswitch/dynamic-string.h" #include "encaps.h" #include "fatal-signal.h" @@ -5386,7 +5494,11 @@ index 366fc9c06..b4eee4848 100644 #include "openvswitch/vconn.h" #include "openvswitch/vlog.h" #include "ovn/actions.h" -@@ -54,6 +58,7 @@ ++#include "ovn/features.h" + #include "lib/chassis-index.h" + #include "lib/extend-table.h" + #include "lib/ip-mcast-index.h" +@@ -54,6 +59,7 @@ #include "openvswitch/poll-loop.h" #include "lib/bitmap.h" #include "lib/hash.h" @@ -5394,7 +5506,7 @@ index 366fc9c06..b4eee4848 100644 #include "smap.h" #include "sset.h" #include "stream-ssl.h" -@@ -77,7 +82,9 @@ static unixctl_cb_func cluster_state_reset_cmd; +@@ -77,10 +83,13 @@ 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; @@ -5405,7 +5517,11 @@ index 366fc9c06..b4eee4848 100644 static unixctl_cb_func debug_delay_nb_cfg_report; #define DEFAULT_BRIDGE_NAME "br-int" -@@ -91,6 +98,15 @@ static unixctl_cb_func debug_delay_nb_cfg_report; ++#define DEFAULT_DATAPATH "system" + #define DEFAULT_PROBE_INTERVAL_MSEC 5000 + #define OFCTRL_DEFAULT_PROBE_INTERVAL_SEC 0 + +@@ -91,6 +100,15 @@ static unixctl_cb_func debug_delay_nb_cfg_report; static char *parse_options(int argc, char *argv[]); OVS_NO_RETURN static void usage(void); @@ -5421,7 +5537,7 @@ index 366fc9c06..b4eee4848 100644 /* Pending packet to be injected into connected OVS. */ struct pending_pkt { /* Setting 'conn' indicates that a request is pending. */ -@@ -98,6 +114,9 @@ struct pending_pkt { +@@ -98,6 +116,9 @@ struct pending_pkt { char *flow_s; }; @@ -5431,7 +5547,7 @@ index 366fc9c06..b4eee4848 100644 struct local_datapath * get_local_datapath(const struct hmap *local_datapaths, uint32_t tunnel_key) { -@@ -242,23 +261,15 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, +@@ -242,23 +263,15 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl, uuid); } @@ -5464,18 +5580,128 @@ index 366fc9c06..b4eee4848 100644 } out:; -@@ -404,6 +415,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'."); +@@ -303,10 +316,6 @@ static const struct ovsrec_bridge * + create_br_int(struct ovsdb_idl_txn *ovs_idl_txn, + const struct ovsrec_open_vswitch_table *ovs_table) + { +- if (!ovs_idl_txn) { +- return NULL; +- } +- + const struct ovsrec_open_vswitch *cfg; + cfg = ovsrec_open_vswitch_table_first(ovs_table); + if (!cfg) { +@@ -370,6 +379,21 @@ create_br_int(struct ovsdb_idl_txn *ovs_idl_txn, + return bridge; + } + ++static const struct ovsrec_datapath * ++create_br_datapath(struct ovsdb_idl_txn *ovs_idl_txn, ++ const struct ovsrec_open_vswitch *cfg, ++ const char *datapath_type) ++{ ++ ovsdb_idl_txn_add_comment(ovs_idl_txn, ++ "ovn-controller: creating bridge datapath '%s'", ++ datapath_type); ++ ++ struct ovsrec_datapath *dp = ovsrec_datapath_insert(ovs_idl_txn); ++ ovsrec_open_vswitch_verify_datapaths(cfg); ++ ovsrec_open_vswitch_update_datapaths_setkey(cfg, datapath_type, dp); ++ return dp; ++} ++ + static const struct ovsrec_bridge * + get_br_int(const struct ovsrec_bridge_table *bridge_table, + const struct ovsrec_open_vswitch_table *ovs_table) +@@ -383,29 +407,69 @@ get_br_int(const struct ovsrec_bridge_table *bridge_table, + return get_bridge(bridge_table, br_int_name(cfg)); + } + +-static const struct ovsrec_bridge * ++static const struct ovsrec_datapath * ++get_br_datapath(const struct ovsrec_open_vswitch *cfg, ++ const char *datapath_type) ++{ ++ for (size_t i = 0; i < cfg->n_datapaths; i++) { ++ if (!strcmp(cfg->key_datapaths[i], datapath_type)) { ++ return cfg->value_datapaths[i]; ++ } ++ } ++ return NULL; ++} ++ ++static void + process_br_int(struct ovsdb_idl_txn *ovs_idl_txn, + const struct ovsrec_bridge_table *bridge_table, +- const struct ovsrec_open_vswitch_table *ovs_table) ++ const struct ovsrec_open_vswitch_table *ovs_table, ++ const struct ovsrec_bridge **br_int_, ++ const struct ovsrec_datapath **br_int_dp_) + { +- const struct ovsrec_bridge *br_int = get_br_int(bridge_table, +- ovs_table); +- if (!br_int) { +- br_int = create_br_int(ovs_idl_txn, ovs_table); +- } +- if (br_int && ovs_idl_txn) { +- const struct ovsrec_open_vswitch *cfg; +- cfg = ovsrec_open_vswitch_table_first(ovs_table); +- ovs_assert(cfg); +- const char *datapath_type = smap_get(&cfg->external_ids, +- "ovn-bridge-datapath-type"); +- /* Check for the datapath_type and set it only if it is defined in +- * cfg. */ +- if (datapath_type && strcmp(br_int->datapath_type, datapath_type)) { +- ovsrec_bridge_set_datapath_type(br_int, datapath_type); ++ const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); ++ const struct ovsrec_datapath *br_int_dp = NULL; ++ ++ ovs_assert(br_int_ && br_int_dp_); ++ if (ovs_idl_txn) { ++ if (!br_int) { ++ br_int = create_br_int(ovs_idl_txn, ovs_table); + } ++ ++ if (br_int) { ++ const struct ovsrec_open_vswitch *cfg = ++ ovsrec_open_vswitch_table_first(ovs_table); ++ ovs_assert(cfg); ++ ++ /* Propagate "ovn-bridge-datapath-type" from OVS table, if any. ++ * Otherwise use the datapath-type set in br-int, if any. ++ * Finally, assume "system" datapath if none configured. ++ */ ++ const char *datapath_type = ++ smap_get(&cfg->external_ids, "ovn-bridge-datapath-type"); ++ ++ if (!datapath_type) { ++ if (br_int->datapath_type[0]) { ++ datapath_type = br_int->datapath_type; ++ } else { ++ datapath_type = DEFAULT_DATAPATH; ++ } ++ } ++ if (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'."); ++ } ++ br_int_dp = get_br_datapath(cfg, datapath_type); ++ if (!br_int_dp) { ++ br_int_dp = create_br_datapath(ovs_idl_txn, cfg, ++ datapath_type); ++ } + } } - return br_int; +- return br_int; ++ *br_int_ = br_int; ++ *br_int_dp_ = br_int_dp; } -@@ -524,7 +539,8 @@ get_ofctrl_probe_interval(struct ovsdb_idl *ovs_idl) + + static const char * +@@ -524,7 +588,8 @@ get_ofctrl_probe_interval(struct ovsdb_idl *ovs_idl) static void update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl, bool *monitor_all_p, bool *reset_ovnsb_idl_min_index, @@ -5485,7 +5711,7 @@ index 366fc9c06..b4eee4848 100644 { const struct ovsrec_open_vswitch *cfg = ovsrec_open_vswitch_first(ovs_idl); if (!cfg) { -@@ -565,9 +581,17 @@ update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl, +@@ -565,9 +630,17 @@ update_sb_db(struct ovsdb_idl *ovs_idl, struct ovsdb_idl *ovnsb_idl, *reset_ovnsb_idl_min_index = false; } @@ -5506,7 +5732,7 @@ index 366fc9c06..b4eee4848 100644 } } -@@ -583,7 +607,18 @@ add_pending_ct_zone_entry(struct shash *pending_ct_zones, +@@ -583,7 +656,18 @@ add_pending_ct_zone_entry(struct shash *pending_ct_zones, pending->state = state; /* Skip flushing zone. */ pending->zone = zone; pending->add = add; @@ -5526,7 +5752,7 @@ index 366fc9c06..b4eee4848 100644 } static void -@@ -798,11 +833,11 @@ restore_ct_zones(const struct ovsrec_bridge_table *bridge_table, +@@ -798,11 +882,11 @@ restore_ct_zones(const struct ovsrec_bridge_table *bridge_table, } } @@ -5540,7 +5766,7 @@ index 366fc9c06..b4eee4848 100644 /* Delay getting nb_cfg if there are monitor condition changes * in flight. It might be that those changes would instruct the -@@ -825,11 +860,14 @@ static void +@@ -825,11 +909,14 @@ static void store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn, const struct sbrec_chassis_private *chassis, const struct ovsrec_bridge *br_int, @@ -5558,7 +5784,7 @@ index 366fc9c06..b4eee4848 100644 } if (sb_txn && chassis && cur_cfg != chassis->nb_cfg) { -@@ -850,6 +888,9 @@ store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn, +@@ -850,6 +937,9 @@ store_nb_cfg(struct ovsdb_idl_txn *sb_txn, struct ovsdb_idl_txn *ovs_txn, cur_cfg_str); free(cur_cfg_str); } @@ -5568,7 +5794,24 @@ index 366fc9c06..b4eee4848 100644 } static const char * -@@ -911,7 +952,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) +@@ -868,6 +958,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) + ovsdb_idl_add_table(ovs_idl, &ovsrec_table_open_vswitch); + ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_external_ids); + ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_bridges); ++ ovsdb_idl_add_column(ovs_idl, &ovsrec_open_vswitch_col_datapaths); + ovsdb_idl_add_table(ovs_idl, &ovsrec_table_interface); + ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_name); + ovsdb_idl_track_add_column(ovs_idl, &ovsrec_interface_col_bfd); +@@ -890,6 +981,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) + ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_ca_cert); + ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_certificate); + ovsdb_idl_add_column(ovs_idl, &ovsrec_ssl_col_private_key); ++ ovsdb_idl_add_table(ovs_idl, &ovsrec_table_datapath); ++ ovsdb_idl_add_column(ovs_idl, &ovsrec_datapath_col_capabilities); + chassis_register_ovs_idl(ovs_idl); + encaps_register_ovs_idl(ovs_idl); + binding_register_ovs_idl(ovs_idl); +@@ -911,7 +1004,8 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) SB_NODE(dhcp_options, "dhcp_options") \ SB_NODE(dhcpv6_options, "dhcpv6_options") \ SB_NODE(dns, "dns") \ @@ -5578,7 +5821,7 @@ index 366fc9c06..b4eee4848 100644 enum sb_engine_node { #define SB_NODE(NAME, NAME_STR) SB_##NAME, -@@ -940,10 +982,6 @@ enum ovs_engine_node { +@@ -940,10 +1034,6 @@ enum ovs_engine_node { OVS_NODES #undef OVS_NODE @@ -5589,7 +5832,7 @@ index 366fc9c06..b4eee4848 100644 struct ed_type_ofctrl_is_connected { bool connected; }; -@@ -964,9 +1002,16 @@ en_ofctrl_is_connected_cleanup(void *data OVS_UNUSED) +@@ -964,9 +1054,16 @@ en_ofctrl_is_connected_cleanup(void *data OVS_UNUSED) static void en_ofctrl_is_connected_run(struct engine_node *node, void *data) { @@ -5606,7 +5849,7 @@ index 366fc9c06..b4eee4848 100644 engine_set_node_state(node, EN_UPDATED); return; } -@@ -1137,8 +1182,7 @@ struct ed_type_runtime_data { +@@ -1137,8 +1234,7 @@ struct ed_type_runtime_data { /* Contains "struct local_datapath" nodes. */ struct hmap local_datapaths; @@ -5616,7 +5859,17 @@ index 366fc9c06..b4eee4848 100644 /* Contains the name of each logical port resident on the local * hypervisor. These logical ports include the VIFs (and their child -@@ -1177,9 +1221,9 @@ struct ed_type_runtime_data { +@@ -1163,6 +1259,9 @@ struct ed_type_runtime_data { + + /* CT zone data. Contains datapaths that had updated CT zones */ + struct hmapx ct_updated_datapaths; ++ ++ /* Contains datapaths that had updated external ports. */ ++ struct hmapx extport_updated_datapaths; + }; + + /* struct ed_type_runtime_data has the below members for tracking the +@@ -1177,9 +1276,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 | @@ -5628,7 +5881,7 @@ index 366fc9c06..b4eee4848 100644 * | | here. | * ------------------------------------------------------------------------ * | | This is a bool which represents if the runtime | -@@ -1206,7 +1250,7 @@ struct ed_type_runtime_data { +@@ -1206,7 +1305,7 @@ struct ed_type_runtime_data { * * --------------------------------------------------------------------- * | local_datapaths | The changes to these runtime data is captured in | @@ -5637,7 +5890,7 @@ index 366fc9c06..b4eee4848 100644 * | local_lport_ids | is not tracked explicitly. | * --------------------------------------------------------------------- * | local_iface_ids | This is used internally within the runtime data | -@@ -1249,7 +1293,7 @@ en_runtime_data_init(struct engine_node *node OVS_UNUSED, +@@ -1249,12 +1348,13 @@ 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); @@ -5646,16 +5899,29 @@ index 366fc9c06..b4eee4848 100644 /* Init the tracked data. */ hmap_init(&data->tracked_dp_bindings); -@@ -1277,7 +1321,7 @@ en_runtime_data_cleanup(void *data) + + hmapx_init(&data->ct_updated_datapaths); ++ hmapx_init(&data->extport_updated_datapaths); + + return data; + } +@@ -1273,12 +1373,14 @@ en_runtime_data_cleanup(void *data) + HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, + &rt_data->local_datapaths) { + free(cur_node->peer_ports); ++ shash_destroy(&cur_node->external_ports); + hmap_remove(&rt_data->local_datapaths, &cur_node->hmap_node); 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); ++ hmapx_destroy(&rt_data->extport_updated_datapaths); } -@@ -1338,6 +1382,8 @@ init_binding_ctx(struct engine_node *node, + static void +@@ -1338,6 +1440,8 @@ init_binding_ctx(struct engine_node *node, engine_get_input("SB_port_binding", node), "datapath"); @@ -5664,7 +5930,7 @@ index 366fc9c06..b4eee4848 100644 b_ctx_in->ovnsb_idl_txn = engine_get_context()->ovnsb_idl_txn; b_ctx_in->ovs_idl_txn = engine_get_context()->ovs_idl_txn; b_ctx_in->sbrec_datapath_binding_by_key = sbrec_datapath_binding_by_key; -@@ -1360,10 +1406,10 @@ init_binding_ctx(struct engine_node *node, +@@ -1360,10 +1464,11 @@ 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; @@ -5674,10 +5940,16 @@ index 366fc9c06..b4eee4848 100644 b_ctx_out->tracked_dp_bindings = NULL; - b_ctx_out->local_lports_changed = NULL; + b_ctx_out->if_mgr = ctrl_ctx->if_mgr; ++ b_ctx_out->extport_updated_datapaths = &rt_data->extport_updated_datapaths; } static void -@@ -1404,7 +1450,7 @@ en_runtime_data_run(struct engine_node *node, void *data) +@@ -1400,11 +1505,12 @@ en_runtime_data_run(struct engine_node *node, void *data) + struct local_datapath *cur_node, *next_node; + HMAP_FOR_EACH_SAFE (cur_node, next_node, hmap_node, local_datapaths) { + free(cur_node->peer_ports); ++ shash_destroy(&cur_node->external_ports); + hmap_remove(local_datapaths, &cur_node->hmap_node); free(cur_node); } hmap_clear(local_datapaths); @@ -5686,16 +5958,18 @@ index 366fc9c06..b4eee4848 100644 sset_destroy(local_lports); sset_destroy(local_lport_ids); sset_destroy(active_tunnels); -@@ -1415,7 +1461,7 @@ en_runtime_data_run(struct engine_node *node, void *data) +@@ -1415,8 +1521,9 @@ 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); ++ hmapx_clear(&rt_data->extport_updated_datapaths); } -@@ -1670,6 +1716,7 @@ en_physical_flow_changes_run(struct engine_node *node, void *data) + struct binding_ctx_in b_ctx_in; +@@ -1670,6 +1777,7 @@ en_physical_flow_changes_run(struct engine_node *node, void *data) { struct ed_type_pfc_data *pfc_tdata = data; pfc_tdata->recompute_physical_flows = true; @@ -5703,7 +5977,7 @@ index 366fc9c06..b4eee4848 100644 engine_set_node_state(node, EN_UPDATED); } -@@ -1696,8 +1743,7 @@ physical_flow_changes_ovs_iface_handler(struct engine_node *node, void *data) +@@ -1696,8 +1804,7 @@ physical_flow_changes_ovs_iface_handler(struct engine_node *node, void *data) struct flow_output_persistent_data { uint32_t conj_id_ofs; @@ -5713,16 +5987,18 @@ index 366fc9c06..b4eee4848 100644 }; struct ed_type_flow_output { -@@ -1778,7 +1824,7 @@ static void init_physical_ctx(struct engine_node *node, +@@ -1778,8 +1885,9 @@ 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; ++ p_ctx->extport_updated_datapaths = &rt_data->extport_updated_datapaths; } -@@ -1836,6 +1882,10 @@ static void init_lflow_ctx(struct engine_node *node, + static void init_lflow_ctx(struct engine_node *node, +@@ -1836,6 +1944,10 @@ static void init_lflow_ctx(struct engine_node *node, (struct sbrec_load_balancer_table *)EN_OVSDB_GET( engine_get_input("SB_load_balancer", node)); @@ -5733,7 +6009,7 @@ index 366fc9c06..b4eee4848 100644 struct ovsrec_open_vswitch_table *ovs_table = (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( engine_get_input("OVS_open_vswitch", node)); -@@ -1873,6 +1923,7 @@ static void init_lflow_ctx(struct engine_node *node, +@@ -1873,6 +1985,7 @@ static void init_lflow_ctx(struct engine_node *node, l_ctx_in->logical_flow_table = logical_flow_table; l_ctx_in->logical_dp_group_table = logical_dp_group_table; l_ctx_in->mc_group_table = multicast_group_table; @@ -5741,7 +6017,7 @@ index 366fc9c06..b4eee4848 100644 l_ctx_in->chassis = chassis; l_ctx_in->lb_table = lb_table; l_ctx_in->local_datapaths = &rt_data->local_datapaths; -@@ -1886,11 +1937,7 @@ static void init_lflow_ctx(struct engine_node *node, +@@ -1886,11 +1999,7 @@ static void init_lflow_ctx(struct engine_node *node, l_ctx_out->meter_table = &fo->meter_table; l_ctx_out->lfrr = &fo->lflow_resource_ref; l_ctx_out->conj_id_ofs = &fo->pd.conj_id_ofs; @@ -5754,7 +6030,7 @@ index 366fc9c06..b4eee4848 100644 l_ctx_out->conj_id_overflow = false; } -@@ -1905,8 +1952,6 @@ en_flow_output_init(struct engine_node *node OVS_UNUSED, +@@ -1905,8 +2014,6 @@ en_flow_output_init(struct engine_node *node OVS_UNUSED, ovn_extend_table_init(&data->meter_table); data->pd.conj_id_ofs = 1; lflow_resource_init(&data->lflow_resource_ref); @@ -5763,7 +6039,7 @@ index 366fc9c06..b4eee4848 100644 return data; } -@@ -1918,7 +1963,7 @@ en_flow_output_cleanup(void *data) +@@ -1918,7 +2025,7 @@ en_flow_output_cleanup(void *data) ovn_extend_table_destroy(&flow_output_data->group_table); ovn_extend_table_destroy(&flow_output_data->meter_table); lflow_resource_destroy(&flow_output_data->lflow_resource_ref); @@ -5772,7 +6048,7 @@ index 366fc9c06..b4eee4848 100644 } static void -@@ -1965,13 +2010,10 @@ en_flow_output_run(struct engine_node *node, void *data) +@@ -1965,13 +2072,10 @@ en_flow_output_run(struct engine_node *node, void *data) } struct controller_engine_ctx *ctrl_ctx = engine_get_context()->client_ctx; @@ -5789,7 +6065,7 @@ index 366fc9c06..b4eee4848 100644 fo->pd.conj_id_ofs = 1; } -@@ -1988,8 +2030,7 @@ en_flow_output_run(struct engine_node *node, void *data) +@@ -1988,8 +2092,7 @@ en_flow_output_run(struct engine_node *node, void *data) ovn_extend_table_clear(meter_table, false /* desired */); lflow_resource_clear(lfrr); fo->pd.conj_id_ofs = 1; @@ -5799,7 +6075,15 @@ index 366fc9c06..b4eee4848 100644 l_ctx_out.conj_id_overflow = false; lflow_run(&l_ctx_in, &l_ctx_out); if (l_ctx_out.conj_id_overflow) { -@@ -2313,6 +2354,23 @@ flow_output_sb_load_balancer_handler(struct engine_node *node, void *data) +@@ -2236,6 +2339,7 @@ flow_output_physical_flow_changes_handler(struct engine_node *node, void *data) + /* This indicates that we need to recompute the physical flows. */ + physical_clear_unassoc_flows_with_db(&fo->flow_table); + physical_clear_dp_flows(&p_ctx, &rt_data->ct_updated_datapaths, ++ &rt_data->extport_updated_datapaths, + &fo->flow_table); + physical_run(&p_ctx, &fo->flow_table); + return true; +@@ -2313,6 +2417,23 @@ flow_output_sb_load_balancer_handler(struct engine_node *node, void *data) return handled; } @@ -5823,7 +6107,7 @@ index 366fc9c06..b4eee4848 100644 struct ovn_controller_exit_args { bool *exiting; bool *restart; -@@ -2389,6 +2447,9 @@ main(int argc, char *argv[]) +@@ -2389,6 +2510,9 @@ main(int argc, char *argv[]) daemonize_complete(); @@ -5833,7 +6117,7 @@ index 366fc9c06..b4eee4848 100644 patch_init(); pinctrl_init(); lflow_init(); -@@ -2440,6 +2501,10 @@ main(int argc, char *argv[]) +@@ -2440,6 +2564,10 @@ main(int argc, char *argv[]) = ip_mcast_index_create(ovnsb_idl_loop.idl); struct ovsdb_idl_index *sbrec_igmp_group = igmp_group_index_create(ovnsb_idl_loop.idl); @@ -5844,7 +6128,7 @@ index 366fc9c06..b4eee4848 100644 ovsdb_idl_track_add_all(ovnsb_idl_loop.idl); ovsdb_idl_omit_alert(ovnsb_idl_loop.idl, -@@ -2566,6 +2631,8 @@ main(int argc, char *argv[]) +@@ -2566,6 +2694,8 @@ main(int argc, char *argv[]) engine_add_input(&en_flow_output, &en_sb_dns, NULL); engine_add_input(&en_flow_output, &en_sb_load_balancer, flow_output_sb_load_balancer_handler); @@ -5853,7 +6137,7 @@ index 366fc9c06..b4eee4848 100644 engine_add_input(&en_ct_zones, &en_ovs_open_vswitch, NULL); engine_add_input(&en_ct_zones, &en_ovs_bridge, NULL); -@@ -2619,11 +2686,13 @@ main(int argc, char *argv[]) +@@ -2619,11 +2749,13 @@ 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); @@ -5868,7 +6152,7 @@ index 366fc9c06..b4eee4848 100644 unixctl_command_register("group-table-list", "", 0, 0, extend_table_list, -@@ -2643,7 +2712,15 @@ main(int argc, char *argv[]) +@@ -2643,7 +2775,15 @@ main(int argc, char *argv[]) unixctl_command_register("recompute", "", 0, 0, engine_recompute_cmd, NULL); @@ -5885,7 +6169,7 @@ index 366fc9c06..b4eee4848 100644 &flow_output_data->pd); bool reset_ovnsb_idl_min_index = false; -@@ -2663,13 +2740,19 @@ main(int argc, char *argv[]) +@@ -2663,13 +2803,19 @@ 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); @@ -5906,7 +6190,7 @@ index 366fc9c06..b4eee4848 100644 char *ovn_version = ovn_get_internal_version(); VLOG_INFO("OVN internal version is : [%s]", ovn_version); -@@ -2679,6 +2762,15 @@ main(int argc, char *argv[]) +@@ -2679,6 +2825,15 @@ main(int argc, char *argv[]) restart = false; bool sb_monitor_all = false; while (!exiting) { @@ -5922,7 +6206,7 @@ index 366fc9c06..b4eee4848 100644 /* If we're paused just run the unixctl server and skip most of the * processing loop. */ -@@ -2703,8 +2795,7 @@ main(int argc, char *argv[]) +@@ -2703,8 +2858,7 @@ main(int argc, char *argv[]) update_sb_db(ovs_idl_loop.idl, ovnsb_idl_loop.idl, &sb_monitor_all, &reset_ovnsb_idl_min_index, @@ -5932,7 +6216,16 @@ index 366fc9c06..b4eee4848 100644 update_ssl_config(ovsrec_ssl_table_get(ovs_idl_loop.idl)); ofctrl_set_probe_interval(get_ofctrl_probe_interval(ovs_idl_loop.idl)); -@@ -2741,6 +2832,16 @@ main(int argc, char *argv[]) +@@ -2736,11 +2890,23 @@ main(int argc, char *argv[]) + ovsrec_bridge_table_get(ovs_idl_loop.idl); + const struct ovsrec_open_vswitch_table *ovs_table = + ovsrec_open_vswitch_table_get(ovs_idl_loop.idl); +- const struct ovsrec_bridge *br_int = +- process_br_int(ovs_idl_txn, bridge_table, ovs_table); ++ const struct ovsrec_bridge *br_int = NULL; ++ const struct ovsrec_datapath *br_int_dp = NULL; ++ process_br_int(ovs_idl_txn, bridge_table, ovs_table, ++ &br_int, &br_int_dp); if (ovsdb_idl_has_ever_connected(ovnsb_idl_loop.idl) && northd_version_match) { @@ -5949,7 +6242,21 @@ index 366fc9c06..b4eee4848 100644 /* Contains the transport zones that this Chassis belongs to */ struct sset transport_zones = SSET_INITIALIZER(&transport_zones); sset_from_delimited_string(&transport_zones, -@@ -2832,11 +2933,13 @@ main(int argc, char *argv[]) +@@ -2758,6 +2924,13 @@ main(int argc, char *argv[]) + &chassis_private); + } + ++ /* If any OVS feature support changed, force a full recompute. */ ++ if (br_int_dp ++ && ovs_feature_support_update(&br_int_dp->capabilities)) { ++ VLOG_INFO("OVS feature set changed, force recompute."); ++ engine_set_force_recompute(true); ++ } ++ + if (br_int) { + ct_zones_data = engine_get_data(&en_ct_zones); + if (ct_zones_data) { +@@ -2832,11 +3005,13 @@ main(int argc, char *argv[]) sbrec_mac_binding_by_lport_ip, sbrec_igmp_group, sbrec_ip_multicast, @@ -5963,7 +6270,7 @@ index 366fc9c06..b4eee4848 100644 br_int, chassis, &runtime_data->local_datapaths, &runtime_data->active_tunnels); -@@ -2852,17 +2955,29 @@ main(int argc, char *argv[]) +@@ -2852,17 +3027,29 @@ main(int argc, char *argv[]) sb_monitor_all); } } @@ -5997,7 +6304,7 @@ index 366fc9c06..b4eee4848 100644 } } -@@ -2888,7 +3003,7 @@ main(int argc, char *argv[]) +@@ -2888,7 +3075,7 @@ main(int argc, char *argv[]) } store_nb_cfg(ovnsb_idl_txn, ovs_idl_txn, chassis_private, @@ -6006,7 +6313,7 @@ index 366fc9c06..b4eee4848 100644 if (pending_pkt.conn) { struct ed_type_addr_sets *as_data = -@@ -2960,6 +3075,7 @@ main(int argc, char *argv[]) +@@ -2960,6 +3147,7 @@ main(int argc, char *argv[]) ovsdb_idl_track_clear(ovs_idl_loop.idl); loop_done: @@ -6014,7 +6321,7 @@ index 366fc9c06..b4eee4848 100644 poll_block(); if (should_service_stop()) { exiting = true; -@@ -3027,6 +3143,7 @@ loop_done: +@@ -3027,6 +3215,7 @@ loop_done: ofctrl_destroy(); pinctrl_destroy(); patch_destroy(); @@ -6022,7 +6329,7 @@ index 366fc9c06..b4eee4848 100644 ovsdb_idl_loop_destroy(&ovs_idl_loop); ovsdb_idl_loop_destroy(&ovnsb_idl_loop); -@@ -3207,19 +3324,32 @@ engine_recompute_cmd(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED, +@@ -3207,19 +3396,32 @@ engine_recompute_cmd(struct unixctl_conn *conn OVS_UNUSED, int argc OVS_UNUSED, } static void @@ -6059,7 +6366,7 @@ index 366fc9c06..b4eee4848 100644 static void cluster_state_reset_cmd(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *idl_reset_) -@@ -3287,3 +3417,13 @@ debug_delay_nb_cfg_report(struct unixctl_conn *conn, int argc OVS_UNUSED, +@@ -3287,3 +3489,13 @@ debug_delay_nb_cfg_report(struct unixctl_conn *conn, int argc OVS_UNUSED, unixctl_command_reply(conn, "no delay for nb_cfg report."); } } @@ -6073,8 +6380,21 @@ index 366fc9c06..b4eee4848 100644 + unixctl_command_reply(conn, ds_cstr(&binding_data)); + ds_destroy(&binding_data); +} +diff --git a/controller/ovn-controller.h b/controller/ovn-controller.h +index 5d9466880..2bf1fecbf 100644 +--- a/controller/ovn-controller.h ++++ b/controller/ovn-controller.h +@@ -67,6 +67,8 @@ struct local_datapath { + + size_t n_peer_ports; + size_t n_allocated_peer_ports; ++ ++ struct shash external_ports; + }; + + struct local_datapath *get_local_datapath(const struct hmap *, diff --git a/controller/physical.c b/controller/physical.c -index fa5d0d692..c7090b351 100644 +index fa5d0d692..c44c5151e 100644 --- a/controller/physical.c +++ b/controller/physical.c @@ -1160,6 +1160,11 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, @@ -6089,7 +6409,7 @@ index fa5d0d692..c7090b351 100644 /* Resubmit to first logical ingress pipeline table. */ put_resubmit(OFTABLE_LOG_INGRESS_PIPELINE, ofpacts_p); ofctrl_add_flow(flow_table, OFTABLE_PHY_TO_LOG, -@@ -1219,6 +1224,24 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, +@@ -1219,6 +1224,70 @@ consider_port_binding(struct ovsdb_idl_index *sbrec_port_binding_by_name, ofport, flow_table); } @@ -6109,12 +6429,58 @@ index fa5d0d692..c7090b351 100644 + ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 160, + binding->header_.uuid.parts[0], &match, + ofpacts_p, &binding->header_.uuid); ++ ++ /* localport traffic directed to external is *not* local */ ++ struct shash_node *node; ++ SHASH_FOR_EACH (node, &ld->external_ports) { ++ const struct sbrec_port_binding *pb = node->data; ++ ++ /* skip ports that are not claimed by this chassis */ ++ if (!pb->chassis) { ++ continue; ++ } ++ if (strcmp(pb->chassis->name, chassis->name)) { ++ continue; ++ } ++ ++ ofpbuf_clear(ofpacts_p); ++ for (int i = 0; i < MFF_N_LOG_REGS; i++) { ++ put_load(0, MFF_REG0 + i, 0, 32, ofpacts_p); ++ } ++ put_resubmit(OFTABLE_LOG_EGRESS_PIPELINE, ofpacts_p); ++ ++ /* allow traffic directed to external MAC address */ ++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); ++ for (int i = 0; i < pb->n_mac; i++) { ++ char *err_str; ++ struct eth_addr peer_mac; ++ if ((err_str = str_to_mac(pb->mac[i], &peer_mac))) { ++ VLOG_WARN_RL( ++ &rl, "Parsing MAC failed for external port: %s, " ++ "with error: %s", pb->logical_port, err_str); ++ free(err_str); ++ continue; ++ } ++ ++ match_init_catchall(&match); ++ match_set_metadata(&match, htonll(dp_key)); ++ match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, ++ port_key); ++ match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0, ++ MLF_LOCALPORT, MLF_LOCALPORT); ++ match_set_dl_dst(&match, peer_mac); ++ ++ ofctrl_add_flow(flow_table, OFTABLE_CHECK_LOOPBACK, 170, ++ binding->header_.uuid.parts[0], &match, ++ ofpacts_p, &binding->header_.uuid); ++ } ++ } + } + } else if (!tun && !is_ha_remote) { /* Remote port connected by localnet port */ /* Table 33, priority 100. -@@ -1839,20 +1862,29 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, +@@ -1839,20 +1908,29 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, continue; } @@ -6151,7 +6517,7 @@ index fa5d0d692..c7090b351 100644 } simap_put(&localvif_to_ofport, iface_id, ofport); -@@ -1860,7 +1892,7 @@ physical_handle_ovs_iface_changes(struct physical_ctx *p_ctx, +@@ -1860,7 +1938,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, @@ -6160,6 +6526,58 @@ index fa5d0d692..c7090b351 100644 flow_table, &ofpacts); } } +@@ -1893,18 +1971,24 @@ physical_clear_unassoc_flows_with_db(struct ovn_desired_flow_table *flow_table) + void + physical_clear_dp_flows(struct physical_ctx *p_ctx, + struct hmapx *ct_updated_datapaths, ++ struct hmapx *extport_updated_datapaths, + struct ovn_desired_flow_table *flow_table) + { + const struct sbrec_port_binding *binding; + SBREC_PORT_BINDING_TABLE_FOR_EACH (binding, p_ctx->port_binding_table) { +- if (!hmapx_find(ct_updated_datapaths, binding->datapath)) { +- continue; ++ if (hmapx_find(ct_updated_datapaths, binding->datapath)) { ++ const struct sbrec_port_binding *peer = ++ get_binding_peer(p_ctx->sbrec_port_binding_by_name, binding); ++ ofctrl_remove_flows(flow_table, &binding->header_.uuid); ++ if (peer) { ++ ofctrl_remove_flows(flow_table, &peer->header_.uuid); ++ } + } +- const struct sbrec_port_binding *peer = +- get_binding_peer(p_ctx->sbrec_port_binding_by_name, binding); +- ofctrl_remove_flows(flow_table, &binding->header_.uuid); +- if (peer) { +- ofctrl_remove_flows(flow_table, &peer->header_.uuid); ++ ++ if (hmapx_find(extport_updated_datapaths, binding->datapath)) { ++ if (!strcmp(binding->type, "localnet")) { ++ ofctrl_remove_flows(flow_table, &binding->header_.uuid); ++ } + } + } + } +diff --git a/controller/physical.h b/controller/physical.h +index 0bf13f268..f9cd883a7 100644 +--- a/controller/physical.h ++++ b/controller/physical.h +@@ -57,6 +57,7 @@ struct physical_ctx { + enum mf_field_id mff_ovn_geneve; + struct shash *local_bindings; + struct hmapx *ct_updated_datapaths; ++ struct hmapx *extport_updated_datapaths; + }; + + void physical_register_ovs_idl(struct ovsdb_idl *); +@@ -65,6 +66,7 @@ void physical_run(struct physical_ctx *, + void physical_clear_unassoc_flows_with_db(struct ovn_desired_flow_table *); + void physical_clear_dp_flows(struct physical_ctx *p_ctx, + struct hmapx *ct_updated_datapaths, ++ struct hmapx *extport_updated_datapaths, + struct ovn_desired_flow_table *flow_table); + void physical_handle_port_binding_changes(struct physical_ctx *, + struct ovn_desired_flow_table *); diff --git a/controller/pinctrl.c b/controller/pinctrl.c index 7e3abf0a4..523a45b9a 100644 --- a/controller/pinctrl.c @@ -8009,10 +8427,18 @@ index db9ef88da..18e37a31f 100644 poll_immediate_wake(); } diff --git a/include/ovn/actions.h b/include/ovn/actions.h -index 9c1ebf4aa..040213177 100644 +index 9c1ebf4aa..f5eb01eb7 100644 --- a/include/ovn/actions.h +++ b/include/ovn/actions.h -@@ -105,6 +105,11 @@ struct ovn_extend_table; +@@ -25,6 +25,7 @@ + #include "openvswitch/hmap.h" + #include "openvswitch/uuid.h" + #include "util.h" ++#include "ovn/features.h" + + struct expr; + struct lexer; +@@ -105,6 +106,11 @@ struct ovn_extend_table; OVNACT(CHK_LB_HAIRPIN, ovnact_result) \ OVNACT(CHK_LB_HAIRPIN_REPLY, ovnact_result) \ OVNACT(CT_SNAT_TO_VIP, ovnact_null) \ @@ -8024,7 +8450,7 @@ index 9c1ebf4aa..040213177 100644 /* enum ovnact_type, with a member OVNACT_ for each action. */ enum OVS_PACKED_ENUM ovnact_type { -@@ -413,6 +418,28 @@ struct ovnact_fwd_group { +@@ -413,6 +419,28 @@ struct ovnact_fwd_group { uint8_t ltable; /* Logical table ID of next table. */ }; @@ -8053,7 +8479,7 @@ index 9c1ebf4aa..040213177 100644 /* Internal use by the helpers below. */ void ovnact_init(struct ovnact *, enum ovnact_type, size_t len); void *ovnact_put(struct ofpbuf *, enum ovnact_type, size_t len); -@@ -627,6 +654,22 @@ enum action_opcode { +@@ -627,6 +655,22 @@ enum action_opcode { * The actions, in OpenFlow 1.3 format, follow the action_header. */ ACTION_OPCODE_REJECT, @@ -8076,7 +8502,7 @@ index 9c1ebf4aa..040213177 100644 }; /* Header. */ -@@ -748,6 +791,10 @@ struct ovnact_encode_params { +@@ -748,6 +792,10 @@ struct ovnact_encode_params { * 'chk_lb_hairpin_reply' to resubmit. */ uint8_t ct_snat_vip_ptable; /* OpenFlow table for * 'ct_snat_to_vip' to resubmit. */ @@ -8120,10 +8546,10 @@ index 0a83ec7a8..032370058 100644 /* Action parsing helper. */ diff --git a/include/ovn/features.h b/include/ovn/features.h new file mode 100644 -index 000000000..10ee46fcd +index 000000000..c35d59b14 --- /dev/null +++ b/include/ovn/features.h -@@ -0,0 +1,22 @@ +@@ -0,0 +1,40 @@ +/* Copyright (c) 2021, Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); @@ -8142,9 +8568,27 @@ index 000000000..10ee46fcd +#ifndef OVN_FEATURES_H +#define OVN_FEATURES_H 1 + ++#include ++ ++#include "smap.h" ++ +/* ovn-controller supported feature names. */ +#define OVN_FEATURE_PORT_UP_NOTIF "port-up-notif" + ++/* OVS datapath supported features. Based on availability OVN might generate ++ * different types of openflows. ++ */ ++enum ovs_feature_support_bits { ++ OVS_CT_ZERO_SNAT_SUPPORT_BIT, ++}; ++ ++enum ovs_feature_value { ++ OVS_CT_ZERO_SNAT_SUPPORT = (1 << OVS_CT_ZERO_SNAT_SUPPORT_BIT), ++}; ++ ++bool ovs_feature_is_supported(enum ovs_feature_value feature); ++bool ovs_feature_support_update(const struct smap *ovs_capabilities); ++ +#endif diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h index aee474856..ef97117b9 100644 @@ -8193,10 +8637,55 @@ index aee474856..ef97117b9 100644 /* OVN logical fields diff --git a/lib/actions.c b/lib/actions.c -index fbaeb34bc..b3433f49e 100644 +index fbaeb34bc..7010fab2b 100644 --- a/lib/actions.c +++ b/lib/actions.c -@@ -1490,6 +1490,12 @@ parse_TCP_RESET(struct action_context *ctx) +@@ -742,6 +742,22 @@ encode_CT_COMMIT_V1(const struct ovnact_ct_commit_v1 *cc, + ct->zone_src.ofs = 0; + ct->zone_src.n_bits = 16; + ++ /* If the datapath supports all-zero SNAT then use it to avoid tuple ++ * collisions at commit time between NATed and firewalled-only sessions. ++ */ ++ ++ if (ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)) { ++ size_t nat_offset = ofpacts->size; ++ ofpbuf_pull(ofpacts, nat_offset); ++ ++ struct ofpact_nat *nat = ofpact_put_NAT(ofpacts); ++ nat->flags = 0; ++ nat->range_af = AF_UNSPEC; ++ nat->flags |= NX_NAT_F_SRC; ++ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset); ++ ct = ofpacts->header; ++ } ++ + size_t set_field_offset = ofpacts->size; + ofpbuf_pull(ofpacts, set_field_offset); + +@@ -792,6 +808,21 @@ encode_CT_COMMIT_V2(const struct ovnact_nest *on, + ct->zone_src.ofs = 0; + ct->zone_src.n_bits = 16; + ++ /* If the datapath supports all-zero SNAT then use it to avoid tuple ++ * collisions at commit time between NATed and firewalled-only sessions. ++ */ ++ if (ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)) { ++ size_t nat_offset = ofpacts->size; ++ ofpbuf_pull(ofpacts, nat_offset); ++ ++ struct ofpact_nat *nat = ofpact_put_NAT(ofpacts); ++ nat->flags = 0; ++ nat->range_af = AF_UNSPEC; ++ nat->flags |= NX_NAT_F_SRC; ++ ofpacts->header = ofpbuf_push_uninit(ofpacts, nat_offset); ++ ct = ofpacts->header; ++ } ++ + size_t set_field_offset = ofpacts->size; + ofpbuf_pull(ofpacts, set_field_offset); + +@@ -1490,6 +1521,12 @@ parse_TCP_RESET(struct action_context *ctx) parse_nested_action(ctx, OVNACT_TCP_RESET, "tcp", ctx->scope); } @@ -8209,7 +8698,7 @@ index fbaeb34bc..b3433f49e 100644 static void parse_ND_NA(struct action_context *ctx) { -@@ -1571,6 +1577,12 @@ format_TCP_RESET(const struct ovnact_nest *nest, struct ds *s) +@@ -1571,6 +1608,12 @@ format_TCP_RESET(const struct ovnact_nest *nest, struct ds *s) format_nested_action(nest, "tcp_reset", s); } @@ -8222,7 +8711,7 @@ index fbaeb34bc..b3433f49e 100644 static void format_ND_NA(const struct ovnact_nest *nest, struct ds *s) { -@@ -1700,6 +1712,14 @@ encode_TCP_RESET(const struct ovnact_nest *on, +@@ -1700,6 +1743,14 @@ encode_TCP_RESET(const struct ovnact_nest *on, encode_nested_actions(on, ep, ACTION_OPCODE_TCP_RESET, ofpacts); } @@ -8237,7 +8726,7 @@ index fbaeb34bc..b3433f49e 100644 static void encode_REJECT(const struct ovnact_nest *on, const struct ovnact_encode_params *ep, -@@ -2742,6 +2762,31 @@ encode_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED, +@@ -2742,6 +2793,31 @@ encode_DHCP6_REPLY(const struct ovnact_null *a OVS_UNUSED, encode_controller_op(ACTION_OPCODE_DHCP6_SERVER, ofpacts); } @@ -8269,7 +8758,7 @@ index fbaeb34bc..b3433f49e 100644 static void parse_SET_QUEUE(struct action_context *ctx) { -@@ -3698,6 +3743,172 @@ encode_CT_SNAT_TO_VIP(const struct ovnact_null *null OVS_UNUSED, +@@ -3698,6 +3774,172 @@ encode_CT_SNAT_TO_VIP(const struct ovnact_null *null OVS_UNUSED, emit_resubmit(ofpacts, ep->ct_snat_vip_ptable); } @@ -8442,7 +8931,7 @@ index fbaeb34bc..b3433f49e 100644 /* Parses an assignment or exchange or put_dhcp_opts action. */ static void parse_set_action(struct action_context *ctx) -@@ -3758,6 +3969,14 @@ parse_set_action(struct action_context *ctx) +@@ -3758,6 +4000,14 @@ parse_set_action(struct action_context *ctx) && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) { parse_chk_lb_hairpin_reply( ctx, &lhs, ovnact_put_CHK_LB_HAIRPIN_REPLY(ctx->ovnacts)); @@ -8457,7 +8946,7 @@ index fbaeb34bc..b3433f49e 100644 } else { parse_assignment_action(ctx, false, &lhs); } -@@ -3812,6 +4031,8 @@ parse_action(struct action_context *ctx) +@@ -3812,6 +4062,8 @@ parse_action(struct action_context *ctx) ovnact_put_IGMP(ctx->ovnacts); } else if (lexer_match_id(ctx->lexer, "tcp_reset")) { parse_TCP_RESET(ctx); @@ -8466,7 +8955,7 @@ index fbaeb34bc..b3433f49e 100644 } else if (lexer_match_id(ctx->lexer, "nd_na")) { parse_ND_NA(ctx); } else if (lexer_match_id(ctx->lexer, "nd_na_router")) { -@@ -3842,10 +4063,14 @@ parse_action(struct action_context *ctx) +@@ -3842,10 +4094,14 @@ parse_action(struct action_context *ctx) parse_fwd_group_action(ctx); } else if (lexer_match_id(ctx->lexer, "handle_dhcpv6_reply")) { ovnact_put_DHCP6_REPLY(ctx->ovnacts); @@ -8481,6 +8970,18 @@ index fbaeb34bc..b3433f49e 100644 } else { lexer_syntax_error(ctx->lexer, "expecting action"); } +diff --git a/lib/automake.mk b/lib/automake.mk +index 250c7aefa..616433332 100644 +--- a/lib/automake.mk ++++ b/lib/automake.mk +@@ -13,6 +13,7 @@ lib_libovn_la_SOURCES = \ + lib/expr.c \ + lib/extend-table.h \ + lib/extend-table.c \ ++ lib/features.c \ + lib/ip-mcast-index.c \ + lib/ip-mcast-index.h \ + lib/mcast-group-index.c \ diff --git a/lib/expr.c b/lib/expr.c index 4566d9110..7b3d3ddb3 100644 --- a/lib/expr.c @@ -8575,6 +9076,96 @@ index 4566d9110..7b3d3ddb3 100644 HMAP_FOR_EACH_POP (m, hmap_node, matches) { free(m->conjunctions); free(m); +diff --git a/lib/features.c b/lib/features.c +new file mode 100644 +index 000000000..87d04ee3f +--- /dev/null ++++ b/lib/features.c +@@ -0,0 +1,84 @@ ++/* Copyright (c) 2021, Red Hat, Inc. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at: ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#include ++#include ++#include ++ ++#include "lib/util.h" ++#include "openvswitch/vlog.h" ++#include "ovn/features.h" ++ ++VLOG_DEFINE_THIS_MODULE(features); ++ ++struct ovs_feature { ++ enum ovs_feature_value value; ++ const char *name; ++}; ++ ++static struct ovs_feature all_ovs_features[] = { ++ { ++ .value = OVS_CT_ZERO_SNAT_SUPPORT, ++ .name = "ct_zero_snat" ++ }, ++}; ++ ++/* A bitmap of OVS features that have been detected as 'supported'. */ ++static uint32_t supported_ovs_features; ++ ++static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); ++ ++static bool ++ovs_feature_is_valid(enum ovs_feature_value feature) ++{ ++ switch (feature) { ++ case OVS_CT_ZERO_SNAT_SUPPORT: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++bool ++ovs_feature_is_supported(enum ovs_feature_value feature) ++{ ++ ovs_assert(ovs_feature_is_valid(feature)); ++ return supported_ovs_features & feature; ++} ++ ++/* Returns 'true' if the set of tracked OVS features has been updated. */ ++bool ++ovs_feature_support_update(const struct smap *ovs_capabilities) ++{ ++ bool updated = false; ++ ++ for (size_t i = 0; i < ARRAY_SIZE(all_ovs_features); i++) { ++ enum ovs_feature_value value = all_ovs_features[i].value; ++ const char *name = all_ovs_features[i].name; ++ bool old_state = supported_ovs_features & value; ++ bool new_state = smap_get_bool(ovs_capabilities, name, false); ++ if (new_state != old_state) { ++ updated = true; ++ if (new_state) { ++ supported_ovs_features |= value; ++ } else { ++ supported_ovs_features &= ~value; ++ } ++ VLOG_INFO_RL(&rl, "OVS Feature: %s, state: %s", name, ++ new_state ? "supported" : "not supported"); ++ } ++ } ++ return updated; ++} diff --git a/lib/inc-proc-eng.c b/lib/inc-proc-eng.c index 916dbbe39..a6337a1d9 100644 --- a/lib/inc-proc-eng.c @@ -8709,10 +9300,53 @@ index 857234677..7e9f5bb70 100644 /* Initialize the data for the engine nodes. It calls each node's diff --git a/lib/lb.c b/lib/lb.c -index a90042e58..f305e9a87 100644 +index a90042e58..4cb46b346 100644 --- a/lib/lb.c +++ b/lib/lb.c -@@ -170,6 +170,24 @@ void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip) +@@ -101,10 +101,7 @@ static + void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb, + const struct ovn_lb_vip *lb_vip, + const struct nbrec_load_balancer *nbrec_lb, +- const char *vip_port_str, const char *backend_ips, +- struct hmap *ports, +- void * (*ovn_port_find)(const struct hmap *ports, +- const char *name)) ++ const char *vip_port_str, const char *backend_ips) + { + lb_vip_nb->vip_port_str = xstrdup(vip_port_str); + lb_vip_nb->backend_ips = xstrdup(backend_ips); +@@ -133,30 +130,6 @@ void ovn_northd_lb_vip_init(struct ovn_northd_lb_vip *lb_vip_nb, + } + + lb_vip_nb->lb_health_check = lb_health_check; +- +- for (size_t j = 0; j < lb_vip_nb->n_backends; j++) { +- struct ovn_lb_backend *backend = &lb_vip->backends[j]; +- struct ovn_northd_lb_backend *backend_nb = &lb_vip_nb->backends_nb[j]; +- +- struct ovn_port *op = NULL; +- char *svc_mon_src_ip = NULL; +- const char *s = smap_get(&nbrec_lb->ip_port_mappings, +- backend->ip_str); +- if (s) { +- char *port_name = xstrdup(s); +- char *p = strstr(port_name, ":"); +- if (p) { +- *p = 0; +- p++; +- op = ovn_port_find(ports, port_name); +- svc_mon_src_ip = xstrdup(p); +- } +- free(port_name); +- } +- +- backend_nb->op = op; +- backend_nb->svc_mon_src_ip = svc_mon_src_ip; +- } + } + + static +@@ -170,11 +143,26 @@ void ovn_northd_lb_vip_destroy(struct ovn_northd_lb_vip *vip) free(vip->backends_nb); } @@ -8735,9 +9369,24 @@ index a90042e58..f305e9a87 100644 +} + struct ovn_northd_lb * - ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - struct hmap *ports, -@@ -189,6 +207,8 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, +-ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, +- struct hmap *ports, +- void * (*ovn_port_find)(const struct hmap *ports, +- const char *name)) ++ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb) + { + struct ovn_northd_lb *lb = xzalloc(sizeof *lb); + +@@ -182,6 +170,8 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, + lb->n_vips = smap_count(&nbrec_lb->vips); + lb->vips = xcalloc(lb->n_vips, sizeof *lb->vips); + lb->vips_nb = xcalloc(lb->n_vips, sizeof *lb->vips_nb); ++ sset_init(&lb->ips_v4); ++ sset_init(&lb->ips_v6); + struct smap_node *node; + size_t n_vips = 0; + +@@ -189,11 +179,18 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, struct ovn_lb_vip *lb_vip = &lb->vips[n_vips]; struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[n_vips]; @@ -8746,25 +9395,27 @@ index a90042e58..f305e9a87 100644 if (!ovn_lb_vip_init(lb_vip, node->key, node->value)) { continue; } -@@ -222,6 +242,9 @@ ovn_northd_lb_create(const struct nbrec_load_balancer *nbrec_lb, - ds_chomp(&sel_fields, ','); - lb->selection_fields = ds_steal_cstr(&sel_fields); + ovn_northd_lb_vip_init(lb_vip_nb, lb_vip, nbrec_lb, +- node->key, node->value, ports, ovn_port_find); ++ node->key, node->value); ++ if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) { ++ sset_add(&lb->ips_v4, lb_vip->vip_str); ++ } else { ++ sset_add(&lb->ips_v6, lb_vip->vip_str); ++ } + n_vips++; } -+ -+ ovn_lb_get_hairpin_snat_ip(&nbrec_lb->header_.uuid, &nbrec_lb->options, -+ &lb->hairpin_snat_ips); - return lb; - } -@@ -258,6 +281,7 @@ ovn_northd_lb_destroy(struct ovn_northd_lb *lb) +@@ -257,6 +254,8 @@ ovn_northd_lb_destroy(struct ovn_northd_lb *lb) + } free(lb->vips); free(lb->vips_nb); ++ sset_destroy(&lb->ips_v4); ++ sset_destroy(&lb->ips_v6); free(lb->selection_fields); -+ destroy_lport_addresses(&lb->hairpin_snat_ips); free(lb->dps); free(lb); - } -@@ -287,6 +311,12 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb) +@@ -287,6 +286,12 @@ ovn_controller_lb_create(const struct sbrec_load_balancer *sbrec_lb) * correct value. */ lb->n_vips = n_vips; @@ -8777,7 +9428,7 @@ index a90042e58..f305e9a87 100644 return lb; } -@@ -297,5 +327,6 @@ ovn_controller_lb_destroy(struct ovn_controller_lb *lb) +@@ -297,5 +302,6 @@ ovn_controller_lb_destroy(struct ovn_controller_lb *lb) ovn_lb_vip_destroy(&lb->vips[i]); } free(lb->vips); @@ -8785,30 +9436,29 @@ index a90042e58..f305e9a87 100644 free(lb); } diff --git a/lib/lb.h b/lib/lb.h -index 6644ad0d8..9a78c72f3 100644 +index 6644ad0d8..58e6bb031 100644 --- a/lib/lb.h +++ b/lib/lb.h -@@ -20,6 +20,7 @@ +@@ -20,6 +20,8 @@ #include #include #include "openvswitch/hmap.h" ++#include "sset.h" +#include "ovn-util.h" struct nbrec_load_balancer; struct sbrec_load_balancer; -@@ -37,6 +38,11 @@ struct ovn_northd_lb { +@@ -37,6 +39,9 @@ struct ovn_northd_lb { struct ovn_northd_lb_vip *vips_nb; size_t n_vips; -+ struct lport_addresses hairpin_snat_ips; /* IP (v4 and/or v6) to be used -+ * as source for hairpinned -+ * traffic. -+ */ ++ struct sset ips_v4; ++ struct sset ips_v6; + size_t n_dps; size_t n_allocated_dps; const struct sbrec_datapath_binding **dps; -@@ -49,6 +55,7 @@ struct ovn_lb_vip { +@@ -49,6 +54,7 @@ struct ovn_lb_vip { struct ovn_lb_backend *backends; size_t n_backends; @@ -8816,7 +9466,19 @@ index 6644ad0d8..9a78c72f3 100644 }; struct ovn_lb_backend { -@@ -88,6 +95,14 @@ struct ovn_controller_lb { +@@ -74,10 +80,7 @@ struct ovn_northd_lb_backend { + const struct sbrec_service_monitor *sbrec_monitor; + }; + +-struct ovn_northd_lb *ovn_northd_lb_create( +- const struct nbrec_load_balancer *, +- struct hmap *ports, +- void * (*ovn_port_find)(const struct hmap *ports, const char *name)); ++struct ovn_northd_lb *ovn_northd_lb_create(const struct nbrec_load_balancer *); + struct ovn_northd_lb * ovn_northd_lb_find(struct hmap *, const struct uuid *); + void ovn_northd_lb_destroy(struct ovn_northd_lb *); + void ovn_northd_lb_add_datapath(struct ovn_northd_lb *, +@@ -88,6 +91,14 @@ struct ovn_controller_lb { struct ovn_lb_vip *vips; size_t n_vips; @@ -9004,10 +9666,72 @@ index 679f47a97..40ecafe57 100644 +#define LOG_PIPELINE_LEN 29 + #endif -diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml -index a9a3a9f4f..ace16281c 100644 ---- a/northd/ovn-northd.8.xml -+++ b/northd/ovn-northd.8.xml +diff --git a/lib/test-ovn-features.c b/lib/test-ovn-features.c +new file mode 100644 +index 000000000..deb97581e +--- /dev/null ++++ b/lib/test-ovn-features.c +@@ -0,0 +1,56 @@ ++/* Copyright (c) 2021, Red Hat, Inc. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at: ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++#include ++ ++#include "ovn/features.h" ++#include "tests/ovstest.h" ++ ++static void ++test_ovn_features(struct ovs_cmdl_context *ctx OVS_UNUSED) ++{ ++ ovs_assert(!ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)); ++ ++ struct smap features = SMAP_INITIALIZER(&features); ++ ++ smap_add(&features, "ct_zero_snat", "false"); ++ ovs_assert(!ovs_feature_support_update(&features)); ++ ovs_assert(!ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)); ++ ++ smap_replace(&features, "ct_zero_snat", "true"); ++ ovs_assert(ovs_feature_support_update(&features)); ++ ovs_assert(ovs_feature_is_supported(OVS_CT_ZERO_SNAT_SUPPORT)); ++ ++ smap_add(&features, "unknown_feature", "true"); ++ ovs_assert(!ovs_feature_support_update(&features)); ++ ++ smap_destroy(&features); ++} ++ ++static void ++test_ovn_features_main(int argc, char *argv[]) ++{ ++ set_program_name(argv[0]); ++ static const struct ovs_cmdl_command commands[] = { ++ {"run", NULL, 0, 0, test_ovn_features, OVS_RO}, ++ {NULL, NULL, 0, 0, NULL, OVS_RO}, ++ }; ++ struct ovs_cmdl_context ctx; ++ ctx.argc = argc - 1; ++ ctx.argv = argv + 1; ++ ovs_cmdl_run_command(&ctx, commands); ++} ++ ++OVSTEST_REGISTER("test-ovn-features", test_ovn_features_main); +diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml +index a9a3a9f4f..9224f2f0e 100644 +--- a/northd/ovn-northd.8.xml ++++ b/northd/ovn-northd.8.xml @@ -307,7 +307,73 @@ @@ -9270,12 +9994,12 @@ index a9a3a9f4f..ace16281c 100644 packets. + - ++ +

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

-+ + +
  • A priority 34000 logical flow is added for each logical switch datapath @@ -9355,7 +10079,7 @@ index a9a3a9f4f..ace16281c 100644 + reg2. For IPv6 traffic the flow also loads the original + destination IP and transport port in registers xxreg1 and + reg2. -+
  • + + +
  • + If the load balancer is created with --reject option and @@ -9364,7 +10088,7 @@ index a9a3a9f4f..ace16281c 100644 + whenever an incoming packet is received for this load-balancer. + Please note using --reject option will disable + empty_lb SB controller event for this load balancer. -
  • ++ +
  • A priority-100 flow commits packets to connection tracker using @@ -9518,7 +10242,22 @@ index a9a3a9f4f..ace16281c 100644

    This table implements switching behavior. It contains these logical -@@ -1481,12 +1584,58 @@ output; +@@ -1406,6 +1509,14 @@ output; + logical ports. +

  • + ++
  • ++ Priority-90 flows for each IP address/VIP/NAT address configured ++ outside its owning router port's subnet. These flows match ARP ++ requests and ND packets for the specific IP addresses. Matched packets ++ are forwarded to the MC_FLOOD multicast group which ++ contains all connected logical ports. ++
  • ++ +
  • + Priority-75 flows for each port connected to a logical router + matching self originated ARP request/ND packets. These packets +@@ -1481,12 +1592,58 @@ output;
  • @@ -9583,7 +10322,7 @@ index a9a3a9f4f..ace16281c 100644
-@@ -1498,9 +1647,11 @@ output; +@@ -1498,9 +1655,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 @@ -9597,7 +10336,7 @@ index a9a3a9f4f..ace16281c 100644

-@@ -1542,20 +1693,39 @@ output; +@@ -1542,20 +1701,39 @@ output;

Egress Table 2: Pre-stateful

@@ -9626,14 +10365,14 @@ index a9a3a9f4f..ace16281c 100644 + (with a match for reg0[0] == 1) by using the + ct_next; action. + - --

Egress Table 4: from-lport ACL hints

++ +
  • + A priority-0 flow that matches all packets to advance to the next + table. +
  • + -+ + +-

    Egress Table 4: from-lport ACL hints

    +

    Egress Table 3: from-lport ACL hints

    This is similar to ingress table ACL hints. @@ -9644,7 +10383,7 @@ index a9a3a9f4f..ace16281c 100644

    This is similar to ingress table ACLs except for -@@ -1592,28 +1762,28 @@ output; +@@ -1592,28 +1770,28 @@ output; @@ -9677,7 +10416,7 @@ index a9a3a9f4f..ace16281c 100644

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

    @@ -9686,7 +10425,7 @@ index a9a3a9f4f..ace16281c 100644

    This is similar to the ingress port security logic in ingress table -@@ -1926,6 +2096,27 @@ next; +@@ -1926,6 +2104,27 @@ next;

    @@ -9714,7 +10453,7 @@ index a9a3a9f4f..ace16281c 100644
  • L3 admission control: A priority-100 flow drops packets that match -@@ -2121,8 +2312,7 @@ eth.src = xreg0[0..47]; +@@ -2121,8 +2320,7 @@ eth.src = xreg0[0..47]; arp.op = 2; /* ARP reply. */ arp.tha = arp.sha; arp.sha = xreg0[0..47]; @@ -9724,7 +10463,16 @@ index a9a3a9f4f..ace16281c 100644 outport = inport; flags.loopback = 1; output; -@@ -2449,6 +2639,16 @@ icmp6 { +@@ -2391,7 +2589,7 @@ icmp6 { + +

    + If ECMP routes with symmetric reply are configured in the +- OVN_Northbound database for a gateway router, a priority-100 ++ OVN_Northbound database for a gateway router, a priority-300 + flow is added for each router port on which symmetric replies are + configured. The matching logic for these ports essentially reverses the + configured logic of the ECMP route. So for instance, a route with a +@@ -2449,6 +2647,16 @@ icmp6 { with an action ct_snat; .

    @@ -9741,7 +10489,7 @@ index a9a3a9f4f..ace16281c 100644

    If the Gateway router has been configured to force SNAT any previously load-balanced packets to B, a priority-100 flow -@@ -2548,7 +2748,11 @@ icmp6 { +@@ -2548,7 +2756,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; @@ -9754,7 +10502,7 @@ index a9a3a9f4f..ace16281c 100644 args will only contain those endpoints whose service monitor status entry in OVN_Southbound db is either online or empty. -@@ -2565,6 +2769,9 @@ icmp6 { +@@ -2565,6 +2777,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;. @@ -9764,7 +10512,7 @@ index a9a3a9f4f..ace16281c 100644

  • -@@ -2579,6 +2786,9 @@ icmp6 { +@@ -2579,6 +2794,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);. @@ -9774,7 +10522,7 @@ index a9a3a9f4f..ace16281c 100644
  • -@@ -2591,6 +2801,18 @@ icmp6 { +@@ -2591,6 +2809,18 @@ 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;. @@ -9793,7 +10541,7 @@ index a9a3a9f4f..ace16281c 100644
  • -@@ -3022,14 +3244,36 @@ outport = P; +@@ -3022,14 +3252,36 @@ outport = P;
  • @@ -9832,7 +10580,7 @@ index a9a3a9f4f..ace16281c 100644 flags.loopback = 1; next; -@@ -3053,7 +3297,51 @@ next; +@@ -3053,7 +3305,51 @@ next;

  • @@ -9885,7 +10633,21 @@ index a9a3a9f4f..ace16281c 100644

    Any packet that reaches this table is an IP packet whose next-hop -@@ -3239,7 +3527,7 @@ next; +@@ -3173,7 +3469,12 @@ next; + column + of table for of type + dnat_and_snat, otherwise the Ethernet address of the +- distributed logical router port. ++ distributed logical router port. Note that if the ++ is not ++ within a subnet on the owning logical router, then OVN will only ++ create ARP resolution flows if the ++ is set to true. Otherwise, no ARP resolution flows ++ will be added. +

    + +

    +@@ -3239,7 +3540,7 @@ next; @@ -9894,7 +10656,7 @@ index a9a3a9f4f..ace16281c 100644

    For distributed logical routers with distributed gateway port configured -@@ -3269,7 +3557,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(L); next; +@@ -3269,7 +3570,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(L); next; and advances to the next table.

    @@ -9903,7 +10665,7 @@ index a9a3a9f4f..ace16281c 100644

    For distributed logical routers with distributed gateway port configured -@@ -3330,7 +3618,7 @@ icmp6 { +@@ -3330,7 +3631,7 @@ icmp6 { and advances to the next table.

    @@ -9912,7 +10674,21 @@ index a9a3a9f4f..ace16281c 100644

    For distributed logical routers where one of the logical router -@@ -3370,7 +3658,7 @@ icmp6 { +@@ -3353,6 +3654,13 @@ icmp6 { + external ip and D is NAT external mac. + + ++

  • ++ For each NAT rule in the OVN Northbound database that can ++ be handled in a distributed manner, a priority-80 logical flow ++ with drop action if the NAT logical port is a virtual port not ++ claimed by any chassis yet. ++
  • ++ +
  • + A priority-50 logical flow with match + outport == GW has actions +@@ -3370,7 +3678,7 @@ icmp6 {
  • @@ -9921,7 +10697,7 @@ index a9a3a9f4f..ace16281c 100644

    In the common case where the Ethernet destination has been resolved, this -@@ -3546,6 +3834,41 @@ nd_ns { +@@ -3546,6 +3854,41 @@ nd_ns { flags.force_snat_for_dnat == 1 && ip with an action ct_snat(B);.

    @@ -9963,7 +10739,7 @@ index a9a3a9f4f..ace16281c 100644

    If the Gateway router in the OVN Northbound database has been configured to force SNAT a packet (that has been previously -@@ -3553,6 +3876,9 @@ nd_ns { +@@ -3553,6 +3896,9 @@ nd_ns { flags.force_snat_for_lb == 1 && ip with an action ct_snat(B);.

    @@ -9973,7 +10749,7 @@ index a9a3a9f4f..ace16281c 100644

    For each configuration in the OVN Northbound database, that asks to change the source IP address of a packet from an IP address of -@@ -3566,14 +3892,18 @@ nd_ns { +@@ -3566,14 +3912,18 @@ nd_ns { options, then the action would be ip4/6.src= (B).

    @@ -9992,7 +10768,7 @@ index a9a3a9f4f..ace16281c 100644

    If the NAT rule has exempted_ext_ips set, then there is an additional flow configured at the priority + 1 of -@@ -3582,7 +3912,9 @@ nd_ns { +@@ -3582,7 +3932,9 @@ nd_ns { . This flow is used to bypass the ct_snat action for a packet which is destinted to exempted_ext_ips.

    @@ -10003,7 +10779,7 @@ index a9a3a9f4f..ace16281c 100644 A priority-0 logical flow with match 1 has actions next;. diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c -index 5a3227568..e78a71728 100644 +index 5a3227568..f2daeacbd 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -37,10 +37,13 @@ @@ -10176,15 +10952,23 @@ index 5a3227568..e78a71728 100644 /* IPAM data. */ struct ipam_info ipam_info; -@@ -633,6 +668,7 @@ struct ovn_datapath { +@@ -633,6 +668,15 @@ struct ovn_datapath { struct lport_addresses dnat_force_snat_addrs; struct lport_addresses lb_force_snat_addrs; + bool lb_force_snat_router_ip; ++ /* The "routable" ssets are subsets of the load balancer ++ * IPs for which IP routes and ARP resolution flows are automatically ++ * added ++ */ ++ struct sset lb_ips_v4; ++ struct sset lb_ips_v4_routable; ++ struct sset lb_ips_v6; ++ struct sset lb_ips_v6_routable; struct ovn_port **localnet_ports; size_t n_localnet_ports; -@@ -723,14 +759,28 @@ init_nat_entries(struct ovn_datapath *od) +@@ -723,14 +767,28 @@ init_nat_entries(struct ovn_datapath *od) } } @@ -10221,7 +11005,44 @@ index 5a3227568..e78a71728 100644 } } -@@ -872,6 +922,20 @@ ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid) +@@ -785,6 +843,28 @@ destroy_nat_entries(struct ovn_datapath *od) + } + } + ++static void ++init_lb_ips(struct ovn_datapath *od) ++{ ++ sset_init(&od->lb_ips_v4); ++ sset_init(&od->lb_ips_v4_routable); ++ sset_init(&od->lb_ips_v6); ++ sset_init(&od->lb_ips_v6_routable); ++} ++ ++static void ++destroy_lb_ips(struct ovn_datapath *od) ++{ ++ if (!od->nbs && !od->nbr) { ++ return; ++ } ++ ++ sset_destroy(&od->lb_ips_v4); ++ sset_destroy(&od->lb_ips_v4_routable); ++ sset_destroy(&od->lb_ips_v6); ++ sset_destroy(&od->lb_ips_v6_routable); ++} ++ + /* A group of logical router datapaths which are connected - either + * directly or indirectly. + * Each logical router can belong to only one group. */ +@@ -843,6 +923,7 @@ ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od) + bitmap_free(od->ipam_info.allocated_ipv4s); + free(od->router_ports); + destroy_nat_entries(od); ++ destroy_lb_ips(od); + free(od->nat_entries); + free(od->localnet_ports); + ovn_ls_port_group_destroy(&od->nb_pgs); +@@ -872,6 +953,20 @@ ovn_datapath_find(struct hmap *datapaths, const struct uuid *uuid) return NULL; } @@ -10242,7 +11063,52 @@ index 5a3227568..e78a71728 100644 static bool ovn_datapath_is_stale(const struct ovn_datapath *od) { -@@ -1472,6 +1536,8 @@ struct ovn_port { +@@ -1259,6 +1354,7 @@ join_datapaths(struct northd_context *ctx, struct hmap *datapaths, + + init_ipam_info_for_datapath(od); + init_mcast_info_for_datapath(od); ++ init_lb_ips(od); + } + + const struct nbrec_logical_router *nbr; +@@ -1290,6 +1386,7 @@ join_datapaths(struct northd_context *ctx, struct hmap *datapaths, + } + init_mcast_info_for_datapath(od); + init_nat_entries(od); ++ init_lb_ips(od); + ovs_list_push_back(lr_list, &od->lr_list); + } + } +@@ -1420,6 +1517,19 @@ build_datapaths(struct northd_context *ctx, struct hmap *datapaths, + } + } + ++/* Structure representing logical router port ++ * routable addresses. This includes DNAT and Load Balancer ++ * addresses. This structure will only be filled in if the ++ * router port is a gateway router port. Otherwise, all pointers ++ * will be NULL and n_addrs will be 0. ++ */ ++struct ovn_port_routable_addresses { ++ /* The parsed routable addresses */ ++ struct lport_addresses *laddrs; ++ /* Number of items in the laddrs array */ ++ size_t n_addrs; ++}; ++ + /* A logical switch port or logical router port. + * + * In steady state, an ovn_port points to a northbound Logical_Switch_Port +@@ -1463,6 +1573,8 @@ struct ovn_port { + + struct lport_addresses lrp_networks; + ++ struct ovn_port_routable_addresses routables; ++ + /* Logical port multicast data. */ + struct mcast_port_info mcast_info; + +@@ -1472,6 +1584,8 @@ struct ovn_port { bool has_unknown; /* If the addresses have 'unknown' defined. */ @@ -10251,7 +11117,64 @@ index 5a3227568..e78a71728 100644 /* The port's peer: * * - A switch port S of type "router" has a router port R as a peer, -@@ -1543,17 +1609,38 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port) +@@ -1487,6 +1601,47 @@ struct ovn_port { + struct ovs_list list; /* In list of similar records. */ + }; + ++static void ++destroy_routable_addresses(struct ovn_port_routable_addresses *ra) ++{ ++ for (size_t i = 0; i < ra->n_addrs; i++) { ++ destroy_lport_addresses(&ra->laddrs[i]); ++ } ++ free(ra->laddrs); ++} ++ ++static char **get_nat_addresses(const struct ovn_port *op, size_t *n, ++ bool routable_only); ++ ++static void ++assign_routable_addresses(struct ovn_port *op) ++{ ++ size_t n; ++ char **nats = get_nat_addresses(op, &n, true); ++ ++ if (!nats) { ++ return; ++ } ++ ++ struct lport_addresses *laddrs = xcalloc(n, sizeof(*laddrs)); ++ size_t n_addrs = 0; ++ for (size_t i = 0; i < n; i++) { ++ int ofs; ++ if (!extract_addresses(nats[i], &laddrs[n_addrs], &ofs)) { ++ free(nats[i]); ++ continue; ++ } ++ n_addrs++; ++ free(nats[i]); ++ } ++ free(nats); ++ ++ /* Everything seems to have worked out */ ++ op->routables.laddrs = laddrs; ++ op->routables.n_addrs = n_addrs; ++} ++ ++ + static void + ovn_port_set_nb(struct ovn_port *op, + const struct nbrec_logical_switch_port *nbsp, +@@ -1536,6 +1691,8 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port) + } + free(port->ps_addrs); + ++ destroy_routable_addresses(&port->routables); ++ + destroy_lport_addresses(&port->lrp_networks); + free(port->json_key); + free(port->key); +@@ -1543,17 +1700,38 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port) } } @@ -10293,7 +11216,29 @@ index 5a3227568..e78a71728 100644 } /* Returns true if the logical switch port 'enabled' column is empty or -@@ -2336,15 +2423,13 @@ join_logical_ports(struct northd_context *ctx, +@@ -1608,6 +1786,21 @@ ipam_is_duplicate_mac(struct eth_addr *ea, uint64_t mac64, bool warn) + return false; + } + ++static struct ovn_port * ++ovn_port_get_peer(struct hmap *ports, struct ovn_port *op) ++{ ++ if (!op->nbsp || !lsp_is_router(op->nbsp) || op->derived) { ++ return NULL; ++ } ++ ++ const char *peer_name = smap_get(&op->nbsp->options, "router-port"); ++ if (!peer_name) { ++ return NULL; ++ } ++ ++ return ovn_port_find(ports, peer_name); ++} ++ + static void + ipam_insert_mac(struct eth_addr *ea, bool check) + { +@@ -2336,15 +2529,13 @@ join_logical_ports(struct northd_context *ctx, for (size_t i = 0; i < od->nbs->n_ports; i++) { const struct nbrec_logical_switch_port *nbsp = od->nbs->ports[i]; @@ -10316,7 +11261,7 @@ index 5a3227568..e78a71728 100644 ovn_port_set_nb(op, nbsp, NULL); ovs_list_remove(&op->list); -@@ -2435,16 +2520,15 @@ join_logical_ports(struct northd_context *ctx, +@@ -2435,16 +2626,15 @@ join_logical_ports(struct northd_context *ctx, continue; } @@ -10342,7 +11287,7 @@ index 5a3227568..e78a71728 100644 ovn_port_set_nb(op, NULL, nbrp); ovs_list_remove(&op->list); ovs_list_push_back(both, &op->list); -@@ -2487,7 +2571,7 @@ join_logical_ports(struct northd_context *ctx, +@@ -2487,7 +2677,7 @@ join_logical_ports(struct northd_context *ctx, char *redirect_name = ovn_chassis_redirect_name(nbrp->name); struct ovn_port *crp = ovn_port_find(ports, redirect_name); @@ -10351,7 +11296,145 @@ index 5a3227568..e78a71728 100644 crp->derived = true; ovn_port_set_nb(crp, NULL, nbrp); ovs_list_remove(&crp->list); -@@ -3179,6 +3263,12 @@ ovn_port_update_sbrec(struct northd_context *ctx, +@@ -2505,6 +2695,8 @@ join_logical_ports(struct northd_context *ctx, + * use during flow creation. */ + od->l3dgw_port = op; + od->l3redirect_port = crp; ++ ++ assign_routable_addresses(op); + } + } + } +@@ -2515,12 +2707,7 @@ join_logical_ports(struct northd_context *ctx, + struct ovn_port *op; + HMAP_FOR_EACH (op, key_node, ports) { + if (op->nbsp && lsp_is_router(op->nbsp) && !op->derived) { +- const char *peer_name = smap_get(&op->nbsp->options, "router-port"); +- if (!peer_name) { +- continue; +- } +- +- struct ovn_port *peer = ovn_port_find(ports, peer_name); ++ struct ovn_port *peer = ovn_port_get_peer(ports, op); + if (!peer || !peer->nbrp) { + continue; + } +@@ -2578,46 +2765,6 @@ join_logical_ports(struct northd_context *ctx, + } + } + +-static void +-get_router_load_balancer_ips(const struct ovn_datapath *od, +- struct sset *all_ips_v4, struct sset *all_ips_v6) +-{ +- if (!od->nbr) { +- return; +- } +- +- for (int i = 0; i < od->nbr->n_load_balancer; i++) { +- struct nbrec_load_balancer *lb = od->nbr->load_balancer[i]; +- struct smap *vips = &lb->vips; +- struct smap_node *node; +- +- SMAP_FOR_EACH (node, vips) { +- /* node->key contains IP:port or just IP. */ +- char *ip_address; +- uint16_t port; +- int addr_family; +- +- if (!ip_address_and_port_from_lb_key(node->key, &ip_address, &port, +- &addr_family)) { +- continue; +- } +- +- struct sset *all_ips; +- if (addr_family == AF_INET) { +- all_ips = all_ips_v4; +- } else { +- all_ips = all_ips_v6; +- } +- +- if (!sset_contains(all_ips, ip_address)) { +- sset_add(all_ips, ip_address); +- } +- +- free(ip_address); +- } +- } +-} +- + /* Returns an array of strings, each consisting of a MAC address followed + * by one or more IP addresses, and if the port is a distributed gateway + * port, followed by 'is_chassis_resident("LPORT_NAME")', where the +@@ -2629,11 +2776,11 @@ get_router_load_balancer_ips(const struct ovn_datapath *od, + * The caller must free each of the n returned strings with free(), + * and must free the returned array when it is no longer needed. */ + static char ** +-get_nat_addresses(const struct ovn_port *op, size_t *n) ++get_nat_addresses(const struct ovn_port *op, size_t *n, bool routable_only) + { + size_t n_nats = 0; + struct eth_addr mac; +- if (!op->nbrp || !op->od || !op->od->nbr ++ if (!op || !op->nbrp || !op->od || !op->od->nbr + || (!op->od->nbr->n_nat && !op->od->nbr->n_load_balancer) + || !eth_addr_from_string(op->nbrp->mac, &mac)) { + *n = n_nats; +@@ -2652,6 +2799,12 @@ get_nat_addresses(const struct ovn_port *op, size_t *n) + const struct nbrec_nat *nat = op->od->nbr->nat[i]; + ovs_be32 ip, mask; + ++ if (routable_only && ++ (!strcmp(nat->type, "snat") || ++ !smap_get_bool(&nat->options, "add_route", false))) { ++ continue; ++ } ++ + char *error = ip_parse_masked(nat->external_ip, &ip, &mask); + if (error || mask != OVS_BE32_MAX) { + free(error); +@@ -2702,22 +2855,26 @@ get_nat_addresses(const struct ovn_port *op, size_t *n) + } + } + +- /* Two sets to hold all load-balancer vips. */ +- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); +- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); +- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); +- + const char *ip_address; +- SSET_FOR_EACH (ip_address, &all_ips_v4) { +- ds_put_format(&c_addresses, " %s", ip_address); +- central_ip_address = true; +- } +- SSET_FOR_EACH (ip_address, &all_ips_v6) { +- ds_put_format(&c_addresses, " %s", ip_address); +- central_ip_address = true; ++ if (routable_only) { ++ SSET_FOR_EACH (ip_address, &op->od->lb_ips_v4_routable) { ++ ds_put_format(&c_addresses, " %s", ip_address); ++ central_ip_address = true; ++ } ++ SSET_FOR_EACH (ip_address, &op->od->lb_ips_v6_routable) { ++ ds_put_format(&c_addresses, " %s", ip_address); ++ central_ip_address = true; ++ } ++ } else { ++ SSET_FOR_EACH (ip_address, &op->od->lb_ips_v4) { ++ ds_put_format(&c_addresses, " %s", ip_address); ++ central_ip_address = true; ++ } ++ SSET_FOR_EACH (ip_address, &op->od->lb_ips_v6) { ++ ds_put_format(&c_addresses, " %s", ip_address); ++ central_ip_address = true; ++ } + } +- sset_destroy(&all_ips_v4); +- sset_destroy(&all_ips_v6); + + if (central_ip_address) { + /* Gratuitous ARP for centralized NAT rules on distributed gateway +@@ -3179,6 +3336,12 @@ ovn_port_update_sbrec(struct northd_context *ctx, } else { sbrec_port_binding_set_ha_chassis_group(op->sb, NULL); } @@ -10364,7 +11447,40 @@ index 5a3227568..e78a71728 100644 } } else { const char *chassis = NULL; -@@ -3308,6 +3398,14 @@ ovn_port_update_sbrec(struct northd_context *ctx, +@@ -3210,7 +3373,6 @@ ovn_port_update_sbrec(struct northd_context *ctx, + } else { + sbrec_port_binding_set_options(op->sb, NULL); + } +- + const char *nat_addresses = smap_get(&op->nbsp->options, + "nat-addresses"); + size_t n_nats = 0; +@@ -3218,7 +3380,7 @@ ovn_port_update_sbrec(struct northd_context *ctx, + if (nat_addresses && !strcmp(nat_addresses, "router")) { + if (op->peer && op->peer->od + && (chassis || op->peer->od->l3redirect_port)) { +- nats = get_nat_addresses(op->peer, &n_nats); ++ nats = get_nat_addresses(op->peer, &n_nats, false); + } + /* Only accept manual specification of ethernet address + * followed by IPv4 addresses on type "l3gateway" ports. */ +@@ -3266,6 +3428,7 @@ ovn_port_update_sbrec(struct northd_context *ctx, + if (add_router_port_garp) { + struct ds garp_info = DS_EMPTY_INITIALIZER; + ds_put_format(&garp_info, "%s", op->peer->lrp_networks.ea_s); ++ + for (size_t i = 0; i < op->peer->lrp_networks.n_ipv4_addrs; + i++) { + ds_put_format(&garp_info, " %s", +@@ -3282,7 +3445,6 @@ ovn_port_update_sbrec(struct northd_context *ctx, + nats[n_nats - 1] = ds_steal_cstr(&garp_info); + ds_destroy(&garp_info); + } +- + sbrec_port_binding_set_nat_addresses(op->sb, + (const char **) nats, n_nats); + for (size_t i = 0; i < n_nats; i++) { +@@ -3308,6 +3470,14 @@ ovn_port_update_sbrec(struct northd_context *ctx, if (op->tunnel_key != op->sb->tunnel_key) { sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); } @@ -10379,7 +11495,7 @@ index 5a3227568..e78a71728 100644 } /* Remove mac_binding entries that refer to logical_ports which are -@@ -3340,6 +3438,26 @@ cleanup_sb_ha_chassis_groups(struct northd_context *ctx, +@@ -3340,6 +3510,26 @@ cleanup_sb_ha_chassis_groups(struct northd_context *ctx, } } @@ -10406,7 +11522,114 @@ index 5a3227568..e78a71728 100644 struct service_monitor_info { struct hmap_node hmap_node; const struct sbrec_service_monitor *sbrec_mon; -@@ -3436,12 +3554,12 @@ ovn_lb_svc_create(struct northd_context *ctx, struct ovn_northd_lb *lb, +@@ -3381,67 +3571,83 @@ create_or_get_service_mon(struct northd_context *ctx, + + static void + ovn_lb_svc_create(struct northd_context *ctx, struct ovn_northd_lb *lb, +- struct hmap *monitor_map) ++ struct hmap *monitor_map, struct hmap *ports) + { + for (size_t i = 0; i < lb->n_vips; i++) { + struct ovn_lb_vip *lb_vip = &lb->vips[i]; + struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; + +- if (!lb_vip_nb->lb_health_check) { +- continue; +- } +- + for (size_t j = 0; j < lb_vip->n_backends; j++) { + struct ovn_lb_backend *backend = &lb_vip->backends[j]; + struct ovn_northd_lb_backend *backend_nb = + &lb_vip_nb->backends_nb[j]; + +- if (backend_nb->op && backend_nb->svc_mon_src_ip) { +- const char *protocol = lb->nlb->protocol; +- if (!protocol || !protocol[0]) { +- protocol = "tcp"; +- } +- backend_nb->health_check = true; +- struct service_monitor_info *mon_info = +- create_or_get_service_mon(ctx, monitor_map, +- backend->ip_str, +- backend_nb->op->nbsp->name, +- backend->port, +- protocol); +- +- ovs_assert(mon_info); +- sbrec_service_monitor_set_options( +- mon_info->sbrec_mon, &lb_vip_nb->lb_health_check->options); +- struct eth_addr ea; +- if (!mon_info->sbrec_mon->src_mac || +- !eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) || +- !eth_addr_equals(ea, svc_monitor_mac_ea)) { +- sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon, +- svc_monitor_mac); ++ struct ovn_port *op = NULL; ++ char *svc_mon_src_ip = NULL; ++ const char *s = smap_get(&lb->nlb->ip_port_mappings, ++ backend->ip_str); ++ if (s) { ++ char *port_name = xstrdup(s); ++ char *p = strstr(port_name, ":"); ++ if (p) { ++ *p = 0; ++ p++; ++ op = ovn_port_find(ports, port_name); ++ svc_mon_src_ip = xstrdup(p); + } ++ free(port_name); ++ } + +- if (!mon_info->sbrec_mon->src_ip || +- strcmp(mon_info->sbrec_mon->src_ip, +- backend_nb->svc_mon_src_ip)) { +- sbrec_service_monitor_set_src_ip( +- mon_info->sbrec_mon, +- backend_nb->svc_mon_src_ip); +- } ++ backend_nb->op = op; ++ backend_nb->svc_mon_src_ip = svc_mon_src_ip; + +- backend_nb->sbrec_monitor = mon_info->sbrec_mon; +- mon_info->required = true; ++ if (!lb_vip_nb->lb_health_check || !op || !svc_mon_src_ip) { ++ continue; ++ } ++ ++ const char *protocol = lb->nlb->protocol; ++ if (!protocol || !protocol[0]) { ++ protocol = "tcp"; ++ } ++ backend_nb->health_check = true; ++ struct service_monitor_info *mon_info = ++ create_or_get_service_mon(ctx, monitor_map, ++ backend->ip_str, ++ backend_nb->op->nbsp->name, ++ backend->port, ++ protocol); ++ ovs_assert(mon_info); ++ sbrec_service_monitor_set_options( ++ mon_info->sbrec_mon, &lb_vip_nb->lb_health_check->options); ++ struct eth_addr ea; ++ if (!mon_info->sbrec_mon->src_mac || ++ !eth_addr_from_string(mon_info->sbrec_mon->src_mac, &ea) || ++ !eth_addr_equals(ea, svc_monitor_mac_ea)) { ++ sbrec_service_monitor_set_src_mac(mon_info->sbrec_mon, ++ svc_monitor_mac); ++ } ++ ++ if (!mon_info->sbrec_mon->src_ip || ++ strcmp(mon_info->sbrec_mon->src_ip, ++ backend_nb->svc_mon_src_ip)) { ++ sbrec_service_monitor_set_src_ip( ++ mon_info->sbrec_mon, ++ backend_nb->svc_mon_src_ip); + } ++ ++ backend_nb->sbrec_monitor = mon_info->sbrec_mon; ++ mon_info->required = true; + } + } } static @@ -10424,7 +11647,7 @@ index 5a3227568..e78a71728 100644 if (lb_vip_nb->lb_health_check) { ds_put_cstr(action, "ct_lb(backends="); -@@ -3463,18 +3581,30 @@ void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip, +@@ -3463,18 +3669,30 @@ void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip, } if (!n_active_backends) { @@ -10459,7 +11682,46 @@ index 5a3227568..e78a71728 100644 ds_chomp(action, ';'); ds_chomp(action, ')'); ds_put_format(action, "; hash_fields=\"%s\");", selection_fields); -@@ -3547,10 +3677,18 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, +@@ -3483,32 +3701,17 @@ void build_lb_vip_ct_lb_actions(struct ovn_lb_vip *lb_vip, + + static void + build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, +- struct hmap *ports, struct hmap *lbs) ++ struct hmap *lbs) + { +- hmap_init(lbs); +- struct hmap monitor_map = HMAP_INITIALIZER(&monitor_map); ++ struct ovn_northd_lb *lb; + +- const struct sbrec_service_monitor *sbrec_mon; +- SBREC_SERVICE_MONITOR_FOR_EACH (sbrec_mon, ctx->ovnsb_idl) { +- uint32_t hash = sbrec_mon->port; +- hash = hash_string(sbrec_mon->ip, hash); +- hash = hash_string(sbrec_mon->logical_port, hash); +- struct service_monitor_info *mon_info = xzalloc(sizeof *mon_info); +- mon_info->sbrec_mon = sbrec_mon; +- mon_info->required = false; +- hmap_insert(&monitor_map, &mon_info->hmap_node, hash); +- } ++ hmap_init(lbs); + + const struct nbrec_load_balancer *nbrec_lb; + NBREC_LOAD_BALANCER_FOR_EACH (nbrec_lb, ctx->ovnnb_idl) { +- struct ovn_northd_lb *lb = +- ovn_northd_lb_create(nbrec_lb, ports, (void *)ovn_port_find); +- hmap_insert(lbs, &lb->hmap_node, uuid_hash(&nbrec_lb->header_.uuid)); +- } +- +- struct ovn_northd_lb *lb; +- HMAP_FOR_EACH (lb, hmap_node, lbs) { +- ovn_lb_svc_create(ctx, lb, &monitor_map); ++ struct ovn_northd_lb *lb_nb = ovn_northd_lb_create(nbrec_lb); ++ hmap_insert(lbs, &lb_nb->hmap_node, ++ uuid_hash(&nbrec_lb->header_.uuid)); + } + + struct ovn_datapath *od; +@@ -3547,10 +3750,18 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, /* Create SB Load balancer records if not present and sync * the SB load balancer columns. */ HMAP_FOR_EACH (lb, hmap_node, lbs) { @@ -10478,7 +11740,7 @@ index 5a3227568..e78a71728 100644 if (!lb->slb) { sbrec_lb = sbrec_load_balancer_insert(ctx->ovnsb_txn); lb->slb = sbrec_lb; -@@ -3564,9 +3702,11 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, +@@ -3564,9 +3775,11 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, sbrec_load_balancer_set_name(lb->slb, lb->nlb->name); sbrec_load_balancer_set_vips(lb->slb, &lb->nlb->vips); sbrec_load_balancer_set_protocol(lb->slb, lb->nlb->protocol); @@ -10490,15 +11752,85 @@ index 5a3227568..e78a71728 100644 } /* Set the list of associated load balanacers to a logical switch -@@ -4821,27 +4961,38 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs) - hmap_destroy(nb_pgs); - } - --static bool --has_stateful_acl(struct ovn_datapath *od) +@@ -3590,6 +3803,29 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, + od->nbs->n_load_balancer); + free(sbrec_lbs); + } ++} ++ +static void -+ls_get_acl_flags(struct ovn_datapath *od) - { ++build_ovn_lb_svcs(struct northd_context *ctx, struct hmap *ports, ++ struct hmap *lbs) ++{ ++ struct hmap monitor_map = HMAP_INITIALIZER(&monitor_map); ++ ++ const struct sbrec_service_monitor *sbrec_mon; ++ SBREC_SERVICE_MONITOR_FOR_EACH (sbrec_mon, ctx->ovnsb_idl) { ++ uint32_t hash = sbrec_mon->port; ++ hash = hash_string(sbrec_mon->ip, hash); ++ hash = hash_string(sbrec_mon->logical_port, hash); ++ struct service_monitor_info *mon_info = xzalloc(sizeof *mon_info); ++ mon_info->sbrec_mon = sbrec_mon; ++ mon_info->required = false; ++ hmap_insert(&monitor_map, &mon_info->hmap_node, hash); ++ } ++ ++ struct ovn_northd_lb *lb; ++ HMAP_FOR_EACH (lb, hmap_node, lbs) { ++ ovn_lb_svc_create(ctx, lb, &monitor_map, ports); ++ } + + struct service_monitor_info *mon_info; + HMAP_FOR_EACH_POP (mon_info, hmap_node, &monitor_map) { +@@ -3602,6 +3838,39 @@ build_ovn_lbs(struct northd_context *ctx, struct hmap *datapaths, + hmap_destroy(&monitor_map); + } + ++static void ++build_lrouter_lbs(struct hmap *datapaths, struct hmap *lbs) ++{ ++ struct ovn_datapath *od; ++ ++ HMAP_FOR_EACH (od, key_node, datapaths) { ++ if (!od->nbr) { ++ continue; ++ } ++ ++ for (size_t i = 0; i < od->nbr->n_load_balancer; i++) { ++ struct ovn_northd_lb *lb = ++ ovn_northd_lb_find(lbs, ++ &od->nbr->load_balancer[i]->header_.uuid); ++ const char *ip_address; ++ bool is_routable = smap_get_bool(&lb->nlb->options, "add_route", ++ false); ++ SSET_FOR_EACH (ip_address, &lb->ips_v4) { ++ sset_add(&od->lb_ips_v4, ip_address); ++ if (is_routable) { ++ sset_add(&od->lb_ips_v4_routable, ip_address); ++ } ++ } ++ SSET_FOR_EACH (ip_address, &lb->ips_v6) { ++ sset_add(&od->lb_ips_v6, ip_address); ++ if (is_routable) { ++ sset_add(&od->lb_ips_v6_routable, ip_address); ++ } ++ } ++ } ++ } ++} ++ + static bool + ovn_port_add_tnlid(struct ovn_port *op, uint32_t tunnel_key) + { +@@ -4821,27 +5090,38 @@ ovn_ls_port_group_destroy(struct hmap *nb_pgs) + hmap_destroy(nb_pgs); + } + +-static bool +-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")) { @@ -10541,7 +11873,7 @@ index 5a3227568..e78a71728 100644 } /* Logical switch ingress table 0: Ingress port security - L2 -@@ -4905,50 +5056,82 @@ build_lswitch_input_port_sec_od( +@@ -4905,50 +5185,82 @@ build_lswitch_input_port_sec_od( } static void @@ -10554,17 +11886,7 @@ index 5a3227568..e78a71728 100644 - struct ds actions = DS_EMPTY_INITIALIZER; - struct ds match = DS_EMPTY_INITIALIZER; - struct ovn_port *op; -+ if (op->nbsp && !op->n_ps_addrs && !strcmp(op->nbsp->type, "") && -+ op->has_unknown) { -+ ds_clear(match); -+ ds_clear(actions); -+ ds_put_format(match, "inport == %s", op->json_key); -+ ds_put_format(actions, REGBIT_LKUP_FDB -+ " = lookup_fdb(inport, eth.src); next;"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 100, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbsp->header_); - +- - /* Egress table 8: Egress port security - IP (priorities 90 and 80) - * if port security enabled. - * @@ -10579,6 +11901,19 @@ index 5a3227568..e78a71728 100644 - if (!op->nbsp || lsp_is_external(op->nbsp)) { - continue; - } ++ if (op->nbsp && !op->n_ps_addrs && !strcmp(op->nbsp->type, "") && ++ op->has_unknown) { ++ ds_clear(match); ++ ds_clear(actions); ++ ds_put_format(match, "inport == %s", op->json_key); ++ ds_put_format(actions, REGBIT_LKUP_FDB ++ " = lookup_fdb(inport, eth.src); next;"); ++ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_LOOKUP_FDB, 100, ++ ds_cstr(match), ds_cstr(actions), ++ &op->nbsp->header_); + +- ds_clear(&actions); +- ds_clear(&match); + ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0"); + ds_clear(actions); + ds_put_cstr(actions, "put_fdb(inport, eth.src); next;"); @@ -10587,9 +11922,7 @@ index 5a3227568..e78a71728 100644 + &op->nbsp->header_); + } +} - -- ds_clear(&actions); -- ds_clear(&match); ++ +static void +build_lswitch_learn_fdb_od( + struct ovn_datapath *od, struct hmap *lflows) @@ -10619,11 +11952,11 @@ index 5a3227568..e78a71728 100644 +{ + + if (op->nbsp && (!lsp_is_external(op->nbsp))) { -+ -+ ds_clear(actions); -+ ds_clear(match); - ds_put_format(&match, "outport == %s", op->json_key); ++ ds_clear(actions); ++ ds_clear(match); ++ + ds_put_format(match, "outport == %s", op->json_key); if (lsp_is_enabled(op->nbsp)) { build_port_security_l2("eth.dst", op->ps_addrs, op->n_ps_addrs, @@ -10651,7 +11984,7 @@ index 5a3227568..e78a71728 100644 &op->nbsp->header_); } -@@ -4956,23 +5139,20 @@ build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths, +@@ -4956,23 +5268,20 @@ build_lswitch_output_port_sec(struct hmap *ports, struct hmap *datapaths, build_port_security_ip(P_OUT, op, lflows, &op->nbsp->header_); } } @@ -10684,7 +12017,7 @@ index 5a3227568..e78a71728 100644 } static void -@@ -5008,8 +5188,6 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op, +@@ -5008,8 +5317,6 @@ skip_port_from_conntrack(struct ovn_datapath *od, struct ovn_port *op, static void build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) { @@ -10693,7 +12026,7 @@ index 5a3227568..e78a71728 100644 /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are * allowed by default. */ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;"); -@@ -5024,7 +5202,7 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) +@@ -5024,7 +5331,7 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) /* If there are any stateful ACL rules in this datapath, we must * send all IP packets through the conntrack action, which handles * defragmentation, in order to match L4 headers. */ @@ -10702,7 +12035,7 @@ index 5a3227568..e78a71728 100644 for (size_t i = 0; i < od->n_router_ports; i++) { skip_port_from_conntrack(od, od->router_ports[i], S_SWITCH_IN_PRE_ACL, S_SWITCH_OUT_PRE_ACL, -@@ -5084,7 +5262,10 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, +@@ -5084,7 +5391,10 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, struct nbrec_load_balancer *lb, int pl, struct shash *meter_groups) { @@ -10714,7 +12047,7 @@ index 5a3227568..e78a71728 100644 return; } -@@ -5124,7 +5305,7 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, +@@ -5124,7 +5434,7 @@ build_empty_lb_event_flow(struct ovn_datapath *od, struct hmap *lflows, } static bool @@ -10723,7 +12056,7 @@ index 5a3227568..e78a71728 100644 { for (int i = 0; i < od->nbs->n_load_balancer; i++) { struct nbrec_load_balancer *nb_lb = od->nbs->load_balancer[i]; -@@ -5190,8 +5371,8 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows, +@@ -5190,8 +5500,8 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows, vip_configured = (vip_configured || lb->n_vips); } @@ -10734,7 +12067,7 @@ index 5a3227568..e78a71728 100644 * * Send all the packets to conntrack in the ingress pipeline if the * logical switch has a load balancer with VIP configured. Earlier -@@ -5221,9 +5402,9 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows, +@@ -5221,9 +5531,9 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows, */ if (vip_configured) { ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, @@ -10746,7 +12079,7 @@ index 5a3227568..e78a71728 100644 } } -@@ -5235,10 +5416,46 @@ build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows) +@@ -5235,10 +5545,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;"); @@ -10793,7 +12126,7 @@ index 5a3227568..e78a71728 100644 ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_STATEFUL, 100, REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;"); } -@@ -5267,6 +5484,17 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) +@@ -5267,6 +5613,17 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) for (size_t i = 0; i < ARRAY_SIZE(stages); i++) { enum ovn_stage stage = stages[i]; @@ -10811,7 +12144,7 @@ index 5a3227568..e78a71728 100644 /* New, not already established connections, may hit either allow * or drop ACLs. For allow ACLs, the connection must also be committed * to conntrack so we set REGBIT_ACL_HINT_ALLOW_NEW. -@@ -5327,9 +5555,6 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) +@@ -5327,9 +5684,6 @@ build_acl_hints(struct ovn_datapath *od, struct hmap *lflows) ovn_lflow_add(lflows, od, stage, 1, "ct.est && ct_label.blocked == 0", REGBIT_ACL_HINT_BLOCK " = 1; " "next;"); @@ -10821,7 +12154,7 @@ index 5a3227568..e78a71728 100644 } } -@@ -5661,13 +5886,22 @@ static void +@@ -5661,13 +6015,22 @@ static void build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *port_groups, const struct shash *meter_groups) { @@ -10848,7 +12181,7 @@ index 5a3227568..e78a71728 100644 if (has_stateful) { /* Ingress and Egress ACL Table (Priority 1). -@@ -5698,21 +5932,23 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, +@@ -5698,21 +6061,23 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, "ip && (!ct.est || (ct.est && ct_label.blocked == 1))", REGBIT_CONNTRACK_COMMIT" = 1; next;"); @@ -10880,7 +12213,7 @@ index 5a3227568..e78a71728 100644 * * Allow reply traffic that is part of an established * conntrack entry that has not been marked for deletion -@@ -5721,14 +5957,15 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, +@@ -5721,14 +6086,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. */ @@ -10904,7 +12237,7 @@ index 5a3227568..e78a71728 100644 /* Ingress and Egress ACL Table (Priority 65535). * -@@ -5741,21 +5978,21 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, +@@ -5741,21 +6107,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. */ @@ -10937,7 +12270,7 @@ index 5a3227568..e78a71728 100644 "nd || nd_ra || nd_rs || mldv1 || mldv2", "next;"); } -@@ -5842,15 +6079,18 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, +@@ -5842,15 +6208,18 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows, actions); } @@ -10964,7 +12297,7 @@ index 5a3227568..e78a71728 100644 } static void -@@ -5914,37 +6154,6 @@ build_qos(struct ovn_datapath *od, struct hmap *lflows) { +@@ -5914,37 +6283,6 @@ build_qos(struct ovn_datapath *od, struct hmap *lflows) { } } @@ -11002,7 +12335,7 @@ index 5a3227568..e78a71728 100644 static void build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, struct ovn_northd_lb *lb) -@@ -5953,11 +6162,20 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, +@@ -5953,11 +6291,20 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, struct ovn_lb_vip *lb_vip = &lb->vips[i]; struct ovn_northd_lb_vip *lb_vip_nb = &lb->vips_nb[i]; @@ -11023,7 +12356,7 @@ index 5a3227568..e78a71728 100644 } const char *proto = NULL; -@@ -5970,12 +6188,17 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, +@@ -5970,12 +6317,17 @@ build_lb_rules(struct ovn_datapath *od, struct hmap *lflows, proto = "sctp"; } } @@ -11044,7 +12377,7 @@ index 5a3227568..e78a71728 100644 struct ds match = DS_EMPTY_INITIALIZER; ds_put_format(&match, "ct.new && %s.dst == %s", ip_match, -@@ -6015,18 +6238,6 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs) +@@ -6015,18 +6367,6 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows, struct hmap *lbs) REGBIT_CONNTRACK_COMMIT" == 1", "ct_commit { ct_label.blocked = 0; }; next;"); @@ -11063,7 +12396,7 @@ index 5a3227568..e78a71728 100644 /* 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 -@@ -6051,40 +6262,50 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows) +@@ -6051,40 +6391,50 @@ build_lb_hairpin(struct ovn_datapath *od, struct hmap *lflows) ovn_lflow_add(lflows, od, S_SWITCH_IN_NAT_HAIRPIN, 0, "1", "next;"); ovn_lflow_add(lflows, od, S_SWITCH_IN_HAIRPIN, 0, "1", "next;"); @@ -11134,7 +12467,263 @@ index 5a3227568..e78a71728 100644 } } -@@ -6754,9 +6975,7 @@ is_vlan_transparent(const struct ovn_datapath *od) +@@ -6371,44 +6721,41 @@ build_lswitch_rport_arp_req_self_orig_flow(struct ovn_port *op, + ds_destroy(&match); + } + +-/* +- * Ingress table 19: Flows that forward ARP/ND requests only to the routers +- * that own the addresses. Other ARP/ND packets are still flooded in the +- * switching domain as regular broadcast. +- */ + static void +-build_lswitch_rport_arp_req_flow_for_ip(struct sset *ips, +- int addr_family, +- struct ovn_port *patch_op, +- struct ovn_datapath *od, +- uint32_t priority, +- struct hmap *lflows, +- const struct ovsdb_idl_row *stage_hint) ++arp_nd_ns_match(struct ds *ips, int addr_family, struct ds *match) + { +- struct ds match = DS_EMPTY_INITIALIZER; +- struct ds actions = DS_EMPTY_INITIALIZER; +- + /* Packets received from VXLAN tunnels have already been through the + * router pipeline so we should skip them. Normally this is done by the + * multicast_group implementation (VXLAN packets skip table 32 which + * delivers to patch ports) but we're bypassing multicast_groups. + */ +- ds_put_cstr(&match, FLAGBIT_NOT_VXLAN " && "); ++ ds_put_cstr(match, FLAGBIT_NOT_VXLAN " && "); + + if (addr_family == AF_INET) { +- ds_put_cstr(&match, "arp.op == 1 && arp.tpa == { "); ++ ds_put_cstr(match, "arp.op == 1 && arp.tpa == {"); + } else { +- ds_put_cstr(&match, "nd_ns && nd.target == { "); ++ ds_put_cstr(match, "nd_ns && nd.target == {"); + } + +- const char *ip_address; +- SSET_FOR_EACH (ip_address, ips) { +- ds_put_format(&match, "%s, ", ip_address); +- } ++ ds_put_cstr(match, ds_cstr_ro(ips)); ++ ds_put_cstr(match, "}"); ++} ++ ++/* ++ * Ingress table 19: Flows that forward ARP/ND requests only to the routers ++ * that own the addresses. Other ARP/ND packets are still flooded in the ++ * switching domain as regular broadcast. ++ */ ++static void ++build_lswitch_rport_arp_req_flow_for_reachable_ip(struct ds *ips, ++ int addr_family, struct ovn_port *patch_op, struct ovn_datapath *od, ++ uint32_t priority, struct hmap *lflows, ++ const struct ovsdb_idl_row *stage_hint) ++{ ++ struct ds match = DS_EMPTY_INITIALIZER; ++ struct ds actions = DS_EMPTY_INITIALIZER; + +- ds_chomp(&match, ' '); +- ds_chomp(&match, ','); +- ds_put_cstr(&match, "}"); ++ arp_nd_ns_match(ips, addr_family, &match); + + /* Send a the packet to the router pipeline. If the switch has non-router + * ports then flood it there as well. +@@ -6431,6 +6778,30 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset *ips, + ds_destroy(&actions); + } + ++/* ++ * Ingress table 19: Flows that forward ARP/ND requests for "unreachable" IPs ++ * (NAT or load balancer IPs configured on a router that are outside the ++ * router's configured subnets). ++ * These ARP/ND packets are flooded in the switching domain as regular ++ * broadcast. ++ */ ++static void ++build_lswitch_rport_arp_req_flow_for_unreachable_ip(struct ds *ips, ++ int addr_family, struct ovn_datapath *od, uint32_t priority, ++ struct hmap *lflows, const struct ovsdb_idl_row *stage_hint) ++{ ++ struct ds match = DS_EMPTY_INITIALIZER; ++ ++ arp_nd_ns_match(ips, addr_family, &match); ++ ++ ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, ++ priority, ds_cstr(&match), ++ "outport = \""MC_FLOOD"\"; output;", ++ stage_hint); ++ ++ ds_destroy(&match); ++} ++ + /* + * Ingress table 19: Flows that forward ARP/ND requests only to the routers + * that own the addresses. +@@ -6457,38 +6828,39 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op, + * router port. + * Priority: 80. + */ +- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); +- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); +- +- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); ++ struct ds ips_v4_match_reachable = DS_EMPTY_INITIALIZER; ++ struct ds ips_v6_match_reachable = DS_EMPTY_INITIALIZER; ++ struct ds ips_v4_match_unreachable = DS_EMPTY_INITIALIZER; ++ struct ds ips_v6_match_unreachable = DS_EMPTY_INITIALIZER; + + const char *ip_addr; +- const char *ip_addr_next; +- SSET_FOR_EACH_SAFE (ip_addr, ip_addr_next, &all_ips_v4) { ++ SSET_FOR_EACH (ip_addr, &op->od->lb_ips_v4) { + ovs_be32 ipv4_addr; + + /* Check if the ovn port has a network configured on which we could + * expect ARP requests for the LB VIP. + */ +- if (ip_parse(ip_addr, &ipv4_addr) && +- lrouter_port_ipv4_reachable(op, ipv4_addr)) { +- continue; ++ if (ip_parse(ip_addr, &ipv4_addr)) { ++ if (lrouter_port_ipv4_reachable(op, ipv4_addr)) { ++ ds_put_format(&ips_v4_match_reachable, "%s, ", ip_addr); ++ } else { ++ ds_put_format(&ips_v4_match_unreachable, "%s, ", ip_addr); ++ } + } +- +- sset_delete(&all_ips_v4, SSET_NODE_FROM_NAME(ip_addr)); + } +- SSET_FOR_EACH_SAFE (ip_addr, ip_addr_next, &all_ips_v6) { ++ SSET_FOR_EACH (ip_addr, &op->od->lb_ips_v6) { + struct in6_addr ipv6_addr; + + /* Check if the ovn port has a network configured on which we could + * expect NS requests for the LB VIP. + */ +- if (ipv6_parse(ip_addr, &ipv6_addr) && +- lrouter_port_ipv6_reachable(op, &ipv6_addr)) { +- continue; ++ if (ipv6_parse(ip_addr, &ipv6_addr)) { ++ if (lrouter_port_ipv6_reachable(op, &ipv6_addr)) { ++ ds_put_format(&ips_v6_match_reachable, "%s, ", ip_addr); ++ } else { ++ ds_put_format(&ips_v6_match_unreachable, "%s, ", ip_addr); ++ } + } +- +- sset_delete(&all_ips_v6, SSET_NODE_FROM_NAME(ip_addr)); + } + + for (size_t i = 0; i < op->od->nbr->n_nat; i++) { +@@ -6509,39 +6881,67 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op, + if (nat_entry_is_v6(nat_entry)) { + struct in6_addr *addr = &nat_entry->ext_addrs.ipv6_addrs[0].addr; + +- if (lrouter_port_ipv6_reachable(op, addr)) { +- sset_add(&all_ips_v6, nat->external_ip); ++ if (!sset_contains(&op->od->lb_ips_v6, nat->external_ip)) { ++ if (lrouter_port_ipv6_reachable(op, addr)) { ++ ds_put_format(&ips_v6_match_reachable, "%s, ", ++ nat->external_ip); ++ } else { ++ ds_put_format(&ips_v6_match_unreachable, "%s, ", ++ nat->external_ip); ++ } + } + } else { + ovs_be32 addr = nat_entry->ext_addrs.ipv4_addrs[0].addr; +- +- if (lrouter_port_ipv4_reachable(op, addr)) { +- sset_add(&all_ips_v4, nat->external_ip); ++ if (!sset_contains(&op->od->lb_ips_v4, nat->external_ip)) { ++ if (lrouter_port_ipv4_reachable(op, addr)) { ++ ds_put_format(&ips_v4_match_reachable, "%s, ", ++ nat->external_ip); ++ } else { ++ ds_put_format(&ips_v4_match_unreachable, "%s, ", ++ nat->external_ip); ++ } + } + } + } + + for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { +- sset_add(&all_ips_v4, op->lrp_networks.ipv4_addrs[i].addr_s); ++ ds_put_format(&ips_v4_match_reachable, "%s, ", ++ op->lrp_networks.ipv4_addrs[i].addr_s); + } + for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { +- sset_add(&all_ips_v6, op->lrp_networks.ipv6_addrs[i].addr_s); +- } +- +- if (!sset_is_empty(&all_ips_v4)) { +- build_lswitch_rport_arp_req_flow_for_ip(&all_ips_v4, AF_INET, sw_op, +- sw_od, 80, lflows, +- stage_hint); ++ ds_put_format(&ips_v6_match_reachable, "%s, ", ++ op->lrp_networks.ipv6_addrs[i].addr_s); ++ } ++ ++ if (ds_last(&ips_v4_match_reachable) != EOF) { ++ ds_chomp(&ips_v4_match_reachable, ' '); ++ ds_chomp(&ips_v4_match_reachable, ','); ++ build_lswitch_rport_arp_req_flow_for_reachable_ip( ++ &ips_v4_match_reachable, AF_INET, sw_op, sw_od, 80, lflows, ++ stage_hint); ++ } ++ if (ds_last(&ips_v6_match_reachable) != EOF) { ++ ds_chomp(&ips_v6_match_reachable, ' '); ++ ds_chomp(&ips_v6_match_reachable, ','); ++ build_lswitch_rport_arp_req_flow_for_reachable_ip( ++ &ips_v6_match_reachable, AF_INET6, sw_op, sw_od, 80, lflows, ++ stage_hint); ++ } ++ ++ if (ds_last(&ips_v4_match_unreachable) != EOF) { ++ ds_chomp(&ips_v4_match_unreachable, ' '); ++ ds_chomp(&ips_v4_match_unreachable, ','); ++ build_lswitch_rport_arp_req_flow_for_unreachable_ip( ++ &ips_v4_match_unreachable, AF_INET, sw_od, 90, lflows, ++ stage_hint); ++ } ++ if (ds_last(&ips_v6_match_unreachable) != EOF) { ++ ds_chomp(&ips_v6_match_unreachable, ' '); ++ ds_chomp(&ips_v6_match_unreachable, ','); ++ build_lswitch_rport_arp_req_flow_for_unreachable_ip( ++ &ips_v6_match_unreachable, AF_INET6, sw_od, 90, lflows, ++ stage_hint); + } +- if (!sset_is_empty(&all_ips_v6)) { +- build_lswitch_rport_arp_req_flow_for_ip(&all_ips_v6, AF_INET6, sw_op, +- sw_od, 80, lflows, +- stage_hint); +- } +- +- sset_destroy(&all_ips_v4); +- sset_destroy(&all_ips_v6); +- + /* Self originated ARP requests/ND need to be flooded as usual. + * + * However, if the switch doesn't have any non-router ports we shouldn't +@@ -6552,6 +6952,10 @@ build_lswitch_rport_arp_req_flows(struct ovn_port *op, + if (sw_od->n_router_ports != sw_od->nbs->n_ports) { + build_lswitch_rport_arp_req_self_orig_flow(op, 75, sw_od, lflows); + } ++ ds_destroy(&ips_v4_match_reachable); ++ ds_destroy(&ips_v6_match_reachable); ++ ds_destroy(&ips_v4_match_unreachable); ++ ds_destroy(&ips_v6_match_unreachable); + } + + static void +@@ -6754,9 +7158,7 @@ is_vlan_transparent(const struct ovn_datapath *od) } static void @@ -11145,7 +12734,7 @@ index 5a3227568..e78a71728 100644 { /* This flow table structure is documented in ovn-northd(8), so please * update ovn-northd.8.xml if you change anything. */ -@@ -6765,49 +6984,127 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -6765,32 +7167,110 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, struct ds actions = DS_EMPTY_INITIALIZER; struct ovn_datapath *od; @@ -11161,13 +12750,6 @@ index 5a3227568..e78a71728 100644 continue; } -- if ((!strcmp(op->nbsp->type, "localnet")) || -- (!strcmp(op->nbsp->type, "vtep"))) { -- ds_clear(&match); -- ds_put_format(&match, "inport == %s", op->json_key); -- ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, -- 100, ds_cstr(&match), "next;", -- &op->nbsp->header_); + ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 0, "1", + "outport = get_fdb(eth.dst); next;"); + @@ -11178,38 +12760,15 @@ index 5a3227568..e78a71728 100644 + } else { + ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 50, + "outport == \"none\"", "drop;"); - } ++ } + ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_UNKNOWN, 0, "1", + "output;"); - } - -- /* Ingress table 13: ARP/ND responder, reply for known IPs. -- * (priority 50). */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbsp) { -- continue; -- } ++ } ++ + ds_destroy(&match); + ds_destroy(&actions); +} - -- if (!strcmp(op->nbsp->type, "virtual")) { -- /* Handle -- * - GARPs for virtual ip which belongs to a logical port -- * of type 'virtual' and bind that port. -- * -- * - ARP reply from the virtual ip which belongs to a logical -- * port of type 'virtual' and bind that port. -- * */ -- ovs_be32 ip; -- const char *virtual_ip = smap_get(&op->nbsp->options, -- "virtual-ip"); -- const char *virtual_parents = smap_get(&op->nbsp->options, -- "virtual-parents"); -- if (!virtual_ip || !virtual_parents || -- !ip_parse(virtual_ip, &ip)) { -- continue; -- } ++ +/* Build pre-ACL and ACL tables for both ingress and egress. + * Ingress tables 3 through 10. Egress tables 0 through 7. */ +static void @@ -11268,17 +12827,27 @@ index 5a3227568..e78a71728 100644 + struct ds *match) +{ + if (op->nbsp) { -+ if ((!strcmp(op->nbsp->type, "localnet")) || -+ (!strcmp(op->nbsp->type, "vtep"))) { + if ((!strcmp(op->nbsp->type, "localnet")) || + (!strcmp(op->nbsp->type, "vtep"))) { +- ds_clear(&match); +- ds_put_format(&match, "inport == %s", op->json_key); + ds_clear(match); + ds_put_format(match, "inport == %s", op->json_key); -+ ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, + ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, +- 100, ds_cstr(&match), "next;", + 100, ds_cstr(match), "next;", -+ &op->nbsp->header_); -+ } -+ } + &op->nbsp->header_); + } + } +} -+ + +- /* Ingress table 13: ARP/ND responder, reply for known IPs. +- * (priority 50). */ +- HMAP_FOR_EACH (op, key_node, ports) { +- if (!op->nbsp) { +- continue; +- } +- +/* Ingress table 13: ARP/ND responder, reply for known IPs. + * (priority 50). */ +static void @@ -11289,27 +12858,19 @@ index 5a3227568..e78a71728 100644 + struct ds *match) +{ + if (op->nbsp) { -+ if (!strcmp(op->nbsp->type, "virtual")) { -+ /* Handle -+ * - GARPs for virtual ip which belongs to a logical port -+ * of type 'virtual' and bind that port. -+ * -+ * - ARP reply from the virtual ip which belongs to a logical -+ * port of type 'virtual' and bind that port. -+ * */ -+ ovs_be32 ip; -+ const char *virtual_ip = smap_get(&op->nbsp->options, -+ "virtual-ip"); -+ const char *virtual_parents = smap_get(&op->nbsp->options, -+ "virtual-parents"); -+ if (!virtual_ip || !virtual_parents || -+ !ip_parse(virtual_ip, &ip)) { + if (!strcmp(op->nbsp->type, "virtual")) { + /* Handle + * - GARPs for virtual ip which belongs to a logical port +@@ -6806,7 +7286,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, + "virtual-parents"); + if (!virtual_ip || !virtual_parents || + !ip_parse(virtual_ip, &ip)) { +- continue; + return; -+ } + } char *tokstr = xstrdup(virtual_parents); - char *save_ptr = NULL; -@@ -6821,21 +7118,21 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -6821,21 +7301,21 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, continue; } @@ -11336,7 +12897,7 @@ index 5a3227568..e78a71728 100644 &vp->nbsp->header_); } -@@ -6850,20 +7147,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -6850,20 +7330,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, if (check_lsp_is_up && !lsp_is_up(op->nbsp) && !lsp_is_router(op->nbsp) && strcmp(op->nbsp->type, "localport")) { @@ -11363,7 +12924,7 @@ index 5a3227568..e78a71728 100644 "eth.dst = eth.src; " "eth.src = %s; " "arp.op = 2; /* ARP reply */ " -@@ -6878,8 +7175,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -6878,8 +7358,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, op->lsp_addrs[i].ipv4_addrs[j].addr_s); ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, @@ -11374,7 +12935,7 @@ index 5a3227568..e78a71728 100644 &op->nbsp->header_); /* Do not reply to an ARP request from the port that owns -@@ -6894,10 +7191,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -6894,10 +7374,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, * address is intended to detect situations where the * network is not working as configured, so dropping the * request would frustrate that intent.) */ @@ -11387,7 +12948,7 @@ index 5a3227568..e78a71728 100644 &op->nbsp->header_); } -@@ -6905,15 +7202,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -6905,15 +7385,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, * unicast IPv6 address and its all-nodes multicast address, * but always respond with the unicast IPv6 address. */ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { @@ -11407,7 +12968,7 @@ index 5a3227568..e78a71728 100644 "%s { " "eth.src = %s; " "ip6.src = %s; " -@@ -6930,93 +7227,99 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -6930,93 +7410,99 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, op->lsp_addrs[i].ea_s); ovn_lflow_add_with_hint(lflows, op->od, S_SWITCH_IN_ARP_ND_RSP, 50, @@ -11564,7 +13125,7 @@ index 5a3227568..e78a71728 100644 } bool is_external = lsp_is_external(op->nbsp); -@@ -7024,7 +7327,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7024,7 +7510,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, !op->nbsp->ha_chassis_group)) { /* If it's an external port and there are no localnet ports * and if it doesn't belong to an HA chassis group ignore it. */ @@ -11573,7 +13134,7 @@ index 5a3227568..e78a71728 100644 } for (size_t i = 0; i < op->n_lsp_addrs; i++) { -@@ -7047,14 +7350,35 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7047,14 +7533,35 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } } @@ -11616,7 +13177,7 @@ index 5a3227568..e78a71728 100644 ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 100, "udp.dst == 53", -@@ -7071,47 +7395,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7071,47 +7578,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 100, dns_match, dns_action); } @@ -11683,7 +13244,7 @@ index 5a3227568..e78a71728 100644 ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 110, "eth.dst == $svc_monitor_mac", -@@ -7120,22 +7430,22 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7120,22 +7613,22 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw; if (mcast_sw_info->enabled) { @@ -11711,7 +13272,7 @@ index 5a3227568..e78a71728 100644 /* Flood all IP multicast traffic destined to 224.0.0.X to all * ports - RFC 4541, section 2.1.2, item 2. -@@ -7157,10 +7467,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7157,10 +7650,10 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, * handled by the L2 multicast flow. */ if (!mcast_sw_info->flood_unregistered) { @@ -11724,7 +13285,7 @@ index 5a3227568..e78a71728 100644 "clone { " "outport = \""MC_MROUTER_FLOOD"\"; " "output; " -@@ -7168,7 +7478,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7168,7 +7661,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } if (mcast_sw_info->flood_static) { @@ -11733,7 +13294,7 @@ index 5a3227568..e78a71728 100644 } /* Explicitly drop the traffic if relay or static flooding -@@ -7176,30 +7486,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7176,30 +7669,33 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, */ if (!mcast_sw_info->flood_relay && !mcast_sw_info->flood_static) { @@ -11778,7 +13339,7 @@ index 5a3227568..e78a71728 100644 struct mcast_switch_info *mcast_sw_info = &igmp_group->datapath->mcast_info.sw; -@@ -7211,57 +7524,62 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7211,57 +7707,62 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovs_be32 group_address = in6_addr_get_mapped_ipv4(&igmp_group->address); if (ip_is_local_multicast(group_address)) { @@ -11856,7 +13417,7 @@ index 5a3227568..e78a71728 100644 /* For ports connected to logical routers add flows to bypass the * broadcast flooding of ARP/ND requests in table 19. We direct the -@@ -7279,15 +7597,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7279,15 +7780,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, struct eth_addr mac; if (ovs_scan(op->nbsp->addresses[i], ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { @@ -11878,7 +13439,7 @@ index 5a3227568..e78a71728 100644 &op->nbsp->header_); } else if (!strcmp(op->nbsp->addresses[i], "unknown")) { if (lsp_is_enabled(op->nbsp)) { -@@ -7300,15 +7618,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7300,15 +7801,15 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { continue; } @@ -11900,7 +13461,7 @@ index 5a3227568..e78a71728 100644 &op->nbsp->header_); } else if (!strcmp(op->nbsp->addresses[i], "router")) { if (!op->peer || !op->peer->nbrp -@@ -7316,8 +7634,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7316,8 +7817,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))) { continue; } @@ -11911,7 +13472,7 @@ index 5a3227568..e78a71728 100644 ETH_ADDR_ARGS(mac)); if (op->peer->od->l3dgw_port && op->peer->od->l3redirect_port -@@ -7343,16 +7661,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7343,16 +7844,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } if (add_chassis_resident_check) { @@ -11932,7 +13493,7 @@ index 5a3227568..e78a71728 100644 &op->nbsp->header_); /* Add ethernet addresses specified in NAT rules on -@@ -7366,19 +7684,19 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7366,19 +7867,19 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, && nat->logical_port && nat->external_mac && eth_addr_from_string(nat->external_mac, &mac)) { @@ -11958,7 +13519,7 @@ index 5a3227568..e78a71728 100644 &op->nbsp->header_); } } -@@ -7392,71 +7710,202 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, +@@ -7392,94 +7893,225 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } } @@ -11978,20 +13539,20 @@ index 5a3227568..e78a71728 100644 - } - } + const struct sbrec_bfd *sb_bt; - -- build_lswitch_output_port_sec(ports, datapaths, lflows); ++ + bool ref; +}; -- ds_destroy(&match); -- ds_destroy(&actions); +- build_lswitch_output_port_sec(ports, datapaths, lflows); +static struct bfd_entry * +bfd_port_lookup(struct hmap *bfd_map, const char *logical_port, + const char *dst_ip) +{ + struct bfd_entry *bfd_e; + uint32_t hash; -+ + +- ds_destroy(&match); +- ds_destroy(&actions); + hash = hash_string(dst_ip, 0); + hash = hash_string(logical_port, hash); + HMAP_FOR_EACH_WITH_HASH (bfd_e, hmap_node, hash, bfd_map) { @@ -12094,9 +13655,9 @@ index 5a3227568..e78a71728 100644 + int min_rx = nb_bt->n_min_rx ? nb_bt->min_rx[0] : BFD_DEF_MINRX; + if (min_rx != sb_bt->min_rx) { + sbrec_bfd_set_min_rx(sb_bt, min_rx); -+ } -+} -+ + } + } + +/* RFC 5881 section 4 + * The source port MUST be in the range 49152 through 65535. + * The same UDP source port number MUST be used for all BFD @@ -12106,20 +13667,36 @@ index 5a3227568..e78a71728 100644 + */ +#define BFD_UDP_SRC_PORT_START 49152 +#define BFD_UDP_SRC_PORT_LEN (65535 - BFD_UDP_SRC_PORT_START) -+ + +-/* Returns a string of the IP address of the router port 'op' that +- * overlaps with 'ip_s". If one is not found, returns NULL. +- * +- * The caller must not free the returned string. */ +-static const char * +-find_lrp_member_ip(const struct ovn_port *op, const char *ip_s) +static int bfd_get_unused_port(unsigned long *bfd_src_ports) -+{ + { +- bool is_ipv4 = strchr(ip_s, '.') ? true : false; + int port; -+ + +- if (is_ipv4) { +- ovs_be32 ip; + port = bitmap_scan(bfd_src_ports, 0, 0, BFD_UDP_SRC_PORT_LEN); + if (port == BFD_UDP_SRC_PORT_LEN) { + return -ENOSPC; - } ++ } + bitmap_set1(bfd_src_ports, port); -+ + +- if (!ip_parse(ip_s, &ip)) { +- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); +- VLOG_WARN_RL(&rl, "bad ip address %s", ip_s); +- return NULL; +- } + return port + BFD_UDP_SRC_PORT_START; - } ++} +- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { +- const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i]; +static void +build_bfd_table(struct northd_context *ctx, struct hmap *bfd_connections, + struct hmap *ports) @@ -12129,7 +13706,8 @@ index 5a3227568..e78a71728 100644 + unsigned long *bfd_src_ports; + struct bfd_entry *bfd_e; + uint32_t hash; -+ + +- if (!((na->network ^ ip) & na->mask)) { + bfd_src_ports = bitmap_allocate(BFD_UDP_SRC_PORT_LEN); + + SBREC_BFD_FOR_EACH (sb_bt, ctx->ovnsb_idl) { @@ -12205,10 +13783,33 @@ index 5a3227568..e78a71728 100644 + + bitmap_free(bfd_src_ports); +} - - /* Returns a string of the IP address of the router port 'op' that - * overlaps with 'ip_s". If one is not found, returns NULL. -@@ -7549,33 +7998,39 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, ++ ++/* Returns a string of the IP address of the router port 'op' that ++ * overlaps with 'ip_s". If one is not found, returns NULL. ++ * ++ * The caller must not free the returned string. */ ++static const char * ++find_lrp_member_ip(const struct ovn_port *op, const char *ip_s) ++{ ++ bool is_ipv4 = strchr(ip_s, '.') ? true : false; ++ ++ if (is_ipv4) { ++ ovs_be32 ip; ++ ++ if (!ip_parse(ip_s, &ip)) { ++ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); ++ VLOG_WARN_RL(&rl, "bad ip address %s", ip_s); ++ return NULL; ++ } ++ ++ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { ++ const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i]; ++ ++ if (!((na->network ^ ip) & na->mask)) { + /* There should be only 1 interface that matches the + * supplied IP. Otherwise, it's a configuration error, + * because subnets of a router's interfaces should NOT +@@ -7549,33 +8181,39 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, struct ds actions = DS_EMPTY_INITIALIZER; if (!strcmp(rule->action, "reroute")) { @@ -12253,7 +13854,7 @@ index 5a3227568..e78a71728 100644 is_ipv4 ? REG_SRC_IPV4 : REG_SRC_IPV6, lrp_addr_s, out_port->lrp_networks.ea_s, -@@ -7588,7 +8043,7 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -7588,7 +8226,7 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, if (pkt_mark) { ds_put_format(&actions, "pkt.mark = %u; ", pkt_mark); } @@ -12262,7 +13863,7 @@ index 5a3227568..e78a71728 100644 } ds_put_format(&match, "%s", rule->match); -@@ -7598,6 +8053,107 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -7598,6 +8236,107 @@ build_routing_policy_flow(struct hmap *lflows, struct ovn_datapath *od, ds_destroy(&actions); } @@ -12370,7 +13971,7 @@ index 5a3227568..e78a71728 100644 struct parsed_route { struct ovs_list list_node; struct in6_addr prefix; -@@ -7619,7 +8175,8 @@ route_hash(struct parsed_route *route) +@@ -7619,7 +8358,8 @@ route_hash(struct parsed_route *route) * Otherwise return NULL. */ static struct parsed_route * parsed_routes_add(struct ovs_list *routes, @@ -12380,7 +13981,7 @@ index 5a3227568..e78a71728 100644 { /* Verify that the next hop is an IP address with an all-ones mask. */ struct in6_addr nexthop; -@@ -7660,6 +8217,25 @@ parsed_routes_add(struct ovs_list *routes, +@@ -7660,6 +8400,25 @@ parsed_routes_add(struct ovs_list *routes, return NULL; } @@ -12406,7 +14007,16 @@ index 5a3227568..e78a71728 100644 struct parsed_route *pr = xzalloc(sizeof *pr); pr->prefix = prefix; pr->plen = plen; -@@ -8102,16 +8678,15 @@ add_route(struct hmap *lflows, const struct ovn_port *op, +@@ -7978,7 +8737,7 @@ add_ecmp_symmetric_reply_flows(struct hmap *lflows, + out_port->lrp_networks.ea_s, + IN6_IS_ADDR_V4MAPPED(&route->prefix) ? "" : "xx", + port_ip, out_port->json_key); +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_ROUTING, 100, ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_ROUTING, 300, + ds_cstr(&match), ds_cstr(&actions), + &st_route->header_); + +@@ -8102,16 +8861,15 @@ add_route(struct hmap *lflows, const struct ovn_port *op, build_route_match(op_inport, network_s, plen, is_src_route, is_ipv4, &match, &priority); @@ -12428,7 +14038,7 @@ index 5a3227568..e78a71728 100644 "%s = %s; " "eth.src = %s; " "outport = %s; " -@@ -8121,11 +8696,20 @@ add_route(struct hmap *lflows, const struct ovn_port *op, +@@ -8121,11 +8879,20 @@ add_route(struct hmap *lflows, const struct ovn_port *op, lrp_addr_s, op->lrp_networks.ea_s, op->json_key); @@ -12449,7 +14059,7 @@ index 5a3227568..e78a71728 100644 ds_destroy(&actions); } -@@ -8203,25 +8787,26 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, +@@ -8203,25 +8970,26 @@ get_force_snat_ip(struct ovn_datapath *od, const char *key_type, return false; } @@ -12483,7 +14093,7 @@ index 5a3227568..e78a71728 100644 const char *proto, struct nbrec_load_balancer *lb, struct shash *meter_groups, struct sset *nat_entries) { -@@ -8230,9 +8815,10 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -8230,9 +8998,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)); @@ -12497,7 +14107,7 @@ index 5a3227568..e78a71728 100644 ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, new_match, new_actions, &lb->header_); free(new_actions); -@@ -8243,11 +8829,12 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -8243,11 +9012,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)); @@ -12514,7 +14124,7 @@ index 5a3227568..e78a71728 100644 } else { ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority, est_match, "ct_dnat;", &lb->header_); -@@ -8320,11 +8907,13 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -8320,11 +9090,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); @@ -12531,7 +14141,7 @@ index 5a3227568..e78a71728 100644 } else { ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120, ds_cstr(&undnat_match), "ct_dnat;", -@@ -8334,6 +8923,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, +@@ -8334,6 +9106,105 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, ds_destroy(&undnat_match); } @@ -12637,7 +14247,7 @@ index 5a3227568..e78a71728 100644 #define ND_RA_MAX_INTERVAL_MAX 1800 #define ND_RA_MAX_INTERVAL_MIN 4 -@@ -8538,14 +9226,12 @@ build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op, +@@ -8538,14 +9409,12 @@ build_lrouter_arp_flow(struct ovn_datapath *od, struct ovn_port *op, "arp.op = 2; /* ARP reply */ " "arp.tha = arp.sha; " "arp.sha = %s; " @@ -12654,7 +14264,7 @@ index 5a3227568..e78a71728 100644 } ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT, priority, -@@ -8788,2375 +9474,2514 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od, +@@ -8788,2375 +9657,2549 @@ build_lrouter_force_snat_flows(struct hmap *lflows, struct ovn_datapath *od, } static void @@ -12667,28 +14277,23 @@ index 5a3227568..e78a71728 100644 { - /* This flow table structure is documented in ovn-northd(8), so please - * update ovn-northd.8.xml if you change anything. */ -+ if (!op->nbrp || !op->peer || !op->od->lb_force_snat_router_ip) { -+ return; -+ } - +- - struct ds match = DS_EMPTY_INITIALIZER; - struct ds actions = DS_EMPTY_INITIALIZER; -+ if (op->lrp_networks.n_ipv4_addrs) { -+ ds_clear(match); -+ ds_clear(actions); - +- - struct ovn_datapath *od; - struct ovn_port *op; -+ ds_put_format(match, "inport == %s && ip4.dst == %s", -+ op->json_key, op->lrp_networks.ipv4_addrs[0].addr_s); -+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_UNSNAT, 110, -+ ds_cstr(match), "ct_snat;"); ++ if (!op->nbrp || !op->peer || !op->od->lb_force_snat_router_ip) { ++ return; ++ } - HMAP_FOR_EACH (od, key_node, datapaths) { - if (!od->nbr) { - continue; - } ++ if (op->lrp_networks.n_ipv4_addrs) { + ds_clear(match); ++ ds_clear(actions); - /* Priority-90-92 flows handle ARP requests and ND packets. Most are - * per logical port but DNAT addresses can be handled per datapath @@ -12700,6 +14305,25 @@ index 5a3227568..e78a71728 100644 - */ - for (int i = 0; i < od->nbr->n_nat; i++) { - struct ovn_nat *nat_entry = &od->nat_entries[i]; ++ ds_put_format(match, "inport == %s && ip4.dst == %s", ++ op->json_key, op->lrp_networks.ipv4_addrs[0].addr_s); ++ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_UNSNAT, 110, ++ ds_cstr(match), "ct_snat;"); + +- /* Skip entries we failed to parse. */ +- if (!nat_entry_is_valid(nat_entry)) { +- continue; +- } ++ ds_clear(match); + +- /* Skip SNAT entries for now, we handle unique SNAT IPs separately +- * below. +- */ +- if (!strcmp(nat_entry->nb->type, "snat")) { +- continue; +- } +- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); +- } + /* Higher priority rules to force SNAT with the router port ip. + * This only takes effect when the packet has already been + * load balanced once. */ @@ -12725,24 +14349,24 @@ index 5a3227568..e78a71728 100644 + ds_clear(match); + ds_clear(actions); -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- continue; -- } +- /* Now handle SNAT entries too, one per unique SNAT IP. */ +- struct shash_node *snat_snode; +- SHASH_FOR_EACH (snat_snode, &od->snat_ips) { +- struct ovn_snat_ip *snat_ip = snat_snode->data; + ds_put_format(match, "inport == %s && ip6.dst == %s", + op->json_key, op->lrp_networks.ipv6_addrs[0].addr_s); + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_UNSNAT, 110, + ds_cstr(match), "ct_snat;"); -- /* Skip SNAT entries for now, we handle unique SNAT IPs separately -- * below. -- */ -- if (!strcmp(nat_entry->nb->type, "snat")) { +- if (ovs_list_is_empty(&snat_ip->snat_entries)) { - continue; - } -- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); + ds_clear(match); -+ + +- struct ovn_nat *nat_entry = +- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), +- struct ovn_nat, ext_addr_list_node); +- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); + /* Higher priority rules to force SNAT with the router port ip. + * This only takes effect when the packet has already been + * load balanced once. */ @@ -12760,13 +14384,14 @@ index 5a3227568..e78a71728 100644 + "balancer", op->json_key, + op->lrp_networks.ipv6_addrs[0].addr_s); } -+ } + } +} -- /* Now handle SNAT entries too, one per unique SNAT IP. */ -- struct shash_node *snat_snode; -- SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -- struct ovn_snat_ip *snat_ip = snat_snode->data; +- /* Logical router ingress table 3: IP Input for IPv4. */ +- HMAP_FOR_EACH (op, key_node, ports) { +- if (!op->nbrp) { +- continue; +- } +static void +build_lrouter_bfd_flows(struct hmap *lflows, struct ovn_port *op) +{ @@ -12774,17 +14399,25 @@ index 5a3227568..e78a71728 100644 + return; + } -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } +- if (op->derived) { +- /* No ingress packets are accepted on a chassisredirect +- * port, so no need to program flows for that port. */ +- continue; +- } + struct ds ip_list = DS_EMPTY_INITIALIZER; + struct ds match = DS_EMPTY_INITIALIZER; -- struct ovn_nat *nat_entry = -- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -- struct ovn_nat, ext_addr_list_node); -- build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); -- } +- if (op->lrp_networks.n_ipv4_addrs) { +- /* L3 admission control: drop packets that originate from an +- * IPv4 address owned by the router or a broadcast address +- * known to the router (priority 100). */ +- ds_clear(&match); +- ds_put_cstr(&match, "ip4.src == "); +- op_put_v4_networks(&match, op, true); +- ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); +- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, +- ds_cstr(&match), "drop;", +- &op->nbrp->header_); + if (op->lrp_networks.n_ipv4_addrs) { + op_put_v4_networks(&ip_list, op, false); + ds_put_format(&match, "ip4.src == %s && udp.dst == 3784", @@ -12798,15 +14431,20 @@ index 5a3227568..e78a71728 100644 + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 110, + ds_cstr(&match), "handle_bfd_msg(); ", + &op->nbrp->header_); - } ++ } + if (op->lrp_networks.n_ipv6_addrs) { + ds_clear(&ip_list); + ds_clear(&match); -- /* Logical router ingress table 3: IP Input for IPv4. */ -- HMAP_FOR_EACH (op, key_node, ports) { -- if (!op->nbrp) { -- continue; +- /* ICMP echo reply. These flows reply to ICMP echo requests +- * received for the router's IP address. Since packets only +- * get here as part of the logical router datapath, the inport +- * (i.e. the incoming locally attached net) does not matter. +- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ +- ds_clear(&match); +- ds_put_cstr(&match, "ip4.dst == "); +- op_put_v4_networks(&match, op, false); +- ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0"); + op_put_v6_networks(&ip_list, op); + ds_put_format(&match, "ip6.src == %s && udp.dst == 3784", + ds_cstr(&ip_list)); @@ -12820,11 +14458,24 @@ index 5a3227568..e78a71728 100644 + ds_cstr(&match), "handle_bfd_msg(); ", + &op->nbrp->header_); + } -+ + +- const char * icmp_actions = "ip4.dst <-> ip4.src; " +- "ip.ttl = 255; " +- "icmp4.type = 0; " +- "flags.loopback = 1; " +- "next; "; +- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, +- ds_cstr(&match), icmp_actions, +- &op->nbrp->header_); +- } + ds_destroy(&ip_list); + ds_destroy(&match); +} -+ + +- /* ICMP time exceeded */ +- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { +- ds_clear(&match); +- ds_clear(&actions); +/* Logical router ingress Table 0: L2 Admission Control + * Generic admission control flows (without inport check). + */ @@ -12839,7 +14490,23 @@ index 5a3227568..e78a71728 100644 + "vlan.present || eth.src[40]", "drop;"); + } +} -+ + +- ds_put_format(&match, +- "inport == %s && ip4 && " +- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); +- ds_put_format(&actions, +- "icmp4 {" +- "eth.dst <-> eth.src; " +- "icmp4.type = 11; /* Time exceeded */ " +- "icmp4.code = 0; /* TTL exceeded in transit */ " +- "ip4.dst = ip4.src; " +- "ip4.src = %s; " +- "ip.ttl = 255; " +- "next; };", +- op->lrp_networks.ipv4_addrs[i].addr_s); +- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, +- ds_cstr(&match), ds_cstr(&actions), +- &op->nbrp->header_); +/* Logical router ingress Table 0: L2 Admission Control + * This table drops packets that the router shouldn’t see at all based + * on their Ethernet headers. @@ -12856,26 +14523,71 @@ index 5a3227568..e78a71728 100644 + return; } - if (op->derived) { -- /* No ingress packets are accepted on a chassisredirect -- * port, so no need to program flows for that port. */ -- continue; +- /* ARP reply. These flows reply to ARP requests for the router's own +- * IP address. */ +- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { +- ds_clear(&match); +- ds_put_format(&match, "arp.spa == %s/%u", +- op->lrp_networks.ipv4_addrs[i].network_s, +- op->lrp_networks.ipv4_addrs[i].plen); +- +- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer +- && op->peer->od->n_localnet_ports) { +- bool add_chassis_resident_check = false; +- if (op == op->od->l3dgw_port) { +- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s +- * should only be sent from the gateway chassis, so that +- * upstream MAC learning points to the gateway chassis. +- * Also need to avoid generation of multiple ARP responses +- * from different chassis. */ +- add_chassis_resident_check = true; +- } else { +- /* Check if the option 'reside-on-redirect-chassis' +- * is set to true on the router port. If set to true +- * and if peer's logical switch has a localnet port, it +- * means the router pipeline for the packets from +- * peer's logical switch is be run on the chassis +- * hosting the gateway port and it should reply to the +- * ARP requests for the router port IPs. +- */ +- add_chassis_resident_check = smap_get_bool( +- &op->nbrp->options, +- "reside-on-redirect-chassis", false); +- } +- +- if (add_chassis_resident_check) { +- ds_put_format(&match, " && is_chassis_resident(%s)", +- op->od->l3redirect_port->json_key); +- } +- } +- +- build_lrouter_arp_flow(op->od, op, +- op->lrp_networks.ipv4_addrs[i].addr_s, +- REG_INPORT_ETH_ADDR, &match, false, 90, +- &op->nbrp->header_, lflows); ++ if (op->derived) { + /* No ingress packets should be received on a chassisredirect + * port. */ + return; } -- if (op->lrp_networks.n_ipv4_addrs) { -- /* L3 admission control: drop packets that originate from an -- * IPv4 address owned by the router or a broadcast address -- * known to the router (priority 100). */ +- /* A set to hold all load-balancer vips that need ARP responses. */ +- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); +- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); +- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); +- +- const char *ip_address; +- SSET_FOR_EACH (ip_address, &all_ips_v4) { - ds_clear(&match); -- ds_put_cstr(&match, "ip4.src == "); -- op_put_v4_networks(&match, op, true); -- ds_put_cstr(&match, " && "REGBIT_EGRESS_LOOPBACK" == 0"); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100, -- ds_cstr(&match), "drop;", -- &op->nbrp->header_); +- if (op == op->od->l3dgw_port) { +- ds_put_format(&match, "is_chassis_resident(%s)", +- op->od->l3redirect_port->json_key); +- } +- +- build_lrouter_arp_flow(op->od, op, +- ip_address, REG_INPORT_ETH_ADDR, +- &match, false, 90, NULL, lflows); +- } + /* 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. @@ -12884,29 +14596,21 @@ index 5a3227568..e78a71728 100644 + ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", + op->lrp_networks.ea_s); -- /* ICMP echo reply. These flows reply to ICMP echo requests -- * received for the router's IP address. Since packets only -- * get here as part of the logical router datapath, the inport -- * (i.e. the incoming locally attached net) does not matter. -- * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ +- SSET_FOR_EACH (ip_address, &all_ips_v6) { - ds_clear(&match); -- ds_put_cstr(&match, "ip4.dst == "); -- op_put_v4_networks(&match, op, false); -- ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0"); +- if (op == op->od->l3dgw_port) { +- ds_put_format(&match, "is_chassis_resident(%s)", +- op->od->l3redirect_port->json_key); +- } + ds_clear(match); + ds_put_format(match, "eth.mcast && inport == %s", op->json_key); + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, + ds_cstr(match), ds_cstr(actions), + &op->nbrp->header_); -- const char * icmp_actions = "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 0; " -- "flags.loopback = 1; " -- "next; "; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -- ds_cstr(&match), icmp_actions, -- &op->nbrp->header_); +- build_lrouter_nd_flow(op->od, op, "nd_na", +- ip_address, NULL, REG_INPORT_ETH_ADDR, +- &match, false, 90, NULL, lflows); + ds_clear(match); + ds_put_format(match, "eth.dst == %s && inport == %s", + op->lrp_networks.ea_s, op->json_key); @@ -12923,46 +14627,76 @@ index 5a3227568..e78a71728 100644 + } +} -- /* ICMP time exceeded */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_clear(&actions); - -- ds_put_format(&match, -- "inport == %s && ip4 && " -- "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); -- ds_put_format(&actions, -- "icmp4 {" -- "eth.dst <-> eth.src; " -- "icmp4.type = 11; /* Time exceeded */ " -- "icmp4.code = 0; /* TTL exceeded in transit */ " -- "ip4.dst = ip4.src; " -- "ip4.src = %s; " -- "ip.ttl = 255; " -- "next; };", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -- ds_cstr(&match), ds_cstr(&actions), -- &op->nbrp->header_); -- } -+/* Logical router ingress Table 1 and 2: Neighbor lookup and learning -+ * lflows for logical routers. */ -+static void -+build_neigh_learning_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct ds *match, struct ds *actions) -+{ -+ if (od->nbr) { - -- /* ARP reply. These flows reply to ARP requests for the router's own -- * IP address. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_put_format(&match, "arp.spa == %s/%u", -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen); +- sset_destroy(&all_ips_v4); +- sset_destroy(&all_ips_v6); +- +- if (!smap_get(&op->od->nbr->options, "chassis") +- && !op->od->l3dgw_port) { +- /* UDP/TCP port unreachable. */ +- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { +- ds_clear(&match); +- ds_put_format(&match, +- "ip4 && ip4.dst == %s && !ip.later_frag && udp", +- op->lrp_networks.ipv4_addrs[i].addr_s); +- const char *action = "icmp4 {" +- "eth.dst <-> eth.src; " +- "ip4.dst <-> ip4.src; " +- "ip.ttl = 255; " +- "icmp4.type = 3; " +- "icmp4.code = 3; " +- "next; };"; +- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, +- 80, ds_cstr(&match), action, +- &op->nbrp->header_); +- +- ds_clear(&match); +- ds_put_format(&match, +- "ip4 && ip4.dst == %s && !ip.later_frag && tcp", +- op->lrp_networks.ipv4_addrs[i].addr_s); +- action = "tcp_reset {" +- "eth.dst <-> eth.src; " +- "ip4.dst <-> ip4.src; " +- "next; };"; +- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, +- 80, ds_cstr(&match), action, +- &op->nbrp->header_); + +- ds_clear(&match); +- ds_put_format(&match, +- "ip4 && ip4.dst == %s && !ip.later_frag", +- op->lrp_networks.ipv4_addrs[i].addr_s); +- action = "icmp4 {" +- "eth.dst <-> eth.src; " +- "ip4.dst <-> ip4.src; " +- "ip.ttl = 255; " +- "icmp4.type = 3; " +- "icmp4.code = 2; " +- "next; };"; +- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, +- 70, ds_cstr(&match), action, +- &op->nbrp->header_); +- } +- } ++/* Logical router ingress Table 1 and 2: Neighbor lookup and learning ++ * lflows for logical routers. */ ++static void ++build_neigh_learning_flows_for_lrouter( ++ struct ovn_datapath *od, struct hmap *lflows, ++ struct ds *match, struct ds *actions) ++{ ++ if (od->nbr) { + +- /* Drop IP traffic destined to router owned IPs except if the IP is +- * also a SNAT IP. Those are dropped later, in stage +- * "lr_in_arp_resolve", if unSNAT was unsuccessful. + /* Learn MAC bindings from ARP/IPv6 ND. -+ * + * +- * Priority 60. +- */ +- build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false, +- lflows); +- +- /* ARP / ND handling for external IP addresses. + * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the + * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp' + * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit. @@ -12971,14 +14705,23 @@ index 5a3227568..e78a71728 100644 + * "lookup_arp_ip" action for ARP request packets, and stores the + * result in REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit; or set that bit + * to "1" directly for ARP response packets. -+ * + * +- * DNAT and SNAT IP addresses are external IP addresses that need ARP +- * handling. + * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup + * for the (nd.target, nd.tll) in the mac binding table using the + * 'lookup_nd' action and stores the result in + * REGBIT_LOOKUP_NEIGHBOR_RESULT bit. If + * "always_learn_from_arp_request" is set to false, + * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT bit is set. -+ * + * +- * These are already taken care globally, per router. The only +- * exception is on the l3dgw_port where we might need to use a +- * different ETH address. +- */ +- if (op != op->od->l3dgw_port) { +- continue; +- } + * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup + * for the (ip6.src, nd.sll) in the mac binding table using the + * 'lookup_nd' action and stores the result in @@ -12995,29 +14738,8 @@ index 5a3227568..e78a71728 100644 + * + * */ -- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer -- && op->peer->od->n_localnet_ports) { -- bool add_chassis_resident_check = false; -- if (op == op->od->l3dgw_port) { -- /* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s -- * should only be sent from the gateway chassis, so that -- * upstream MAC learning points to the gateway chassis. -- * Also need to avoid generation of multiple ARP responses -- * from different chassis. */ -- add_chassis_resident_check = true; -- } else { -- /* Check if the option 'reside-on-redirect-chassis' -- * is set to true on the router port. If set to true -- * and if peer's logical switch has a localnet port, it -- * means the router pipeline for the packets from -- * peer's logical switch is be run on the chassis -- * hosting the gateway port and it should reply to the -- * ARP requests for the router port IPs. -- */ -- add_chassis_resident_check = smap_get_bool( -- &op->nbrp->options, -- "reside-on-redirect-chassis", false); -- } +- for (size_t i = 0; i < op->od->nbr->n_nat; i++) { +- struct ovn_nat *nat_entry = &op->od->nat_entries[i]; + /* Flows for LOOKUP_NEIGHBOR. */ + bool learn_from_arp_request = smap_get_bool(&od->nbr->options, + "always_learn_from_arp_request", true); @@ -13029,10 +14751,9 @@ index 5a3227568..e78a71728 100644 + ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, + "arp.op == 2", ds_cstr(actions)); -- if (add_chassis_resident_check) { -- ds_put_format(&match, " && is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } +- /* Skip entries we failed to parse. */ +- if (!nat_entry_is_valid(nat_entry)) { +- continue; - } + ds_clear(actions); + ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT @@ -13042,11 +14763,30 @@ index 5a3227568..e78a71728 100644 + ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na", + ds_cstr(actions)); -- build_lrouter_arp_flow(op->od, op, -- op->lrp_networks.ipv4_addrs[i].addr_s, -- REG_INPORT_ETH_ADDR, &match, false, 90, -- &op->nbrp->header_, lflows); +- /* Skip SNAT entries for now, we handle unique SNAT IPs separately +- * below. +- */ +- if (!strcmp(nat_entry->nb->type, "snat")) { +- continue; +- } +- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); +- } +- +- /* Now handle SNAT entries too, one per unique SNAT IP. */ +- struct shash_node *snat_snode; +- SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) { +- struct ovn_snat_ip *snat_ip = snat_snode->data; +- +- if (ovs_list_is_empty(&snat_ip->snat_entries)) { +- continue; +- } +- +- struct ovn_nat *nat_entry = +- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), +- struct ovn_nat, ext_addr_list_node); +- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); - } +- } + ds_clear(actions); + ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT + " = lookup_nd(inport, ip6.src, nd.sll); %snext;", @@ -13056,22 +14796,36 @@ index 5a3227568..e78a71728 100644 + ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns", + ds_cstr(actions)); -- /* A set to hold all load-balancer vips that need ARP responses. */ -- struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -- struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -- get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); +- /* NAT, Defrag and load balancing. */ +- HMAP_FOR_EACH (od, key_node, datapaths) { +- if (!od->nbr) { +- continue; +- } + /* For other packet types, we can skip neighbor learning. + * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1", + REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;"); -- const char *ip_address; -- SSET_FOR_EACH (ip_address, &all_ips_v4) { -- ds_clear(&match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(&match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } +- /* 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) { +- continue; +- } + /* Flows for LEARN_NEIGHBOR. */ + /* Skip Neighbor learning if not required. */ + ds_clear(match); @@ -13081,52 +14835,34 @@ index 5a3227568..e78a71728 100644 + ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100, + ds_cstr(match), "next;"); -- build_lrouter_arp_flow(op->od, op, -- ip_address, REG_INPORT_ETH_ADDR, -- &match, false, 90, NULL, lflows); -- } +- struct sset nat_entries = SSET_INITIALIZER(&nat_entries); + ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, + "arp", "put_arp(inport, arp.spa, arp.sha); next;"); -- SSET_FOR_EACH (ip_address, &all_ips_v6) { -- ds_clear(&match); -- if (op == op->od->l3dgw_port) { -- ds_put_format(&match, "is_chassis_resident(%s)", -- op->od->l3redirect_port->json_key); -- } +- 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); + ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, + "nd_na", "put_nd(inport, nd.target, nd.tll); next;"); -- build_lrouter_nd_flow(op->od, op, "nd_na", -- ip_address, NULL, REG_INPORT_ETH_ADDR, -- &match, false, 90, NULL, lflows); -- } +- for (int i = 0; i < od->nbr->n_nat; i++) { +- const struct nbrec_nat *nat; + ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, + "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;"); + } -- sset_destroy(&all_ips_v4); -- sset_destroy(&all_ips_v6); +- nat = od->nbr->nat[i]; +} -- if (!smap_get(&op->od->nbr->options, "chassis") -- && !op->od->l3dgw_port) { -- /* UDP/TCP port unreachable. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag && udp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- const char *action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 3; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(&match), action, -- &op->nbrp->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; +/* Logical router ingress Table 1: Neighbor lookup lflows + * for logical router ports. */ +static void @@ -13136,27 +14872,32 @@ index 5a3227568..e78a71728 100644 +{ + if (op->nbrp) { -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag && tcp", -- op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "tcp_reset {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 80, ds_cstr(&match), action, -- &op->nbrp->header_); +- 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; +- } + bool learn_from_arp_request = smap_get_bool(&op->od->nbr->options, + "always_learn_from_arp_request", true); -- ds_clear(&match); -- ds_put_format(&match, -- "ip4 && ip4.dst == %s && !ip.later_frag", -+ /* Check if we need to learn mac-binding from ARP requests. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ if (!learn_from_arp_request) { -+ /* ARP request to this address should always get learned, +- 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; ++ /* Check if we need to learn mac-binding from ARP requests. */ ++ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { ++ if (!learn_from_arp_request) { ++ /* ARP request to this address should always get learned, + * so add a priority-110 flow to set + * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */ + ds_clear(match); @@ -13166,21 +14907,27 @@ index 5a3227568..e78a71728 100644 + op->json_key, + op->lrp_networks.ipv4_addrs[i].network_s, + op->lrp_networks.ipv4_addrs[i].plen, - op->lrp_networks.ipv4_addrs[i].addr_s); -- action = "icmp4 {" -- "eth.dst <-> eth.src; " -- "ip4.dst <-> ip4.src; " -- "ip.ttl = 255; " -- "icmp4.type = 3; " -- "icmp4.code = 2; " -- "next; };"; -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, -- 70, ds_cstr(&match), action, ++ op->lrp_networks.ipv4_addrs[i].addr_s); + if (op->od->l3dgw_port && op == op->od->l3dgw_port + && op->od->l3redirect_port) { + ds_put_format(match, " && is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); -+ } + } +- /* 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); + const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT + " = lookup_arp(inport, arp.spa, arp.sha); " + REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;" @@ -13188,8 +14935,31 @@ index 5a3227568..e78a71728 100644 + ovn_lflow_add_with_hint(lflows, op->od, + S_ROUTER_IN_LOOKUP_NEIGHBOR, 110, + ds_cstr(match), actions_s, - &op->nbrp->header_); ++ &op->nbrp->header_); } +- 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; +- } +- } 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_clear(match); + ds_put_format(match, + "inport == %s && arp.spa == %s/%u && arp.op == 1", @@ -13200,7 +14970,7 @@ index 5a3227568..e78a71728 100644 + && op->od->l3redirect_port) { + ds_put_format(match, " && is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); -+ } + } + ds_clear(actions); + ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT + " = lookup_arp(inport, arp.spa, arp.sha); %snext;", @@ -13211,18 +14981,26 @@ index 5a3227568..e78a71728 100644 + S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, + ds_cstr(match), ds_cstr(actions), + &op->nbrp->header_); - } ++ } + } +} -- /* Drop IP traffic destined to router owned IPs except if the IP is -- * also a SNAT IP. Those are dropped later, in stage -- * "lr_in_arp_resolve", if unSNAT was unsuccessful. -- * -- * Priority 60. -- */ -- build_lrouter_drop_own_dest(op, S_ROUTER_IN_IP_INPUT, 60, false, -- lflows); +- /* 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; +- } +- } +/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: IPv6 Router + * Adv (RA) options and response. */ +static void @@ -13234,31 +15012,57 @@ index 5a3227568..e78a71728 100644 + return; + } -- /* ARP / ND handling for external IP addresses. -- * -- * DNAT and SNAT IP addresses are external IP addresses that need ARP -- * handling. -- * -- * These are already taken care globally, per router. The only -- * exception is on the l3dgw_port where we might need to use a -- * different ETH address. -- */ -- if (op != op->od->l3dgw_port) { -- continue; -- } +- /* 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;"); +- } + if (!op->lrp_networks.n_ipv6_addrs) { + return; + } -- for (size_t i = 0; i < op->od->nbr->n_nat; i++) { -- struct ovn_nat *nat_entry = &op->od->nat_entries[i]; +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, +- 90, ds_cstr(&match), +- ds_cstr(&actions), +- &nat->header_); +- } else { +- /* Distributed router. */ + struct smap options; + smap_clone(&options, &op->sb->options); -- /* Skip entries we failed to parse. */ -- if (!nat_entry_is_valid(nat_entry)) { -- continue; -- } +- /* 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); +- } + /* enable IPv6 prefix delegation */ + bool prefix_delegation = smap_get_bool(&op->nbrp->options, + "prefix_delegation", false); @@ -13268,14 +15072,12 @@ index 5a3227568..e78a71728 100644 + smap_add(&options, "ipv6_prefix_delegation", + prefix_delegation ? "true" : "false"); -- /* Skip SNAT entries for now, we handle unique SNAT IPs separately -- * below. -- */ -- if (!strcmp(nat_entry->nb->type, "snat")) { -- continue; -- } -- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -- } +- 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;"); +- } + bool ipv6_prefix = smap_get_bool(&op->nbrp->options, + "prefix", false); + if (!lrport_is_enabled(op->nbrp)) { @@ -13285,23 +15087,43 @@ index 5a3227568..e78a71728 100644 + ipv6_prefix ? "true" : "false"); + sbrec_port_binding_set_options(op->sb, &options); -- /* Now handle SNAT entries too, one per unique SNAT IP. */ -- struct shash_node *snat_snode; -- SHASH_FOR_EACH (snat_snode, &op->od->snat_ips) { -- struct ovn_snat_ip *snat_ip = snat_snode->data; +- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT, +- 100, +- ds_cstr(&match), ds_cstr(&actions), +- &nat->header_); +- } +- } + smap_destroy(&options); -- if (ovs_list_is_empty(&snat_ip->snat_entries)) { -- continue; -- } +- /* 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); +- } + const char *address_mode = smap_get( + &op->nbrp->ipv6_ra_configs, "address_mode"); -- struct ovn_nat *nat_entry = -- CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -- struct ovn_nat, ext_addr_list_node); -- build_lrouter_port_nat_arp_nd_flow(op, nat_entry, lflows); -- } +- 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; "); +- } + if (!address_mode) { + return; + } @@ -13312,339 +15134,38 @@ index 5a3227568..e78a71728 100644 + VLOG_WARN_RL(&rl, "Invalid address mode [%s] defined", + address_mode); + return; - } ++ } -- /* NAT, Defrag and load balancing. */ -- HMAP_FOR_EACH (od, key_node, datapaths) { -- if (!od->nbr) { -- continue; -- } -- -- /* 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) { -- 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 { +- ds_put_format(&actions, "flags.loopback = 1; " +- "ct_dnat(%s", nat->logical_ip); + if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", + false)) { + copy_ra_to_sb(op, address_mode); + } -- struct sset nat_entries = SSET_INITIALIZER(&nat_entries); -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", -+ op->json_key); -+ ds_clear(actions); - -- 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); -+ const char *mtu_s = smap_get( -+ &op->nbrp->ipv6_ra_configs, "mtu"); - -- for (int i = 0; i < od->nbr->n_nat; i++) { -- const struct nbrec_nat *nat; -+ /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ -+ uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; - -- nat = od->nbr->nat[i]; -+ ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" -+ "addr_mode = \"%s\", slla = %s", -+ address_mode, op->lrp_networks.ea_s); -+ if (mtu > 0) { -+ ds_put_format(actions, ", mtu = %u", mtu); -+ } - -- 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; -+ const char *prf = smap_get_def( -+ &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); -+ if (strcmp(prf, "MEDIUM")) { -+ ds_put_format(actions, ", router_preference = \"%s\"", prf); -+ } - -- 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; -- } -+ bool add_rs_response_flow = false; - -- 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 (!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; -- } -- } 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; -- } -- } -+ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { -+ continue; -+ } - -- /* 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; -- } -- } -+ ds_put_format(actions, ", prefix = %s/%u", -+ op->lrp_networks.ipv6_addrs[i].network_s, -+ op->lrp_networks.ipv6_addrs[i].plen); - -- /* 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_UNSNAT, -- 90, ds_cstr(&match), -- ds_cstr(&actions), -- &nat->header_); -- } else { -- /* Distributed router. */ -- -- /* 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); -- } -- -- 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_UNSNAT, -- 100, -- ds_cstr(&match), ds_cstr(&actions), -- &nat->header_); -- } -- } -+ add_rs_response_flow = true; -+ } - -- /* 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); -- } -+ if (add_rs_response_flow) { -+ ds_put_cstr(actions, "); next;"); -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, -+ 50, ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ ds_clear(actions); -+ ds_clear(match); -+ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " -+ "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); - -- 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; "); -- } -+ char ip6_str[INET6_ADDRSTRLEN + 1]; -+ struct in6_addr lla; -+ in6_generate_lla(op->lrp_networks.ea, &lla); -+ memset(ip6_str, 0, sizeof(ip6_str)); -+ ipv6_string_mapped(ip6_str, &lla); -+ ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " -+ "ip6.dst = ip6.src; ip6.src = %s; " -+ "outport = inport; flags.loopback = 1; " -+ "output;", -+ op->lrp_networks.ea_s, ip6_str); -+ ovn_lflow_add_with_hint(lflows, op->od, -+ S_ROUTER_IN_ND_RA_RESPONSE, 50, -+ ds_cstr(match), ds_cstr(actions), -+ &op->nbrp->header_); -+ } -+} - -- 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); -+/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS -+ * responder, by default goto next. (priority 0). */ -+static void -+build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) -+{ -+ if (od->nbr) { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); -+ } -+} - - if (nat->external_port_range[0]) { - ds_put_format(&actions, ",%s", - nat->external_port_range); - } - ds_put_format(&actions, ");"); - } -+/* Logical router ingress table IP_ROUTING : IP Routing. -+ * -+ * A packet that arrives at this table is an IP packet that should be -+ * routed to the address in 'ip[46].dst'. -+ * -+ * For regular routes without ECMP, table IP_ROUTING sets outport to the -+ * correct output port, eth.src to the output port's MAC address, and -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -+ * advances to the next table. -+ * -+ * For ECMP routes, i.e. multiple routes with same policy and prefix, table -+ * IP_ROUTING remembers ECMP group id and selects a member id, and advances -+ * to table IP_ROUTING_ECMP, which sets outport, eth.src and -+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. -+ */ -+static void -+build_ip_routing_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows) -+{ -+ if (op->nbrp) { ++ ds_clear(match); ++ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", ++ op->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_); - } else { - /* Distributed router. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen, NULL, false, -+ &op->nbrp->header_); -+ } ++ const char *mtu_s = smap_get( ++ &op->nbrp->ipv6_ra_configs, "mtu"); - /* Traffic received on l3dgw_port is subject to NAT. */ - ds_clear(&match); @@ -13664,14 +15185,8 @@ index 5a3227568..e78a71728 100644 - lrouter_nat_add_ext_ip_match(od, lflows, &match, nat, - is_v6, true, mask); - } -+ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -+ add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, -+ op->lrp_networks.ipv6_addrs[i].network_s, -+ op->lrp_networks.ipv6_addrs[i].plen, NULL, false, -+ &op->nbrp->header_); -+ } -+ } -+} ++ /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ ++ uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; - if (!strcmp(nat->type, "dnat_and_snat") && stateless) { - ds_put_format(&actions, "ip%s.dst=%s; next;", @@ -13684,31 +15199,24 @@ index 5a3227568..e78a71728 100644 - } - ds_put_format(&actions, ");"); - } -+static void -+build_static_route_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, -+ struct hmap *ports, struct hmap *bfd_connections) -+{ -+ if (od->nbr) { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, -+ REG_ECMP_GROUP_ID" == 0", "next;"); ++ ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" ++ "addr_mode = \"%s\", slla = %s", ++ address_mode, op->lrp_networks.ea_s); ++ if (mtu > 0) { ++ ds_put_format(actions, ", mtu = %u", mtu); ++ } - ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, 100, - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } -+ struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); -+ struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); -+ struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); -+ struct ecmp_groups_node *group; -+ for (int i = 0; i < od->nbr->n_static_routes; i++) { -+ struct parsed_route *route = -+ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i], -+ bfd_connections); -+ if (!route) { -+ continue; - } -- +- } ++ const char *prf = smap_get_def( ++ &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); ++ if (strcmp(prf, "MEDIUM")) { ++ ds_put_format(actions, ", router_preference = \"%s\"", prf); ++ } + - /* ARP resolve for NAT IPs. */ - if (od->l3dgw_port) { - if (!strcmp(nat->type, "snat")) { @@ -13721,7 +15229,8 @@ index 5a3227568..e78a71728 100644 - 120, ds_cstr(&match), "next;", - &nat->header_); - } -- ++ bool add_rs_response_flow = false; + - if (!sset_contains(&nat_entries, nat->external_ip)) { - ds_clear(&match); - ds_put_format( @@ -13741,16 +15250,17 @@ index 5a3227568..e78a71728 100644 - &nat->header_); - sset_add(&nat_entries, nat->external_ip); - } -+ group = ecmp_groups_find(&ecmp_groups, route); -+ if (group) { -+ ecmp_groups_add_route(group, route); - } else { +- } 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); - } -- ++ for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { ++ if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { ++ continue; ++ } + - /* 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 @@ -13870,14 +15380,11 @@ index 5a3227568..e78a71728 100644 - nat->external_port_range); - } - ds_put_format(&actions, ");"); -+ const struct parsed_route *existed_route = -+ unique_routes_remove(&unique_routes, route); -+ if (existed_route) { -+ group = ecmp_groups_add(&ecmp_groups, existed_route); -+ if (group) { -+ ecmp_groups_add_route(group, route); - } -- +- } ++ ds_put_format(actions, ", prefix = %s/%u", ++ op->lrp_networks.ipv6_addrs[i].network_s, ++ op->lrp_networks.ipv6_addrs[i].plen); + - /* The priority here is calculated such that the - * nat->logical_ip with the longest mask gets a higher - * priority. */ @@ -13887,7 +15394,9 @@ index 5a3227568..e78a71728 100644 - &nat->header_); - } - } -- ++ add_rs_response_flow = true; ++ } + - /* Logical router ingress table 0: - * For NAT on a distributed router, add rules allowing - * ingress traffic with eth.dst matching nat->external_mac @@ -13901,7 +15410,16 @@ index 5a3227568..e78a71728 100644 - ds_clear(&actions); - ds_put_format(&actions, REG_INPORT_ETH_ADDR " = %s; next;", - od->l3dgw_port->lrp_networks.ea_s); -- ++ if (add_rs_response_flow) { ++ ds_put_cstr(actions, "); next;"); ++ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, ++ 50, ds_cstr(match), ds_cstr(actions), ++ &op->nbrp->header_); ++ ds_clear(actions); ++ ds_clear(match); ++ ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " ++ "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); + - ds_clear(&match); - ds_put_format(&match, - "eth.dst == "ETH_ADDR_FMT" && inport == %s" @@ -13913,7 +15431,23 @@ index 5a3227568..e78a71728 100644 - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } -- ++ char ip6_str[INET6_ADDRSTRLEN + 1]; ++ struct in6_addr lla; ++ in6_generate_lla(op->lrp_networks.ea, &lla); ++ memset(ip6_str, 0, sizeof(ip6_str)); ++ ipv6_string_mapped(ip6_str, &lla); ++ ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " ++ "ip6.dst = ip6.src; ip6.src = %s; " ++ "outport = inport; flags.loopback = 1; " ++ "output;", ++ op->lrp_networks.ea_s, ip6_str); ++ ovn_lflow_add_with_hint(lflows, op->od, ++ S_ROUTER_IN_ND_RA_RESPONSE, 50, ++ ds_cstr(match), ds_cstr(actions), ++ &op->nbrp->header_); ++ } ++} + - /* 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 @@ -13939,7 +15473,17 @@ index 5a3227568..e78a71728 100644 - 100, ds_cstr(&match), - ds_cstr(&actions), &nat->header_); - } -- ++/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS ++ * responder, by default goto next. (priority 0). */ ++static void ++build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) ++{ ++ if (od->nbr) { ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); ++ } ++} + - /* 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 @@ -13955,11 +15499,32 @@ index 5a3227568..e78a71728 100644 - if (!distributed) { - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); - } else { +- } else { - ds_put_format(&match, " && is_chassis_resident(\"%s\")", - nat->logical_port); - } -- ++/* Logical router ingress table IP_ROUTING : IP Routing. ++ * ++ * A packet that arrives at this table is an IP packet that should be ++ * routed to the address in 'ip[46].dst'. ++ * ++ * For regular routes without ECMP, table IP_ROUTING sets outport to the ++ * correct output port, eth.src to the output port's MAC address, and ++ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address ++ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and ++ * advances to the next table. ++ * ++ * For ECMP routes, i.e. multiple routes with same policy and prefix, table ++ * IP_ROUTING remembers ECMP group id and selects a member id, and advances ++ * to table IP_ROUTING_ECMP, which sets outport, eth.src and ++ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. ++ */ ++static void ++build_ip_routing_flows_for_lrouter_port( ++ struct ovn_port *op, struct hmap *ports,struct hmap *lflows) ++{ ++ if (op->nbrp) { + - ds_clear(&actions); - ds_put_format(&actions, - "clone { ct_clear; " @@ -13975,8 +15540,13 @@ index 5a3227568..e78a71728 100644 - ds_cstr(&match), ds_cstr(&actions), - &nat->header_); - } -- } -- ++ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { ++ add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, ++ op->lrp_networks.ipv4_addrs[i].network_s, ++ op->lrp_networks.ipv4_addrs[i].plen, NULL, false, ++ &op->nbrp->header_); + } + - /* Handle force SNAT options set in the gateway router. */ - if (!od->l3dgw_port) { - if (dnat_force_snat_ip) { @@ -13999,9 +15569,8 @@ index 5a3227568..e78a71728 100644 - 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"); -+ unique_routes_add(&unique_routes, route); - } - } +- } +- } - - /* For gateway router, re-circulate every packet through - * the DNAT zone. This helps with the following. @@ -14014,6 +15583,11 @@ index 5a3227568..e78a71728 100644 - * 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;"); ++ for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { ++ add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, ++ op->lrp_networks.ipv6_addrs[i].network_s, ++ op->lrp_networks.ipv6_addrs[i].plen, NULL, false, ++ &op->nbrp->header_); } - - /* Load balancing and packet defrag are only valid on @@ -14021,12 +15595,12 @@ index 5a3227568..e78a71728 100644 - if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) { - sset_destroy(&nat_entries); - continue; -+ HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { -+ /* add a flow in IP_ROUTING, and one flow for each member in -+ * IP_ROUTING_ECMP. */ -+ build_ecmp_route_flow(lflows, od, ports, group); ++ } else if (lsp_is_router(op->nbsp)) { ++ struct ovn_port *peer = ovn_port_get_peer(ports, op); ++ if (!peer || !peer->nbrp || !peer->lrp_networks.n_ipv4_addrs) { ++ return; } -- + - /* A set to hold all ips that need defragmentation and tracking. */ - struct sset all_ips = SSET_INITIALIZER(&all_ips); - @@ -14091,24 +15665,33 @@ index 5a3227568..e78a71728 100644 - proto, lb_vip->vip_port); - prio = 120; - } -- ++ for (int i = 0; i < op->od->n_router_ports; i++) { ++ struct ovn_port *router_port = ovn_port_get_peer( ++ ports, op->od->router_ports[i]); ++ if (!router_port || !router_port->nbrp || router_port == peer) { ++ continue; ++ } + - if (od->l3redirect_port) { - ds_put_format(&match, " && is_chassis_resident(%s)", - od->l3redirect_port->json_key); -- } ++ struct ovn_port_routable_addresses *ra = &router_port->routables; ++ for (size_t j = 0; j < ra->n_addrs; j++) { ++ struct lport_addresses *laddrs = &ra->laddrs[j]; ++ for (size_t k = 0; k < laddrs->n_ipv4_addrs; k++) { ++ add_route(lflows, peer, ++ peer->lrp_networks.ipv4_addrs[0].addr_s, ++ laddrs->ipv4_addrs[k].network_s, ++ laddrs->ipv4_addrs[k].plen, NULL, false, ++ &peer->nbrp->header_); + } - add_router_lb_flow(lflows, od, &match, &actions, prio, - lb_force_snat_ip, lb_vip, proto, - nb_lb, meter_groups, &nat_entries); -- } -+ const struct unique_routes_node *ur; -+ HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { -+ build_static_route_flow(lflows, od, ports, ur->route); + } } - sset_destroy(&all_ips); - sset_destroy(&nat_entries); -+ ecmp_groups_destroy(&ecmp_groups); -+ unique_routes_destroy(&unique_routes); -+ parsed_routes_destroy(&parsed_routes); } - - ds_destroy(&match); @@ -14117,15 +15700,13 @@ index 5a3227568..e78a71728 100644 -/* Logical router ingress Table 0: L2 Admission Control - * Generic admission control flows (without inport check). -+/* IP Multicast lookup. Here we set the output port, adjust TTL and -+ * advance to next table (priority 500). - */ +- */ static void -build_adm_ctrl_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows) -+build_mcast_lookup_flows_for_lrouter( ++build_static_route_flows_for_lrouter( + struct ovn_datapath *od, struct hmap *lflows, -+ struct ds *match, struct ds *actions) ++ struct hmap *ports, struct hmap *bfd_connections) { if (od->nbr) { - /* Logical VLANs not supported. @@ -14134,6 +15715,8 @@ index 5a3227568..e78a71728 100644 - "vlan.present || eth.src[40]", "drop;"); - } -} ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, ++ REG_ECMP_GROUP_ID" == 0", "next;"); -/* Logical router ingress Table 0: L2 Admission Control - * This table drops packets that the router shouldn’t see at all based @@ -14148,51 +15731,49 @@ index 5a3227568..e78a71728 100644 - if (!lrport_is_enabled(op->nbrp)) { - /* Drop packets from disabled logical ports (since logical flow - * tables are default-drop). */ -+ /* Drop IPv6 multicast traffic that shouldn't be forwarded, -+ * i.e., router solicitation and router advertisement. -+ */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, -+ "nd_rs || nd_ra", "drop;"); -+ if (!od->mcast_info.rtr.relay) { - return; +- return; ++ struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); ++ struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); ++ struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); ++ struct ecmp_groups_node *group; ++ for (int i = 0; i < od->nbr->n_static_routes; i++) { ++ struct parsed_route *route = ++ parsed_routes_add(&parsed_routes, od->nbr->static_routes[i], ++ bfd_connections); ++ if (!route) { ++ continue; ++ } ++ group = ecmp_groups_find(&ecmp_groups, route); ++ if (group) { ++ ecmp_groups_add_route(group, route); ++ } else { ++ const struct parsed_route *existed_route = ++ unique_routes_remove(&unique_routes, route); ++ if (existed_route) { ++ group = ecmp_groups_add(&ecmp_groups, existed_route); ++ if (group) { ++ ecmp_groups_add_route(group, route); ++ } ++ } else { ++ unique_routes_add(&unique_routes, route); ++ } ++ } } - +- - if (op->derived) { - /* No ingress packets should be received on a chassisredirect - * port. */ - return; -+ struct ovn_igmp_group *igmp_group; -+ -+ LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { -+ ds_clear(match); -+ ds_clear(actions); -+ if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -+ ds_put_format(match, "ip4 && ip4.dst == %s ", -+ igmp_group->mcgroup.name); -+ } else { -+ ds_put_format(match, "ip6 && ip6.dst == %s ", -+ igmp_group->mcgroup.name); -+ } -+ if (od->mcast_info.rtr.flood_static) { -+ ds_put_cstr(actions, -+ "clone { " -+ "outport = \""MC_STATIC"\"; " -+ "ip.ttl--; " -+ "next; " -+ "};"); -+ } -+ ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", -+ igmp_group->mcgroup.name); -+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, -+ ds_cstr(match), ds_cstr(actions)); ++ HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { ++ /* add a flow in IP_ROUTING, and one flow for each member in ++ * IP_ROUTING_ECMP. */ ++ build_ecmp_route_flow(lflows, od, ports, group); } - +- - /* 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. -+ /* If needed, flood unregistered multicast on statically configured -+ * ports. Otherwise drop any multicast traffic. - */ +- */ - ds_clear(actions); - ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;", - op->lrp_networks.ea_s); @@ -14212,50 +15793,32 @@ index 5a3227568..e78a71728 100644 - * should only be received on the gateway chassis. */ - ds_put_format(match, " && is_chassis_resident(%s)", - op->od->l3redirect_port->json_key); -+ if (od->mcast_info.rtr.flood_static) { -+ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -+ "ip4.mcast || ip6.mcast", -+ "clone { " -+ "outport = \""MC_STATIC"\"; " -+ "ip.ttl--; " -+ "next; " -+ "};"); -+ } else { -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -+ "ip4.mcast || ip6.mcast", "drop;"); ++ const struct unique_routes_node *ur; ++ HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { ++ build_static_route_flow(lflows, od, ports, ur->route); } - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50, - ds_cstr(match), ds_cstr(actions), - &op->nbrp->header_); ++ ecmp_groups_destroy(&ecmp_groups); ++ unique_routes_destroy(&unique_routes); ++ parsed_routes_destroy(&parsed_routes); } } - -/* Logical router ingress Table 1 and 2: Neighbor lookup and learning - * lflows for logical routers. */ -+/* Logical router ingress table POLICY: Policy. -+ * -+ * A packet that arrives at this table is an IP packet that should be -+ * permitted/denied/rerouted to the address in the rule's nexthop. -+ * This table sets outport to the correct out_port, -+ * eth.src to the output port's MAC address, -+ * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -+ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -+ * advances to the next table for ARP/ND resolution. */ ++/* IP Multicast lookup. Here we set the output port, adjust TTL and ++ * advance to next table (priority 500). ++ */ static void -build_neigh_learning_flows_for_lrouter( -+build_ingress_policy_flows_for_lrouter( ++build_mcast_lookup_flows_for_lrouter( struct ovn_datapath *od, struct hmap *lflows, -- struct ds *match, struct ds *actions) -+ struct hmap *ports) + struct ds *match, struct ds *actions) { if (od->nbr) { -+ /* This is a catch-all rule. It has the lowest priority (0) -+ * does a match-all("1") and pass-through (next) */ -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", -+ REG_ECMP_GROUP_ID" = 0; next;"); -+ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150, -+ REG_ECMP_GROUP_ID" == 0", "next;"); - /* Learn MAC bindings from ARP/IPv6 ND. - * @@ -14290,7 +15853,15 @@ index 5a3227568..e78a71728 100644 - * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT is not set. - * - * */ -- ++ /* Drop IPv6 multicast traffic that shouldn't be forwarded, ++ * i.e., router solicitation and router advertisement. ++ */ ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, ++ "nd_rs || nd_ra", "drop;"); ++ if (!od->mcast_info.rtr.relay) { ++ return; ++ } + - /* Flows for LOOKUP_NEIGHBOR. */ - bool learn_from_arp_request = smap_get_bool(&od->nbr->options, - "always_learn_from_arp_request", true); @@ -14301,7 +15872,8 @@ index 5a3227568..e78a71728 100644 - REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); - ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, - "arp.op == 2", ds_cstr(actions)); -- ++ struct ovn_igmp_group *igmp_group; + - ds_clear(actions); - ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT - " = lookup_nd(inport, nd.target, nd.tll); %snext;", @@ -14309,7 +15881,30 @@ index 5a3227568..e78a71728 100644 - REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1; "); - ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na", - ds_cstr(actions)); -- ++ LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { ++ ds_clear(match); ++ ds_clear(actions); ++ if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { ++ ds_put_format(match, "ip4 && ip4.dst == %s ", ++ igmp_group->mcgroup.name); ++ } else { ++ ds_put_format(match, "ip6 && ip6.dst == %s ", ++ igmp_group->mcgroup.name); ++ } ++ if (od->mcast_info.rtr.flood_static) { ++ ds_put_cstr(actions, ++ "clone { " ++ "outport = \""MC_STATIC"\"; " ++ "ip.ttl--; " ++ "next; " ++ "};"); ++ } ++ ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", ++ igmp_group->mcgroup.name); ++ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, ++ ds_cstr(match), ds_cstr(actions)); ++ } + - ds_clear(actions); - ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT - " = lookup_nd(inport, ip6.src, nd.sll); %snext;", @@ -14318,18 +15913,49 @@ index 5a3227568..e78a71728 100644 - " = lookup_nd_ip(inport, ip6.src); "); - ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns", - ds_cstr(actions)); -- ++ /* If needed, flood unregistered multicast on statically configured ++ * ports. Otherwise drop any multicast traffic. ++ */ ++ if (od->mcast_info.rtr.flood_static) { ++ ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, ++ "ip4.mcast || ip6.mcast", ++ "clone { " ++ "outport = \""MC_STATIC"\"; " ++ "ip.ttl--; " ++ "next; " ++ "};"); ++ } else { ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, ++ "ip4.mcast || ip6.mcast", "drop;"); ++ } ++ } ++} + - /* For other packet types, we can skip neighbor learning. - * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */ - ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1", - REGBIT_LOOKUP_NEIGHBOR_RESULT" = 1; next;"); -+ /* Convert routing policies to flows. */ -+ uint16_t ecmp_group_id = 1; -+ for (int i = 0; i < od->nbr->n_policies; i++) { -+ const struct nbrec_logical_router_policy *rule -+ = od->nbr->policies[i]; -+ bool is_ecmp_reroute = -+ (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1); ++/* Logical router ingress table POLICY: Policy. ++ * ++ * A packet that arrives at this table is an IP packet that should be ++ * permitted/denied/rerouted to the address in the rule's nexthop. ++ * This table sets outport to the correct out_port, ++ * eth.src to the output port's MAC address, ++ * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address ++ * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and ++ * advances to the next table for ARP/ND resolution. */ ++static void ++build_ingress_policy_flows_for_lrouter( ++ struct ovn_datapath *od, struct hmap *lflows, ++ struct hmap *ports) ++{ ++ if (od->nbr) { ++ /* This is a catch-all rule. It has the lowest priority (0) ++ * does a match-all("1") and pass-through (next) */ ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", ++ REG_ECMP_GROUP_ID" = 0; next;"); ++ ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY_ECMP, 150, ++ REG_ECMP_GROUP_ID" == 0", "next;"); - /* Flows for LEARN_NEIGHBOR. */ - /* Skip Neighbor learning if not required. */ @@ -14339,6 +15965,16 @@ index 5a3227568..e78a71728 100644 - " || "REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" == 0"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100, - ds_cstr(match), "next;"); ++ /* Convert routing policies to flows. */ ++ uint16_t ecmp_group_id = 1; ++ for (int i = 0; i < od->nbr->n_policies; i++) { ++ const struct nbrec_logical_router_policy *rule ++ = od->nbr->policies[i]; ++ bool is_ecmp_reroute = ++ (!strcmp(rule->action, "reroute") && rule->n_nexthops > 1); + +- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, +- "arp", "put_arp(inport, arp.spa, arp.sha); next;"); + if (is_ecmp_reroute) { + build_ecmp_routing_policy_flows(lflows, od, ports, rule, + ecmp_group_id); @@ -14352,7 +15988,7 @@ index 5a3227568..e78a71728 100644 +} - ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "arp", "put_arp(inport, arp.spa, arp.sha); next;"); +- "nd_na", "put_nd(inport, nd.target, nd.tll); next;"); +/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ +static void +build_arp_resolve_flows_for_lrouter( @@ -14365,16 +16001,44 @@ index 5a3227568..e78a71728 100644 + "ip4.mcast || ip6.mcast", "next;"); - ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "nd_na", "put_nd(inport, nd.target, nd.tll); next;"); +- "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", + "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;"); - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90, -- "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;"); ++ + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", + "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;"); ++ } ++} ++ ++static void ++routable_addresses_to_lflows(struct hmap *lflows, struct ovn_port *router_port, ++ struct ovn_port *peer, struct ds *match, ++ struct ds *actions) ++{ ++ struct ovn_port_routable_addresses *ra = &router_port->routables; ++ if (!ra->n_addrs) { ++ return; } -- + ++ for (size_t i = 0; i < ra->n_addrs; i++) { ++ ds_clear(match); ++ ds_put_format(match, "outport == %s && "REG_NEXT_HOP_IPV4" == {", ++ peer->json_key); ++ bool first = true; ++ for (size_t j = 0; j < ra->laddrs[i].n_ipv4_addrs; j++) { ++ if (!first) { ++ ds_put_cstr(match, ", "); ++ } ++ ds_put_cstr(match, ra->laddrs[i].ipv4_addrs[j].addr_s); ++ first = false; ++ } ++ ds_put_cstr(match, "}"); ++ ++ ds_clear(actions); ++ ds_put_format(actions, "eth.dst = %s; next;", ra->laddrs[i].ea_s); ++ ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE, 100, ++ ds_cstr(match), ds_cstr(actions)); ++ } } -/* Logical router ingress Table 1: Neighbor lookup lflows @@ -14552,17 +16216,15 @@ index 5a3227568..e78a71728 100644 + /* Get the Logical_Router_Port that the + * Logical_Switch_Port is connected to, as + * 'peer'. */ -+ const char *peer_name = smap_get( -+ &op->od->router_ports[k]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { ++ struct ovn_port *peer = ovn_port_get_peer( ++ ports, op->od->router_ports[k]); ++ if (!peer || !peer->nbrp) { + continue; + } - struct smap options; - smap_clone(&options, &op->sb->options); -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { ++ if (!find_lrp_member_ip(peer, ip_s)) { + continue; + } @@ -14574,9 +16236,10 @@ index 5a3227568..e78a71728 100644 - } - smap_add(&options, "ipv6_prefix_delegation", - prefix_delegation ? "true" : "false"); -+ if (!find_lrp_member_ip(peer, ip_s)) { -+ continue; -+ } ++ ds_clear(match); ++ ds_put_format(match, "outport == %s && " ++ REG_NEXT_HOP_IPV4 " == %s", ++ peer->json_key, ip_s); - bool ipv6_prefix = smap_get_bool(&op->nbrp->options, - "prefix", false); @@ -14584,14 +16247,8 @@ index 5a3227568..e78a71728 100644 - ipv6_prefix = false; - } - smap_add(&options, "ipv6_prefix", -- ipv6_prefix ? "true" : "false"); -- sbrec_port_binding_set_options(op->sb, &options); -+ ds_clear(match); -+ ds_put_format(match, "outport == %s && " -+ REG_NEXT_HOP_IPV4 " == %s", -+ peer->json_key, ip_s); - -- smap_destroy(&options); +- ipv6_prefix ? "true" : "false"); +- sbrec_port_binding_set_options(op->sb, &options); + ds_clear(actions); + ds_put_format(actions, "eth.dst = %s; next;", ea_s); + ovn_lflow_add_with_hint(lflows, peer->od, @@ -14602,18 +16259,22 @@ index 5a3227568..e78a71728 100644 + } + } -- const char *address_mode = smap_get( -- &op->nbrp->ipv6_ra_configs, "address_mode"); +- smap_destroy(&options); + for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { + const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; + for (size_t k = 0; k < op->od->n_router_ports; k++) { + /* Get the Logical_Router_Port that the + * Logical_Switch_Port is connected to, as + * 'peer'. */ -+ const char *peer_name = smap_get( -+ &op->od->router_ports[k]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { ++ struct ovn_port *peer = ovn_port_get_peer( ++ ports, op->od->router_ports[k]); ++ if (!peer || !peer->nbrp) { ++ continue; ++ } + +- const char *address_mode = smap_get( +- &op->nbrp->ipv6_ra_configs, "address_mode"); ++ if (!find_lrp_member_ip(peer, ip_s)) { + continue; + } @@ -14628,30 +16289,11 @@ index 5a3227568..e78a71728 100644 - address_mode); - return; - } -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); -+ if (!peer || !peer->nbrp) { -+ continue; -+ } - -- if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", -- false)) { -- copy_ra_to_sb(op, address_mode); -- } -+ if (!find_lrp_member_ip(peer, ip_s)) { -+ continue; -+ } - -- ds_clear(match); -- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", -- op->json_key); -- ds_clear(actions); + ds_clear(match); + ds_put_format(match, "outport == %s && " + REG_NEXT_HOP_IPV6 " == %s", + peer->json_key, ip_s); - -- const char *mtu_s = smap_get( -- &op->nbrp->ipv6_ra_configs, "mtu"); ++ + ds_clear(actions); + ds_put_format(actions, "eth.dst = %s; next;", ea_s); + ovn_lflow_add_with_hint(lflows, peer->od, @@ -14672,9 +16314,7 @@ index 5a3227568..e78a71728 100644 + * resolved by router pipeline using the arp{} action. + * The MAC_Binding entry for the virtual ip might be invalid. */ + ovs_be32 ip; - -- /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ -- uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; ++ + const char *vip = smap_get(&op->nbsp->options, + "virtual-ip"); + const char *virtual_parents = smap_get(&op->nbsp->options, @@ -14683,45 +16323,23 @@ index 5a3227568..e78a71728 100644 + !ip_parse(vip, &ip) || !op->sb) { + return; + } - -- ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" -- "addr_mode = \"%s\", slla = %s", -- address_mode, op->lrp_networks.ea_s); -- if (mtu > 0) { -- ds_put_format(actions, ", mtu = %u", mtu); -- } ++ + if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] || + !op->sb->chassis) { + /* The virtual port is not claimed yet. */ + for (size_t i = 0; i < op->od->n_router_ports; i++) { -+ const char *peer_name = smap_get( -+ &op->od->router_ports[i]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- const char *prf = smap_get_def( -- &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); -- if (strcmp(prf, "MEDIUM")) { -- ds_put_format(actions, ", router_preference = \"%s\"", prf); -- } -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); ++ struct ovn_port *peer = ovn_port_get_peer( ++ ports, op->od->router_ports[i]); + if (!peer || !peer->nbrp) { + continue; + } - -- bool add_rs_response_flow = false; ++ + if (find_lrp_member_ip(peer, vip)) { + ds_clear(match); + ds_put_format(match, "outport == %s && " + REG_NEXT_HOP_IPV4 " == %s", + peer->json_key, vip); - -- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { -- continue; -- } ++ + const char *arp_actions = + "eth.dst = 00:00:00:00:00:00; next;"; + ovn_lflow_add_with_hint(lflows, peer->od, @@ -14738,10 +16356,7 @@ index 5a3227568..e78a71728 100644 + if (!vp || !vp->nbsp) { + return; + } - -- ds_put_format(actions, ", prefix = %s/%u", -- op->lrp_networks.ipv6_addrs[i].network_s, -- op->lrp_networks.ipv6_addrs[i].plen); ++ + for (size_t i = 0; i < vp->n_lsp_addrs; i++) { + bool found_vip_network = false; + const char *ea_s = vp->lsp_addrs[i].ea_s; @@ -14749,65 +16364,31 @@ index 5a3227568..e78a71728 100644 + /* Get the Logical_Router_Port that the + * Logical_Switch_Port is connected to, as + * 'peer'. */ -+ const char *peer_name = smap_get( -+ &vp->od->router_ports[j]->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ continue; -+ } - -- add_rs_response_flow = true; -- } + struct ovn_port *peer = -+ ovn_port_find(ports, peer_name); ++ ovn_port_get_peer(ports, vp->od->router_ports[j]); + if (!peer || !peer->nbrp) { + continue; + } -- if (add_rs_response_flow) { -- ds_put_cstr(actions, "); next;"); -- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, -- 50, ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- ds_clear(actions); -- ds_clear(match); -- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " -- "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); +- if (smap_get_bool(&op->nbrp->ipv6_ra_configs, "send_periodic", +- false)) { +- copy_ra_to_sb(op, address_mode); +- } + if (!find_lrp_member_ip(peer, vip)) { + continue; + } -- char ip6_str[INET6_ADDRSTRLEN + 1]; -- struct in6_addr lla; -- in6_generate_lla(op->lrp_networks.ea, &lla); -- memset(ip6_str, 0, sizeof(ip6_str)); -- ipv6_string_mapped(ip6_str, &lla); -- ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " -- "ip6.dst = ip6.src; ip6.src = %s; " -- "outport = inport; flags.loopback = 1; " -- "output;", -- op->lrp_networks.ea_s, ip6_str); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_ND_RA_RESPONSE, 50, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -- } --} +- ds_clear(match); +- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && nd_rs", +- op->json_key); +- ds_clear(actions); + ds_clear(match); + ds_put_format(match, "outport == %s && " + REG_NEXT_HOP_IPV4 " == %s", + peer->json_key, vip); --/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS -- * responder, by default goto next. (priority 0). */ --static void --build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) --{ -- if (od->nbr) { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); -- } --} +- const char *mtu_s = smap_get( +- &op->nbrp->ipv6_ra_configs, "mtu"); + ds_clear(actions); + ds_put_format(actions, "eth.dst = %s; next;", ea_s); + ovn_lflow_add_with_hint(lflows, peer->od, @@ -14819,27 +16400,8 @@ index 5a3227568..e78a71728 100644 + break; + } --/* Logical router ingress table IP_ROUTING : IP Routing. -- * -- * A packet that arrives at this table is an IP packet that should be -- * routed to the address in 'ip[46].dst'. -- * -- * For regular routes without ECMP, table IP_ROUTING sets outport to the -- * correct output port, eth.src to the output port's MAC address, and -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -- * advances to the next table. -- * -- * For ECMP routes, i.e. multiple routes with same policy and prefix, table -- * IP_ROUTING remembers ECMP group id and selects a member id, and advances -- * to table IP_ROUTING_ECMP, which sets outport, eth.src and -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. -- */ --static void --build_ip_routing_flows_for_lrouter_port( -- struct ovn_port *op, struct hmap *lflows) --{ -- if (op->nbrp) { +- /* As per RFC 2460, 1280 is minimum IPv6 MTU. */ +- uint32_t mtu = (mtu_s && atoi(mtu_s) >= 1280) ? atoi(mtu_s) : 0; + if (found_vip_network) { + break; + } @@ -14848,56 +16410,33 @@ index 5a3227568..e78a71728 100644 + } else if (lsp_is_router(op->nbsp)) { + /* This is a logical switch port that connects to a router. */ -- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -- add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, -- op->lrp_networks.ipv4_addrs[i].network_s, -- op->lrp_networks.ipv4_addrs[i].plen, NULL, false, -- &op->nbrp->header_); +- ds_put_format(actions, REGBIT_ND_RA_OPTS_RESULT" = put_nd_ra_opts(" +- "addr_mode = \"%s\", slla = %s", +- address_mode, op->lrp_networks.ea_s); +- if (mtu > 0) { +- ds_put_format(actions, ", mtu = %u", mtu); +- } + /* The peer of this switch port is the router port for which + * we need to add logical flows such that it can resolve + * ARP entries for all the other router ports connected to + * the switch in question. */ -+ -+ const char *peer_name = smap_get(&op->nbsp->options, -+ "router-port"); -+ if (!peer_name) { -+ return; - } - -- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { -- add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, -- op->lrp_networks.ipv6_addrs[i].network_s, -- op->lrp_networks.ipv6_addrs[i].plen, NULL, false, -- &op->nbrp->header_); -+ struct ovn_port *peer = ovn_port_find(ports, peer_name); ++ struct ovn_port *peer = ovn_port_get_peer(ports, op); + if (!peer || !peer->nbrp) { + return; - } -- } --} ++ } --static void --build_static_route_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports) --{ -- if (od->nbr) { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, -- REG_ECMP_GROUP_ID" == 0", "next;"); +- const char *prf = smap_get_def( +- &op->nbrp->ipv6_ra_configs, "router_preference", "MEDIUM"); +- if (strcmp(prf, "MEDIUM")) { +- ds_put_format(actions, ", router_preference = \"%s\"", prf); +- } + if (peer->od->nbr && + smap_get_bool(&peer->od->nbr->options, + "dynamic_neigh_routers", false)) { + return; + } -- struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); -- struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); -- struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); -- struct ecmp_groups_node *group; -- for (int i = 0; i < od->nbr->n_static_routes; i++) { -- struct parsed_route *route = -- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]); -- if (!route) { +- bool add_rs_response_flow = false; + for (size_t i = 0; i < op->od->n_router_ports; i++) { + const char *router_port_name = smap_get( + &op->od->router_ports[i]->nbsp->options, @@ -14905,52 +16444,21 @@ index 5a3227568..e78a71728 100644 + struct ovn_port *router_port = ovn_port_find(ports, + router_port_name); + if (!router_port || !router_port->nbrp) { - continue; - } -- group = ecmp_groups_find(&ecmp_groups, route); -- if (group) { -- ecmp_groups_add_route(group, route); -- } else { -- const struct parsed_route *existed_route = -- unique_routes_remove(&unique_routes, route); -- if (existed_route) { -- group = ecmp_groups_add(&ecmp_groups, existed_route); -- if (group) { -- ecmp_groups_add_route(group, route); -- } -- } else { -- unique_routes_add(&unique_routes, route); -- } -+ ++ continue; ++ } + +- for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { +- if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].network)) { +- continue; +- } + /* Skip the router port under consideration. */ + if (router_port == peer) { + continue; - } -- } -- HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { -- /* add a flow in IP_ROUTING, and one flow for each member in -- * IP_ROUTING_ECMP. */ -- build_ecmp_route_flow(lflows, od, ports, group); -- } -- const struct unique_routes_node *ur; -- HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { -- build_static_route_flow(lflows, od, ports, ur->route); -- } -- ecmp_groups_destroy(&ecmp_groups); -- unique_routes_destroy(&unique_routes); -- parsed_routes_destroy(&parsed_routes); -- } --} ++ } --/* IP Multicast lookup. Here we set the output port, adjust TTL and -- * advance to next table (priority 500). -- */ --static void --build_mcast_lookup_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows, -- struct ds *match, struct ds *actions) --{ -- if (od->nbr) { +- ds_put_format(actions, ", prefix = %s/%u", +- op->lrp_networks.ipv6_addrs[i].network_s, +- op->lrp_networks.ipv6_addrs[i].plen); + if (router_port->lrp_networks.n_ipv4_addrs) { + ds_clear(match); + ds_put_format(match, "outport == %s && " @@ -14958,14 +16466,8 @@ index 5a3227568..e78a71728 100644 + peer->json_key); + op_put_v4_networks(match, router_port, false); -- /* Drop IPv6 multicast traffic that shouldn't be forwarded, -- * i.e., router solicitation and router advertisement. -- */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, -- "nd_rs || nd_ra", "drop;"); -- if (!od->mcast_info.rtr.relay) { -- return; -- } +- add_rs_response_flow = true; +- } + ds_clear(actions); + ds_put_format(actions, "eth.dst = %s; next;", + router_port->lrp_networks.ea_s); @@ -14975,7 +16477,15 @@ index 5a3227568..e78a71728 100644 + &op->nbsp->header_); + } -- struct ovn_igmp_group *igmp_group; +- if (add_rs_response_flow) { +- ds_put_cstr(actions, "); next;"); +- ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ND_RA_OPTIONS, +- 50, ds_cstr(match), ds_cstr(actions), +- &op->nbrp->header_); +- ds_clear(actions); +- ds_clear(match); +- ds_put_format(match, "inport == %s && ip6.dst == ff02::2 && " +- "nd_ra && "REGBIT_ND_RA_OPTS_RESULT, op->json_key); + if (router_port->lrp_networks.n_ipv6_addrs) { + ds_clear(match); + ds_put_format(match, "outport == %s && " @@ -14983,23 +16493,22 @@ index 5a3227568..e78a71728 100644 + peer->json_key); + op_put_v6_networks(match, router_port); -- LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { -- ds_clear(match); -- ds_clear(actions); -- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { -- ds_put_format(match, "ip4 && ip4.dst == %s ", -- igmp_group->mcgroup.name); -- } else { -- ds_put_format(match, "ip6 && ip6.dst == %s ", -- igmp_group->mcgroup.name); -- } -- if (od->mcast_info.rtr.flood_static) { -- ds_put_cstr(actions, -- "clone { " -- "outport = \""MC_STATIC"\"; " -- "ip.ttl--; " -- "next; " -- "};"); +- char ip6_str[INET6_ADDRSTRLEN + 1]; +- struct in6_addr lla; +- in6_generate_lla(op->lrp_networks.ea, &lla); +- memset(ip6_str, 0, sizeof(ip6_str)); +- ipv6_string_mapped(ip6_str, &lla); +- ds_put_format(actions, "eth.dst = eth.src; eth.src = %s; " +- "ip6.dst = ip6.src; ip6.src = %s; " +- "outport = inport; flags.loopback = 1; " +- "output;", +- op->lrp_networks.ea_s, ip6_str); +- ovn_lflow_add_with_hint(lflows, op->od, +- S_ROUTER_IN_ND_RA_RESPONSE, 50, +- ds_cstr(match), ds_cstr(actions), +- &op->nbrp->header_); +- } +-} + ds_clear(actions); + ds_put_format(actions, "eth.dst = %s; next;", + router_port->lrp_networks.ea_s); @@ -15007,77 +16516,80 @@ index 5a3227568..e78a71728 100644 + S_ROUTER_IN_ARP_RESOLVE, 100, + ds_cstr(match), ds_cstr(actions), + &op->nbsp->header_); - } -- ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", -- igmp_group->mcgroup.name); -- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, -- ds_cstr(match), ds_cstr(actions)); -- } -- -- /* If needed, flood unregistered multicast on statically configured -- * ports. Otherwise drop any multicast traffic. -- */ -- if (od->mcast_info.rtr.flood_static) { -- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -- "ip4.mcast || ip6.mcast", -- "clone { " -- "outport = \""MC_STATIC"\"; " -- "ip.ttl--; " -- "next; " -- "};"); -- } else { -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, -- "ip4.mcast || ip6.mcast", "drop;"); - } ++ } + +-/* Logical router ingress table ND_RA_OPTIONS & ND_RA_RESPONSE: RS +- * responder, by default goto next. (priority 0). */ +-static void +-build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) +-{ +- if (od->nbr) { +- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_OPTIONS, 0, "1", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_IN_ND_RA_RESPONSE, 0, "1", "next;"); ++ if (smap_get(&peer->od->nbr->options, "chassis") || ++ (peer->od->l3dgw_port && peer == peer->od->l3dgw_port)) { ++ routable_addresses_to_lflows(lflows, router_port, peer, ++ match, actions); ++ } ++ } } + } --/* Logical router ingress table POLICY: Policy. +-/* Logical router ingress table IP_ROUTING : IP Routing. +/* Local router ingress table CHK_PKT_LEN: Check packet length. * - * A packet that arrives at this table is an IP packet that should be -- * permitted/denied/rerouted to the address in the rule's nexthop. -- * This table sets outport to the correct out_port, -- * eth.src to the output port's MAC address, -- * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address -- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and -- * advances to the next table for ARP/ND resolution. */ +- * routed to the address in 'ip[46].dst'. + * Any IPv4 packet with outport set to the distributed gateway + * router port, check the packet length and store the result in the + * 'REGBIT_PKT_LARGER' register bit. -+ * + * +- * For regular routes without ECMP, table IP_ROUTING sets outport to the +- * correct output port, eth.src to the output port's MAC address, and +- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address +- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and +- * advances to the next table. + * Local router ingress table LARGER_PKTS: Handle larger packets. -+ * + * +- * For ECMP routes, i.e. multiple routes with same policy and prefix, table +- * IP_ROUTING remembers ECMP group id and selects a member id, and advances +- * to table IP_ROUTING_ECMP, which sets outport, eth.src and +- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 for the selected ECMP member. +- */ + * Any IPv4 packet with outport set to the distributed gateway + * router port and the 'REGBIT_PKT_LARGER' register bit is set, + * generate ICMPv4 packet with type 3 (Destination Unreachable) and + * code 4 (Fragmentation needed). + * */ static void --build_ingress_policy_flows_for_lrouter( +-build_ip_routing_flows_for_lrouter_port( +- struct ovn_port *op, struct hmap *lflows) +build_check_pkt_len_flows_for_lrouter( - struct ovn_datapath *od, struct hmap *lflows, -- struct hmap *ports) ++ struct ovn_datapath *od, struct hmap *lflows, + struct hmap *ports, + struct ds *match, struct ds *actions) { - if (od->nbr) { -- /* This is a catch-all rule. It has the lowest priority (0) -- * does a match-all("1") and pass-through (next) */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", "next;"); +- if (op->nbrp) { ++ if (od->nbr) { -- /* Convert routing policies to flows. */ -- for (int i = 0; i < od->nbr->n_policies; i++) { -- const struct nbrec_logical_router_policy *rule -- = od->nbr->policies[i]; -- build_routing_policy_flow(lflows, od, ports, rule, &rule->header_); +- for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { +- add_route(lflows, op, op->lrp_networks.ipv4_addrs[i].addr_s, +- op->lrp_networks.ipv4_addrs[i].network_s, +- op->lrp_networks.ipv4_addrs[i].plen, NULL, false, +- &op->nbrp->header_); +- } + /* Packets are allowed by default. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 0, "1", + "next;"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1", + "next;"); -+ + +- for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { +- add_route(lflows, op, op->lrp_networks.ipv6_addrs[i].addr_s, +- op->lrp_networks.ipv6_addrs[i].network_s, +- op->lrp_networks.ipv6_addrs[i].plen, NULL, false, +- &op->nbrp->header_); + if (od->l3dgw_port && od->l3redirect_port) { + int gw_mtu = 0; + if (od->l3dgw_port->nbrp) { @@ -15169,7 +16681,6 @@ index 5a3227568..e78a71728 100644 } } --/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ +/* Logical router ingress table GW_REDIRECT: Gateway redirect. + * + * For traffic with outport equal to the l3dgw_port @@ -15178,28 +16689,46 @@ index 5a3227568..e78a71728 100644 + * the central instance of the l3dgw_port. + */ static void --build_arp_resolve_flows_for_lrouter( -- struct ovn_datapath *od, struct hmap *lflows) +-build_static_route_flows_for_lrouter( +build_gateway_redirect_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, + struct ovn_datapath *od, struct hmap *lflows, +- struct hmap *ports) + struct ds *match, struct ds *actions) { if (od->nbr) { -- /* Multicast packets already have the outport set so just advance to -- * next table (priority 500). */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500, -- "ip4.mcast || ip6.mcast", "next;"); +- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_ECMP, 150, +- REG_ECMP_GROUP_ID" == 0", "next;"); + if (od->l3dgw_port && od->l3redirect_port) { + const struct ovsdb_idl_row *stage_hint = NULL; -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", -- "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;"); +- struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); +- struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); +- struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); +- struct ecmp_groups_node *group; +- for (int i = 0; i < od->nbr->n_static_routes; i++) { +- struct parsed_route *route = +- parsed_routes_add(&parsed_routes, od->nbr->static_routes[i]); +- if (!route) { +- continue; +- } +- group = ecmp_groups_find(&ecmp_groups, route); +- if (group) { +- ecmp_groups_add_route(group, route); +- } else { +- const struct parsed_route *existed_route = +- unique_routes_remove(&unique_routes, route); +- if (existed_route) { +- group = ecmp_groups_add(&ecmp_groups, existed_route); +- if (group) { +- ecmp_groups_add_route(group, route); +- } +- } else { +- unique_routes_add(&unique_routes, route); +- } + if (od->l3dgw_port->nbrp) { + stage_hint = &od->l3dgw_port->nbrp->header_; -+ } - -- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", -- "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;"); + } ++ + /* For traffic with outport == l3dgw_port, if the + * packet did not match any higher priority redirect + * rule, then the traffic is redirected to the central @@ -15213,57 +16742,53 @@ index 5a3227568..e78a71728 100644 + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50, + ds_cstr(match), ds_cstr(actions), + stage_hint); -+ } + } +- HMAP_FOR_EACH (group, hmap_node, &ecmp_groups) { +- /* add a flow in IP_ROUTING, and one flow for each member in +- * IP_ROUTING_ECMP. */ +- build_ecmp_route_flow(lflows, od, ports, group); +- } +- const struct unique_routes_node *ur; +- HMAP_FOR_EACH (ur, hmap_node, &unique_routes) { +- build_static_route_flow(lflows, od, ports, ur->route); +- } +- ecmp_groups_destroy(&ecmp_groups); +- unique_routes_destroy(&unique_routes); +- parsed_routes_destroy(&parsed_routes); + + /* Packets are allowed by default. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_GW_REDIRECT, 0, "1", "next;"); } } --/* Local router ingress table ARP_RESOLVE: ARP Resolution. -+/* Local router ingress table ARP_REQUEST: ARP request. - * -- * Any unicast packet that reaches this table is an IP packet whose -- * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 -- * (ip4.dst/ipv6.dst is the final destination). -- * This table resolves the IP address in -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and -- * an Ethernet address in eth.dst. +-/* IP Multicast lookup. Here we set the output port, adjust TTL and +- * advance to next table (priority 500). - */ ++/* Local router ingress table ARP_REQUEST: ARP request. ++ * + * In the common case where the Ethernet destination has been resolved, + * this table outputs the packet (priority 0). Otherwise, it composes + * and sends an ARP/IPv6 NA request (priority 100). */ static void --build_arp_resolve_flows_for_lrouter_port( -- struct ovn_port *op, struct hmap *lflows, -- struct hmap *ports, +-build_mcast_lookup_flows_for_lrouter( +build_arp_request_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows, + struct ovn_datapath *od, struct hmap *lflows, struct ds *match, struct ds *actions) { -- if (op->nbsp && !lsp_is_enabled(op->nbsp)) { -- return; -- } -+ if (od->nbr) { + if (od->nbr) { + for (int i = 0; i < od->nbr->n_static_routes; i++) { + const struct nbrec_logical_router_static_route *route; -- if (op->nbrp) { -- /* This is a logical router port. If next-hop IP address in -- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this -- * router port, then the packet is intended to eventually be sent -- * to this logical port. Set the destination mac address using -- * this port's mac address. -- * -- * The packet is still in peer's logical pipeline. So the match -- * should be on peer's outport. */ -- if (op->peer && op->nbrp->peer) { -- if (op->lrp_networks.n_ipv4_addrs) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 "== ", -- op->peer->json_key); -- op_put_v4_networks(match, op, false); +- /* Drop IPv6 multicast traffic that shouldn't be forwarded, +- * i.e., router solicitation and router advertisement. +- */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 550, +- "nd_rs || nd_ra", "drop;"); +- if (!od->mcast_info.rtr.relay) { +- return; +- } +- +- struct ovn_igmp_group *igmp_group; + route = od->nbr->static_routes[i]; + struct in6_addr gw_ip6; + unsigned int plen; @@ -15273,14 +16798,8 @@ index 5a3227568..e78a71728 100644 + continue; + } -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, op->peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), -- &op->nbrp->header_); -+ ds_clear(match); +- LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) { + ds_clear(match); + ds_put_format(match, "eth.dst == 00:00:00:00:00:00 && " + "ip6 && " REG_NEXT_HOP_IPV6 " == %s", + route->nexthop); @@ -15292,7 +16811,27 @@ index 5a3227568..e78a71728 100644 + char sn_addr_s[INET6_ADDRSTRLEN + 1]; + ipv6_string_mapped(sn_addr_s, &sn_addr); + -+ ds_clear(actions); + ds_clear(actions); +- if (IN6_IS_ADDR_V4MAPPED(&igmp_group->address)) { +- ds_put_format(match, "ip4 && ip4.dst == %s ", +- igmp_group->mcgroup.name); +- } else { +- ds_put_format(match, "ip6 && ip6.dst == %s ", +- igmp_group->mcgroup.name); +- } +- if (od->mcast_info.rtr.flood_static) { +- ds_put_cstr(actions, +- "clone { " +- "outport = \""MC_STATIC"\"; " +- "ip.ttl--; " +- "next; " +- "};"); +- } +- ds_put_format(actions, "outport = \"%s\"; ip.ttl--; next;", +- igmp_group->mcgroup.name); +- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 500, +- ds_cstr(match), ds_cstr(actions)); +- } + ds_put_format(actions, + "nd_ns { " + "eth.dst = "ETH_ADDR_FMT"; " @@ -15301,11 +16840,25 @@ index 5a3227568..e78a71728 100644 + "output; " + "};", ETH_ADDR_ARGS(eth_dst), sn_addr_s, + route->nexthop); -+ + +- /* If needed, flood unregistered multicast on statically configured +- * ports. Otherwise drop any multicast traffic. +- */ +- if (od->mcast_info.rtr.flood_static) { +- ovn_lflow_add_unique(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, +- "ip4.mcast || ip6.mcast", +- "clone { " +- "outport = \""MC_STATIC"\"; " +- "ip.ttl--; " +- "next; " +- "};"); +- } else { +- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450, +- "ip4.mcast || ip6.mcast", "drop;"); + ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ARP_REQUEST, 200, + ds_cstr(match), ds_cstr(actions), + &route->header_); -+ } + } + + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 100, + "eth.dst == 00:00:00:00:00:00 && ip4", @@ -15323,28 +16876,48 @@ index 5a3227568..e78a71728 100644 + "output; " + "};"); + ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_REQUEST, 0, "1", "output;"); -+ } -+} -+ + } + } + +-/* Logical router ingress table POLICY: Policy. +/* Logical router egress table DELIVERY: Delivery (priority 100-110). -+ * + * +- * A packet that arrives at this table is an IP packet that should be +- * permitted/denied/rerouted to the address in the rule's nexthop. +- * This table sets outport to the correct out_port, +- * eth.src to the output port's MAC address, +- * and REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 to the next-hop IP address +- * (leaving 'ip[46].dst', the packet’s final destination, unchanged), and +- * advances to the next table for ARP/ND resolution. */ + * Priority 100 rules deliver packets to enabled logical ports. + * Priority 110 rules match multicast packets and update the source + * mac before delivering to enabled logical ports. IP multicast traffic + * bypasses S_ROUTER_IN_IP_ROUTING route lookups. + */ -+static void + static void +-build_ingress_policy_flows_for_lrouter( +- struct ovn_datapath *od, struct hmap *lflows, +- struct hmap *ports) +build_egress_delivery_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, + struct ds *match, struct ds *actions) -+{ + { +- if (od->nbr) { +- /* This is a catch-all rule. It has the lowest priority (0) +- * does a match-all("1") and pass-through (next) */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_POLICY, 0, "1", "next;"); + if (op->nbrp) { + if (!lrport_is_enabled(op->nbrp)) { + /* Drop packets to disabled logical ports (since logical flow + * tables are default-drop). */ + return; + } -+ + +- /* Convert routing policies to flows. */ +- for (int i = 0; i < od->nbr->n_policies; i++) { +- const struct nbrec_logical_router_policy *rule +- = od->nbr->policies[i]; +- build_routing_policy_flow(lflows, od, ports, rule, &rule->header_); + if (op->derived) { + /* No egress packets should be processed in the context of + * a chassisredirect port. The chassisredirect port should @@ -15365,21 +16938,27 @@ index 5a3227568..e78a71728 100644 + op->lrp_networks.ea_s); + ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 110, + ds_cstr(match), ds_cstr(actions)); -+ } + } + + ds_clear(match); + ds_put_format(match, "outport == %s", op->json_key); + ovn_lflow_add(lflows, op->od, S_ROUTER_OUT_DELIVERY, 100, + ds_cstr(match), "output;"); -+ } -+ -+} + } + -+static void + } + +-/* Local router ingress table ARP_RESOLVE: ARP Resolution. */ + static void +-build_arp_resolve_flows_for_lrouter( +build_misc_local_traffic_drop_flows_for_lrouter( -+ struct ovn_datapath *od, struct hmap *lflows) -+{ -+ if (od->nbr) { + struct ovn_datapath *od, struct hmap *lflows) + { + if (od->nbr) { +- /* Multicast packets already have the outport set so just advance to +- * next table (priority 500). */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500, +- "ip4.mcast || ip6.mcast", "next;"); + /* L3 admission control: drop multicast and broadcast source, localhost + * source or destination, and zero network source or destination + * (priority 100). */ @@ -15391,7 +16970,9 @@ index 5a3227568..e78a71728 100644 + "ip4.src == 0.0.0.0/8 || " + "ip4.dst == 0.0.0.0/8", + "drop;"); -+ + +- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4", +- "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;"); + /* Drop ARP packets (priority 85). ARP request packets for router's own + * IPs are handled with priority-90 flows. + * Drop IPv6 ND packets (priority 85). ND NA packets for router's own @@ -15399,7 +16980,9 @@ index 5a3227568..e78a71728 100644 + */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85, + "arp || nd", "drop;"); -+ + +- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6", +- "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;"); + /* Allow IPv6 multicast traffic that's supposed to reach the + * router pipeline (e.g., router solicitations). + */ @@ -15427,14 +17010,28 @@ index 5a3227568..e78a71728 100644 + /* Pass other traffic not already handled to the next table for + * routing. */ + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); -+ } -+} -+ -+static void + } + } + +-/* Local router ingress table ARP_RESOLVE: ARP Resolution. +- * +- * Any unicast packet that reaches this table is an IP packet whose +- * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 +- * (ip4.dst/ipv6.dst is the final destination). +- * This table resolves the IP address in +- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and +- * an Ethernet address in eth.dst. +- */ + static void +-build_arp_resolve_flows_for_lrouter_port( +build_dhcpv6_reply_flows_for_lrouter_port( -+ struct ovn_port *op, struct hmap *lflows, + struct ovn_port *op, struct hmap *lflows, +- struct hmap *ports, +- struct ds *match, struct ds *actions) + struct ds *match) -+{ + { +- if (op->nbsp && !lsp_is_enabled(op->nbsp)) { +- return; + if (op->nbrp && (!op->derived)) { + for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + ds_clear(match); @@ -15445,10 +17042,34 @@ index 5a3227568..e78a71728 100644 + ds_cstr(match), + "reg0 = 0; handle_dhcpv6_reply;"); + } -+ } -+ + } + +- if (op->nbrp) { +- /* This is a logical router port. If next-hop IP address in +- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this +- * router port, then the packet is intended to eventually be sent +- * to this logical port. Set the destination mac address using +- * this port's mac address. +- * +- * The packet is still in peer's logical pipeline. So the match +- * should be on peer's outport. */ +- if (op->peer && op->nbrp->peer) { +- if (op->lrp_networks.n_ipv4_addrs) { +- ds_clear(match); +- ds_put_format(match, "outport == %s && " +- REG_NEXT_HOP_IPV4 "== ", +- op->peer->json_key); +- op_put_v4_networks(match, op, false); +} -+ + +- ds_clear(actions); +- ds_put_format(actions, "eth.dst = %s; next;", +- op->lrp_networks.ea_s); +- ovn_lflow_add_with_hint(lflows, op->peer->od, +- S_ROUTER_IN_ARP_RESOLVE, 100, +- ds_cstr(match), ds_cstr(actions), +- &op->nbrp->header_); +- } +static void +build_ipv6_input_flows_for_lrouter_port( + struct ovn_port *op, struct hmap *lflows, @@ -15464,7 +17085,13 @@ index 5a3227568..e78a71728 100644 + ds_put_cstr(match, "ip6.dst == "); + op_put_v6_networks(match, op); + ds_put_cstr(match, " && icmp6.type == 128 && icmp6.code == 0"); -+ + +- if (op->lrp_networks.n_ipv6_addrs) { +- ds_clear(match); +- ds_put_format(match, "outport == %s && " +- REG_NEXT_HOP_IPV6 " == ", +- op->peer->json_key); +- op_put_v6_networks(match, op); + const char *lrp_actions = + "ip6.dst <-> ip6.src; " + "ip.ttl = 255; " @@ -15475,7 +17102,14 @@ index 5a3227568..e78a71728 100644 + ds_cstr(match), lrp_actions, + &op->nbrp->header_); + } -+ + +- ds_clear(actions); +- ds_put_format(actions, "eth.dst = %s; next;", +- op->lrp_networks.ea_s); +- ovn_lflow_add_with_hint(lflows, op->peer->od, +- S_ROUTER_IN_ARP_RESOLVE, 100, +- ds_cstr(match), ds_cstr(actions), +- &op->nbrp->header_); + /* ND reply. These flows reply to ND solicitations for the + * router's own IP address. */ + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { @@ -15490,24 +17124,39 @@ index 5a3227568..e78a71728 100644 + ds_put_format(match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); } - -- if (op->lrp_networks.n_ipv6_addrs) { ++ + build_lrouter_nd_flow(op->od, op, "nd_na_router", + op->lrp_networks.ipv6_addrs[i].addr_s, + op->lrp_networks.ipv6_addrs[i].sn_addr_s, + REG_INPORT_ETH_ADDR, match, false, 90, + &op->nbrp->header_, lflows); -+ } -+ + } + +- if (!op->derived && op->od->l3redirect_port) { +- const char *redirect_type = smap_get(&op->nbrp->options, +- "redirect-type"); +- if (redirect_type && !strcasecmp(redirect_type, "bridged")) { +- /* Packet is on a non gateway chassis and +- * has an unresolved ARP on a network behind gateway +- * chassis attached router port. Since, redirect type +- * is "bridged", instead of calling "get_arp" +- * on this node, we will redirect the packet to gateway +- * chassis, by setting destination mac router port mac.*/ + /* UDP/TCP/SCTP port unreachable */ + if (!smap_get(&op->od->nbr->options, "chassis") + && !op->od->l3dgw_port) { + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { ds_clear(match); - ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV6 " == ", -- op->peer->json_key); -- op_put_v6_networks(match, op); +- "!is_chassis_resident(%s)", op->json_key, +- op->od->l3redirect_port->json_key); +- ds_clear(actions); +- ds_put_format(actions, "eth.dst = %s; next;", +- op->lrp_networks.ea_s); +- +- ovn_lflow_add_with_hint(lflows, op->od, +- S_ROUTER_IN_ARP_RESOLVE, 50, +- ds_cstr(match), ds_cstr(actions), + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag && tcp", + op->lrp_networks.ipv6_addrs[i].addr_s); @@ -15517,14 +17166,25 @@ index 5a3227568..e78a71728 100644 + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, -+ &op->nbrp->header_); + &op->nbrp->header_); +- } +- } -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); -- ovn_lflow_add_with_hint(lflows, op->peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), ds_cstr(actions), +- /* Drop IP traffic destined to router owned IPs. Part of it is dropped +- * in stage "lr_in_ip_input" but traffic that could have been unSNATed +- * but didn't match any existing session might still end up here. +- * +- * Priority 1. +- */ +- build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true, +- lflows); +- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp) +- && strcmp(op->nbsp->type, "virtual")) { +- /* This is a logical switch port that backs a VM or a container. +- * Extract its addresses. For each of the address, go through all +- * the router ports attached to the switch (to which this port +- * connects) and if the address in question is reachable from the +- * router port, add an ARP/ND entry in that router's pipeline. */ + ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag && sctp", @@ -15535,27 +17195,23 @@ index 5a3227568..e78a71728 100644 + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 80, ds_cstr(match), action, - &op->nbrp->header_); -- } -- } ++ &op->nbrp->header_); -- if (!op->derived && op->od->l3redirect_port) { -- const char *redirect_type = smap_get(&op->nbrp->options, -- "redirect-type"); -- if (redirect_type && !strcasecmp(redirect_type, "bridged")) { -- /* Packet is on a non gateway chassis and -- * has an unresolved ARP on a network behind gateway -- * chassis attached router port. Since, redirect type -- * is "bridged", instead of calling "get_arp" -- * on this node, we will redirect the packet to gateway -- * chassis, by setting destination mac router port mac.*/ - ds_clear(match); -- ds_put_format(match, "outport == %s && " -- "!is_chassis_resident(%s)", op->json_key, -- op->od->l3redirect_port->json_key); -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", -- op->lrp_networks.ea_s); +- for (size_t i = 0; i < op->n_lsp_addrs; i++) { +- const char *ea_s = op->lsp_addrs[i].ea_s; +- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { +- const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s; +- for (size_t k = 0; k < op->od->n_router_ports; k++) { +- /* Get the Logical_Router_Port that the +- * Logical_Switch_Port is connected to, as +- * 'peer'. */ +- const char *peer_name = smap_get( +- &op->od->router_ports[k]->nbsp->options, +- "router-port"); +- if (!peer_name) { +- continue; +- } ++ ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag && udp", + op->lrp_networks.ipv6_addrs[i].addr_s); @@ -15570,9 +17226,10 @@ index 5a3227568..e78a71728 100644 + 80, ds_cstr(match), action, + &op->nbrp->header_); -- ovn_lflow_add_with_hint(lflows, op->od, -- S_ROUTER_IN_ARP_RESOLVE, 50, -- ds_cstr(match), ds_cstr(actions), +- struct ovn_port *peer = ovn_port_find(ports, peer_name); +- if (!peer || !peer->nbrp) { +- continue; +- } + ds_clear(match); + ds_put_format(match, + "ip6 && ip6.dst == %s && !ip.later_frag", @@ -15586,25 +17243,13 @@ index 5a3227568..e78a71728 100644 + "next; };"; + ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, + 70, ds_cstr(match), action, - &op->nbrp->header_); - } - } ++ &op->nbrp->header_); ++ } ++ } -- /* Drop IP traffic destined to router owned IPs. Part of it is dropped -- * in stage "lr_in_ip_input" but traffic that could have been unSNATed -- * but didn't match any existing session might still end up here. -- * -- * Priority 1. -- */ -- build_lrouter_drop_own_dest(op, S_ROUTER_IN_ARP_RESOLVE, 1, true, -- lflows); -- } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp) -- && strcmp(op->nbsp->type, "virtual")) { -- /* This is a logical switch port that backs a VM or a container. -- * Extract its addresses. For each of the address, go through all -- * the router ports attached to the switch (to which this port -- * connects) and if the address in question is reachable from the -- * router port, add an ARP/ND entry in that router's pipeline. */ +- if (!find_lrp_member_ip(peer, ip_s)) { +- continue; +- } + /* ICMPv6 time exceeded */ + for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { + /* skip link-local address */ @@ -15612,23 +17257,22 @@ index 5a3227568..e78a71728 100644 + continue; + } -- for (size_t i = 0; i < op->n_lsp_addrs; i++) { -- const char *ea_s = op->lsp_addrs[i].ea_s; -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) { -- const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s; -- for (size_t k = 0; k < op->od->n_router_ports; k++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &op->od->router_ports[k]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } +- ds_clear(match); +- ds_put_format(match, "outport == %s && " +- REG_NEXT_HOP_IPV4 " == %s", +- peer->json_key, ip_s); + ds_clear(match); + ds_clear(actions); -+ + +- ds_clear(actions); +- ds_put_format(actions, "eth.dst = %s; next;", ea_s); +- ovn_lflow_add_with_hint(lflows, peer->od, +- S_ROUTER_IN_ARP_RESOLVE, 100, +- ds_cstr(match), +- ds_cstr(actions), +- &op->nbsp->header_); +- } +- } + ds_put_format(match, + "inport == %s && ip6 && " + "ip6.src == %s/%d && " @@ -15652,13 +17296,22 @@ index 5a3227568..e78a71728 100644 + } + } -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { +- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { +- const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; +- for (size_t k = 0; k < op->od->n_router_ports; k++) { +- /* Get the Logical_Router_Port that the +- * Logical_Switch_Port is connected to, as +- * 'peer'. */ +- const char *peer_name = smap_get( +- &op->od->router_ports[k]->nbsp->options, +- "router-port"); +- if (!peer_name) { - continue; - } +} -- if (!find_lrp_member_ip(peer, ip_s)) { +- struct ovn_port *peer = ovn_port_find(ports, peer_name); +- if (!peer || !peer->nbrp) { - continue; - } +static void @@ -15667,10 +17320,9 @@ index 5a3227568..e78a71728 100644 +{ + if (od->nbr) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, ip_s); +- if (!find_lrp_member_ip(peer, ip_s)) { +- continue; +- } + /* Priority-90-92 flows handle ARP requests and ND packets. Most are + * per logical port but DNAT addresses can be handled per datapath + * for non gateway router ports. @@ -15682,57 +17334,14 @@ index 5a3227568..e78a71728 100644 + for (int i = 0; i < od->nbr->n_nat; i++) { + struct ovn_nat *nat_entry = &od->nat_entries[i]; -- ds_clear(actions); -- ds_put_format(actions, "eth.dst = %s; next;", ea_s); -- ovn_lflow_add_with_hint(lflows, peer->od, -- S_ROUTER_IN_ARP_RESOLVE, 100, -- ds_cstr(match), -- ds_cstr(actions), -- &op->nbsp->header_); -- } -+ /* Skip entries we failed to parse. */ -+ if (!nat_entry_is_valid(nat_entry)) { -+ continue; - } - -- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) { -- const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s; -- for (size_t k = 0; k < op->od->n_router_ports; k++) { -- /* Get the Logical_Router_Port that the -- * Logical_Switch_Port is connected to, as -- * 'peer'. */ -- const char *peer_name = smap_get( -- &op->od->router_ports[k]->nbsp->options, -- "router-port"); -- if (!peer_name) { -- continue; -- } -- -- struct ovn_port *peer = ovn_port_find(ports, peer_name); -- if (!peer || !peer->nbrp) { -- continue; -- } -- -- if (!find_lrp_member_ip(peer, ip_s)) { -- continue; -- } -+ /* Skip SNAT entries for now, we handle unique SNAT IPs separately -+ * below. -+ */ -+ if (!strcmp(nat_entry->nb->type, "snat")) { -+ continue; -+ } -+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); -+ } - - ds_clear(match); - ds_put_format(match, "outport == %s && " - REG_NEXT_HOP_IPV6 " == %s", - peer->json_key, ip_s); -+ /* Now handle SNAT entries too, one per unique SNAT IP. */ -+ struct shash_node *snat_snode; -+ SHASH_FOR_EACH (snat_snode, &od->snat_ips) { -+ struct ovn_snat_ip *snat_ip = snat_snode->data; ++ /* Skip entries we failed to parse. */ ++ if (!nat_entry_is_valid(nat_entry)) { ++ continue; ++ } - ds_clear(actions); - ds_put_format(actions, "eth.dst = %s; next;", ea_s); @@ -15742,10 +17351,14 @@ index 5a3227568..e78a71728 100644 - ds_cstr(actions), - &op->nbsp->header_); - } -+ if (ovs_list_is_empty(&snat_ip->snat_entries)) { ++ /* Skip SNAT entries for now, we handle unique SNAT IPs separately ++ * below. ++ */ ++ if (!strcmp(nat_entry->nb->type, "snat")) { + continue; } -- } ++ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); + } - } else if (op->od->n_router_ports && !lsp_is_router(op->nbsp) - && !strcmp(op->nbsp->type, "virtual")) { - /* This is a virtual port. Add ARP replies for the virtual ip with @@ -15764,13 +17377,11 @@ index 5a3227568..e78a71728 100644 - if (!vip || !virtual_parents || - !ip_parse(vip, &ip) || !op->sb) { - return; -+ struct ovn_nat *nat_entry = -+ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), -+ struct ovn_nat, ext_addr_list_node); -+ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); - } -+ } -+} +- } ++ /* Now handle SNAT entries too, one per unique SNAT IP. */ ++ struct shash_node *snat_snode; ++ SHASH_FOR_EACH (snat_snode, &od->snat_ips) { ++ struct ovn_snat_ip *snat_ip = snat_snode->data; - if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] || - !op->sb->chassis) { @@ -15782,11 +17393,27 @@ index 5a3227568..e78a71728 100644 - if (!peer_name) { - continue; - } -- ++ if (ovs_list_is_empty(&snat_ip->snat_entries)) { ++ continue; ++ } + - struct ovn_port *peer = ovn_port_find(ports, peer_name); - if (!peer || !peer->nbrp) { - continue; - } ++ struct ovn_nat *nat_entry = ++ CONTAINER_OF(ovs_list_front(&snat_ip->snat_entries), ++ struct ovn_nat, ext_addr_list_node); ++ build_lrouter_nat_arp_nd_flow(od, nat_entry, lflows); ++ } ++ } ++} + +- if (find_lrp_member_ip(peer, vip)) { +- ds_clear(match); +- ds_put_format(match, "outport == %s && " +- REG_NEXT_HOP_IPV4 " == %s", +- peer->json_key, vip); +/* Logical router ingress table 3: IP Input for IPv4. */ +static void +build_lrouter_ipv4_ip_input(struct ovn_port *op, @@ -15808,21 +17435,6 @@ index 5a3227568..e78a71728 100644 + ds_cstr(match), "drop;", + &op->nbrp->header_); -- if (find_lrp_member_ip(peer, vip)) { -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, vip); -+ /* ICMP echo reply. These flows reply to ICMP echo requests -+ * received for the router's IP address. Since packets only -+ * get here as part of the logical router datapath, the inport -+ * (i.e. the incoming locally attached net) does not matter. -+ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ -+ ds_clear(match); -+ ds_put_cstr(match, "ip4.dst == "); -+ op_put_v4_networks(match, op, false); -+ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0"); - - const char *arp_actions = - "eth.dst = 00:00:00:00:00:00; next;"; - ovn_lflow_add_with_hint(lflows, peer->od, @@ -15839,15 +17451,15 @@ index 5a3227568..e78a71728 100644 - if (!vp || !vp->nbsp) { - return; - } -+ const char * icmp_actions = "ip4.dst <-> ip4.src; " -+ "ip.ttl = 255; " -+ "icmp4.type = 0; " -+ "flags.loopback = 1; " -+ "next; "; -+ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, -+ ds_cstr(match), icmp_actions, -+ &op->nbrp->header_); -+ } ++ /* ICMP echo reply. These flows reply to ICMP echo requests ++ * received for the router's IP address. Since packets only ++ * get here as part of the logical router datapath, the inport ++ * (i.e. the incoming locally attached net) does not matter. ++ * The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */ ++ ds_clear(match); ++ ds_put_cstr(match, "ip4.dst == "); ++ op_put_v4_networks(match, op, false); ++ ds_put_cstr(match, " && icmp4.type == 8 && icmp4.code == 0"); - for (size_t i = 0; i < vp->n_lsp_addrs; i++) { - bool found_vip_network = false; @@ -15862,22 +17474,36 @@ index 5a3227568..e78a71728 100644 - if (!peer_name) { - continue; - } -+ /* BFD msg handling */ -+ build_lrouter_bfd_flows(lflows, op); ++ const char * icmp_actions = "ip4.dst <-> ip4.src; " ++ "ip.ttl = 255; " ++ "icmp4.type = 0; " ++ "flags.loopback = 1; " ++ "next; "; ++ ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, ++ ds_cstr(match), icmp_actions, ++ &op->nbrp->header_); ++ } - struct ovn_port *peer = - ovn_port_find(ports, peer_name); - if (!peer || !peer->nbrp) { - continue; - } ++ /* BFD msg handling */ ++ build_lrouter_bfd_flows(lflows, op); + +- if (!find_lrp_member_ip(peer, vip)) { +- continue; +- } + /* ICMP time exceeded */ + for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { + ds_clear(match); + ds_clear(actions); -- if (!find_lrp_member_ip(peer, vip)) { -- continue; -- } +- ds_clear(match); +- ds_put_format(match, "outport == %s && " +- REG_NEXT_HOP_IPV4 " == %s", +- peer->json_key, vip); + ds_put_format(match, + "inport == %s && ip4 && " + "ip.ttl == {0, 1} && !ip.later_frag", op->json_key); @@ -15896,18 +17522,6 @@ index 5a3227568..e78a71728 100644 + &op->nbrp->header_); + } -- ds_clear(match); -- ds_put_format(match, "outport == %s && " -- REG_NEXT_HOP_IPV4 " == %s", -- peer->json_key, vip); -+ /* ARP reply. These flows reply to ARP requests for the router's own -+ * IP address. */ -+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { -+ ds_clear(match); -+ ds_put_format(match, "arp.spa == %s/%u", -+ op->lrp_networks.ipv4_addrs[i].network_s, -+ op->lrp_networks.ipv4_addrs[i].plen); - - ds_clear(actions); - ds_put_format(actions, "eth.dst = %s; next;", ea_s); - ovn_lflow_add_with_hint(lflows, peer->od, @@ -15917,6 +17531,14 @@ index 5a3227568..e78a71728 100644 - &op->nbsp->header_); - found_vip_network = true; - break; ++ /* ARP reply. These flows reply to ARP requests for the router's own ++ * IP address. */ ++ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { ++ ds_clear(match); ++ ds_put_format(match, "arp.spa == %s/%u", ++ op->lrp_networks.ipv4_addrs[i].network_s, ++ op->lrp_networks.ipv4_addrs[i].plen); ++ + if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer + && op->peer->od->n_localnet_ports) { + bool add_chassis_resident_check = false; @@ -15961,39 +17583,34 @@ index 5a3227568..e78a71728 100644 - * we need to add logical flows such that it can resolve - * ARP entries for all the other router ports connected to - * the switch in question. */ -+ /* A set to hold all load-balancer vips that need ARP responses. */ -+ struct sset all_ips_v4 = SSET_INITIALIZER(&all_ips_v4); -+ struct sset all_ips_v6 = SSET_INITIALIZER(&all_ips_v6); -+ get_router_load_balancer_ips(op->od, &all_ips_v4, &all_ips_v6); - -- const char *peer_name = smap_get(&op->nbsp->options, -- "router-port"); -- if (!peer_name) { -- return; -- } + const char *ip_address; -+ if (sset_count(&all_ips_v4)) { ++ if (sset_count(&op->od->lb_ips_v4)) { + ds_clear(match); + if (op == op->od->l3dgw_port) { + ds_put_format(match, "is_chassis_resident(%s)", + op->od->l3redirect_port->json_key); + } +- const char *peer_name = smap_get(&op->nbsp->options, +- "router-port"); +- if (!peer_name) { +- return; +- } ++ struct ds load_balancer_ips_v4 = DS_EMPTY_INITIALIZER; + - struct ovn_port *peer = ovn_port_find(ports, peer_name); - if (!peer || !peer->nbrp) { - return; - } -+ struct ds load_balancer_ips_v4 = DS_EMPTY_INITIALIZER; ++ /* For IPv4 we can just create one rule with all required IPs. */ ++ ds_put_cstr(&load_balancer_ips_v4, "{ "); ++ ds_put_and_free_cstr(&load_balancer_ips_v4, ++ sset_join(&op->od->lb_ips_v4, ", ", " }")); - if (peer->od->nbr && - smap_get_bool(&peer->od->nbr->options, - "dynamic_neigh_routers", false)) { - return; -+ /* For IPv4 we can just create one rule with all required IPs. */ -+ ds_put_cstr(&load_balancer_ips_v4, "{ "); -+ ds_put_and_free_cstr(&load_balancer_ips_v4, -+ sset_join(&all_ips_v4, ", ", " }")); -+ + build_lrouter_arp_flow(op->od, op, ds_cstr(&load_balancer_ips_v4), + REG_INPORT_ETH_ADDR, + match, false, 90, NULL, lflows); @@ -16008,7 +17625,7 @@ index 5a3227568..e78a71728 100644 - router_port_name); - if (!router_port || !router_port->nbrp) { - continue; -+ SSET_FOR_EACH (ip_address, &all_ips_v6) { ++ SSET_FOR_EACH (ip_address, &op->od->lb_ips_v6) { + ds_clear(match); + if (op == op->od->l3dgw_port) { + ds_put_format(match, "is_chassis_resident(%s)", @@ -16025,9 +17642,6 @@ index 5a3227568..e78a71728 100644 + } - if (router_port->lrp_networks.n_ipv4_addrs) { -+ sset_destroy(&all_ips_v4); -+ sset_destroy(&all_ips_v6); -+ + if (!smap_get(&op->od->nbr->options, "chassis") + && !op->od->l3dgw_port) { + /* UDP/TCP/SCTP port unreachable. */ @@ -16669,7 +18283,11 @@ index 5a3227568..e78a71728 100644 + is_v6 ? "6" : "4", nat->external_ip); + } else { + ds_put_format(actions, "ct_snat(%s", nat->external_ip); -+ + +- /* Allow other multicast if relay enabled (priority 82). */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82, +- "ip4.mcast || ip6.mcast", +- od->mcast_info.rtr.relay ? "next;" : "drop;"); + if (nat->external_port_range[0]) { + ds_put_format(actions, ",%s", + nat->external_port_range); @@ -16700,27 +18318,26 @@ index 5a3227568..e78a71728 100644 + } + ds_clear(actions); -- /* Allow other multicast if relay enabled (priority 82). */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 82, -- "ip4.mcast || ip6.mcast", -- od->mcast_info.rtr.relay ? "next;" : "drop;"); +- /* Drop Ethernet local broadcast. By definition this traffic should +- * not be forwarded.*/ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50, +- "eth.bcast", "drop;"); + if (nat->allowed_ext_ips || nat->exempted_ext_ips) { + lrouter_nat_add_ext_ip_match(od, lflows, match, nat, + is_v6, false, mask); + } -- /* Drop Ethernet local broadcast. By definition this traffic should -- * not be forwarded.*/ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50, -- "eth.bcast", "drop;"); +- /* TTL discard */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, +- "ip4 && ip.ttl == {0, 1}", "drop;"); + if (distributed) { + ds_put_format(actions, "eth.src = "ETH_ADDR_FMT"; ", + ETH_ADDR_ARGS(mac)); + } -- /* TTL discard */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30, -- "ip4 && ip.ttl == {0, 1}", "drop;"); +- /* Pass other traffic not already handled to the next table for +- * routing. */ +- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); + if (!strcmp(nat->type, "dnat_and_snat") && stateless) { + ds_put_format(actions, "ip%s.src=%s; next;", + is_v6 ? "6" : "4", nat->external_ip); @@ -16732,10 +18349,7 @@ index 5a3227568..e78a71728 100644 + } + ds_put_format(actions, ");"); + } - -- /* Pass other traffic not already handled to the next table for -- * routing. */ -- ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;"); ++ + /* The priority here is calculated such that the + * nat->logical_ip with the longest mask gets a higher + * priority. */ @@ -16966,6 +18580,7 @@ index 5a3227568..e78a71728 100644 +static void +build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od, + struct hmap *lflows, ++ struct hmap *ports, + struct shash *meter_groups, + struct hmap *lbs, + struct ds *match, struct ds *actions) @@ -17101,10 +18716,21 @@ index 5a3227568..e78a71728 100644 - op->json_key, - op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen); -+ "ip%s.src == %s && outport == %s && " -+ "is_chassis_resident(\"%s\")", ++ "ip%s.src == %s && outport == %s", + is_v6 ? "6" : "4", nat->logical_ip, -+ od->l3dgw_port->json_key, nat->logical_port); ++ od->l3dgw_port->json_key); ++ /* Add a rule to drop traffic from a distributed NAT if ++ * the virtual port has not claimed yet becaused otherwise ++ * the traffic will be centralized misconfiguring the TOR switch. ++ */ ++ struct ovn_port *op = ovn_port_find(ports, nat->logical_port); ++ if (op && op->nbsp && !strcmp(op->nbsp->type, "virtual")) { ++ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, ++ 80, ds_cstr(match), "drop;", ++ &nat->header_); ++ } ++ ds_put_format(match, " && is_chassis_resident(\"%s\")", ++ nat->logical_port); + ds_put_format(actions, "eth.src = %s; %s = %s; next;", + nat->external_mac, + is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4, @@ -17137,7 +18763,9 @@ index 5a3227568..e78a71728 100644 + ds_clear(actions); ds_put_format(actions, - "icmp6 {" -- "eth.dst <-> eth.src; " ++ "clone { ct_clear; " ++ "inport = outport; outport = \"\"; " + "eth.dst <-> eth.src; " - "ip6.dst = ip6.src; " - "ip6.src = %s; " - "ip.ttl = 255; " @@ -17146,8 +18774,6 @@ index 5a3227568..e78a71728 100644 - "next; };", - op->lrp_networks.ipv6_addrs[i].addr_s); - ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_IP_INPUT, 40, -+ "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); @@ -17218,7 +18844,7 @@ index 5a3227568..e78a71728 100644 struct lswitch_flow_build_info { struct hmap *datapaths; struct hmap *ports; -@@ -11177,7 +12002,8 @@ struct lswitch_flow_build_info { +@@ -11177,7 +12220,8 @@ struct lswitch_flow_build_info { static void build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, @@ -17228,7 +18854,7 @@ index 5a3227568..e78a71728 100644 { /* Build Logical Switch Flows. */ build_lswitch_lflows_pre_acl_and_acl(od, lsi->port_groups, lsi->lflows, -@@ -11186,13 +12012,20 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, +@@ -11186,13 +12230,20 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, build_fwd_group_lflows(od, lsi->lflows); build_lswitch_lflows_admission_control(od, lsi->lflows); build_lswitch_input_port_sec_od(od, lsi->lflows); @@ -17250,17 +18876,18 @@ index 5a3227568..e78a71728 100644 build_mcast_lookup_flows_for_lrouter(od, lsi->lflows, &lsi->match, &lsi->actions); build_ingress_policy_flows_for_lrouter(od, lsi->lflows, lsi->ports); -@@ -11204,6 +12037,9 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, +@@ -11204,6 +12255,10 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, build_arp_request_flows_for_lrouter(od, lsi->lflows, &lsi->match, &lsi->actions); build_misc_local_traffic_drop_flows_for_lrouter(od, lsi->lflows); + build_lrouter_arp_nd_for_datapath(od, lsi->lflows); -+ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->meter_groups, -+ lsi->lbs, &lsi->match, &lsi->actions); ++ build_lrouter_nat_defrag_and_lb(od, lsi->lflows, lsi->ports, ++ lsi->meter_groups, lsi->lbs, &lsi->match, ++ &lsi->actions); } /* Helper function to combine all lflow generation which is iterated by port. -@@ -11216,6 +12052,20 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, +@@ -11216,13 +12271,27 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, /* Build Logical Switch Flows. */ build_lswitch_input_port_sec_op(op, lsi->lflows, &lsi->actions, &lsi->match); @@ -17281,7 +18908,15 @@ index 5a3227568..e78a71728 100644 /* Build Logical Router Flows. */ build_adm_ctrl_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, -@@ -11232,6 +12082,10 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, + &lsi->actions); + build_neigh_learning_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, + &lsi->actions); +- build_ip_routing_flows_for_lrouter_port(op, lsi->lflows); ++ build_ip_routing_flows_for_lrouter_port(op, lsi->ports, lsi->lflows); + build_ND_RA_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, + &lsi->actions); + build_arp_resolve_flows_for_lrouter_port(op, lsi->lflows, lsi->ports, +@@ -11232,6 +12301,10 @@ build_lswitch_and_lrouter_iterate_by_op(struct ovn_port *op, build_dhcpv6_reply_flows_for_lrouter_port(op, lsi->lflows, &lsi->match); build_ipv6_input_flows_for_lrouter_port(op, lsi->lflows, &lsi->match, &lsi->actions); @@ -17292,7 +18927,7 @@ index 5a3227568..e78a71728 100644 } static void -@@ -11239,10 +12093,13 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, +@@ -11239,10 +12312,13 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *port_groups, struct hmap *lflows, struct hmap *mcgroups, struct hmap *igmp_groups, @@ -17307,7 +18942,7 @@ index 5a3227568..e78a71728 100644 char *svc_check_match = xasprintf("eth.dst == %s", svc_monitor_mac); -@@ -11264,22 +12121,28 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, +@@ -11264,22 +12340,28 @@ build_lswitch_and_lrouter_flows(struct hmap *datapaths, struct hmap *ports, * will move here and will be reogranized by iterator type. */ HMAP_FOR_EACH (od, key_node, datapaths) { @@ -17343,7 +18978,7 @@ index 5a3227568..e78a71728 100644 } struct ovn_dp_group { -@@ -11356,13 +12219,14 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths, +@@ -11356,13 +12438,14 @@ build_lflows(struct northd_context *ctx, struct hmap *datapaths, struct hmap *ports, struct hmap *port_groups, struct hmap *mcgroups, struct hmap *igmp_groups, struct shash *meter_groups, @@ -17360,7 +18995,7 @@ index 5a3227568..e78a71728 100644 /* Collecting all unique datapath groups. */ struct hmap dp_groups = HMAP_INITIALIZER(&dp_groups); -@@ -11801,17 +12665,20 @@ static void +@@ -11801,17 +12884,20 @@ static void sync_meters_iterate_nb_meter(struct northd_context *ctx, const char *meter_name, const struct nbrec_meter *nb_meter, @@ -17384,7 +19019,7 @@ index 5a3227568..e78a71728 100644 if (new_sb_meter || bands_need_update(nb_meter, sb_meter)) { struct sbrec_meter_band **sb_bands; -@@ -11833,6 +12700,24 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, +@@ -11833,6 +12919,24 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, sbrec_meter_set_unit(sb_meter, nb_meter->unit); } @@ -17409,7 +19044,7 @@ index 5a3227568..e78a71728 100644 /* Each entry in the Meter and Meter_Band tables in OVN_Northbound have * a corresponding entries in the Meter and Meter_Band tables in * OVN_Southbound. Additionally, ACL logs that use fair meters have -@@ -11840,9 +12725,10 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, +@@ -11840,9 +12944,10 @@ sync_meters_iterate_nb_meter(struct northd_context *ctx, */ static void sync_meters(struct northd_context *ctx, struct hmap *datapaths, @@ -17421,7 +19056,7 @@ index 5a3227568..e78a71728 100644 const struct sbrec_meter *sb_meter; SBREC_METER_FOR_EACH (sb_meter, ctx->ovnsb_idl) { -@@ -11852,7 +12738,7 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, +@@ -11852,7 +12957,7 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, const struct nbrec_meter *nb_meter; NBREC_METER_FOR_EACH (nb_meter, ctx->ovnnb_idl) { sync_meters_iterate_nb_meter(ctx, nb_meter->name, nb_meter, @@ -17430,7 +19065,7 @@ index 5a3227568..e78a71728 100644 } /* -@@ -11866,19 +12752,28 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, +@@ -11866,19 +12971,28 @@ sync_meters(struct northd_context *ctx, struct hmap *datapaths, continue; } for (size_t i = 0; i < od->nbs->n_acls; i++) { @@ -17468,7 +19103,7 @@ index 5a3227568..e78a71728 100644 struct shash_node *node, *next; SHASH_FOR_EACH_SAFE (node, next, &sb_meters) { sbrec_meter_delete(node->data); -@@ -12274,6 +13169,7 @@ ovnnb_db_run(struct northd_context *ctx, +@@ -12274,6 +13388,7 @@ ovnnb_db_run(struct northd_context *ctx, struct hmap igmp_groups; struct shash meter_groups = SHASH_INITIALIZER(&meter_groups); struct hmap lbs; @@ -17476,7 +19111,7 @@ index 5a3227568..e78a71728 100644 /* Sync ipsec configuration. * Copy nb_cfg from northbound to southbound database. -@@ -12354,6 +13250,10 @@ ovnnb_db_run(struct northd_context *ctx, +@@ -12354,28 +13469,36 @@ ovnnb_db_run(struct northd_context *ctx, use_logical_dp_groups = smap_get_bool(&nb->options, "use_logical_dp_groups", false); @@ -17487,7 +19122,17 @@ index 5a3227568..e78a71728 100644 controller_event_en = smap_get_bool(&nb->options, "controller_event", false); check_lsp_is_up = !smap_get_bool(&nb->options, -@@ -12368,14 +13268,16 @@ ovnnb_db_run(struct northd_context *ctx, + "ignore_lsp_down", false); + + build_datapaths(ctx, datapaths, lr_list); ++ build_ovn_lbs(ctx, datapaths, &lbs); ++ build_lrouter_lbs(datapaths, &lbs); + build_ports(ctx, sbrec_chassis_by_name, datapaths, ports); +- build_ovn_lbs(ctx, datapaths, ports, &lbs); ++ build_ovn_lb_svcs(ctx, ports, &lbs); + build_ipam(datapaths, ports); + build_port_group_lswitches(ctx, &port_groups, ports); + build_lrouter_groups(ports, lr_list); build_ip_mcast(ctx, datapaths); build_mcast_groups(ctx, datapaths, ports, &mcast_groups, &igmp_groups); build_meter_groups(ctx, &meter_groups); @@ -17506,7 +19151,7 @@ index 5a3227568..e78a71728 100644 struct ovn_northd_lb *lb; HMAP_FOR_EACH_POP (lb, hmap_node, &lbs) { -@@ -12393,9 +13295,13 @@ ovnnb_db_run(struct northd_context *ctx, +@@ -12393,9 +13516,13 @@ ovnnb_db_run(struct northd_context *ctx, HMAP_FOR_EACH_SAFE (pg, next_pg, key_node, &port_groups) { ovn_port_group_destroy(&port_groups, pg); } @@ -17520,7 +19165,7 @@ index 5a3227568..e78a71728 100644 struct shash_node *node, *next; SHASH_FOR_EACH_SAFE (node, next, &meter_groups) { -@@ -12542,7 +13448,17 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports, +@@ -12542,7 +13669,17 @@ handle_port_binding_changes(struct northd_context *ctx, struct hmap *ports, continue; } @@ -17539,7 +19184,7 @@ index 5a3227568..e78a71728 100644 if (!op->nbsp->up || *op->nbsp->up != up) { nbrec_logical_switch_port_set_up(op->nbsp, &up, 1); } -@@ -12690,7 +13606,7 @@ static const char *rbac_encap_update[] = +@@ -12690,7 +13827,7 @@ static const char *rbac_encap_update[] = static const char *rbac_port_binding_auth[] = {""}; static const char *rbac_port_binding_update[] = @@ -17548,7 +19193,7 @@ index 5a3227568..e78a71728 100644 static const char *rbac_mac_binding_auth[] = {""}; -@@ -13176,6 +14092,8 @@ main(int argc, char *argv[]) +@@ -13176,6 +14313,8 @@ main(int argc, char *argv[]) &sbrec_port_binding_col_ha_chassis_group); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_virtual_parent); @@ -17557,7 +19202,7 @@ index 5a3227568..e78a71728 100644 ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_chassis); ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_gateway_chassis_col_name); -@@ -13324,9 +14242,25 @@ main(int argc, char *argv[]) +@@ -13324,9 +14463,25 @@ main(int argc, char *argv[]) add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_name); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_vips); add_column_noalert(ovnsb_idl_loop.idl, &sbrec_load_balancer_col_protocol); @@ -17583,7 +19228,7 @@ index 5a3227568..e78a71728 100644 struct ovsdb_idl_index *sbrec_chassis_by_name = chassis_index_create(ovnsb_idl_loop.idl); -@@ -13350,6 +14284,15 @@ main(int argc, char *argv[]) +@@ -13350,6 +14505,15 @@ main(int argc, char *argv[]) state.had_lock = false; state.paused = false; while (!exiting) { @@ -17599,7 +19244,7 @@ index 5a3227568..e78a71728 100644 if (!state.paused) { if (!ovsdb_idl_has_lock(ovnsb_idl_loop.idl) && !ovsdb_idl_is_lock_contended(ovnsb_idl_loop.idl)) -@@ -13421,6 +14364,7 @@ main(int argc, char *argv[]) +@@ -13421,6 +14585,7 @@ main(int argc, char *argv[]) unixctl_server_run(unixctl); unixctl_server_wait(unixctl); @@ -17607,7 +19252,7 @@ index 5a3227568..e78a71728 100644 if (exiting) { poll_immediate_wake(); } -@@ -13449,6 +14393,7 @@ main(int argc, char *argv[]) +@@ -13449,6 +14614,7 @@ main(int argc, char *argv[]) } } @@ -17693,7 +19338,7 @@ index 269e3a888..29019809c 100644 "isRoot": true}} } diff --git a/ovn-nb.xml b/ovn-nb.xml -index c9ab25ceb..5f5c2cda0 100644 +index c9ab25ceb..b85a51868 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -226,6 +226,21 @@ @@ -17718,7 +19363,7 @@ index c9ab25ceb..5f5c2cda0 100644

    These options control how routes are advertised between OVN -@@ -1635,6 +1650,30 @@ +@@ -1635,6 +1650,42 @@ See External IDs at the beginning of this document. @@ -17745,11 +19390,23 @@ index c9ab25ceb..5f5c2cda0 100644 + option, the force_snat_for_lb option configured for the router + pipeline will not be applied for this load balancer. + ++ ++ ++ If set to true, then neighbor routers will have logical ++ flows added that will allow for routing to the VIP IP. It also will ++ have ARP resolution logical flows added. By setting this option, it ++ means there is no reason to create a ++ from neighbor routers to ++ this NAT address. It also means that no ARP request is required for ++ neighbor routers to learn the IP-MAC mapping for this VIP IP. For ++ more information about what flows are added for IP routes, please ++ see the ovn-northd manpage section on IP Routing. ++ + -@@ -1917,16 +1956,29 @@ +@@ -1917,16 +1968,29 @@

    @@ -17789,7 +19446,22 @@ index c9ab25ceb..5f5c2cda0 100644

    -@@ -2634,6 +2686,13 @@ +@@ -1945,7 +2009,13 @@ + false by default. It is recommended to set to + true when a large number of logical routers are + connected to the same logical switch but most of them never need to +- send traffic between each other. ++ send traffic between each other. By default, ovn-northd does not ++ create mappings to NAT and load balancer addresess. However, for NAT ++ and load balancer addresses that have the add_route ++ option added, ovn-northd will create logical flows that map NAT and ++ load balancer IP addresses to the appropriate MAC address. Setting ++ dynamic_neigh_routers to true will prevent ++ the automatic creation of these logical flows. +

    +
    + +@@ -2634,6 +2704,13 @@

    @@ -17803,7 +19475,16 @@ index c9ab25ceb..5f5c2cda0 100644 ovn-ic populates this key if the route is learned from the global database. In this case the value -@@ -2713,18 +2772,34 @@ +@@ -2655,7 +2732,7 @@ + + + +- It true, then new traffic that arrives over this route will have ++ If true, then new traffic that arrives over this route will have + its reply traffic bypass ECMP route selection and will be sent out + this route instead. Note that this option overrides any rules set + in the table. This option +@@ -2713,18 +2790,34 @@
  • @@ -17839,7 +19520,27 @@ index c9ab25ceb..5f5c2cda0 100644

    Marks the packet with the value specified when the router policy -@@ -3702,4 +3777,71 @@ +@@ -2894,6 +2987,19 @@ + tracking state or not. + + ++ ++ If set to true, then neighbor routers will have logical ++ flows added that will allow for routing to the NAT address. It also will ++ have ARP resolution logical flows added. By setting this option, it means ++ there is no reason to create a ++ from neighbor routers to this NAT address. It also means that no ARP ++ request is required for neighbor routers to learn the IP-MAC mapping for ++ this NAT address. This option only applies to NATs of type ++ dnat and dnat_and_snat. For more information ++ about what flows are added for IP routes, please see the ++ ovn-northd manpage section on IP Routing. ++ ++ + + + See External IDs at the beginning of this document. +@@ -3702,4 +3808,71 @@

  • @@ -18222,11 +19923,11 @@ index c13994848..258a12b4e 100644 diff --git a/ovs b/ovs new file mode 160000 -index 000000000..ac85cdb38 +index 000000000..e6ad4d8d9 --- /dev/null +++ b/ovs @@ -0,0 +1 @@ -+Subproject commit ac85cdb38c1f33e7952bc4c0347d6c7873fb56a1 ++Subproject commit e6ad4d8d9c9273f226ec9a993b64fccfb50bdf4c diff --git a/tests/atlocal.in b/tests/atlocal.in index d9a4c91d4..5ebc8e117 100644 --- a/tests/atlocal.in @@ -18242,22 +19943,26 @@ index d9a4c91d4..5ebc8e117 100644 unset http_proxy unset https_proxy diff --git a/tests/automake.mk b/tests/automake.mk -index c5c286eae..d60cb8105 100644 +index c5c286eae..7202c6299 100644 --- a/tests/automake.mk +++ b/tests/automake.mk -@@ -31,7 +31,9 @@ TESTSUITE_AT = \ +@@ -31,7 +31,10 @@ TESTSUITE_AT = \ tests/ovn-controller-vtep.at \ tests/ovn-ic.at \ tests/ovn-macros.at \ - tests/ovn-performance.at + tests/ovn-performance.at \ + tests/ovn-ofctrl-seqno.at \ ++ tests/ovn-features.at \ + tests/ovn-lflow-cache.at SYSTEM_KMOD_TESTSUITE_AT = \ tests/system-common-macros.at \ -@@ -202,7 +204,15 @@ noinst_PROGRAMS += tests/ovstest +@@ -200,9 +203,19 @@ $(srcdir)/package.m4: $(top_srcdir)/configure.ac + + noinst_PROGRAMS += tests/ovstest tests_ovstest_SOURCES = \ ++ include/ovn/features.h \ tests/ovstest.c \ tests/ovstest.h \ - tests/test-ovn.c @@ -18269,7 +19974,8 @@ index c5c286eae..d60cb8105 100644 + controller/lflow-cache.c \ + controller/lflow-cache.h \ + controller/ofctrl-seqno.c \ -+ controller/ofctrl-seqno.h ++ controller/ofctrl-seqno.h \ ++ lib/test-ovn-features.c tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \ $(OVS_LIBDIR)/libopenvswitch.la lib/libovn.la @@ -18348,10 +20054,51 @@ index cb582811f..b2261d285 100644 OVS_WAIT_UNTIL([test -z "`ovn-sbctl list Chassis | grep -- br-vtep_lswitch`"]) OVS_WAIT_UNTIL([test -z "`vtep-ctl list physical_port p0`"]) diff --git a/tests/ovn-controller.at b/tests/ovn-controller.at -index 1b4679963..1f922f78f 100644 +index 1b4679963..a23cd006c 100644 --- a/tests/ovn-controller.at +++ b/tests/ovn-controller.at -@@ -414,3 +414,100 @@ OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], [1]) +@@ -140,23 +140,24 @@ sysid=$(ovs-vsctl get Open_vSwitch . external_ids:system-id) + check_datapath_type () { + datapath_type=$1 + chassis_datapath_type=$(ovn-sbctl get Chassis ${sysid} other_config:datapath-type | sed -e 's/"//g') #" +- test "${datapath_type}" = "${chassis_datapath_type}" ++ ovs_datapath_type=$(ovs-vsctl get Bridge br-int datapath-type) ++ test "${datapath_type}" = "${chassis_datapath_type}" && test "${datapath_type}" = "${ovs_datapath_type}" + } + +-OVS_WAIT_UNTIL([check_datapath_type ""]) ++OVS_WAIT_UNTIL([check_datapath_type system]) + + ovs-vsctl set Bridge br-int datapath-type=foo + OVS_WAIT_UNTIL([check_datapath_type foo]) + + # Change "ovn-bridge-mappings" value. It should not change the "datapath-type". + ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-mappings=foo-mapping +-check_datapath_type foo ++AT_CHECK([check_datapath_type foo]) + + ovs-vsctl set Bridge br-int datapath-type=bar + OVS_WAIT_UNTIL([check_datapath_type bar]) + + ovs-vsctl set Bridge br-int datapath-type=\"\" +-OVS_WAIT_UNTIL([check_datapath_type ""]) ++OVS_WAIT_UNTIL([check_datapath_type system]) + + # Set the datapath_type in external_ids:ovn-bridge-datapath-type. + ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foo +@@ -165,11 +166,9 @@ OVS_WAIT_UNTIL([check_datapath_type foo]) + # Change the br-int's datapath type to bar. + # It should be reset to foo since ovn-bridge-datapath-type is configured. + ovs-vsctl set Bridge br-int datapath-type=bar +-OVS_WAIT_UNTIL([test foo=`ovs-vsctl get Bridge br-int datapath-type`]) + OVS_WAIT_UNTIL([check_datapath_type foo]) + + ovs-vsctl set Open_vSwitch . external_ids:ovn-bridge-datapath-type=foobar +-OVS_WAIT_UNTIL([test foobar=`ovs-vsctl get Bridge br-int datapath-type`]) + OVS_WAIT_UNTIL([check_datapath_type foobar]) + + expected_iface_types=$(ovs-vsctl get Open_vSwitch . iface_types | tr -d '[[]] ""') +@@ -414,3 +413,100 @@ OVS_WAIT_UNTIL([ovs-vsctl get Bridge br-int external_ids:ovn-nb-cfg], [0], [1]) OVN_CLEANUP([hv1]) AT_CLEANUP @@ -18452,6 +20199,20 @@ index 1b4679963..1f922f78f 100644 + +OVN_CLEANUP([hv1]) +AT_CLEANUP +diff --git a/tests/ovn-features.at b/tests/ovn-features.at +new file mode 100644 +index 000000000..36bd83055 +--- /dev/null ++++ b/tests/ovn-features.at +@@ -0,0 +1,8 @@ ++# ++# Unit tests for the lib/features.c module. ++# ++AT_BANNER([OVN unit tests - features]) ++ ++AT_SETUP([ovn -- unit test -- OVS feature detection tests]) ++AT_CHECK([ovstest test-ovn-features run], [0], []) ++AT_CLEANUP diff --git a/tests/ovn-lflow-cache.at b/tests/ovn-lflow-cache.at new file mode 100644 index 000000000..e5e9ed1e8 @@ -18864,10 +20625,10 @@ index 000000000..e5e9ed1e8 +AT_CHECK([ovstest test-lflow-cache lflow_cache_negative], [0], []) +AT_CLEANUP diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at -index 59e500c57..4cf14b1f2 100644 +index 59e500c57..247b0613e 100644 --- a/tests/ovn-macros.at +++ b/tests/ovn-macros.at -@@ -417,6 +417,40 @@ wait_column() { +@@ -417,6 +417,63 @@ wait_column() { echo "$column in $db table $table has value $found, from the following rows:" ovn-${db}ctl list $table]) } @@ -18905,14 +20666,47 @@ index 59e500c57..4cf14b1f2 100644 + OVS_WAIT_WHILE([test 24 = $(wc -c ${pcap_file}-tx.pcap | cut -d " " -f1)]) +} + ++# Receive a packet on a dummy netdev interface. If we expect packets to be ++# recorded, then wait until the pcap file reflects the change. ++netdev_dummy_receive() { ++ local interface="$1" ++ local packet="$2" ++ local hv="$3" ++ local pcap_file="$4" ++ ++ if test -n "pcap_file" ; then ++ ts_old=$(stat -c %y "$pcap_file") ++ fi ++ if test -n "$hv" ; then ++ as "$hv" ovs-appctl netdev-dummy/receive "$interface" "$packet" ++ else ++ ovs-appctl netdev-dummy/receive "$interface" "$packet" ++ fi ++ if test -n "$pcap_file" ; then ++ OVS_WAIT_WHILE( ++ [ts_new=$(stat -c %y "$pcap_file") ++ test "$ts_new" = "$ts_old"]) ++ fi ++} ++ OVS_END_SHELL_HELPERS m4_define([OVN_POPULATE_ARP], [AT_CHECK(ovn_populate_arp__, [0], [ignore])]) diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at -index 01edfcbc1..8af55161f 100644 +index 01edfcbc1..25f7c8260 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at -@@ -1539,34 +1539,35 @@ AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +@@ -465,6 +465,9 @@ AT_CHECK([ovn-nbctl lsp-add ls0 lp0]) + AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.2 lp0 00:00:00:01:02], [1], [], + [ovn-nbctl: invalid mac address 00:00:00:01:02. + ]) ++AT_CHECK([ovn-nbctl --add-route lr-nat-add lr0 snat 30.0.0.2 192.168.1.2], [1], [], ++[ovn-nbctl: routes cannot be added for snat types. ++]) + + dnl Add snat and dnat + AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24]) +@@ -1539,34 +1542,35 @@ AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl dnl Add ecmp routes AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.0/24 11.0.0.1]) AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.2]) @@ -18962,7 +20756,7 @@ index 01edfcbc1..8af55161f 100644 AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes 10.0.0.0/24 11.0.0.3 dst-ip -@@ -1605,7 +1606,16 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0]) +@@ -1605,7 +1609,16 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0]) AT_CHECK([ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.1]) AT_CHECK([ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1]) AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0]) @@ -18980,7 +20774,7 @@ index 01edfcbc1..8af55161f 100644 AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes -@@ -1615,9 +1625,20 @@ IPv4 Routes +@@ -1615,9 +1628,20 @@ IPv4 Routes IPv6 Routes 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 @@ -19004,7 +20798,7 @@ index 01edfcbc1..8af55161f 100644 dnl --------------------------------------------------------------------- diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at -index 90ca0a4db..7a0dcaec0 100644 +index 90ca0a4db..0c316e860 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -605,11 +605,12 @@ wait_row_count Port_Binding 0 logical_port=sw0-pext1 'chassis!=[[]]' @@ -19961,7 +21755,7 @@ index 90ca0a4db..7a0dcaec0 100644 AT_SETUP([ovn -- logical gatapath groups]) AT_KEYWORDS([use_logical_dp_groups]) ovn_start -@@ -2173,3 +2360,745 @@ dnl Number of common flows should be the same. +@@ -2173,3 +2360,1090 @@ dnl Number of common flows should be the same. check_row_count Logical_Flow ${n_flows_common} logical_dp_group=${dp_group_uuid} AT_CLEANUP @@ -20707,6 +22501,351 @@ index 90ca0a4db..7a0dcaec0 100644 +]) + +AT_CLEANUP ++ ++# XXX This test currently only runs for ovn-northd.c. The test fails ++# with ovn-northd-ddlog because of the section where 2 HA_Chassis_Groups ++# are used by 2 routers. For some reason, this causes ovn-northd-ddlog ++# to stop processing new changes to the northbound database and to ++# seemingly infinitely loop. This issue has been reported, but there is ++# currently no fix for it. Once this issue is fixed, we can run this ++# test for both C and DDLog versions of northd. ++AT_SETUP([ovn -- NAT and Load Balancer flows]) ++ ++# Determine if expected flows are present. The only parameter to this ++# function is the number of expected flows per NAT destination address. ++# This should always be either 0 or 1. 0 means that we do not expect ++# lflows to be present. 1 means we expect an lflow to be present ++check_lflows() { ++ expected=$1 ++ ro1_flows=$(ovn-sbctl lflow-list ro1) ++ ++ ro1_ip_routing=$(grep lr_in_ip_routing <<< "$ro1_flows") ++ match=$(grep -c "match=(ip4.dst == 20.0.0.100/32)" <<< "$ro1_ip_routing") ++ AT_CHECK([test "$expected" = "$match"]) ++ ++ ro1_arp_resolve=$(grep lr_in_arp_resolve <<< "$ro1_flows") ++ match=$(grep -c 'match=(outport == "ro1-sw" && reg0 == {20.0.0.100})' <<< "$ro1_arp_resolve") ++ AT_CHECK([test "$expected" = "$match"]) ++ ++ ro2_flows=$(ovn-sbctl lflow-list ro2) ++ ++ ro2_ip_routing=$(grep lr_in_ip_routing <<< "$ro2_flows") ++ match=$(grep -c "match=(ip4.dst == 10.0.0.100/32)" <<< "$ro2_ip_routing") ++ AT_CHECK([test "$expected" = "$match"]) ++ ++ ro2_arp_resolve=$(grep lr_in_arp_resolve <<< "$ro2_flows") ++ match=$(grep -c 'match=(outport == "ro2-sw" && reg0 == {10.0.0.100})' <<< "$ro2_arp_resolve") ++ AT_CHECK([test "$expected" = "$match"]) ++} ++ ++ovn_start ++ ++AS_BOX([Setting up the logical network]) ++ ++check ovn-nbctl ls-add sw ++ ++check ovn-nbctl lr-add ro1 ++check ovn-nbctl lrp-add ro1 ro1-sw 00:00:00:00:00:01 10.0.0.1/24 ++check ovn-nbctl lsp-add sw sw-ro1 ++ ++check ovn-nbctl lr-add ro2 ++check ovn-nbctl lrp-add ro2 ro2-sw 00:00:00:00:00:02 20.0.0.1/24 ++check ovn-nbctl --wait=sb lsp-add sw sw-ro2 ++ ++check ovn-nbctl ls-add ls1 ++check ovn-nbctl lsp-add ls1 vm1 ++check ovn-nbctl lsp-set-addresses vm1 "00:00:00:00:01:02 192.168.1.2" ++check ovn-nbctl lrp-add ro1 ro1-ls1 00:00:00:00:01:01 192.168.1.1/24 ++check ovn-nbctl lsp-add ls1 ls1-ro1 ++check ovn-nbctl lsp-set-type ls1-ro1 router ++check ovn-nbctl lsp-set-addresses ls1-ro1 router ++check ovn-nbctl lsp-set-options ls1-ro1 router-port=ro1-ls1 ++ ++check ovn-nbctl ls-add ls2 ++check ovn-nbctl lsp-add ls2 vm2 ++check ovn-nbctl lsp-set-addresses vm2 "00:00:00:00:02:02 192.168.2.2" ++check ovn-nbctl lrp-add ro2 ro2-ls2 00:00:00:00:02:01 192.168.2.1/24 ++check ovn-nbctl lsp-add ls2 ls2-ro2 ++check ovn-nbctl lsp-set-type ls2-ro2 router ++check ovn-nbctl lsp-set-addresses ls2-ro2 router ++check ovn-nbctl lsp-set-options ls2-ro2 router-port=ro2-ls2 ++ ++check ovn-nbctl ha-chassis-group-add grp1 ++check ovn-nbctl ha-chassis-group-add-chassis grp1 hv1 100 ++grp1_uuid=$(ovn-nbctl --columns=_uuid --bare find HA_Chassis_group name=grp1) ++ ++check ovn-nbctl ha-chassis-group-add grp2 ++check ovn-nbctl ha-chassis-group-add-chassis grp2 hv2 100 ++grp2_uuid=$(ovn-nbctl --columns=_uuid --bare find HA_Chassis_group name=grp2) ++ ++AS_BOX([Checking that unconnected logical switch ports generate no lflows]) ++ ++check_lflows 0 ++ ++AS_BOX([Checking that connected logical switch ports have no lflows for non-gateway ports]) ++ ++check ovn-nbctl lsp-set-type sw-ro1 router ++check ovn-nbctl lsp-set-addresses sw-ro1 router ++check ovn-nbctl lsp-set-options sw-ro1 router-port=ro1-sw ++ ++check ovn-nbctl lsp-set-type sw-ro2 router ++check ovn-nbctl lsp-set-addresses sw-ro2 router ++check ovn-nbctl --wait=sb lsp-set-options sw-ro2 router-port=ro2-sw ++ ++check_lflows 0 ++ ++AS_BOX([Checking that NAT flows are not installed for non-gateway routers]) ++ ++check ovn-nbctl lr-nat-add ro1 dnat 10.0.0.100 192.168.1.100 ++check ovn-nbctl lr-nat-add ro2 dnat 20.0.0.100 192.168.2.100 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that non-routable NAT flows are not installed for gateway routers]) ++ ++check ovn-nbctl lrp-set-gateway-chassis ro1-sw hv1 100 ++check ovn-nbctl --wait=sb lrp-set-gateway-chassis ro2-sw hv2 100 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that routable NAT flows are installed when gateway chassis exists]) ++ ++check ovn-nbctl lr-nat-del ro1 ++check ovn-nbctl lr-nat-del ro2 ++check ovn-nbctl --add-route lr-nat-add ro1 dnat 10.0.0.100 192.168.1.100 ++check ovn-nbctl --add-route lr-nat-add ro2 dnat 20.0.0.100 192.168.2.100 ++ ++check_lflows 1 ++ ++AS_BOX([Checking that NAT flows are not installed for routers with gateway chassis removed]) ++ ++check ovn-nbctl lrp-del-gateway-chassis ro1-sw hv1 ++check ovn-nbctl --wait=sb lrp-del-gateway-chassis ro2-sw hv2 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that NAT flows are installed for routers with HA_Chassis_Group]) ++ ++check ovn-nbctl set logical_router_port ro1-sw ha_chassis_group="$grp1_uuid" ++check ovn-nbctl --wait=sb set logical_router_port ro2-sw ha_chassis_group="$grp2_uuid" ++ ++check_lflows 1 ++ ++AS_BOX([Checking that NAT flows are not installed for routers with HA_Chassis_Group removed]) ++ ++check ovn-nbctl clear logical_router_port ro1-sw ha_chassis_group ++check ovn-nbctl --wait=sb clear logical_router_port ro2-sw ha_chassis_group ++ ++check_lflows 0 ++ ++AS_BOX([Checking that Floating IP NAT flows are not installed with no gateway port set]) ++ ++check ovn-nbctl lr-nat-del ro1 ++check ovn-nbctl lr-nat-del ro2 ++ ++check ovn-nbctl lr-nat-add ro1 dnat_and_snat 10.0.0.100 192.168.1.2 vm1 00:00:00:00:00:01 ++check ovn-nbctl lr-nat-add ro2 dnat_and_snat 20.0.0.100 192.168.2.2 vm2 00:00:00:00:00:02 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that non-routable Floating IP NAT flows are not installed for gateway routers]) ++ ++check ovn-nbctl lrp-set-gateway-chassis ro1-sw hv1 100 ++check ovn-nbctl --wait=sb lrp-set-gateway-chassis ro2-sw hv2 100 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that routable Floating IP NAT flows are installed for gateway routers]) ++check ovn-nbctl lr-nat-del ro1 ++check ovn-nbctl lr-nat-del ro2 ++ ++check ovn-nbctl --add-route lr-nat-add ro1 dnat_and_snat 10.0.0.100 192.168.1.2 vm1 00:00:00:00:00:01 ++check ovn-nbctl --add-route lr-nat-add ro2 dnat_and_snat 20.0.0.100 192.168.2.2 vm2 00:00:00:00:00:02 ++ ++check_lflows 1 ++ ++AS_BOX([Checking that Floating IP NAT flows are not installed for routers with gateway chassis removed]) ++ ++check ovn-nbctl lrp-del-gateway-chassis ro1-sw hv1 ++check ovn-nbctl --wait=sb lrp-del-gateway-chassis ro2-sw hv2 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that Floating IP NAT flows are installed for routers with ha_chassis_group]) ++ ++grp1_uuid=$(ovn-nbctl --columns=_uuid --bare find HA_Chassis_group name=grp1) ++check ovn-nbctl set logical_router_port ro1-sw ha_chassis_group="$grp1_uuid" ++ ++grp2_uuid=$(ovn-nbctl --columns=_uuid --bare find HA_Chassis_group name=grp2) ++check ovn-nbctl --wait=sb set logical_router_port ro2-sw ha_chassis_group="$grp2_uuid" ++ ++check_lflows 1 ++ ++AS_BOX([Checking that Floating IP NAT flows are not installed for routers with HA_Chassis_Group removed]) ++ ++check ovn-nbctl clear logical_router_port ro1-sw ha_chassis_group ++check ovn-nbctl --wait=sb clear logical_router_port ro2-sw ha_chassis_group ++ ++check_lflows 0 ++ ++AS_BOX([Checking that Load Balancer VIP flows are not installed for routers with no gateway port]) ++ ++check ovn-nbctl lr-nat-del ro1 ++check ovn-nbctl lr-nat-del ro2 ++ ++check ovn-nbctl lb-add lb1 10.0.0.100 192.168.1.2 ++check ovn-nbctl lr-lb-add ro1 lb1 ++ ++check ovn-nbctl lb-add lb2 20.0.0.100 192.168.2.2 ++check ovn-nbctl --wait=sb lr-lb-add ro2 lb2 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that non-routable Load Balancer VIP flows are not installed for gateway routers]) ++ ++check ovn-nbctl lrp-set-gateway-chassis ro1-sw hv1 100 ++check ovn-nbctl --wait=sb lrp-set-gateway-chassis ro2-sw hv2 100 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that routable Load Balancer VIP flows are installed for gateway routers]) ++ ++check ovn-nbctl lr-lb-del ro1 lb1 ++check ovn-nbctl lr-lb-del ro2 lb2 ++check ovn-nbctl lb-del lb1 ++check ovn-nbctl lb-del lb2 ++ ++check ovn-nbctl --add-route lb-add lb1 10.0.0.100 192.168.1.2 ++check ovn-nbctl --add-route lb-add lb2 20.0.0.100 192.168.2.2 ++check ovn-nbctl lr-lb-add ro1 lb1 ++check ovn-nbctl --wait=sb lr-lb-add ro2 lb2 ++ ++check_lflows 1 ++ ++AS_BOX([Checking that Load Balancer VIP flows are not installed for routers with gateway chassis removed]) ++ ++check ovn-nbctl lrp-del-gateway-chassis ro1-sw hv1 ++check ovn-nbctl --wait=sb lrp-del-gateway-chassis ro2-sw hv2 ++ ++check_lflows 0 ++ ++AS_BOX([Checking that Load Balancer VIP flows are installed for routers with ha_chassis_group]) ++ ++grp1_uuid=$(ovn-nbctl --columns=_uuid --bare find HA_Chassis_group name=grp1) ++check ovn-nbctl set logical_router_port ro1-sw ha_chassis_group="$grp1_uuid" ++ ++grp2_uuid=$(ovn-nbctl --columns=_uuid --bare find HA_Chassis_group name=grp2) ++check ovn-nbctl --wait=sb set logical_router_port ro2-sw ha_chassis_group="$grp2_uuid" ++ ++check_lflows 1 ++ ++AS_BOX([Checking that Load Balancer VIP flows are not iinstalled for routers with HA_Chassis_Group removed]) ++ ++check ovn-nbctl clear logical_router_port ro1-sw ha_chassis_group ++check ovn-nbctl --wait=sb clear logical_router_port ro2-sw ha_chassis_group ++ ++check_lflows 0 ++ ++AT_CLEANUP ++ ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([ovn -- ARP flood for unreachable addresses]) ++ovn_start ++ ++AS_BOX([Setting up the logical network]) ++ ++# This network is the same as the one from "Router Address Propagation" ++check ovn-nbctl ls-add sw ++ ++check ovn-nbctl lr-add ro1 ++check ovn-nbctl lrp-add ro1 ro1-sw 00:00:00:00:00:01 10.0.0.1/24 ++check ovn-nbctl lsp-add sw sw-ro1 ++check ovn-nbctl lsp-set-type sw-ro1 router ++check ovn-nbctl lsp-set-addresses sw-ro1 router ++check ovn-nbctl lsp-set-options sw-ro1 router-port=ro1-sw ++ ++check ovn-nbctl lr-add ro2 ++check ovn-nbctl lrp-add ro2 ro2-sw 00:00:00:00:00:02 20.0.0.1/24 ++check ovn-nbctl lsp-add sw sw-ro2 ++check ovn-nbctl lsp-set-type sw-ro2 router ++check ovn-nbctl lsp-set-addresses sw-ro2 router ++check ovn-nbctl --wait=sb lsp-set-options sw-ro2 router-port=ro2-sw ++ ++check ovn-nbctl ls-add ls1 ++check ovn-nbctl lsp-add ls1 vm1 ++check ovn-nbctl lsp-set-addresses vm1 "00:00:00:00:01:02 192.168.1.2" ++check ovn-nbctl lrp-add ro1 ro1-ls1 00:00:00:00:01:01 192.168.1.1/24 ++check ovn-nbctl lsp-add ls1 ls1-ro1 ++check ovn-nbctl lsp-set-type ls1-ro1 router ++check ovn-nbctl lsp-set-addresses ls1-ro1 router ++check ovn-nbctl lsp-set-options ls1-ro1 router-port=ro1-ls1 ++ ++check ovn-nbctl ls-add ls2 ++check ovn-nbctl lsp-add ls2 vm2 ++check ovn-nbctl lsp-set-addresses vm2 "00:00:00:00:02:02 192.168.2.2" ++check ovn-nbctl lrp-add ro2 ro2-ls2 00:00:00:00:02:01 192.168.2.1/24 ++check ovn-nbctl lsp-add ls2 ls2-ro2 ++check ovn-nbctl lsp-set-type ls2-ro2 router ++check ovn-nbctl lsp-set-addresses ls2-ro2 router ++check ovn-nbctl lsp-set-options ls2-ro2 router-port=ro2-ls2 ++ ++AS_BOX([Ensure that unreachable flood flows are not installed, since no addresses are unreachable]) ++ ++AT_CHECK([ovn-sbctl lflow-list sw | grep "ls_in_l2_lkup" | grep "priority=90" -c], [1], [dnl ++0 ++]) ++ ++AS_BOX([Adding some reachable NAT addresses]) ++ ++check ovn-nbctl lr-nat-add ro1 dnat 10.0.0.100 192.168.1.100 ++check ovn-nbctl lr-nat-add ro1 snat 10.0.0.200 192.168.1.200/30 ++ ++check ovn-nbctl lr-nat-add ro2 dnat 20.0.0.100 192.168.2.100 ++check ovn-nbctl --wait=sb lr-nat-add ro2 snat 20.0.0.200 192.168.2.200/30 ++ ++AS_BOX([Ensure that unreachable flood flows are not installed, since all addresses are reachable]) ++ ++AT_CHECK([ovn-sbctl lflow-list sw | grep "ls_in_l2_lkup" | grep "priority=90" -c], [1], [dnl ++0 ++]) ++ ++AS_BOX([Adding some unreachable NAT addresses]) ++ ++check ovn-nbctl lr-nat-add ro1 dnat 30.0.0.100 192.168.1.130 ++check ovn-nbctl lr-nat-add ro1 snat 30.0.0.200 192.168.1.148/30 ++ ++check ovn-nbctl lr-nat-add ro2 dnat 40.0.0.100 192.168.2.130 ++check ovn-nbctl --wait=sb lr-nat-add ro2 snat 40.0.0.200 192.168.2.148/30 ++ ++AS_BOX([Ensure that unreachable flood flows are installed, since there are unreachable addresses]) ++ ++ovn-sbctl lflow-list ++ ++# We expect two flows to be installed, one per connected router port on sw ++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep priority=90 -c], [0], [dnl ++2 ++]) ++ ++# We expect that the installed flows will match the unreachable DNAT addresses only. ++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep priority=90 | grep "arp.tpa == {30.0.0.100}" -c], [0], [dnl ++1 ++]) ++ ++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep priority=90 | grep "arp.tpa == {40.0.0.100}" -c], [0], [dnl ++1 ++]) ++ ++# Ensure that we do not create flows for SNAT addresses ++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep priority=90 | grep "arp.tpa == {30.0.0.200}" -c], [1], [dnl ++0 ++]) ++ ++AT_CHECK([ovn-sbctl lflow-list sw | grep ls_in_l2_lkup | grep priority=90 | grep "arp.tpa == {40.0.0.200}" -c], [1], [dnl ++0 ++]) ++ ++AT_CLEANUP ++]) diff --git a/tests/ovn-ofctrl-seqno.at b/tests/ovn-ofctrl-seqno.at new file mode 100644 index 000000000..59dfea947 @@ -21099,7 +23238,7 @@ index 6cc5b2174..e510c6cef 100644 for i in 1 2; do diff --git a/tests/ovn.at b/tests/ovn.at -index 2e0bc9c53..0d3a1d1cb 100644 +index 2e0bc9c53..264342037 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -693,6 +693,11 @@ ip,nw_src=4.0.0.0/4.0.0.0 @@ -22129,7 +24268,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=sb sync ovn-sbctl dump-flows -@@ -11483,10 +11497,100 @@ for i in 1 2; do +@@ -11483,10 +11497,184 @@ for i in 1 2; do done done @@ -22219,18 +24358,102 @@ index 2e0bc9c53..0d3a1d1cb 100644 +tpa=$(ip_to_hex 10 0 0 100) +send_garp 1 000000000001 ffffffffffff $spa $tpa + -+dnl traffic from localport should not be sent to localnet -+AT_CHECK([tcpdump -r hv1/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000064 | wc -l],[0],[dnl ++dnl traffic from localport should not be sent to localnet ++AT_CHECK([tcpdump -r hv1/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000064 | wc -l],[0],[dnl ++0 ++],[ignore]) ++ ++OVN_CLEANUP([hv1]) ++AT_CLEANUP ++ ++AT_SETUP([localport doesn't suppress ARP directed to external port]) ++ ++ovn_start ++net_add n1 ++ ++check ovs-vsctl add-br br-phys ++check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys ++ovn_attach n1 br-phys 192.168.0.1 ++ ++check ovn-nbctl ls-add ls ++ ++# create topology to allow to talk from localport through localnet to external port ++check ovn-nbctl lsp-add ls lp ++check ovn-nbctl lsp-set-addresses lp "00:00:00:00:00:01 10.0.0.1" ++check ovn-nbctl lsp-set-type lp localport ++check ovs-vsctl add-port br-int lp -- set Interface lp external-ids:iface-id=lp ++ ++check ovn-nbctl --wait=sb ha-chassis-group-add hagrp ++check ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp main 10 ++check ovn-nbctl lsp-add ls lext ++check ovn-nbctl lsp-set-addresses lext "00:00:00:00:00:02 10.0.0.2" ++check ovn-nbctl lsp-set-type lext external ++hagrp_uuid=`ovn-nbctl --bare --columns _uuid find ha_chassis_group name=hagrp` ++check ovn-nbctl set logical_switch_port lext ha_chassis_group=$hagrp_uuid ++ ++check ovn-nbctl lsp-add ls ln ++check ovn-nbctl lsp-set-addresses ln unknown ++check ovn-nbctl lsp-set-type ln localnet ++check ovn-nbctl lsp-set-options ln network_name=phys ++check ovn-nbctl --wait=hv sync ++ ++# also create second external port AFTER localnet to check that order is irrelevant ++check ovn-nbctl lsp-add ls lext2 ++check ovn-nbctl lsp-set-addresses lext2 "00:00:00:00:00:10 10.0.0.10" ++check ovn-nbctl lsp-set-type lext2 external ++check ovn-nbctl set logical_switch_port lext2 ha_chassis_group=$hagrp_uuid ++check ovn-nbctl --wait=hv sync ++ ++# create and immediately delete an external port to later check that flows for ++# deleted ports are not left over in flow table ++check ovn-nbctl lsp-add ls lext-deleted ++check ovn-nbctl lsp-set-addresses lext-deleted "00:00:00:00:00:03 10.0.0.3" ++check ovn-nbctl lsp-set-type lext-deleted external ++check ovn-nbctl set logical_switch_port lext-deleted ha_chassis_group=$hagrp_uuid ++check ovn-nbctl --wait=hv sync ++check ovn-nbctl lsp-del lext-deleted ++check ovn-nbctl --wait=hv sync ++ ++send_garp() { ++ local inport=$1 eth_src=$2 eth_dst=$3 spa=$4 tpa=$5 ++ local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa} ++ ovs-appctl netdev-dummy/receive $inport $request ++} ++ ++spa=$(ip_to_hex 10 0 0 1) ++tpa=$(ip_to_hex 10 0 0 2) ++send_garp lp 000000000001 000000000002 $spa $tpa ++ ++spa=$(ip_to_hex 10 0 0 1) ++tpa=$(ip_to_hex 10 0 0 10) ++send_garp lp 000000000001 000000000010 $spa $tpa ++ ++spa=$(ip_to_hex 10 0 0 1) ++tpa=$(ip_to_hex 10 0 0 3) ++send_garp lp 000000000001 000000000003 $spa $tpa ++ ++dnl external traffic from localport should be sent to localnet ++AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000002 | wc -l],[0],[dnl ++1 ++],[ignore]) ++ ++#dnl ...regardless of localnet / external ports creation order ++AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a00000a | wc -l],[0],[dnl ++1 ++],[ignore]) ++ ++dnl traffic from localport should not be sent to deleted external port ++AT_CHECK([tcpdump -r main/br-phys_n1-tx.pcap arp[[24:4]]=0x0a000003 | wc -l],[0],[dnl +0 +],[ignore]) + -+OVN_CLEANUP([hv1]) +AT_CLEANUP ++]) + AT_SETUP([ovn -- 1 LR with HA distributed router gateway port]) ovn_start -@@ -11553,6 +11657,7 @@ ovn-nbctl lsp-set-type ln-outside localnet +@@ -11553,6 +11741,7 @@ ovn-nbctl lsp-set-type ln-outside localnet ovn-nbctl lsp-set-options ln-outside network_name=phys # Allow some time for ovn-northd and ovn-controller to catch up. @@ -22238,7 +24461,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync echo "---------NB dump-----" -@@ -11626,20 +11731,20 @@ echo $hv2_gw1_ofport +@@ -11626,20 +11815,20 @@ echo $hv2_gw1_ofport echo $hv2_gw2_ofport echo "--- hv1 ---" @@ -22263,7 +24486,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \ | wc -l], [0], [1 ]) -@@ -11676,15 +11781,16 @@ ovn-nbctl --id=@gc0 create Gateway_Chassis \ +@@ -11676,15 +11865,16 @@ ovn-nbctl --id=@gc0 create Gateway_Chassis \ set Logical_Router_Port outside 'gateway_chassis=[@gc0,@gc1]' @@ -22282,7 +24505,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \ | wc -l], [0], [1 ]) -@@ -11837,12 +11943,12 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid +@@ -11837,12 +12027,12 @@ ovn-nbctl set Logical_Router_Port outside ha_chassis_group=$hagrp1_uuid wait_row_count HA_Chassis_Group 1 wait_row_count HA_Chassis 2 @@ -22297,7 +24520,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 grep active_backup | grep slaves:$hv2_gw1_ofport,$hv2_gw2_ofport \ | wc -l], [0], [1 ]) -@@ -11894,12 +12000,12 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis +@@ -11894,12 +12084,12 @@ wait_column "$exp_ref_ch_list" HA_Chassis_Group ref_chassis # Increase the priority of gw2 ovn-nbctl --wait=sb ha-chassis-group-add-chassis hagrp1 gw2 40 @@ -22312,7 +24535,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 grep active_backup | grep slaves:$hv2_gw2_ofport,$hv2_gw1_ofport \ | wc -l], [0], [1 ]) -@@ -12041,6 +12147,7 @@ AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) +@@ -12041,6 +12231,7 @@ AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet]) AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1]) # wait for earlier changes to take effect @@ -22320,7 +24543,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync reset_pcap_file() { -@@ -12241,6 +12348,7 @@ ovn-nbctl lsp-set-type ln-outside localnet +@@ -12241,6 +12432,7 @@ ovn-nbctl lsp-set-type ln-outside localnet ovn-nbctl lsp-set-options ln-outside network_name=phys # Allow some time for ovn-northd and ovn-controller to catch up. @@ -22328,7 +24551,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync # currently when ovn-controller is restarted, the old entry is deleted -@@ -12878,6 +12986,45 @@ test_tcp_syn_packet() { +@@ -12878,6 +13070,45 @@ test_tcp_syn_packet() { check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } @@ -22374,7 +24597,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Create hypervisors hv[123]. # Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3. # Add all of the vifs to a single logical switch sw0. -@@ -12904,8 +13051,6 @@ for i in 1 2 3; do +@@ -12904,8 +13135,6 @@ for i in 1 2 3; do done OVN_POPULATE_ARP @@ -22383,7 +24606,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 for i in 1 2 3; do : > vif${i}1.expected -@@ -12916,6 +13061,7 @@ check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p11\"" reject +@@ -12916,6 +13145,7 @@ check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p11\"" reject check ovn-nbctl --log acl-add sw0 from-lport 1000 "inport == \"sw0-p21\"" reject # Allow some time for ovn-northd and ovn-controller to catch up. @@ -22391,7 +24614,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync ovn-sbctl dump-flows > sbflows -@@ -12931,6 +13077,10 @@ test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(i +@@ -12931,6 +13161,10 @@ test_tcp_syn_packet 11 1 000000000011 000000000021 $(ip_to_hex 192 168 1 11) $(i test_tcp_syn_packet 21 2 000000000021 000000000011 $(ip_to_hex 192 168 1 21) $(ip_to_hex 192 168 1 11) 0000 8b40 3039 0000 b85f 70e4 test_tcp_syn_packet 31 3 000000000031 000000000012 $(ip_to_hex 192 168 1 31) $(ip_to_hex 192 168 1 12) 0000 8b40 3039 0000 b854 70d9 @@ -22402,7 +24625,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 for i in 1 2 3; do OVN_CHECK_PACKETS([hv$i/vif${i}1-tx.pcap], [vif${i}1.expected]) done -@@ -13062,8 +13212,8 @@ done +@@ -13062,8 +13296,8 @@ done OVN_POPULATE_ARP # Allow some time for ovn-northd and ovn-controller to catch up. @@ -22413,7 +24636,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... # -@@ -13142,10 +13292,6 @@ for is in 1 2 3; do +@@ -13142,10 +13376,6 @@ for is in 1 2 3; do done done @@ -22424,7 +24647,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Now check the packets actually received against the ones expected. for i in 1 2 3; do for j in 1 2 3; do -@@ -13284,8 +13430,8 @@ done +@@ -13284,8 +13514,8 @@ done OVN_POPULATE_ARP # Allow some time for ovn-northd and ovn-controller to catch up. @@ -22435,7 +24658,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 lsp_to_mac() { echo f0:00:00:00:0${1:0:1}:${1:1:2} -@@ -13391,10 +13537,6 @@ for is in 1 2 3; do +@@ -13391,10 +13621,6 @@ for is in 1 2 3; do done done @@ -22446,7 +24669,31 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Now check the packets actually received against the ones expected. for i in 1 2 3; do for j in 1 2 3; do -@@ -13704,6 +13846,7 @@ grep conjunction.*conjunction.*conjunction | wc -l`]) +@@ -13563,11 +13789,11 @@ check ovn-nbctl --wait=hv acl-add ls1 to-lport 1001 \ + # port numbers, e.g. 11 for vif11. + test_ip() { + # This packet has bad checksums but logical L3 routing doesn't check. +- local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 ++ local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 pcap_file=$6 + local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\ + ${dst_ip}0035111100080000 +- shift; shift; shift; shift; shift +- as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet ++ shift; shift; shift; shift; shift; shift ++ netdev_dummy_receive hv1-vif1 $packet hv1 "$pcap_file" + for outport; do + echo $packet >> $outport.expected + done +@@ -13587,7 +13813,7 @@ options:rxq_pcap=${pcap_file}-rx.pcap + sip=`ip_to_hex 10 0 0 4` + dip=`ip_to_hex 10 0 0 6` + +-test_ip 1 f00000000001 f00000000002 $sip $dip 2 ++test_ip 1 f00000000001 f00000000002 $sip $dip hv1/vif2-tx.pcap 2 + + cat 2.expected > expout + $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +@@ -13704,6 +13930,7 @@ grep conjunction.*conjunction.*conjunction | wc -l`]) ovn-nbctl acl-del ls1 to-lport 1001 \ 'ip4 && ip4.src == $set1 && ip4.dst == $set1' @@ -22454,7 +24701,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.10 actions=conjunction(10,1/2) # priority=2001,ip,metadata=0x1,nw_dst=10.0.0.8 actions=conjunction(11,1/2) -@@ -13725,27 +13868,30 @@ AT_CLEANUP +@@ -13725,27 +13952,30 @@ AT_CLEANUP AT_SETUP([ovn -- Superseding ACLs with conjunction]) ovn_start @@ -22493,7 +24740,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 set interface hv1-vif2 external-ids:iface-id=ls1-lp2 \ options:tx_pcap=hv1/vif2-tx.pcap \ options:rxq_pcap=hv1/vif2-rx.pcap \ -@@ -13765,7 +13911,8 @@ test_ip() { +@@ -13765,7 +13995,8 @@ test_ip() { local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}\ ${dst_ip}0035111100080000 shift; shift; shift; shift; shift @@ -22503,7 +24750,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 for outport; do echo $packet >> $outport.expected done -@@ -13774,19 +13921,51 @@ ${dst_ip}0035111100080000 +@@ -13774,19 +14005,51 @@ ${dst_ip}0035111100080000 reset_pcap_file() { local iface=$1 local pcap_file=$2 @@ -22562,7 +24809,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. for src in `seq 1 2`; do -@@ -13814,21 +13993,21 @@ rm -f 2.packets +@@ -13814,21 +14077,21 @@ rm -f 2.packets > 2.expected # Add two less restrictive allow ACLs for src IP 10.0.0.1. @@ -22595,7 +24842,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]) # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. -@@ -13858,40 +14037,38 @@ reset_pcap_file hv1-vif2 hv1/vif2 +@@ -13858,40 +14121,38 @@ reset_pcap_file hv1-vif2 hv1/vif2 rm -f 2.packets > 2.expected @@ -22656,7 +24903,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]) # Traffic 10.0.0.1, 10.0.0.2 -> 10.0.0.3, 10.0.0.4 should be allowed. -@@ -13917,20 +14094,43 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets +@@ -13917,20 +14178,43 @@ $PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > 2.packets AT_CHECK([cat 2.packets], [0], [expout]) # Re-add the less restrictive allow ACL for src IP 10.0.0.1 @@ -22710,7 +24957,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]) OVN_CLEANUP([hv1]) -@@ -13983,8 +14183,8 @@ ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:0 +@@ -13983,8 +14267,8 @@ ovn-nbctl create Address_Set name=set1 addresses=\"f0:00:00:00:00:11\",\"f0:00:0 OVN_POPULATE_ARP # Allow some time for ovn-northd and ovn-controller to catch up. @@ -22721,7 +24968,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Make sure there is no attempt to adding duplicated flows by ovn-controller AT_FAIL_IF([test -n "`grep duplicate hv1/ovn-controller.log`"]) -@@ -14224,6 +14424,7 @@ done +@@ -14224,6 +14508,7 @@ done OVN_POPULATE_ARP # allow some time for ovn-northd and ovn-controller to catch up. @@ -22729,7 +24976,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync test_ip_packet 1 1 000000000001 00000000ff01 $(ip_to_hex 192 168 1 1) $(ip_to_hex 192 168 2 1) $(ip_to_hex 192 168 1 254) 0000 f87c ea96 -@@ -14294,6 +14495,45 @@ test_tcp_syn_packet() { +@@ -14294,6 +14579,45 @@ test_tcp_syn_packet() { as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } @@ -22775,12 +25022,14 @@ index 2e0bc9c53..0d3a1d1cb 100644 # test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER TCP_SPORT TCP_DPORT TCP_CHKSUM EXP_TCP_RST_CHKSUM # # Causes a packet to be received on INPORT of the hypervisor HV. The packet is a TCP syn segment with -@@ -14314,6 +14554,36 @@ test_tcp6_packet() { +@@ -14314,9 +14638,39 @@ test_tcp6_packet() { as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet } +-# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA EXP_ICMP_CODE EXP_ICMP_CHKSUM +# test_tcp6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_ROUTER SCTP_SPORT SCTP_DPORT SCTP_INIT_TAG SCTP_CHKSUM EXP_SCTP_ABORT_CHKSUM -+# + # +-# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 +# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an SCTP INIT chunk with +# ETH_SRC, ETH_DST, IPV6_SRC, IPV6_ROUTER, SCTP_SPORT, SCTP_DPORT and SCTP_CHKSUM as specified. +# The INIT "initiate_tag" will be set to SCTP_INIT_TAG. @@ -22809,10 +25058,13 @@ index 2e0bc9c53..0d3a1d1cb 100644 + check as hv$hv ovs-appctl netdev-dummy/receive vif$inport $packet +} + - # test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA EXP_ICMP_CODE EXP_ICMP_CHKSUM - # - # Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 -@@ -14365,16 +14635,17 @@ done ++# test_ip6_packet INPORT HV ETH_SRC ETH_DST IPV6_SRC IPV6_DST IPV6_PROTO IPV6_LEN DATA EXP_ICMP_CODE EXP_ICMP_CHKSUM ++# ++# Causes a packet to be received on INPORT of the hypervisor HV. The packet is an IPv6 + # packet with ETH_SRC, ETH_DST, IPV6_SRC, IPV6_DST, IPV6_PROTO, IPV6_LEN and DATA as specified. + # EXP_ICMP_CODE and EXP_ICMP_CHKSUM are the code and checksum of the icmp6 packet sent by OVN logical router + test_ip6_packet() { +@@ -14365,16 +14719,17 @@ done OVN_POPULATE_ARP # allow some time for ovn-northd and ovn-controller to catch up. @@ -22832,7 +25084,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [vif2.expected]) OVN_CLEANUP([hv1], [hv2]) -@@ -14439,7 +14710,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ +@@ -14439,7 +14794,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ OVN_POPULATE_ARP @@ -22842,7 +25094,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac && ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && -@@ -14632,6 +14904,8 @@ OVS_WAIT_UNTIL( +@@ -14632,6 +14988,8 @@ OVS_WAIT_UNTIL( logical_port=ls1-lp_ext1` test "$chassis" = "$hv1_uuid"]) @@ -22851,7 +25103,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1 (ovn-sbctl dump-flows lr0; ovn-sbctl dump-flows ls1) > sbflows as hv1 ovs-ofctl dump-flows br-int > brintflows -@@ -14912,6 +15186,7 @@ OVS_WAIT_UNTIL( +@@ -14912,6 +15270,7 @@ OVS_WAIT_UNTIL( [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ logical_port=ls1-lp_ext1` test "$chassis" = "$hv2_uuid"]) @@ -22859,7 +25111,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in hv2 AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | \ -@@ -15026,6 +15301,7 @@ OVS_WAIT_UNTIL( +@@ -15026,6 +15385,7 @@ OVS_WAIT_UNTIL( [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ logical_port=ls1-lp_ext1` test "$chassis" = "$hv1_uuid"]) @@ -22867,7 +25119,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 as hv1 ovs-vsctl show -@@ -15106,6 +15382,7 @@ OVS_WAIT_UNTIL( +@@ -15106,6 +15466,7 @@ OVS_WAIT_UNTIL( [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ logical_port=ls1-lp_ext1` test "$chassis" = "$hv3_uuid"]) @@ -22875,7 +25127,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 as hv1 ovs-vsctl show -@@ -15190,11 +15467,12 @@ OVS_WAIT_UNTIL( +@@ -15190,11 +15551,12 @@ OVS_WAIT_UNTIL( [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ logical_port=ls1-lp_ext1` test "$chassis" = "$hv1_uuid"]) @@ -22889,7 +25141,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 grep -c "actions=drop"], [0], [1 ]) -@@ -15207,6 +15485,7 @@ OVS_WAIT_UNTIL( +@@ -15207,6 +15569,7 @@ OVS_WAIT_UNTIL( [chassis=`ovn-sbctl --bare --columns chassis find port_binding \ logical_port=ls1-lp_ext1` test "$chassis" = "$hv2_uuid"]) @@ -22897,7 +25149,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 as hv1 OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) -@@ -15357,7 +15636,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ +@@ -15357,7 +15720,8 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ OVN_POPULATE_ARP @@ -22907,7 +25159,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 packet="inport==\"sw1-p1\" && eth.src==$sw1_p1_mac && eth.dst==$sw1_ro_mac && ip4 && ip.ttl==64 && ip4.src==$sw1_p1_ip && ip4.dst==$sw2_p1_ip && -@@ -15640,6 +15920,7 @@ test_ip6_packet_larger() { +@@ -15640,6 +16004,7 @@ test_ip6_packet_larger() { fi } @@ -22915,7 +25167,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync ovn-nbctl show > nbdump -@@ -15789,6 +16070,7 @@ ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \ +@@ -15789,6 +16154,7 @@ ovn-nbctl lsp-add sw1 rp-sw1 -- set Logical_Switch_Port rp-sw1 \ ovn-nbctl lsp-add sw0 sw0-p0 \ -- lsp-set-addresses sw0-p0 "f0:00:00:01:02:03 192.168.1.2 2001::2" @@ -22923,7 +25175,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl lsp-add sw0 sw0-p1 \ -- lsp-set-addresses sw0-p1 "f0:00:00:11:02:03 192.168.1.3 2001::3" -@@ -15799,6 +16081,7 @@ ovn-nbctl lr-nat-add lr0 snat 172.16.1.1 192.168.1.0/24 +@@ -15799,6 +16165,7 @@ ovn-nbctl lr-nat-add lr0 snat 172.16.1.1 192.168.1.0/24 ovn-nbctl lr-nat-add lr0 snat 2002::1 2001::/64 OVN_POPULATE_ARP @@ -22931,7 +25183,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync ovn-sbctl dump-flows > sbflows -@@ -15847,6 +16130,14 @@ ovn-nbctl --wait=hv sync +@@ -15847,6 +16214,14 @@ ovn-nbctl --wait=hv sync ovn-sbctl dump-flows > sbflows2 AT_CAPTURE_FILE([sbflows2]) @@ -22946,7 +25198,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 dst_ip=$(ip_to_hex 172 16 2 10) fip_ip=$(ip_to_hex 172 16 1 2) src_ip=$(ip_to_hex 192 168 1 3) -@@ -15857,6 +16148,8 @@ echo $(get_arp_req f00000010204 $fip_ip $gw_router_ip) >> expected +@@ -15857,6 +16232,8 @@ echo $(get_arp_req f00000010204 $fip_ip $gw_router_ip) >> expected send_arp_reply 2 1 $gw_router_mac f00000010204 $gw_router_ip $fip_ip echo "${gw_router_mac}f0000001020408004500001c00004000fe0121b4${fip_ip}${dst_ip}${data}" >> expected @@ -22955,7 +25207,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) OVN_CLEANUP([hv1],[hv2]) -@@ -16045,6 +16338,7 @@ for i in 1 2 3 4 5; do +@@ -16045,6 +16422,7 @@ for i in 1 2 3 4 5; do done dnl Wait for the changes to be propagated @@ -22963,7 +25215,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync dnl Assert that each Chassis has a tunnel formed to every other Chassis -@@ -16324,6 +16618,7 @@ ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24 +@@ -16324,6 +16702,7 @@ ovn-nbctl lrp-add router router-to-ls2 00:00:01:01:02:05 192.168.2.3/24 ovn-nbctl lsp-add ls1 ls1-to-router -- set Logical_Switch_Port ls1-to-router type=router options:router-port=router-to-ls1 -- lsp-set-addresses ls1-to-router router ovn-nbctl lsp-add ls2 ls2-to-router -- set Logical_Switch_Port ls2-to-router type=router options:router-port=router-to-ls2 -- lsp-set-addresses ls2-to-router router @@ -22971,7 +25223,24 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=sb sync #ovn-sbctl dump-flows -@@ -16449,57 +16744,69 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \ +@@ -16410,6 +16789,16 @@ send_arp_reply() { + as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request + } + ++send_icmp_packet() { ++ local inport=$1 hv=$2 eth_src=$3 eth_dst=$4 ipv4_src=$5 ipv4_dst=$6 ip_chksum=$7 data=$8 ++ shift 8 ++ ++ local ip_ttl=ff ++ local ip_len=001c ++ local packet=${eth_dst}${eth_src}08004500${ip_len}00004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${data} ++ as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $packet ++} ++ + net_add n1 + + sim_add hv1 +@@ -16449,57 +16838,69 @@ ovs-vsctl -- add-port br-int hv2-vif2 -- \ ovn-nbctl ls-add sw0 @@ -23068,10 +25337,10 @@ index 2e0bc9c53..0d3a1d1cb 100644 +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 @@ -23080,7 +25349,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync # Check that logical flows are added for sw0-vir in lsp_in_arp_rsp pipeline -@@ -16547,6 +16854,30 @@ ovs-vsctl del-port hv1-vif3 +@@ -16547,6 +16948,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], []) @@ -23111,7 +25380,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # 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 -@@ -16555,12 +16886,10 @@ spa=$(ip_to_hex 10 0 0 10) +@@ -16555,12 +16980,10 @@ 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 @@ -23128,7 +25397,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # There should be an arp resolve flow to resolve the virtual_ip with the # sw0-p1's MAC. -@@ -16570,6 +16899,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows2 | grep "reg0 == 10.0.0.10" | sed 's/ +@@ -16570,6 +16993,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;) ]) @@ -23142,13 +25411,29 @@ index 2e0bc9c53..0d3a1d1cb 100644 # 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) -@@ -16578,6 +16914,15 @@ ovn-sbctl clear port_binding $pb_uuid virtual_parent +@@ -16578,6 +17008,31 @@ ovn-sbctl clear port_binding $pb_uuid virtual_parent OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns chassis find port_binding \ logical_port=sw0-vir) = x]) +wait_row_count nb:Logical_Switch_Port 1 up=false name=sw0-vir + +check ovn-nbctl --wait=hv sync ++ ++# verify the traffic from virtual port is discarded if the port is not claimed ++AT_CHECK([grep lr_in_gw_redirect lr0-flows2 | grep "ip4.src == 10.0.0.10"], [0], [dnl ++ table=17(lr_in_gw_redirect ), priority=100 , match=(ip4.src == 10.0.0.10 && outport == "lr0-public" && is_chassis_resident("sw0-vir")), action=(eth.src = 10:54:00:00:00:10; reg1 = 172.168.0.50; next;) ++ table=17(lr_in_gw_redirect ), priority=80 , match=(ip4.src == 10.0.0.10 && outport == "lr0-public"), action=(drop;) ++]) ++ ++eth_src=505400000003 ++eth_dst=00000000ff01 ++ip_src=$(ip_to_hex 10 0 0 10) ++ip_dst=$(ip_to_hex 172 168 0 101) ++send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 0000000000000000000000 ++AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | awk '/table=25, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl ++priority=80,ip,reg15=0x3,metadata=0x3,nw_src=10.0.0.10 actions=drop ++]) ++ +# hv1 should remove the flow for the ACL with is_chassis_redirect check for sw0-vir. +check_virtual_offlows_not_present hv1 + @@ -23158,7 +25443,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # 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 -@@ -16588,6 +16933,60 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) +@@ -16588,6 +17043,60 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p1]) @@ -23219,7 +25504,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # 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 -@@ -16602,10 +17001,11 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) +@@ -16602,10 +17111,11 @@ logical_port=sw0-vir) = x$hv1_ch_uuid], [0], []) OVS_WAIT_UNTIL([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p3]) @@ -23233,7 +25518,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-sbctl dump-flows lr0 > lr0-flows3 AT_CAPTURE_FILE([lr0-flows3]) cp ovn-sb/ovn-sb.db lr0-flows3.db -@@ -16613,6 +17013,13 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows3 | grep "reg0 == 10.0.0.10" | sed 's +@@ -16613,6 +17123,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;) ]) @@ -23247,7 +25532,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir # and sw0-p2 shpuld be its virtual_parent. eth_src=505400000004 -@@ -16627,16 +17034,24 @@ logical_port=sw0-vir) = x$hv2_ch_uuid], [0], []) +@@ -16627,16 +17144,24 @@ logical_port=sw0-vir) = x$hv2_ch_uuid], [0], []) AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p2]) @@ -23274,7 +25559,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Now send arp reply from sw0-p1. hv1 should claim sw0-vir # and sw0-p1 shpuld be its virtual_parent. eth_src=505400000003 -@@ -16652,12 +17067,22 @@ sleep 1 +@@ -16652,12 +17177,22 @@ sleep 1 AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p1]) @@ -23297,7 +25582,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Delete hv1-vif1 port. hv1 should release sw0-vir as hv1 ovs-vsctl del-port hv1-vif1 -@@ -16668,6 +17093,8 @@ sleep 1 +@@ -16668,6 +17203,8 @@ sleep 1 AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = x]) @@ -23306,7 +25591,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # 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. ovn-sbctl dump-flows lr0 > lr0-flows6 -@@ -16676,6 +17103,15 @@ AT_CHECK([grep lr_in_arp_resolve lr0-flows6 | grep "reg0 == 10.0.0.10" | sed 's/ +@@ -16676,6 +17213,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;) ]) @@ -23322,7 +25607,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Now send arp reply from sw0-p2. hv2 should claim sw0-vir # and sw0-p2 should be its virtual_parent. eth_src=505400000004 -@@ -16691,12 +17127,22 @@ sleep 1 +@@ -16691,12 +17237,22 @@ sleep 1 AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = xsw0-p2]) @@ -23345,7 +25630,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Delete sw0-p2 logical port ovn-nbctl lsp-del sw0-p2 -@@ -16705,6 +17151,8 @@ logical_port=sw0-vir) = x], [0], []) +@@ -16705,6 +17261,8 @@ logical_port=sw0-vir) = x], [0], []) AT_CHECK([test x$(ovn-sbctl --bare --columns virtual_parent find port_binding \ logical_port=sw0-vir) = x]) @@ -23354,7 +25639,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Clear virtual_ip column of sw0-vir. There should be no bind_vport flows. ovn-nbctl --wait=hv remove logical_switch_port sw0-vir options virtual-ip -@@ -16722,6 +17170,14 @@ AT_CHECK([grep ls_in_arp_rsp sw0-flows3 | grep bind_vport | sed 's/table=../tabl +@@ -16722,6 +17280,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;) ]) @@ -23369,7 +25654,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 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]) -@@ -16731,6 +17187,38 @@ ovn-sbctl dump-flows lr0 > lr0-flows8 +@@ -16731,6 +17297,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]) @@ -23408,7 +25693,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP -@@ -16807,22 +17295,22 @@ ovs-vsctl -- add-port br-int vif33 -- \ +@@ -16807,22 +17405,22 @@ ovs-vsctl -- add-port br-int vif33 -- \ options:rxq_pcap=hv$i/vif33-rx.pcap \ ofport-request=33 @@ -23435,7 +25720,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync ovn-sbctl lflow-list > sbflows AT_CAPTURE_FILE([sbflows]) -@@ -16889,6 +17377,8 @@ AT_CHECK_UNQUOTED([ovn-sbctl get controller_event $uuid event_info:load_balancer +@@ -16889,6 +17487,8 @@ AT_CHECK_UNQUOTED([ovn-sbctl get controller_event $uuid event_info:load_balancer "$uuid_lb2" ]) @@ -23444,7 +25729,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP -@@ -17108,6 +17598,27 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \ +@@ -17108,6 +17708,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 @@ -23472,7 +25757,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVN_POPULATE_ARP # Enable IGMP snooping on sw1. -@@ -17124,21 +17635,16 @@ ovn-sbctl dump-flows > sbflows +@@ -17124,21 +17745,16 @@ ovn-sbctl dump-flows > sbflows AT_CAPTURE_FILE([expected]) AT_CAPTURE_FILE([received]) > expected @@ -23500,7 +25785,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-sbctl dump-flows > sbflows2 # Inject IGMP Join for 239.0.1.68 on sw1-p11. -@@ -17156,9 +17662,9 @@ wait_row_count IGMP_Group 2 address=239.0.1.68 +@@ -17156,9 +17772,9 @@ wait_row_count IGMP_Group 2 address=239.0.1.68 check ovn-nbctl --wait=hv sync AT_CAPTURE_FILE([sbflows3]) @@ -23511,7 +25796,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Send traffic and make sure it gets forwarded only on the two ports that # joined. > expected -@@ -17172,22 +17678,6 @@ store_ip_multicast_pkt \ +@@ -17172,22 +17788,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 @@ -23534,7 +25819,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVS_WAIT_UNTIL( [check_packets 'hv1/vif1-tx.pcap expected' \ 'hv2/vif1-tx.pcap expected' \ -@@ -17207,6 +17697,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ +@@ -17207,6 +17807,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ wait_row_count IGMP_Group 1 address=239.0.1.68 check ovn-nbctl --wait=hv sync @@ -23542,7 +25827,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Send traffic and make sure it gets forwarded only on the port that joined. as hv1 reset_pcap_file hv1-vif1 hv1/vif1 as hv2 reset_pcap_file hv2-vif1 hv2/vif1 -@@ -17246,6 +17737,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ +@@ -17246,6 +17847,7 @@ send_igmp_v3_report hv1-vif1 hv1 \ # Check that the IGMP Group is learned. wait_row_count IGMP_Group 1 address=224.0.0.42 @@ -23550,7 +25835,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Send traffic and make sure it gets flooded to all ports. as hv1 reset_pcap_file hv1-vif1 hv1/vif1 as hv1 reset_pcap_file hv1-vif2 hv1/vif2 -@@ -17275,15 +17767,27 @@ check ovn-nbctl set Logical_Switch sw2 \ +@@ -17275,15 +17877,27 @@ check ovn-nbctl set Logical_Switch sw2 \ other_config:mcast_eth_src="00:00:00:00:02:fe" \ other_config:mcast_ip4_src="20.0.0.254" @@ -23583,7 +25868,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Disable IGMP querier on sw2. check ovn-nbctl set Logical_Switch sw2 \ -@@ -17296,6 +17800,7 @@ check ovn-nbctl set Logical_Switch sw3 \ +@@ -17296,6 +17910,7 @@ check ovn-nbctl set Logical_Switch sw3 \ check ovn-nbctl --wait=hv sync @@ -23591,7 +25876,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Send traffic from sw3 and make sure rtr doesn't relay it. > expected_empty -@@ -17345,6 +17850,7 @@ send_igmp_v3_report hv2-vif3 hv2 \ +@@ -17345,6 +17960,7 @@ send_igmp_v3_report hv2-vif3 hv2 \ wait_row_count IGMP_Group 2 address=239.0.1.68 check ovn-nbctl --wait=hv sync @@ -23599,7 +25884,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Send traffic from sw3 and make sure it is relayed by rtr. # to ports that joined. > expected_routed_sw1 -@@ -17394,6 +17900,7 @@ send_igmp_v3_report hv1-vif4 hv1 \ +@@ -17394,6 +18010,7 @@ send_igmp_v3_report hv1-vif4 hv1 \ wait_row_count IGMP_Group 3 address=239.0.1.68 check ovn-nbctl --wait=hv sync @@ -23607,7 +25892,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Send traffic from sw3 and make sure it is relayed by rtr # to ports that joined. > expected_routed_sw1 -@@ -17493,6 +18000,7 @@ send_igmp_v3_report hv1-vif2 hv1 \ +@@ -17493,6 +18110,7 @@ send_igmp_v3_report hv1-vif2 hv1 \ wait_row_count IGMP_Group 1 address=239.0.1.68 check ovn-nbctl --wait=hv sync @@ -23615,7 +25900,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Send traffic from sw1-p21 send_ip_multicast_pkt hv2-vif1 hv2 \ 000000000001 01005e000144 \ -@@ -17790,6 +18298,7 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \ +@@ -17790,6 +18408,7 @@ check ovs-vsctl -- add-port br-int hv2-vif4 -- \ ofport-request=1 check ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys @@ -23623,7 +25908,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync AT_CAPTURE_FILE([sbflows]) -@@ -18470,6 +18979,7 @@ m4_define([DVR_N_S_ARP_HANDLING], +@@ -18470,6 +19089,7 @@ m4_define([DVR_N_S_ARP_HANDLING], # Set a hypervisor as gateway chassis, for router port 172.31.0.1 ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 @@ -23631,7 +25916,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=sb sync wait_row_count Port_Binding 1 logical_port=cr-router-to-underlay -@@ -18689,6 +19199,7 @@ m4_define([DVR_N_S_PING], +@@ -18689,6 +19309,7 @@ m4_define([DVR_N_S_PING], ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 ovn-nbctl lrp-set-redirect-type router-to-underlay bridged @@ -23639,7 +25924,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=sb sync -@@ -18816,7 +19327,7 @@ m4_define([DVR_N_S_PING], +@@ -18816,7 +19437,7 @@ m4_define([DVR_N_S_PING], OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv4/vif-north-tx.pcap], [vif-north.expected]) # Confirm that packets did not go out via tunnel port. @@ -23648,7 +25933,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]]) # Confirm that packet went out via localnet port -@@ -18919,6 +19430,7 @@ ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 +@@ -18919,6 +19540,7 @@ ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02 ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 OVN_POPULATE_ARP @@ -23656,7 +25941,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync as hv1 ovs-appctl -t ovn-controller vlog/set dbg -@@ -18945,7 +19457,8 @@ list mac_binding], [0], [lr0-sw0 +@@ -18945,7 +19567,8 @@ list mac_binding], [0], [lr0-sw0 50:54:00:00:00:03 ]) @@ -23666,7 +25951,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \ grep controller | grep -v n_packets=0 | wc -l`]) -@@ -18962,7 +19475,8 @@ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_p +@@ -18962,7 +19585,8 @@ OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_p # The packet should not be sent to ovn-controller. The packet # count should be 1 only. @@ -23676,7 +25961,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \ grep controller | grep -v n_packets=0 | wc -l`]) -@@ -18975,7 +19489,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa +@@ -18975,7 +19599,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa # The garp packet should be sent to ovn-controller and the mac_binding entry # should be updated. @@ -23686,7 +25971,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check_row_count MAC_Binding 1 -@@ -19000,7 +19515,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa +@@ -19000,7 +19625,8 @@ send_garp 1 1 $eth_src $eth_dst $spa $tpa # The garp packet should be sent to ovn-controller and the mac_binding entry # should be updated. @@ -23696,7 +25981,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVS_WAIT_UNTIL( [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:33 \ -@@ -19021,7 +19537,8 @@ OVS_WAIT_UNTIL( +@@ -19021,7 +19647,8 @@ OVS_WAIT_UNTIL( | grep n_packets=1 | wc -l`] ) @@ -23706,7 +25991,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Now send ARP reply packet with IP - 10.0.0.40 and mac 505400000023 eth_src=505400000023 -@@ -19038,7 +19555,8 @@ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa +@@ -19038,7 +19665,8 @@ send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa # The garp packet should be sent to ovn-controller and the mac_binding entry # should be updated. @@ -23716,7 +26001,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Wait for an entry in table=67 for the learnt mac_binding entry. -@@ -19054,7 +19572,8 @@ OVS_WAIT_UNTIL( +@@ -19054,7 +19682,8 @@ OVS_WAIT_UNTIL( | grep n_packets=1 | wc -l`] ) @@ -23726,7 +26011,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa OVS_WAIT_UNTIL( -@@ -19062,7 +19581,8 @@ OVS_WAIT_UNTIL( +@@ -19062,7 +19691,8 @@ OVS_WAIT_UNTIL( | grep n_packets=2 | wc -l`] ) @@ -23736,7 +26021,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP -@@ -19100,8 +19620,7 @@ ovn-nbctl lsp-add ls1 lp11 +@@ -19100,8 +19730,7 @@ ovn-nbctl lsp-add ls1 lp11 ovn-nbctl lsp-set-addresses lp11 "f0:00:00:00:00:11" ovn-nbctl lsp-set-port-security lp11 f0:00:00:00:00:11 @@ -23746,7 +26031,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=sb sync ovn-nbctl show -@@ -19270,6 +19789,7 @@ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 +@@ -19270,6 +19899,7 @@ ovn-nbctl lrp-set-gateway-chassis router-to-underlay hv3 ovn-nbctl --stateless lr-nat-add router dnat_and_snat 172.31.0.100 192.168.1.1 ovn-nbctl lrp-set-redirect-type router-to-underlay bridged @@ -23754,7 +26039,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=sb sync -@@ -19534,6 +20054,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public +@@ -19534,6 +20164,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public check ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 OVN_POPULATE_ARP @@ -23762,7 +26047,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync wait_row_count Service_Monitor 2 -@@ -19542,7 +20063,14 @@ AT_CAPTURE_FILE([sbflows]) +@@ -19542,7 +20173,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, @@ -23778,7 +26063,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]) AT_CAPTURE_FILE([sbflows2]) -@@ -19722,6 +20250,7 @@ ovn-nbctl lsp-set-options ln-public network_name=public +@@ -19722,6 +20360,7 @@ ovn-nbctl lsp-set-options ln-public network_name=public ovn-nbctl --wait=hv lrp-set-gateway-chassis lr0-public hv1 20 OVN_POPULATE_ARP @@ -23786,7 +26071,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync # And now for the anticlimax. We need to ensure that there is no -@@ -19861,6 +20390,7 @@ check ovs-vsctl -- add-port br-int hv1-vif2 -- \ +@@ -19861,6 +20500,7 @@ check ovs-vsctl -- add-port br-int hv1-vif2 -- \ ofport-request=3 OVN_POPULATE_ARP @@ -23794,7 +26079,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync ovn-sbctl dump-flows > sbflows -@@ -20216,6 +20746,7 @@ ovn-nbctl lsp-add lsw0 lp1 +@@ -20216,6 +20856,7 @@ ovn-nbctl lsp-add lsw0 lp1 ovn-nbctl lsp-set-addresses lp1 "f0:00:00:00:00:01 10.0.0.1" ovn-nbctl acl-add lsw0 from-lport 1000 'eth.type == 0x1234' drop @@ -23802,7 +26087,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync # Trace with --ovs should see ovs flow related to the ACL -@@ -20310,6 +20841,7 @@ for az in `seq 1 $n_az`; do +@@ -20310,6 +20951,7 @@ for az in `seq 1 $n_az`; do done check ovn-nbctl --wait=hv sync ovn-sbctl list Port_Binding > az$az.ports @@ -23810,7 +26095,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 done # Pre-populate the hypervisors' ARP tables so that we don't lose any -@@ -20485,6 +21017,7 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \ +@@ -20485,6 +21127,7 @@ ovs-vsctl -- add-port br-int hv1-vif3 -- \ # wait for earlier changes to take effect check ovn-nbctl --wait=hv sync @@ -23818,7 +26103,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-sbctl dump-flows > sbflows AT_CAPTURE_FILE([sbflows]) -@@ -20672,8 +21205,9 @@ build_tcp_syn() { +@@ -20672,8 +21315,9 @@ build_tcp_syn() { send_ipv4_pkt() { local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 @@ -23830,7 +26115,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 local hp_l4_payload=${11} local outfile=${12} -@@ -20681,8 +21215,10 @@ send_ipv4_pkt() { +@@ -20681,8 +21325,10 @@ send_ipv4_pkt() { local eth=${eth_dst}${eth_src}0800 local hp_eth=${eth_src}${eth_dst}0800 @@ -23843,7 +26128,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 local packet=${eth}${ip}${l4_payload} local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload} -@@ -20694,15 +21230,16 @@ send_ipv6_pkt() { +@@ -20694,15 +21340,16 @@ send_ipv6_pkt() { local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 local ip_src=$5 ip_dst=$6 ip_proto=$7 ip_len=$8 local l4_payload=$9 @@ -23863,7 +26148,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 local packet=${eth}${ip}${l4_payload} local hp_packet=${hp_eth}${hp_ip}${hp_l4_payload} -@@ -20724,16 +21261,26 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ +@@ -20724,16 +21371,26 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ # One logical switch with IPv4 and IPv6 load balancers that hairpin the # traffic. @@ -23894,7 +26179,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl lr-add rtr ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24 4200::00ff/64 -@@ -20743,59 +21290,324 @@ ovn-nbctl lsp-add sw sw-rtr \ +@@ -20743,59 +21400,324 @@ ovn-nbctl lsp-add sw sw-rtr \ -- lsp-set-options sw-rtr router-port=rtr-sw ovn-nbctl --wait=hv sync @@ -23929,13 +26214,10 @@ index 2e0bc9c53..0d3a1d1cb 100644 + ${tcp_payload} \ + $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \ + expected - - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - --# Inject IPv4 UDP packet from lsp. --udp_payload=$(build_udp 84d0 0fc8 6666) --hp_udp_payload=$(build_udp 84d0 07e5 6e49) ++ ++# Check that traffic is hairpinned. ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) ++ +# Check learned hairpin reply flows. +OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl + table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.88,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] @@ -23950,10 +26232,8 @@ index 2e0bc9c53..0d3a1d1cb 100644 +# Inject IPv4 TCP packets from lsp. +tcp_payload=$(build_tcp_syn 84d0 1f90 05a7) +hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 156f) - send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ - $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ -- 11 001e 35f4 \ -- ${udp_payload} ${hp_udp_payload} \ ++send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ ++ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ + 06 0028 \ + ${tcp_payload} \ + $(ip_to_hex 88 88 88 87) ${hp_tcp_payload} \ @@ -23966,18 +26246,12 @@ index 2e0bc9c53..0d3a1d1cb 100644 + 06 0028 \ + ${tcp_payload} \ + $(ip_to_hex 88 88 88 89) ${hp_tcp_payload} \ - expected ++ expected # Check that traffic is hairpinned. OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) --# Inject IPv6 TCP packet from lsp. --tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9) --hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0) --send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -- 42000000000000000000000000000001 88000000000000000000000000000088 \ -- 06 0014 \ -- ${tcp_payload} ${hp_tcp_payload} \ +-# Inject IPv4 UDP packet from lsp. +# Check learned hairpin reply flows. +OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl + table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] @@ -23987,10 +26261,12 @@ index 2e0bc9c53..0d3a1d1cb 100644 +AS_BOX([IPv4 UDP Hairpin]) + +# Inject IPv4 UDP packets from lsp. -+udp_payload=$(build_udp 84d0 0fc8 6666) -+hp_udp_payload=$(build_udp 84d0 07e5 6e49) -+send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ + udp_payload=$(build_udp 84d0 0fc8 6666) + hp_udp_payload=$(build_udp 84d0 07e5 6e49) + send_ipv4_pkt hv1 hv1-vif1 000000000001 000000000100 \ + $(ip_to_hex 42 42 42 1) $(ip_to_hex 88 88 88 88) \ +- 11 001e 35f4 \ +- ${udp_payload} ${hp_udp_payload} \ + 11 001e \ + ${udp_payload} \ + $(ip_to_hex 88 88 88 88) ${hp_udp_payload} \ @@ -24003,12 +26279,11 @@ index 2e0bc9c53..0d3a1d1cb 100644 + 11 001e \ + ${udp_payload} \ + $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \ - expected - - # Check that traffic is hairpinned. - OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) - --# Inject IPv6 UDP packet from lsp. ++ expected ++ ++# Check that traffic is hairpinned. ++OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) ++ +# Check learned hairpin reply flows. +OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl + table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] @@ -24038,11 +26313,12 @@ index 2e0bc9c53..0d3a1d1cb 100644 + 11 001e \ + ${udp_payload} \ + $(ip_to_hex 88 88 88 89) ${hp_udp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ + expected + + # Check that traffic is hairpinned. + OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) + +-# Inject IPv6 TCP packet from lsp. +# Check learned hairpin reply flows. +OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl + table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] @@ -24054,11 +26330,12 @@ index 2e0bc9c53..0d3a1d1cb 100644 +AS_BOX([IPv6 TCP Hairpin]) + +# Inject IPv6 TCP packets from lsp. -+tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9) -+hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0) -+send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ -+ 42000000000000000000000000000001 88000000000000000000000000000088 \ -+ 06 0014 \ + tcp_payload=$(build_tcp_syn 84d0 1f90 3ff9) + hp_tcp_payload=$(build_tcp_syn 84d0 0fc9 4fc0) + send_ipv6_pkt hv1 hv1-vif1 000000000001 000000000100 \ + 42000000000000000000000000000001 88000000000000000000000000000088 \ + 06 0014 \ +- ${tcp_payload} ${hp_tcp_payload} \ + ${tcp_payload} \ + 88000000000000000000000000000088 ${hp_tcp_payload} \ + expected @@ -24107,11 +26384,12 @@ index 2e0bc9c53..0d3a1d1cb 100644 + 06 0014 \ + ${tcp_payload} \ + 88000000000000000000000000000089 ${hp_tcp_payload} \ -+ expected -+ -+# Check that traffic is hairpinned. -+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) -+ + expected + + # Check that traffic is hairpinned. + OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected]) + +-# Inject IPv6 UDP packet from lsp. +# Check learned hairpin reply flows. +OVS_WAIT_FOR_OUTPUT([as hv1 ovs-ofctl dump-flows br-int table=69 | ofctl_strip_all | grep -v NXST], [0], [dnl + table=69, tcp,metadata=0x1,nw_src=42.42.42.1,nw_dst=88.88.88.87,tp_src=4041 actions=load:0x1->NXM_NX_REG10[[7]] @@ -24238,7 +26516,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 OVN_CLEANUP([hv1]) AT_CLEANUP -@@ -20936,6 +21748,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public +@@ -20936,6 +21858,7 @@ check ovn-nbctl lsp-set-options ln-public network_name=public check ovn-nbctl lrp-set-gateway-chassis lr0-public hv1 20 check ovn-nbctl lr-nat-add lr0 snat 172.168.0.100 10.0.0.0/24 check ovn-nbctl --wait=hv sync @@ -24246,7 +26524,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 wait_row_count datapath_binding 1 external-ids:name=lr0 lr0_dp_uuid=$(ovn-sbctl --bare --columns _uuid list datapath_binding lr0) -@@ -21156,31 +21969,31 @@ AT_CHECK([ +@@ -21156,31 +22079,31 @@ AT_CHECK([ AT_CHECK([ovn-sbctl lflow-list | grep -E "lr_in_policy.*priority=1001" | sort], [0], [dnl table=12(lr_in_policy ), priority=1001 , dnl @@ -24283,7 +26561,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]) OVN_CLEANUP([hv1]) -@@ -21602,22 +22415,22 @@ AT_CHECK([test ! -z $p1_zoneid]) +@@ -21602,22 +22525,22 @@ AT_CHECK([test ! -z $p1_zoneid]) p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g') AT_CHECK([test ! -z $p2_zoneid]) @@ -24311,7 +26589,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0]) p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g') -@@ -21629,16 +22442,16 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup]) +@@ -21629,16 +22552,16 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p1) = xup]) p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g') AT_CHECK([test ! -z $p1_zoneid]) @@ -24331,7 +26609,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 reg15=0x${p2_dpkey} | grep REG13 | wc -l) -eq 0]) p2_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p2 | sed 's/"//g') -@@ -21646,7 +22459,7 @@ AT_CHECK([test -z $p2_zoneid]) +@@ -21646,7 +22569,7 @@ AT_CHECK([test -z $p2_zoneid]) ovn-nbctl lsp-del sw0-p1 @@ -24340,7 +26618,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 reg15=0x${p1_dpkey} | grep REG13 | wc -l) -eq 0]) p1_zoneid=$(as hv1 ovs-vsctl get bridge br-int external_ids:ct-zone-sw0-p1 | sed 's/"//g') -@@ -21723,6 +22536,7 @@ check ovn-nbctl --policy="src-ip" lr-route-add DR 10.0.0.0/24 20.0.0.2 +@@ -21723,6 +22646,7 @@ check ovn-nbctl --policy="src-ip" lr-route-add DR 10.0.0.0/24 20.0.0.2 check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.2 check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 10.0.0.0/24 172.16.0.3 @@ -24348,38 +26626,20 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync # Ensure ECMP symmetric reply flows are not present on any hypervisor. -@@ -21753,26 +22567,25 @@ ovn-nbctl set Logical_Router $gw_uuid options:chassis=hv1 +@@ -21753,13 +22677,109 @@ ovn-nbctl set Logical_Router $gw_uuid options:chassis=hv1 ovn-nbctl --wait=hv sync # And ensure that ECMP symmetric reply flows are present only on hv1 --AT_CHECK([ -- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=15 | \ -- grep "priority=100" | \ -- grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c) --]) --AT_CHECK([ -- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \ -- grep "priority=200" | \ -- grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) --]) +as hv1 ovs-ofctl dump-flows br-int > hv1flows +AT_CAPTURE_FILE([hv1flows]) +as hv2 ovs-ofctl dump-flows br-int > hv2flows +AT_CAPTURE_FILE([hv2flows]) - - AT_CHECK([ -- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=15 | \ -- grep "priority=100" | \ -- grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c) --]) --AT_CHECK([ -- test 0 -eq $(as hv2 ovs-ofctl dump-flows br-int table=21 | \ -- grep "priority=200" | \ -- grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) ++ ++AT_CHECK([ + for hv in 1 2; do + grep table=15 hv${hv}flows | \ + grep "priority=100" | \ -+ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" ++ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" + + grep table=22 hv${hv}flows | \ + grep "priority=200" | \ @@ -24389,10 +26649,129 @@ index 2e0bc9c53..0d3a1d1cb 100644 +1 +0 +0 ++]) ++ ++OVN_CLEANUP([hv1], [hv2]) ++AT_CLEANUP ++ ++AT_SETUP([ovn -- Symmetric IPv6 ECMP reply flows]) ++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 ++ ++sim_add hv2 ++as hv2 ++check ovs-vsctl add-br br-phys ++ovn_attach n1 br-phys 192.168.0.2 ++ ++# Logical network ++# ++# ls1 \ ++# \ ++# DR -- join -- GW -- ext ++# / ++# ls2 / ++# ++# ls1 and ls2 are internal switches connected to distributed router ++# DR. DR is then connected via a join switch to gateway router GW. ++# GW is then connected to external switch ext. In real life, this ++# would likely have a localnet port, but for the purposes of this test ++# it is unnecessary. ++ ++ovn-nbctl create Logical_Router name=DR ++gw_uuid=$(ovn-nbctl create Logical_Router name=GW) ++ ++check ovn-nbctl ls-add ls1 ++check ovn-nbctl ls-add ls2 ++check ovn-nbctl ls-add join ++check ovn-nbctl ls-add ext ++ ++# Connect ls1 to DR ++check ovn-nbctl lrp-add DR dr-ls1 00:00:01:01:02:03 1001::1/64 ++check ovn-nbctl lsp-add ls1 ls1-dr -- set Logical_Switch_Port ls1-dr \ ++ type=router options:router-port=dr-ls1 addresses='"00:00:01:01:02:03"' ++ ++# Connect ls2 to DR ++check ovn-nbctl lrp-add DR dr-ls2 00:00:01:01:02:04 1001::2/64 ++check ovn-nbctl lsp-add ls2 ls2-dr -- set Logical_Switch_Port ls2-dr \ ++ type=router options:router-port=dr-ls2 addresses='"00:00:01:01:02:04"' ++ ++# Connect join to DR ++check ovn-nbctl lrp-add DR dr-join 00:00:02:01:02:03 2001::1/64 ++check ovn-nbctl lsp-add join join-dr -- set Logical_Switch_Port join-dr \ ++ type=router options:router-port=dr-join addresses='"00:00:02:01:02:03"' ++ ++# Connect join to GW ++check ovn-nbctl lrp-add GW gw-join 00:00:02:01:02:04 2001::2/64 ++check ovn-nbctl lsp-add join join-gw -- set Logical_Switch_Port join-gw \ ++ type=router options:router-port=gw-join addresses='"00:00:02:01:02:04"' ++ ++# Connect ext to GW ++check ovn-nbctl lrp-add GW gw-ext 00:00:03:01:02:03 7001::1/64 ++check ovn-nbctl lsp-add ext ext-gw -- set Logical_Switch_Port ext-gw \ ++ type=router options:router-port=gw-ext addresses='"00:00:03:01:02:03"' ++ ++check ovn-nbctl lr-route-add GW 1001::/64 2001::1 ++check ovn-nbctl --policy="src-ip" lr-route-add DR 1001::0/64 2001::2 ++ ++# Now add some ECMP routes to the GW router. ++check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 1001::/64 7001::2 ++check ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add GW 1001::/64 7001::3 ++ ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync ++ ++# Ensure ECMP symmetric reply flows are not present on any hypervisor. + AT_CHECK([ +- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=15 | \ ++ test 0 -eq $(as hv1 ovs-ofctl dump-flows br-int table=15 | \ + grep "priority=100" | \ + grep "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" -c) + ]) + AT_CHECK([ +- test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \ ++ test 0 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \ + grep "priority=200" | \ + grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) + ]) +@@ -21775,6 +22795,32 @@ AT_CHECK([ + grep "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" -c) ]) ++# Now make GW a gateway router on hv1 ++ovn-nbctl set Logical_Router $gw_uuid options:chassis=hv1 ++ovn-nbctl --wait=hv sync ++ ++# And ensure that ECMP symmetric reply flows are present only on hv1 ++as hv1 ovs-ofctl dump-flows br-int > hv1flows ++AT_CAPTURE_FILE([hv1flows]) ++as hv2 ovs-ofctl dump-flows br-int > hv2flows ++AT_CAPTURE_FILE([hv2flows]) ++ ++AT_CHECK([ ++ for hv in 1 2; do ++ grep table=15 hv${hv}flows | \ ++ grep "priority=100" | \ ++ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" ++ ++ grep table=22 hv${hv}flows | \ ++ grep "priority=200" | \ ++ grep -c "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" ++ done; :], [0], [dnl ++1 ++1 ++0 ++0 ++]) ++ OVN_CLEANUP([hv1], [hv2]) -@@ -21856,6 +22669,7 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ + AT_CLEANUP + +@@ -21856,6 +22902,7 @@ ovs-vsctl -- add-port br-int hv2-vif1 -- \ # for ARP resolution). OVN_POPULATE_ARP @@ -24400,7 +26779,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync AT_CHECK([ovn-sbctl lflow-list | grep lr_in_arp_resolve | grep 10.0.0.1], [1], []) -@@ -21895,22 +22709,22 @@ as hv1 +@@ -21895,22 +22942,22 @@ as hv1 ovs-vsctl add-br br-phys ovn_attach n1 br-phys 192.168.0.1 @@ -24436,7 +26815,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 as hv1 ovs-vsctl -- add-port br-int hv1-vif1 -- \ -@@ -21934,93 +22748,186 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \ +@@ -21934,116 +22981,209 @@ ovs-vsctl -- add-port br-int hv1-vif4 -- \ options:rxq_pcap=hv1/vif4-rx.pcap \ ofport-request=4 @@ -24452,6 +26831,41 @@ index 2e0bc9c53..0d3a1d1cb 100644 +check ovn-nbctl pg-add pg0 sw0-p1 sw0-p2 +check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow +check ovn-nbctl --wait=hv sync ++ ++# wait_conj_id_count COUNT ["ID COUNT [MATCH]"]... ++# ++# Waits until COUNT flows matching against conj_id appear in the ++# 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", ++# verifies that there are COUNT flows in table 45 that match ++# aginst conj_id=ID and (if MATCH) is nonempty, match MATCH. ++wait_conj_id_count() { ++ AT_CAPTURE_FILE([hv1flows]) ++ local retval ++ case $1 in ++ (0) retval=1 ;; ++ (*) retval=0 ;; ++ esac ++ ++ echo "waiting for $1 conj_id flows..." ++ OVS_WAIT_FOR_OUTPUT_UNQUOTED( ++ [ovs-ofctl dump-flows br-int > hv1flows ++ grep table=44 hv1flows | grep -c conj_id], ++ [$retval], [$1 ++]) ++ ++ shift ++ for arg; do ++ set -- $arg; id=$1 count=$2 match=$3 ++ echo "checking that there are $count ${match:+$match }flows with conj_id=$id..." ++ AT_CHECK_UNQUOTED( ++ [grep table=44 hv1flows | grep "$match" | grep -c conj_id=$id], ++ [0], [$count ++]) ++ done ++} -OVS_WAIT_UNTIL([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) - @@ -24492,81 +26906,39 @@ index 2e0bc9c53..0d3a1d1cb 100644 -OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) - -ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow --ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=6")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=7")]) -- --# Flush the lflow cache. --as hv1 ovn-appctl -t ovn-controller flush-lflow-cache --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")]) -- --# Disable lflow caching. -+# wait_conj_id_count COUNT ["ID COUNT [MATCH]"]... -+# -+# Waits until COUNT flows matching against conj_id appear in the -+# 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", -+# verifies that there are COUNT flows in table 45 that match -+# aginst conj_id=ID and (if MATCH) is nonempty, match MATCH. -+wait_conj_id_count() { -+ AT_CAPTURE_FILE([hv1flows]) -+ local retval -+ case $1 in -+ (0) retval=1 ;; -+ (*) retval=0 ;; -+ esac -+ -+ echo "waiting for $1 conj_id flows..." -+ OVS_WAIT_FOR_OUTPUT_UNQUOTED( -+ [ovs-ofctl dump-flows br-int > hv1flows -+ grep table=44 hv1flows | grep -c conj_id], -+ [$retval], [$1 -+]) -+ -+ shift -+ for arg; do -+ set -- $arg; id=$1 count=$2 match=$3 -+ echo "checking that there are $count ${match:+$match }flows with conj_id=$id..." -+ AT_CHECK_UNQUOTED( -+ [grep table=44 hv1flows | grep "$match" | grep -c conj_id=$id], -+ [0], [$count -+]) -+ done -+} - --as hv1 ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false +-ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow +-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep tcp | grep -c "conj_id=6")]) +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep udp | grep -c "conj_id=7")]) +- +-# Flush the lflow cache. +-as hv1 ovn-appctl -t ovn-controller flush-lflow-cache +-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")]) +- +-# Disable lflow caching. +AS_BOX([Add sw0-p3 to the port group pg0. The conj_id should be 2.]) +check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 +wait_conj_id_count 1 "2 1" --# Wait until ovn-enble-lflow-cache is processed by ovn-controller. --OVS_WAIT_UNTIL([ -- test $(ovn-sbctl get chassis hv1 other_config:ovn-enable-lflow-cache) = '"false"' --]) +-as hv1 ovs-vsctl set open . external_ids:ovn-enable-lflow-cache=false +AS_BOX([Add sw0p4 to the port group pg0. The conj_id should be 2.]) +check ovn-nbctl --wait=hv pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 sw0-p4 +wait_conj_id_count 1 "2 1" --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")]) +-# Wait until ovn-enble-lflow-cache is processed by ovn-controller. +-OVS_WAIT_UNTIL([ +- test $(ovn-sbctl get chassis hv1 other_config:ovn-enable-lflow-cache) = '"false"' +-]) +AS_BOX([Add another ACL with conjunction.]) +check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow +wait_conj_id_count 2 "2 1 tcp" "3 1 udp" - --# Remove port sw0-p4 from port group. --ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 --OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=4")]) --AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=5")]) ++ +AS_BOX([Delete tcp ACL.]) +check ovn-nbctl --wait=hv acl-del pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" +wait_conj_id_count 1 "3 1 udp" - ++ +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" @@ -24576,16 +26948,23 @@ index 2e0bc9c53..0d3a1d1cb 100644 +AS_BOX([Add another tcp ACL.]) +check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && inport == @pg0 && ip4 && tcp.dst >= 84 && tcp.dst <= 86" allow +wait_conj_id_count 3 "3 1 udp" "4 1 tcp" "5 1 tcp" -+ + +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=2")]) +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=3")]) +AS_BOX([Clear ACLs.]) +check ovn-nbctl --wait=hv clear port_group pg0 acls +wait_conj_id_count 0 -+ + +-# Remove port sw0-p4 from port group. +-ovn-nbctl pg-set-ports pg0 sw0-p1 sw0-p2 sw0-p3 +-OVS_WAIT_UNTIL([test 2 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id")]) +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=4")]) +-AT_CHECK([test 1 = $(as hv1 ovs-ofctl dump-flows br-int table=45 | grep -c "conj_id=5")]) +AS_BOX([Add TCP ACL.]) +check ovn-nbctl --wait=hv acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && tcp.dst >= 80 && tcp.dst <= 82" allow +check ovn-nbctl acl-add pg0 to-lport 1002 "outport == @pg0 && ip4 && udp.dst >= 80 && udp.dst <= 82" allow +wait_conj_id_count 2 "6 1 tcp" "7 1 udp" -+ + +AS_BOX([Flush lflow cache.]) +as hv1 ovn-appctl -t ovn-controller lflow-cache/flush +wait_conj_id_count 2 "2 1" "3 1" @@ -24613,15 +26992,20 @@ index 2e0bc9c53..0d3a1d1cb 100644 AT_CLEANUP +-AT_SETUP([ovn -- Delete Port_Binding and OVS port Incremental Processing]) +AT_SETUP([ovn -- lflow cache operations]) -+ovn_start -+net_add n1 -+sim_add hv1 + ovn_start +- + net_add n1 + sim_add hv1 + -+as hv1 -+ovs-vsctl add-br br-phys + as hv1 + ovs-vsctl add-br br-phys +-ovn_attach n1 br-phys 192.168.0.10 +ovn_attach n1 br-phys 192.168.0.1 -+ + +-ovn-nbctl ls-add ls +-ovn-nbctl lsp-add ls lsp +as hv1 +ovs-vsctl -- add-port br-int hv1-vif1 \ + -- set interface hv1-vif1 external-ids:iface-id=lsp1 \ @@ -24635,17 +27019,26 @@ index 2e0bc9c53..0d3a1d1cb 100644 + -- create Address_Set name=as1 addresses=\"10.0.0.1\",\"10.0.0.2\" +check ovn-nbctl --wait=hv sync +wait_for_ports_up lsp1 lsp2 -+ + +-as hv1 ovs-vsctl \ +- -- add-port br-int vif1 \ +- -- set Interface vif1 external_ids:iface-id=lsp +get_cache_count () { + local cache_name=$1 + as hv1 ovn-appctl -t ovn-controller lflow-cache/show-stats | grep ${cache_name} | awk '{ print $3 }' +} -+ + +-# Wait for port to be bound. +-wait_row_count Chassis 1 name=hv1 +-ch=$(fetch_column Chassis _uuid name=hv1) +-wait_row_count Port_Binding 1 logical_port=lsp chassis=$ch +AS_BOX([Check matches caching]) +conj_id_cnt=$(get_cache_count cache-conj-id) +expr_cnt=$(get_cache_count cache-expr) +matches_cnt=$(get_cache_count cache-matches) -+ + +-# Pause ovn-controller. +-as hv1 ovn-appctl -t ovn-controller debug/pause +check ovn-nbctl acl-add ls1 from-lport 1 '1' drop +check ovn-nbctl --wait=hv sync + @@ -24693,10 +27086,33 @@ index 2e0bc9c53..0d3a1d1cb 100644 +OVN_CLEANUP([hv1]) +AT_CLEANUP + - AT_SETUP([ovn -- Delete Port_Binding and OVS port Incremental Processing]) - ovn_start ++AT_SETUP([ovn -- Delete Port_Binding and OVS port 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 ++ ++ovn-nbctl ls-add ls ++ovn-nbctl lsp-add ls lsp ++ ++as hv1 ovs-vsctl \ ++ -- add-port br-int vif1 \ ++ -- set Interface vif1 external_ids:iface-id=lsp ++ ++# Wait for port to be bound. ++wait_row_count Chassis 1 name=hv1 ++ch=$(fetch_column Chassis _uuid name=hv1) ++wait_row_count Port_Binding 1 logical_port=lsp chassis=$ch ++ ++# Pause ovn-controller. ++as hv1 ovn-appctl -t ovn-controller debug/pause -@@ -22131,6 +23038,77 @@ AT_CHECK_UNQUOTED([grep -c "output:4" offlows_table65_2.txt], [0], [dnl + # Delete port binding and OVS port. The updates will be processed in the same + # loop in ovn-controller when it resumes. +@@ -22131,6 +23271,77 @@ AT_CHECK_UNQUOTED([grep -c "output:4" offlows_table65_2.txt], [0], [dnl OVN_CLEANUP([hv1]) AT_CLEANUP @@ -24774,7 +27190,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Test dropping traffic destined to router owned IPs. AT_SETUP([ovn -- gateway router drop traffic for own IPs]) ovn_start -@@ -22145,7 +23123,8 @@ ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router +@@ -22145,7 +23356,8 @@ ovn-nbctl lsp-add s1 lsp-s1-r1 -- set Logical_Switch_Port lsp-s1-r1 type=router # Create logical port p1 in s1 ovn-nbctl lsp-add s1 p1 \ @@ -24784,7 +27200,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 # Create two hypervisor and create OVS ports corresponding to logical ports. net_add n1 -@@ -22165,6 +23144,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ +@@ -22165,6 +23377,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ # for ARP resolution). OVN_POPULATE_ARP @@ -24792,7 +27208,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync sw_key=$(ovn-sbctl --bare --columns tunnel_key list datapath_binding r1) -@@ -22208,7 +23188,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep +@@ -22208,7 +23421,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep ]) # The packet should've been dropped in the lr_in_arp_resolve stage. @@ -24801,7 +27217,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 1 ]) -@@ -22281,6 +23261,7 @@ check test "$hvt2" -gt 0 +@@ -22281,6 +23494,7 @@ check test "$hvt2" -gt 0 # Then wait for 9 out of 10 sleep 1 check as hv3 ovn-appctl -t ovn-controller exit --restart @@ -24809,7 +27225,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=sb sync wait_row_count Chassis_Private 9 name!=hv3 nb_cfg=2 check_row_count Chassis_Private 1 name=hv3 nb_cfg=1 -@@ -22454,6 +23435,7 @@ ovn-nbctl set logical_router gw_router options:chassis=hv3 +@@ -22454,6 +23668,7 @@ ovn-nbctl set logical_router gw_router options:chassis=hv3 ovn-nbctl lr-nat-add gw_router snat 172.16.0.200 30.0.0.0/24 ovn-nbctl lr-nat-add gw_router snat 172.16.0.201 30.0.0.3 @@ -24817,7 +27233,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync # Create an interface in br-phys in hv2 and send ARP request for 172.16.0.100 -@@ -22643,6 +23625,7 @@ check ovn-nbctl acl-add ls1 to-lport 1001 \ +@@ -22643,6 +23858,7 @@ check ovn-nbctl acl-add ls1 to-lport 1001 \ check ovn-nbctl acl-add ls1 to-lport 1001 \ 'outport == "lsp1" && ip4 && ip4.src == {10.0.0.2, 10.0.0.3}' allow @@ -24825,7 +27241,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check ovn-nbctl --wait=hv sync sip=`ip_to_hex 10 0 0 2` -@@ -22811,6 +23794,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ +@@ -22811,6 +24027,7 @@ ovs-vsctl -- add-port br-int hv1-vif1 -- \ options:rxq_pcap=hv1/vif1-rx.pcap \ ofport-request=1 @@ -24833,7 +27249,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ovn-nbctl --wait=hv sync # Expected conjunction flows: -@@ -22869,6 +23853,7 @@ as hv1 ovs-vsctl \ +@@ -22869,6 +24086,7 @@ as hv1 ovs-vsctl \ ovn-nbctl --wait=hv sync # hv1 ovn-controller should not bind sw0-p2. @@ -24841,7 +27257,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 check_row_count Port_Binding 0 logical_port=sw0-p2 chassis=$c # Trigger recompute and sw0-p2 should not be claimed. -@@ -22976,93 +23961,79 @@ check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp +@@ -22976,93 +24194,79 @@ check ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp check ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp check ovn-nbctl --wait=hv lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp @@ -24964,7 +27380,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]) check ovn-nbctl lsp-add sw0 sw0-p2 -@@ -23070,184 +24041,159 @@ check ovn-nbctl lsp-add sw0 sw0-p2 +@@ -23070,184 +24274,159 @@ check ovn-nbctl lsp-add sw0 sw0-p2 OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xup]) OVS_WAIT_UNTIL( @@ -25236,7 +27652,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ]) check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp -@@ -23255,65 +24201,115 @@ check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp +@@ -23255,65 +24434,115 @@ check ovn-nbctl --wait=hv ls-lb-add sw1 lb-ipv6-udp # Number of hairpin flows shouldn't change as it doesn't depend on how many # datapaths the LB is applied. OVS_WAIT_UNTIL( @@ -25394,7 +27810,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 as hv2 ovs-vsctl del-port hv2-vif1 OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) -@@ -23321,75 +24317,73 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) +@@ -23321,75 +24550,73 @@ OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-p2) = xdown]) as hv2 ovn-appctl -t ovn-controller recompute OVS_WAIT_UNTIL( @@ -25493,7 +27909,7 @@ index 2e0bc9c53..0d3a1d1cb 100644 ) OVN_CLEANUP([hv1], [hv2]) -@@ -23541,3 +24535,1318 @@ as ovn-nb +@@ -23541,3 +24768,1318 @@ as ovn-nb OVS_APP_EXIT_AND_WAIT([ovsdb-server]) AT_CLEANUP @@ -26855,8 +29271,55 @@ index 05b17ebce..8b1b03e24 100644 dnl OVS_WAIT_WHILE(COMMAND[, IF-FAILED]) dnl +diff --git a/tests/ovstest.c b/tests/ovstest.c +index 068dcbb9b..86a60db36 100644 +--- a/tests/ovstest.c ++++ b/tests/ovstest.c +@@ -23,6 +23,7 @@ + #include + #include "command-line.h" + #include "openvswitch/dynamic-string.h" ++#include "openvswitch/vlog.h" + #include "ovstest.h" + #include "util.h" + +@@ -124,6 +125,22 @@ main(int argc, char *argv[]) + "use --help for usage"); + } + ++ /* Disable logging to the console when running tests. ++ * ++ * By default, OVN and OVS errors and warnings are written to ++ * stderr. GNU Autotest automatically fails a test if unexpected ++ * data is written to stderr. This causes two problems: ++ * 1) Unit tests that attempt off-nominal code paths may ++ * fail because of a warning message in OVN or OVS. To get ++ * around this, it is common for tests to pass "[ignore]" ++ * to AT_CHECK's stderr parameter so that OVN/OVS log messages ++ * do not cause failures. But... ++ * 2) Passing "[ignore]" makes it so that unit tests cannot ++ * then print their own messages to stderr to help debug ++ * test failures. ++ */ ++ vlog_set_levels(NULL, VLF_CONSOLE, VLL_OFF); ++ + add_top_level_commands(); + if (argc > 1) { + struct ovs_cmdl_context ctx = { +diff --git a/tests/system-common-macros.at b/tests/system-common-macros.at +index c8fa6f03f..b742a2cb9 100644 +--- a/tests/system-common-macros.at ++++ b/tests/system-common-macros.at +@@ -330,3 +330,7 @@ m4_define([OVS_CHECK_IPROUTE_ENCAP], + # OVS_CHECK_CT_CLEAR() + m4_define([OVS_CHECK_CT_CLEAR], + [AT_SKIP_IF([! grep -q "Datapath supports ct_clear action" ovs-vswitchd.log])]) ++ ++# OVS_CHECK_CT_ZERO_SNAT() ++m4_define([OVS_CHECK_CT_ZERO_SNAT], ++ [AT_SKIP_IF([! grep -q "Datapath supports ct_zero_snat" ovs-vswitchd.log])])) diff --git a/tests/system-ovn.at b/tests/system-ovn.at -index d59f7c97e..bd27b01a0 100644 +index d59f7c97e..51f695938 100644 --- a/tests/system-ovn.at +++ b/tests/system-ovn.at @@ -1574,6 +1574,18 @@ OVS_WAIT_UNTIL([ @@ -26878,67 +29341,404 @@ index d59f7c97e..bd27b01a0 100644 OVS_APP_EXIT_AND_WAIT([ovn-controller]) as ovn-sb -@@ -2212,6 +2224,46 @@ tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply= +@@ -2212,6 +2224,143 @@ tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply= + + OVS_WAIT_UNTIL([check_est_flows], [check established flows]) + ++ovn-nbctl set logical_router R2 options:lb_force_snat_ip=router_ip ++ ++# Destroy the load balancer and create again. ovn-controller will ++# clear the OF flows and re add again and clears the n_packets ++# for these flows. ++ovn-nbctl destroy load_balancer $uuid ++uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"` ++ovn-nbctl set logical_router R2 load_balancer=$uuid ++ ++# Config OVN load-balancer with another VIP (this time with ports). ++ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.168.2.2:80"' ++ ++ovn-nbctl list load_balancer ++ovn-sbctl dump-flows R2 ++OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=41 | \ ++grep 'nat(src=20.0.0.2)']) ++ ++rm -f wget*.log ++ ++dnl Test load-balancing that includes L4 ports in NAT. ++for i in `seq 1 20`; do ++ echo Request $i ++ NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) ++done ++ ++dnl Each server should have at least one connection. ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | ++sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl ++tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=) ++tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=) ++]) ++ ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | ++sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl ++tcp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=,dport=),reply=(src=192.168.1.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=) ++tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply=(src=192.168.2.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=) ++]) ++ ++OVS_WAIT_UNTIL([check_est_flows], [check established flows]) ++ ++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([ovn-northd]) ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/connection dropped.*/d"]) ++AT_CLEANUP ++ ++AT_SETUP([ovn -- load balancing in gateway router hairpin scenario]) ++AT_KEYWORDS([ovnlb]) ++ ++CHECK_CONNTRACK() ++CHECK_CONNTRACK_NAT() ++ovn_start ++OVS_TRAFFIC_VSWITCHD_START() ++ADD_BR([br-int]) ++ADD_BR([br-ext], [set Bridge br-ext fail-mode=standalone]) ++ ++# 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 lr-add R1 ++ ++check ovn-nbctl ls-add sw0 ++check ovn-nbctl ls-add public ++ ++check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 ++check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 ++ ++check ovn-nbctl set logical_router R1 options:chassis=hv1 ++ ++check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ ++ type=router options:router-port=rp-sw0 \ ++ -- lsp-set-addresses sw0-rp router ++ ++check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ ++ type=router options:router-port=rp-public \ ++ -- lsp-set-addresses public-rp router ++ ++check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext ++ ++check ovn-nbctl lsp-add public public1 \ ++ -- lsp-set-addresses public1 unknown \ ++ -- lsp-set-type public1 localnet \ ++ -- lsp-set-options public1 network_name=phynet ++ ++ADD_NAMESPACES(server) ++ADD_VETH(s1, server, br-ext, "172.16.1.100/24", "1a:00:00:00:00:01", \ ++ "172.16.1.1") ++ ++OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""]) ++ ++ADD_NAMESPACES(client) ++ADD_VETH(c1, client, br-ext, "172.16.1.110/24", "1a:00:00:00:00:02", \ ++ "172.16.1.1") ++ ++OVS_WAIT_UNTIL([test "$(ip netns exec client ip a | grep fe80 | grep tentative)" = ""]) ++ ++# Start webservers in 'server'. ++OVS_START_L7([server], [http]) ++ ++# Create a load balancer and associate to R1 ++check ovn-nbctl lb-add lb1 172.16.1.150:80 172.16.1.100:80 ++check ovn-nbctl lr-lb-add R1 lb1 ++ ++check ovn-nbctl --wait=hv sync ++ ++for i in $(seq 1 5); do ++ echo Request $i ++ NS_CHECK_EXEC([client], [wget 172.16.1.100 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) ++done ++ ++# Now send the traffic from client to the VIP - 172.16.1.150 ++check ovn-nbctl set logical_router R1 options:lb_force_snat_ip=router_ip ++check ovn-nbctl --wait=hv sync ++ ++for i in $(seq 1 5); do ++ echo Request $i ++ NS_CHECK_EXEC([client], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) ++done ++ + OVS_APP_EXIT_AND_WAIT([ovn-controller]) + + as ovn-sb +@@ -2225,6 +2374,7 @@ OVS_APP_EXIT_AND_WAIT([ovn-northd]) + + as + OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/Failed to acquire.*/d + /connection dropped.*/d"]) + AT_CLEANUP + +@@ -4151,7 +4301,7 @@ ovn-nbctl lsp-set-type sw1-lr0 router + ovn-nbctl lsp-set-addresses sw1-lr0 router + ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 + +-ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 ++ovn-nbctl --reject lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 + + ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 + ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 +@@ -4266,6 +4416,20 @@ ovn-sbctl list service_monitor + OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ + service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`]) + ++# Stop webserer in sw1-p1 ++pid_file=$(cat l7_pid_file) ++NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)]) ++ ++NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &]) ++OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ ++service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`]) ++NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4]) ++ ++OVS_WAIT_UNTIL([ ++ n_reset=$(cat rst.pcap | wc -l) ++ test "${n_reset}" = "1" ++]) ++ + OVS_APP_EXIT_AND_WAIT([ovn-controller]) + + as ovn-sb +@@ -4309,10 +4473,14 @@ start_daemon ovn-controller + # One logical switch with IPv4 load balancers that hairpin the traffic. + ovn-nbctl ls-add sw + ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01 +-ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp +-ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp ++ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp ++ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp ++ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp ++ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp + ovn-nbctl ls-lb-add sw lb-ipv4-tcp ++ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup + ovn-nbctl ls-lb-add sw lb-ipv4-udp ++ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup - OVS_WAIT_UNTIL([check_est_flows], [check established flows]) + ovn-nbctl lr-add rtr + ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24 +@@ -4328,24 +4496,26 @@ ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \ + ovn-nbctl --wait=hv -t 3 sync -+ovn-nbctl set logical_router R2 options:lb_force_snat_ip=router_ip -+ -+# Destroy the load balancer and create again. ovn-controller will -+# clear the OF flows and re add again and clears the n_packets -+# for these flows. -+ovn-nbctl destroy load_balancer $uuid -+uuid=`ovn-nbctl create load_balancer vips:30.0.0.1="192.168.1.2,192.168.2.2"` -+ovn-nbctl set logical_router R2 load_balancer=$uuid + # Start IPv4 TCP server on lsp. +-NS_CHECK_EXEC([lsp], [timeout 2s nc -l 42.42.42.1 4041 &], [0]) ++NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 42.42.42.1 4041 &], [0]) + +-# Check that IPv4 TCP hairpin connection succeeds. ++# Check that IPv4 TCP hairpin connection succeeds on both VIPs. + NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0]) ++NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0]) + + # Capture IPv4 UDP hairpinned packets. +-filter="src 88.88.88.88 and dst 42.42.42.1 and dst port 2021 and udp" +-NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp ${filter} > lsp.pcap &]) ++filter="dst 42.42.42.1 and dst port 2021 and udp" ++NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp ${filter} > lsp.pcap &]) + + sleep 1 + + # Generate IPv4 UDP hairpin traffic. + NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -z &], [0]) ++NS_CHECK_EXEC([lsp], [nc -u 88.88.88.89 4040 -z &], [0]) + + # Check hairpin traffic. + OVS_WAIT_UNTIL([ + total_pkts=$(cat lsp.pcap | wc -l) +- test "${total_pkts}" = "1" ++ test "${total_pkts}" = "2" + ]) + + OVS_APP_EXIT_AND_WAIT([ovn-controller]) +@@ -4388,10 +4558,14 @@ start_daemon ovn-controller + # One logical switch with IPv6 load balancers that hairpin the traffic. + ovn-nbctl ls-add sw + ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01 +-ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp +-ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp ++ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp ++ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp ++ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp ++ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp + ovn-nbctl ls-lb-add sw lb-ipv6-tcp ++ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup + ovn-nbctl ls-lb-add sw lb-ipv6-udp ++ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup + + ovn-nbctl lr-add rtr + ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 4200::00ff/64 +@@ -4406,24 +4580,26 @@ OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)" + ovn-nbctl --wait=hv -t 3 sync + + # Start IPv6 TCP server on lsp. +-NS_CHECK_EXEC([lsp], [timeout 2s nc -l 4200::1 4041 &], [0]) ++NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 4200::1 4041 &], [0]) + +-# Check that IPv6 TCP hairpin connection succeeds. ++# Check that IPv6 TCP hairpin connection succeeds on both VIPs. + NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0]) ++NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0]) + + # Capture IPv4 UDP hairpinned packets. +-filter="src 8800::0088 and dst 4200::1 and dst port 2021 and udp" +-NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp $filter > lsp.pcap &]) ++filter="dst 4200::1 and dst port 2021 and udp" ++NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp $filter > lsp.pcap &]) + + sleep 1 + + # Generate IPv6 UDP hairpin traffic. + NS_CHECK_EXEC([lsp], [nc -u 8800::0088 4040 -z &], [0]) ++NS_CHECK_EXEC([lsp], [nc -u 8800::0089 4040 -z &], [0]) + + # Check hairpin traffic. + OVS_WAIT_UNTIL([ + total_pkts=$(cat lsp.pcap | wc -l) +- test "${total_pkts}" = "1" ++ test "${total_pkts}" = "2" + ]) + + OVS_APP_EXIT_AND_WAIT([ovn-controller]) +@@ -4545,7 +4721,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 + ]) +@@ -5073,6 +5249,196 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d + + AT_CLEANUP + ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([ovn -- load-balancer and firewall tuple conflict IPv4]) ++AT_SKIP_IF([test $HAVE_NC = no]) ++AT_KEYWORDS([ovnlb]) + -+# Config OVN load-balancer with another VIP (this time with ports). -+ovn-nbctl set load_balancer $uuid vips:'"30.0.0.2:8000"'='"192.168.1.2:80,192.168.2.2:80"' ++CHECK_CONNTRACK() ++CHECK_CONNTRACK_NAT() ++ovn_start ++OVS_TRAFFIC_VSWITCHD_START() ++OVS_CHECK_CT_ZERO_SNAT() ++ADD_BR([br-int]) + -+ovn-nbctl list load_balancer -+ovn-sbctl dump-flows R2 -+OVS_WAIT_UNTIL([ovs-ofctl -O OpenFlow13 dump-flows br-int table=41 | \ -+grep 'nat(src=20.0.0.2)']) ++# 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 + -+rm -f wget*.log ++# Start ovn-controller ++start_daemon ovn-controller + -+dnl Test load-balancing that includes L4 ports in NAT. -+for i in `seq 1 20`; do -+ echo Request $i -+ NS_CHECK_EXEC([alice1], [wget 30.0.0.2:8000 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -+done ++# Logical network: ++# 1 logical switch connetected to one logical router. ++# 2 VMs, one used as backend for a load balancer. ++ ++check ovn-nbctl \ ++ -- lr-add rtr \ ++ -- lrp-add rtr rtr-ls 00:00:00:00:01:00 42.42.42.1/24 \ ++ -- ls-add ls \ ++ -- lsp-add ls ls-rtr \ ++ -- lsp-set-addresses ls-rtr 00:00:00:00:01:00 \ ++ -- lsp-set-type ls-rtr router \ ++ -- lsp-set-options ls-rtr router-port=rtr-ls \ ++ -- lsp-add ls vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ ++ -- lsp-add ls vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ ++ -- lb-add lb-test 66.66.66.66:666 42.42.42.2:4242 tcp \ ++ -- ls-lb-add ls lb-test ++ ++ADD_NAMESPACES(vm1) ++ADD_VETH(vm1, vm1, br-int, "42.42.42.2/24", "00:00:00:00:00:01", "42.42.42.1") ++ ++ADD_NAMESPACES(vm2) ++ADD_VETH(vm2, vm2, br-int, "42.42.42.3/24", "00:00:00:00:00:02", "42.42.42.1") ++ ++# Wait for ovn-controller to catch up. ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync + -+dnl Each server should have at least one connection. -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(30.0.0.2) | -+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl -+tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.1.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=) -+tcp,orig=(src=172.16.1.2,dst=30.0.0.2,sport=,dport=),reply=(src=192.168.2.2,dst=172.16.1.2,sport=,dport=),zone=,labels=0x2,protoinfo=(state=) ++# Start IPv4 TCP server on vm1. ++NETNS_DAEMONIZE([vm1], [nc -k -l 42.42.42.2 4242], [nc-vm1.pid]) ++ ++# Make sure connecting to the VIP works. ++NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2000 -z]) ++ ++# Start IPv4 TCP connection to VIP from vm2. ++NS_CHECK_EXEC([vm2], [nc 66.66.66.66 666 -p 2001 -z]) ++ ++# Check conntrack. We expect two entries: ++# - one in vm1's zone (firewall) ++# - one in vm2's zone (dnat) ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ ++grep "orig=.src=42\.42\.42\.3" | \ ++sed -e 's/port=2001/port=/g' \ ++ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ ++ -e 's/state=[[0-9_A-Z]]*/state=/g' \ ++ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl ++tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=) ++tcp,orig=(src=42.42.42.3,dst=66.66.66.66,sport=,dport=666),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) +]) + -+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(20.0.0.2) | -+sed -e 's/zone=[[0-9]]*/zone=/'], [0], [dnl -+tcp,orig=(src=172.16.1.2,dst=192.168.1.2,sport=,dport=),reply=(src=192.168.1.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=) -+tcp,orig=(src=172.16.1.2,dst=192.168.2.2,sport=,dport=),reply=(src=192.168.2.2,dst=20.0.0.2,sport=,dport=),zone=,protoinfo=(state=) ++# Start IPv4 TCP connection to backend IP from vm2 which would require ++# additional source port translation to avoid a tuple conflict. ++NS_CHECK_EXEC([vm2], [nc 42.42.42.2 4242 -p 2001 -z]) ++ ++# Check conntrack. We expect three entries: ++# - one in vm1's zone (firewall) - reused from the previous connection. ++# - one in vm2's zone (dnat) - still in TIME_WAIT after the previous connection. ++# - one in vm2's zone (firewall + additional all-zero SNAT) ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ ++grep "orig=.src=42\.42\.42\.3" | \ ++sed -e 's/port=2001/port=/g' \ ++ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ ++ -e 's/state=[[0-9_A-Z]]*/state=/g' \ ++ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl ++tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=) ++tcp,orig=(src=42.42.42.3,dst=42.42.42.2,sport=,dport=4242),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,protoinfo=(state=) ++tcp,orig=(src=42.42.42.3,dst=66.66.66.66,sport=,dport=666),reply=(src=42.42.42.2,dst=42.42.42.3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) +]) + -+OVS_WAIT_UNTIL([check_est_flows], [check established flows]) ++AT_CLEANUP ++]) + - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - - as ovn-sb -@@ -2228,6 +2280,105 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d - /connection dropped.*/d"]) - AT_CLEANUP - -+AT_SETUP([ovn -- load balancing in gateway router hairpin scenario]) ++OVN_FOR_EACH_NORTHD([ ++AT_SETUP([ovn -- load-balancer and firewall tuple conflict IPv6]) ++AT_SKIP_IF([test $HAVE_NC = no]) +AT_KEYWORDS([ovnlb]) + +CHECK_CONNTRACK() +CHECK_CONNTRACK_NAT() +ovn_start +OVS_TRAFFIC_VSWITCHD_START() ++OVS_CHECK_CT_ZERO_SNAT() +ADD_BR([br-int]) -+check ovs-vsctl add-br br-ext -+ + +# Set external-ids in br-int needed for ovn-controller +ovs-vsctl \ @@ -26951,65 +29751,300 @@ index d59f7c97e..bd27b01a0 100644 +# Start ovn-controller +start_daemon ovn-controller + -+check ovn-nbctl lr-add R1 ++# Logical network: ++# 1 logical switch connetected to one logical router. ++# 2 VMs, one used as backend for a load balancer. ++ ++check ovn-nbctl \ ++ -- lr-add rtr \ ++ -- lrp-add rtr rtr-ls 00:00:00:00:01:00 4242::1/64 \ ++ -- ls-add ls \ ++ -- lsp-add ls ls-rtr \ ++ -- lsp-set-addresses ls-rtr 00:00:00:00:01:00 \ ++ -- lsp-set-type ls-rtr router \ ++ -- lsp-set-options ls-rtr router-port=rtr-ls \ ++ -- lsp-add ls vm1 -- lsp-set-addresses vm1 00:00:00:00:00:01 \ ++ -- lsp-add ls vm2 -- lsp-set-addresses vm2 00:00:00:00:00:02 \ ++ -- lb-add lb-test [[6666::1]]:666 [[4242::2]]:4242 tcp \ ++ -- ls-lb-add ls lb-test ++ ++ADD_NAMESPACES(vm1) ++ADD_VETH(vm1, vm1, br-int, "4242::2/64", "00:00:00:00:00:01", "4242::1") ++OVS_WAIT_UNTIL([test "$(ip netns exec vm1 ip a | grep 4242::2 | grep tentative)" = ""]) ++ ++ADD_NAMESPACES(vm2) ++ADD_VETH(vm2, vm2, br-int, "4242::3/64", "00:00:00:00:00:02", "4242::1") ++OVS_WAIT_UNTIL([test "$(ip netns exec vm2 ip a | grep 4242::3 | grep tentative)" = ""]) ++ ++# Wait for ovn-controller to catch up. ++wait_for_ports_up ++check ovn-nbctl --wait=hv sync + -+check ovn-nbctl ls-add sw0 -+check ovn-nbctl ls-add public ++# Start IPv6 TCP server on vm1. ++NETNS_DAEMONIZE([vm1], [nc -k -l 4242::2 4242], [nc-vm1.pid]) ++ ++# Make sure connecting to the VIP works. ++NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2000 -z]) ++ ++# Start IPv6 TCP connection to VIP from vm2. ++NS_CHECK_EXEC([vm2], [nc 6666::1 666 -p 2001 -z]) ++ ++# Check conntrack. We expect two entries: ++# - one in vm1's zone (firewall) ++# - one in vm2's zone (dnat) ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ ++grep "orig=.src=4242::3" | \ ++sed -e 's/port=2001/port=/g' \ ++ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ ++ -e 's/state=[[0-9_A-Z]]*/state=/g' \ ++ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl ++tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=) ++tcp,orig=(src=4242::3,dst=6666::1,sport=,dport=666),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) ++]) + -+check ovn-nbctl lrp-add R1 rp-sw0 00:00:01:01:02:03 192.168.1.1/24 -+check ovn-nbctl lrp-add R1 rp-public 00:00:02:01:02:03 172.16.1.1/24 ++# Start IPv6 TCP connection to backend IP from vm2 which would require ++# additional source port translation to avoid a tuple conflict. ++NS_CHECK_EXEC([vm2], [nc 4242::2 4242 -p 2001 -z]) ++ ++# Check conntrack. We expect three entries: ++# - one in vm1's zone (firewall) - reused from the previous connection. ++# - one in vm2's zone (dnat) - still in TIME_WAIT after the previous connection. ++# - one in vm2's zone (firewall + additional all-zero SNAT) ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | grep 2001 | \ ++grep "orig=.src=4242::3" | \ ++sed -e 's/port=2001/port=/g' \ ++ -e 's/sport=4242,dport=[[0-9]]\+/sport=4242,dport=/g' \ ++ -e 's/state=[[0-9_A-Z]]*/state=/g' \ ++ -e 's/zone=[[0-9]]*/zone=/' | sort], [0], [dnl ++tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=) ++tcp,orig=(src=4242::3,dst=4242::2,sport=,dport=4242),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,protoinfo=(state=) ++tcp,orig=(src=4242::3,dst=6666::1,sport=,dport=666),reply=(src=4242::2,dst=4242::3,sport=4242,dport=),zone=,labels=0x2,protoinfo=(state=) ++]) + -+check ovn-nbctl set logical_router R1 options:chassis=hv1 ++AT_CLEANUP ++]) + -+check ovn-nbctl lsp-add sw0 sw0-rp -- set Logical_Switch_Port sw0-rp \ -+ type=router options:router-port=rp-sw0 \ -+ -- lsp-set-addresses sw0-rp router + # When a lport is released on a chassis, ovn-controller was + # not clearing some of the flowss in the table 33 leading + # to packet drops if ct() is hit. +@@ -5309,8 +5675,10 @@ OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d + + AT_CLEANUP + +-AT_SETUP([ovn -- controller I-P handling when ovs iface ofport is -1]) ++AT_SETUP([ovn -- ECMP IPv6 symmetric reply]) ++AT_KEYWORDS([ecmp]) + ++CHECK_CONNTRACK() + ovn_start + + OVS_TRAFFIC_VSWITCHD_START() +@@ -5327,51 +5695,208 @@ ovs-vsctl \ + # Start ovn-controller + start_daemon ovn-controller + +-ovn-nbctl ls-add sw0 +-ovn-nbctl lsp-add sw0 sw0-port1 +-ovn-nbctl lsp-set-addresses sw0-port1 "10:54:00:00:00:03 10.0.0.3" ++# Logical network: ++# Alice is connected to gateway router R1. R1 is connected to two "external" ++# routers, R2 and R3 via an "ext" switch. ++# Bob is connected to both R2 and R3. R1 contains two ECMP routes, one through R2 ++# and one through R3, to Bob. ++# ++# alice -- R1 -- ext ---- R2 ++# | \ ++# | bob ++# | / ++# + ----- R3 ++# ++# For this test, Bob sends request traffic through R2 to Alice. We want to ensure that ++# all response traffic from Alice is routed through R2 as well. + +-ovs-vsctl add-port br-int p1 -- \ +- set Interface p1 external_ids:iface-id=sw0-port1 -- \ +- set Interface p1 type=internal ++ovn-nbctl create Logical_Router name=R1 options:chassis=hv1 ++ovn-nbctl create Logical_Router name=R2 ++ovn-nbctl create Logical_Router name=R3 + +-OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-port1) = xup]) +-ovs-vsctl set interface p1 type=\"\" +-OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-port1) = xdown]) ++ovn-nbctl ls-add alice ++ovn-nbctl ls-add bob ++ovn-nbctl ls-add ext + +-OVS_APP_EXIT_AND_WAIT([ovn-controller]) ++# connect alice to R1 ++ovn-nbctl lrp-add R1 alice 00:00:01:01:02:03 fd01::1/64 ++ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ ++ type=router options:router-port=alice addresses='"00:00:01:01:02:03"' + +-as ovn-sb +-OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++# connect bob to R2 ++ovn-nbctl lrp-add R2 R2_bob 00:00:02:01:02:03 fd07::2/64 ++ovn-nbctl lsp-add bob rp2-bob -- set Logical_Switch_Port rp2-bob \ ++ type=router options:router-port=R2_bob addresses='"00:00:02:01:02:03"' + +-as ovn-nb +-OVS_APP_EXIT_AND_WAIT([ovsdb-server]) ++# connect bob to R3 ++ovn-nbctl lrp-add R3 R3_bob 00:00:02:01:02:04 fd07::3/64 ++ovn-nbctl lsp-add bob rp3-bob -- set Logical_Switch_Port rp3-bob \ ++ type=router options:router-port=R3_bob addresses='"00:00:02:01:02:04"' + +-as northd +-OVS_APP_EXIT_AND_WAIT([ovn-northd]) ++# Connect R1 to ext ++ovn-nbctl lrp-add R1 R1_ext 00:00:04:01:02:03 fd02::1/64 ++ovn-nbctl lsp-add ext r1-ext -- set Logical_Switch_Port r1-ext \ ++ type=router options:router-port=R1_ext addresses='"00:00:04:01:02:03"' + +-as +-OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +-/connection dropped.*/d +-/could not open network device p1*/d"]) ++# Connect R2 to ext ++ovn-nbctl lrp-add R2 R2_ext 00:00:04:01:02:04 fd02::2/64 ++ovn-nbctl lsp-add ext r2-ext -- set Logical_Switch_Port r2-ext \ ++ type=router options:router-port=R2_ext addresses='"00:00:04:01:02:04"' + +-AT_CLEANUP ++# Connect R3 to ext ++ovn-nbctl lrp-add R3 R3_ext 00:00:04:01:02:05 fd02::3/64 ++ovn-nbctl lsp-add ext r3-ext -- set Logical_Switch_Port r3-ext \ ++ type=router options:router-port=R3_ext addresses='"00:00:04:01:02:05"' + +-AT_SETUP([ovn -- ARP resolution for SNAT IP]) +-ovn_start +-OVS_TRAFFIC_VSWITCHD_START() ++# Install ECMP routes for alice. ++ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add R1 fd01::/126 fd02::2 ++ovn-nbctl --ecmp-symmetric-reply --policy="src-ip" lr-route-add R1 fd01::/126 fd02::3 + +-ADD_BR([br-int]) +-ADD_BR([br-ext]) ++# Static Routes ++ovn-nbctl lr-route-add R2 fd01::/64 fd02::1 ++ovn-nbctl lr-route-add R3 fd01::/64 fd02::1 + +-ovs-ofctl add-flow br-ext action=normal +-# 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 ++# Logical port 'alice1' in switch 'alice'. ++ADD_NAMESPACES(alice1) ++ADD_VETH(alice1, alice1, br-int, "fd01::2/64", "f0:00:00:01:02:04", \ ++ "fd01::1") ++OVS_WAIT_UNTIL([test "$(ip netns exec alice1 ip a | grep fd01::2 | grep tentative)" = ""]) ++ovn-nbctl lsp-add alice alice1 \ ++-- lsp-set-addresses alice1 "f0:00:00:01:02:04 fd01::2" ++ ++# Logical port 'bob1' in switch 'bob'. ++ADD_NAMESPACES(bob1) ++ADD_VETH(bob1, bob1, br-int, "fd07::1/64", "f0:00:00:01:02:06", \ ++ "fd07::2") ++OVS_WAIT_UNTIL([test "$(ip netns exec bob1 ip a | grep fd07::1 | grep tentative)" = ""]) ++ovn-nbctl lsp-add bob bob1 \ ++-- lsp-set-addresses bob1 "f0:00:00:01:02:06 fd07::1" ++ ++# Ensure ovn-controller is caught up ++ovn-nbctl --wait=hv sync + -+check ovn-nbctl lsp-add public public-rp -- set Logical_Switch_Port public-rp \ -+ type=router options:router-port=rp-public \ -+ -- lsp-set-addresses public-rp router ++on_exit 'ovs-ofctl dump-flows br-int' ++ ++# Later in this test we will check for a datapath flow that matches: ++# "ct_state(+new-est-rpl+trk).*ct(.*label=0x200000401020400000000/.*)". Due ++# to the way OVS generates datapath flows with wildcards, ICMPv6 NS flows will ++# evict this datapath flow. In order to ensure that the flow does not ++# get evicted, we send one ping packet in order to carry out neighbor ++# discovery. We then flush the datpath to remove the NS flows so that the flow ++# "ct_state(+new-est-rpl+trk).*ct(.*label=0x200000401020400000000/.*)" will ++# be present when we check for it. ++NS_CHECK_EXEC([bob1], [ping -q -c 2 -i 0.3 -w 15 fd01::2 | FORMAT_PING], \ ++[0], [dnl ++2 packets transmitted, 2 received, 0% packet loss, time 0ms ++]) ++ovs-appctl dpctl/del-flows + -+check ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=phynet:br-ext ++# 'bob1' should be able to ping 'alice1' directly. ++NS_CHECK_EXEC([bob1], [ping -q -c 20 -i 0.3 -w 15 fd01::2 | FORMAT_PING], \ ++[0], [dnl ++20 packets transmitted, 20 received, 0% packet loss, time 0ms ++]) + -+check ovn-nbctl lsp-add public public1 \ -+ -- lsp-set-addresses public1 unknown \ -+ -- lsp-set-type public1 localnet \ -+ -- lsp-set-options public1 network_name=phynet ++# Ensure conntrack entry is present. We should not try to predict ++# the tunnel key for the output port, so we strip it from the labels ++# and just ensure that the known ethernet address is present. ++AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(fd01::2) | \ ++sed -e 's/zone=[[0-9]]*/zone=/' | ++sed -e 's/labels=0x[[0-9a-f]]*00000401020400000000/labels=0x00000401020400000000/'], [0], [dnl ++icmpv6,orig=(src=fd07::1,dst=fd01::2,id=,type=128,code=0),reply=(src=fd01::2,dst=fd07::1,id=,type=129,code=0),zone=,labels=0x00000401020400000000 ++]) + -+ADD_NAMESPACES(server) -+ADD_VETH(s1, server, br-ext, "172.16.1.100/24", "1a:00:00:00:00:01", \ -+ "172.16.1.1") ++# Ensure datapaths show conntrack states as expected ++# Like with conntrack entries, we shouldn't try to predict ++# port binding tunnel keys. So omit them from expected labels. ++AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(+new-est-rpl+trk).*ct(.*label=0x200000401020400000000/.*)' -c], [0], [dnl ++1 ++]) ++AT_CHECK([ovs-appctl dpctl/dump-flows | grep 'ct_state(-new+est+rpl+trk).*ct_label(0x.*00000401020400000000/.*)' -c], [0], [dnl ++1 ++]) + -+OVS_WAIT_UNTIL([test "$(ip netns exec server ip a | grep fe80 | grep tentative)" = ""]) ++ovs-ofctl dump-flows br-int + -+ADD_NAMESPACES(client) -+ADD_VETH(c1, client, br-ext, "172.16.1.110/24", "1a:00:00:00:00:02", \ -+ "172.16.1.1") ++OVS_APP_EXIT_AND_WAIT([ovn-controller]) + -+OVS_WAIT_UNTIL([test "$(ip netns exec client ip a | grep fe80 | grep tentative)" = ""]) ++as ovn-sb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + -+# Start webservers in 'server'. -+OVS_START_L7([server], [http]) ++as ovn-nb ++OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + -+# Create a load balancer and associate to R1 -+check ovn-nbctl lb-add lb1 172.16.1.150:80 172.16.1.100:80 -+check ovn-nbctl lr-lb-add R1 lb1 ++as northd ++OVS_APP_EXIT_AND_WAIT([ovn-northd]) + -+check ovn-nbctl --wait=hv sync ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d ++/connection dropped.*/d"]) + -+for i in $(seq 1 5); do -+ echo Request $i -+ NS_CHECK_EXEC([client], [wget 172.16.1.100 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -+done ++AT_CLEANUP ++AT_SETUP([ovn -- controller I-P handling when ovs iface ofport is -1]) ++ ++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 + -+# Now send the traffic from client to the VIP - 172.16.1.150 -+check ovn-nbctl set logical_router R1 options:lb_force_snat_ip=router_ip -+check ovn-nbctl --wait=hv sync ++ovn-nbctl ls-add sw0 ++ovn-nbctl lsp-add sw0 sw0-port1 ++ovn-nbctl lsp-set-addresses sw0-port1 "10:54:00:00:00:03 10.0.0.3" + -+for i in $(seq 1 5); do -+ echo Request $i -+ NS_CHECK_EXEC([client], [wget 172.16.1.150 -t 5 -T 1 --retry-connrefused -v -o wget$i.log]) -+done ++ovs-vsctl add-port br-int p1 -- \ ++ set Interface p1 external_ids:iface-id=sw0-port1 -- \ ++ set Interface p1 type=internal ++ ++OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-port1) = xup]) ++ovs-vsctl set interface p1 type=\"\" ++OVS_WAIT_UNTIL([test x$(ovn-nbctl lsp-get-up sw0-port1) = xdown]) + +OVS_APP_EXIT_AND_WAIT([ovn-controller]) + @@ -27024,151 +30059,30 @@ index d59f7c97e..bd27b01a0 100644 + +as +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d -+/Failed to acquire.*/d -+/connection dropped.*/d"]) -+AT_CLEANUP ++/connection dropped.*/d ++/could not open network device p1*/d"]) + - AT_SETUP([ovn -- load balancing in gateway router - IPv6]) - AT_KEYWORDS([ovnlb]) - -@@ -4151,7 +4302,7 @@ ovn-nbctl lsp-set-type sw1-lr0 router - ovn-nbctl lsp-set-addresses sw1-lr0 router - ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1 - --ovn-nbctl lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 -+ovn-nbctl --reject lb-add lb1 10.0.0.10:80 10.0.0.3:80,20.0.0.3:80 - - ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:10.0.0.3=sw0-p1:10.0.0.2 - ovn-nbctl --wait=sb set load_balancer . ip_port_mappings:20.0.0.3=sw1-p1:20.0.0.2 -@@ -4266,6 +4417,20 @@ ovn-sbctl list service_monitor - OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ - service_monitor protocol=udp | sed '/^$/d' | grep offline | wc -l`]) - -+# Stop webserer in sw1-p1 -+pid_file=$(cat l7_pid_file) -+NS_CHECK_EXEC([sw1-p1], [kill $(cat $pid_file)]) ++AT_CLEANUP + -+NS_CHECK_EXEC([sw0-p2], [tcpdump -c 1 -neei sw0-p2 ip[[33:1]]=0x14 > rst.pcap &]) -+OVS_WAIT_UNTIL([test 2 = `ovn-sbctl --bare --columns status find \ -+service_monitor protocol=tcp | sed '/^$/d' | grep offline | wc -l`]) -+NS_CHECK_EXEC([sw0-p2], [wget 10.0.0.10 -v -o wget$i.log],[4]) ++AT_SETUP([ovn -- ARP resolution for SNAT IP]) ++ovn_start ++OVS_TRAFFIC_VSWITCHD_START() + -+OVS_WAIT_UNTIL([ -+ n_reset=$(cat rst.pcap | wc -l) -+ test "${n_reset}" = "1" -+]) ++ADD_BR([br-int]) ++ADD_BR([br-ext]) + - OVS_APP_EXIT_AND_WAIT([ovn-controller]) - - as ovn-sb -@@ -4309,10 +4474,14 @@ start_daemon ovn-controller - # One logical switch with IPv4 load balancers that hairpin the traffic. - ovn-nbctl ls-add sw - ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01 --ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp --ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp -+ovn-nbctl lb-add lb-ipv4-tcp 88.88.88.88:8080 42.42.42.1:4041 tcp -+ovn-nbctl lb-add lb-ipv4-tcp-dup 88.88.88.89:8080 42.42.42.1:4041 tcp -+ovn-nbctl lb-add lb-ipv4-udp 88.88.88.88:4040 42.42.42.1:2021 udp -+ovn-nbctl lb-add lb-ipv4-udp-dup 88.88.88.89:4040 42.42.42.1:2021 udp - ovn-nbctl ls-lb-add sw lb-ipv4-tcp -+ovn-nbctl ls-lb-add sw lb-ipv4-tcp-dup - ovn-nbctl ls-lb-add sw lb-ipv4-udp -+ovn-nbctl ls-lb-add sw lb-ipv4-udp-dup - - ovn-nbctl lr-add rtr - ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 42.42.42.254/24 -@@ -4328,24 +4497,26 @@ ADD_VETH(lsp, lsp, br-int, "42.42.42.1/24", "00:00:00:00:00:01", \ - ovn-nbctl --wait=hv -t 3 sync - - # Start IPv4 TCP server on lsp. --NS_CHECK_EXEC([lsp], [timeout 2s nc -l 42.42.42.1 4041 &], [0]) -+NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 42.42.42.1 4041 &], [0]) - --# Check that IPv4 TCP hairpin connection succeeds. -+# Check that IPv4 TCP hairpin connection succeeds on both VIPs. - NS_CHECK_EXEC([lsp], [nc 88.88.88.88 8080 -z], [0]) -+NS_CHECK_EXEC([lsp], [nc 88.88.88.89 8080 -z], [0]) - - # Capture IPv4 UDP hairpinned packets. --filter="src 88.88.88.88 and dst 42.42.42.1 and dst port 2021 and udp" --NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp ${filter} > lsp.pcap &]) -+filter="dst 42.42.42.1 and dst port 2021 and udp" -+NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp ${filter} > lsp.pcap &]) - - sleep 1 - - # Generate IPv4 UDP hairpin traffic. - NS_CHECK_EXEC([lsp], [nc -u 88.88.88.88 4040 -z &], [0]) -+NS_CHECK_EXEC([lsp], [nc -u 88.88.88.89 4040 -z &], [0]) - - # Check hairpin traffic. - OVS_WAIT_UNTIL([ - total_pkts=$(cat lsp.pcap | wc -l) -- test "${total_pkts}" = "1" -+ test "${total_pkts}" = "2" - ]) - - OVS_APP_EXIT_AND_WAIT([ovn-controller]) -@@ -4388,10 +4559,14 @@ start_daemon ovn-controller - # One logical switch with IPv6 load balancers that hairpin the traffic. - ovn-nbctl ls-add sw - ovn-nbctl lsp-add sw lsp -- lsp-set-addresses lsp 00:00:00:00:00:01 --ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp --ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp -+ovn-nbctl lb-add lb-ipv6-tcp [[8800::0088]]:8080 [[4200::1]]:4041 tcp -+ovn-nbctl lb-add lb-ipv6-tcp-dup [[8800::0089]]:8080 [[4200::1]]:4041 tcp -+ovn-nbctl lb-add lb-ipv6-udp [[8800::0088]]:4040 [[4200::1]]:2021 udp -+ovn-nbctl lb-add lb-ipv6-udp-dup [[8800::0089]]:4040 [[4200::1]]:2021 udp - ovn-nbctl ls-lb-add sw lb-ipv6-tcp -+ovn-nbctl ls-lb-add sw lb-ipv6-tcp-dup - ovn-nbctl ls-lb-add sw lb-ipv6-udp -+ovn-nbctl ls-lb-add sw lb-ipv6-udp-dup - - ovn-nbctl lr-add rtr - ovn-nbctl lrp-add rtr rtr-sw 00:00:00:00:01:00 4200::00ff/64 -@@ -4406,24 +4581,26 @@ OVS_WAIT_UNTIL([test "$(ip netns exec lsp ip a | grep 4200::1 | grep tentative)" - ovn-nbctl --wait=hv -t 3 sync - - # Start IPv6 TCP server on lsp. --NS_CHECK_EXEC([lsp], [timeout 2s nc -l 4200::1 4041 &], [0]) -+NS_CHECK_EXEC([lsp], [timeout 2s nc -k -l 4200::1 4041 &], [0]) - --# Check that IPv6 TCP hairpin connection succeeds. -+# Check that IPv6 TCP hairpin connection succeeds on both VIPs. - NS_CHECK_EXEC([lsp], [nc 8800::0088 8080 -z], [0]) -+NS_CHECK_EXEC([lsp], [nc 8800::0089 8080 -z], [0]) - - # Capture IPv4 UDP hairpinned packets. --filter="src 8800::0088 and dst 4200::1 and dst port 2021 and udp" --NS_CHECK_EXEC([lsp], [tcpdump -n -c 1 -i lsp $filter > lsp.pcap &]) -+filter="dst 4200::1 and dst port 2021 and udp" -+NS_CHECK_EXEC([lsp], [tcpdump -n -c 2 -i lsp $filter > lsp.pcap &]) - - sleep 1 - - # Generate IPv6 UDP hairpin traffic. - NS_CHECK_EXEC([lsp], [nc -u 8800::0088 4040 -z &], [0]) -+NS_CHECK_EXEC([lsp], [nc -u 8800::0089 4040 -z &], [0]) - - # Check hairpin traffic. - OVS_WAIT_UNTIL([ - total_pkts=$(cat lsp.pcap | wc -l) -- test "${total_pkts}" = "1" -+ test "${total_pkts}" = "2" - ]) - - OVS_APP_EXIT_AND_WAIT([ovn-controller]) -@@ -4545,7 +4722,7 @@ OVS_WAIT_UNTIL([ - ]) ++ovs-ofctl add-flow br-ext action=normal ++# 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 - 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 - ]) -@@ -5505,3 +5682,280 @@ as + # Start ovn-controller + start_daemon ovn-controller +@@ -5505,3 +6030,489 @@ as OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d /.*terminating with signal 15.*/d"]) AT_CLEANUP @@ -27360,7 +30274,6 @@ index d59f7c97e..bd27b01a0 100644 +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 + @@ -27448,6 +30361,216 @@ index d59f7c97e..bd27b01a0 100644 +as +OVS_TRAFFIC_VSWITCHD_STOP(["/failed to query port patch-.*/d +/connection dropped.*/d"]) ++ ++AT_CLEANUP ++ ++AT_SETUP(ovn -- DNAT LR hairpin IPv4) ++AT_KEYWORDS(hairpin) ++ ++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_daemon ovn-controller ++ ++# Logical network: ++# Two VMs ++# * VM1 with IP address 192.168.100.5 ++# * VM2 with IP address 192.168.100.6 ++# The VMs connect to logical switch ls1. ++# ++# An external router with IP address 172.18.1.2. We simulate this with a network namespace. ++# There will be no traffic going here in this test. ++# The external router connects to logical switch ls-pub ++# ++# One logical router (lr1) connects to ls1 and ls-pub. The router port connected to ls-pub is ++# a gateway port. ++# * The subnet connected to ls1 is 192.168.100.0/24. The Router IP address is 192.168.100.1 ++# * The subnet connected to ls-pub is 172.18.1.0/24. The Router IP address is 172.168.1.1 ++# lr1 has the following attributes: ++# * It has a "default" static route that sends traffic out the gateway router port. ++# * It has a DNAT rule that translates 172.18.2.10 to 192.168.100.6 (VM2) ++# ++# In this test, we want to ensure that a ping from VM1 to IP address 172.18.2.10 reaches VM2. ++ ++ovn-nbctl ls-add ls1 ++ovn-nbctl lsp-add ls1 vm1 -- lsp-set-addresses vm1 "00:00:00:00:00:05 192.168.100.5" ++ovn-nbctl lsp-add ls1 vm2 -- lsp-set-addresses vm2 "00:00:00:00:00:06 192.168.100.6" ++ ++ovn-nbctl ls-add ls-pub ++ovn-nbctl lsp-add ls-pub ext-router -- lsp-set-addresses ext-router "00:00:00:00:01:02 172.18.1.2" ++ ++ovn-nbctl lr-add lr1 ++ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 192.168.100.1/24 ++ovn-nbctl lsp-add ls1 ls1-lr1 \ ++ -- lsp-set-type ls1-lr1 router \ ++ -- lsp-set-addresses ls1-lr1 00:00:00:00:00:01 \ ++ -- lsp-set-options ls1-lr1 router-port=lr1-ls1 ++ ++ovn-nbctl lrp-add lr1 lr1-ls-pub 00:00:00:00:01:01 172.18.1.1/24 ++ovn-nbctl lrp-set-gateway-chassis lr1-ls-pub hv1 ++ovn-nbctl lsp-add ls-pub ls-pub-lr1 \ ++ -- lsp-set-type ls-pub-lr1 router \ ++ -- lsp-set-addresses ls-pub-lr1 00:00:00:00:01:01 \ ++ -- lsp-set-options ls-pub-lr1 router-port=lr1-ls-pub ++ ++ovn-nbctl lr-nat-add lr1 snat 172.18.1.1 192.168.100.0/24 ++ovn-nbctl lr-nat-add lr1 dnat_and_snat 172.18.2.10 192.168.100.6 ++ovn-nbctl lr-route-add lr1 0.0.0.0/0 172.18.1.2 ++ ++#ls1_uuid=$(fetch_column Port_Binding datapath logical_port=vm1) ++#ovn-sbctl create MAC_Binding ip=172.18.2.10 datapath=$ls1_uuid logical_port=vm2 mac="00:00:00:00:00:06" ++ ++OVN_POPULATE_ARP ++ovn-nbctl --wait=hv sync ++ ++ADD_NAMESPACES(vm1) ++ADD_VETH(vm1, vm1, br-int, "192.168.100.5/24", "00:00:00:00:00:05", \ ++ "192.168.100.1") ++ ++ADD_NAMESPACES(vm2) ++ADD_VETH(vm2, vm2, br-int, "192.168.100.6/24", "00:00:00:00:00:06", \ ++ "192.168.100.1") ++ ++ADD_NAMESPACES(ext-router) ++ADD_VETH(ext-router, ext-router, br-int, "172.18.1.2/24", "00:00:00:00:01:02", \ ++ "172.18.1.1") ++ ++# Let's take a quick look at the logical flows ++ovn-sbctl lflow-list ++ ++# Let's check what ovn-trace says... ++ovn-trace ls1 'inport == "vm1" && eth.src == 00:00:00:00:00:05 && ip4.src == 192.168.100.5 && eth.dst == 00:00:00:00:00:01 && ip4.dst == 172.18.2.10 && ip.ttl == 32' ++ ++# A ping from vm1 should hairpin in lr1 and successfully DNAT to vm2 ++NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.2.10 | FORMAT_PING], \ ++[0], [dnl ++3 packets transmitted, 3 received, 0% packet loss, time 0ms ++]) ++kill $(pidof 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([ovn-northd]) ++ ++as ++OVS_TRAFFIC_VSWITCHD_STOP(["/.*error receiving.*/d ++/.*terminating with signal 15.*/d"]) ++ ++AT_CLEANUP ++ ++AT_SETUP([ovn -- Floating IP outside router subnet IPv4]) ++AT_KEYWORDS(NAT) ++ ++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_daemon ovn-controller ++ ++# Logical network: ++# Two VMs ++# * VM1 with IP address 192.168.100.5 ++# * VM2 with IP address 192.168.200.5 ++# ++# VM1 connects to logical switch ls1. ls1 connects to logical router lr1. ++# VM2 connects to logical switch ls2. ls2 connects to logical router lr2. ++# lr1 and lr2 both connect to logical switch ls-pub. ++# * lr1's interface that connects to ls-pub has IP address 172.18.2.110/24 ++# * lr2's interface that connects to ls-pub has IP address 172.18.1.173/24 ++# ++# lr1 has the following attributes: ++# * It has a DNAT rule that translates 172.18.2.11 to 192.168.100.5 (VM1) ++# ++# lr2 has the following attributes: ++# * It has a DNAT rule that translates 172.18.2.12 to 192.168.200.5 (VM2) ++# ++# In this test, we want to ensure that a ping from VM1 to IP address 172.18.2.12 reaches VM2. ++# When the NAT rules are set up, there should be MAC_Bindings created that allow for traffic ++# to exit lr1, go through ls-pub, and reach the NAT external IP configured on lr2. ++ ++check ovn-nbctl ls-add ls1 ++check ovn-nbctl lsp-add ls1 vm1 -- lsp-set-addresses vm1 "00:00:00:00:01:05 192.168.100.5" ++ ++check ovn-nbctl ls-add ls2 ++check ovn-nbctl lsp-add ls2 vm2 -- lsp-set-addresses vm2 "00:00:00:00:02:05 192.168.200.5" ++ ++check ovn-nbctl ls-add ls-pub ++ ++check ovn-nbctl lr-add lr1 ++check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:01:01 192.168.100.1/24 ++check ovn-nbctl lsp-add ls1 ls1-lr1 \ ++ -- lsp-set-type ls1-lr1 router \ ++ -- lsp-set-addresses ls1-lr1 router \ ++ -- lsp-set-options ls1-lr1 router-port=lr1-ls1 ++ ++check ovn-nbctl lr-add lr2 ++check ovn-nbctl lrp-add lr2 lr2-ls2 00:00:00:00:02:01 192.168.200.1/24 ++check ovn-nbctl lsp-add ls2 ls2-lr2 \ ++ -- lsp-set-type ls2-lr2 router \ ++ -- lsp-set-addresses ls2-lr2 router \ ++ -- lsp-set-options ls2-lr2 router-port=lr2-ls2 ++ ++check ovn-nbctl lrp-add lr1 lr1-ls-pub 00:00:00:00:03:01 172.18.2.110/24 ++check ovn-nbctl lrp-set-gateway-chassis lr1-ls-pub hv1 ++check ovn-nbctl lsp-add ls-pub ls-pub-lr1 \ ++ -- lsp-set-type ls-pub-lr1 router \ ++ -- lsp-set-addresses ls-pub-lr1 router \ ++ -- lsp-set-options ls-pub-lr1 router-port=lr1-ls-pub ++ ++check ovn-nbctl lrp-add lr2 lr2-ls-pub 00:00:00:00:03:02 172.18.1.173/24 ++check ovn-nbctl lrp-set-gateway-chassis lr2-ls-pub hv1 ++check ovn-nbctl lsp-add ls-pub ls-pub-lr2 \ ++ -- lsp-set-type ls-pub-lr2 router \ ++ -- lsp-set-addresses ls-pub-lr2 router \ ++ -- lsp-set-options ls-pub-lr2 router-port=lr2-ls-pub ++ ++# Putting --add-route on these NAT rules means there is no need to ++# add any static routes. ++check ovn-nbctl --add-route lr-nat-add lr1 dnat_and_snat 172.18.2.11 192.168.100.5 vm1 00:00:00:00:03:01 ++check ovn-nbctl --add-route lr-nat-add lr2 dnat_and_snat 172.18.2.12 192.168.200.5 vm2 00:00:00:00:03:02 ++ ++ADD_NAMESPACES(vm1) ++ADD_VETH(vm1, vm1, br-int, "192.168.100.5/24", "00:00:00:00:01:05", \ ++ "192.168.100.1") ++ ++ADD_NAMESPACES(vm2) ++ADD_VETH(vm2, vm2, br-int, "192.168.200.5/24", "00:00:00:00:02:05", \ ++ "192.168.200.1") ++ ++OVN_POPULATE_ARP ++check ovn-nbctl --wait=hv sync ++ ++AS_BOX([Testing a ping]) ++ ++NS_CHECK_EXEC([vm1], [ping -q -c 3 -i 0.3 -w 2 172.18.2.12 | FORMAT_PING], \ ++[0], [dnl ++3 packets transmitted, 3 received, 0% packet loss, time 0ms ++]) ++ +AT_CLEANUP diff --git a/tests/test-ovn.c b/tests/test-ovn.c index 49a1947f6..3fbe90b32 100644 @@ -27550,13 +30673,14 @@ index 000000000..721032f82 + +#endif /* tests/test-utils.h */ diff --git a/tests/testsuite.at b/tests/testsuite.at -index 960227dcc..6cbf3d21a 100644 +index 960227dcc..3b4d06189 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at -@@ -26,6 +26,8 @@ m4_include([tests/ovn.at]) +@@ -26,6 +26,9 @@ m4_include([tests/ovn.at]) m4_include([tests/ovn-performance.at]) m4_include([tests/ovn-northd.at]) m4_include([tests/ovn-nbctl.at]) ++m4_include([tests/ovn-features.at]) +m4_include([tests/ovn-lflow-cache.at]) +m4_include([tests/ovn-ofctrl-seqno.at]) m4_include([tests/ovn-sbctl.at]) @@ -27824,7 +30948,7 @@ index 59302296b..2cab592ce 100644 The following example adds a load balancer.

    diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c -index d19e1b6c6..ec373d094 100644 +index d19e1b6c6..5db7478a3 100644 --- a/utilities/ovn-nbctl.c +++ b/utilities/ovn-nbctl.c @@ -29,9 +29,11 @@ @@ -27914,7 +31038,15 @@ index d19e1b6c6..ec373d094 100644 [OPTIONS KEY=VALUE ...] \n\ add a policy to router\n\ lr-policy-del ROUTER [{PRIORITY | UUID} [MATCH]]\n\ -@@ -1249,6 +1310,7 @@ static void +@@ -717,6 +778,7 @@ Policy commands:\n\ + NAT commands:\n\ + [--stateless]\n\ + [--portrange]\n\ ++ [--add-route]\n\ + lr-nat-add ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]\n\ + [EXTERNAL_PORT_RANGE]\n\ + add a NAT to ROUTER\n\ +@@ -1249,6 +1311,7 @@ static void nbctl_ls_del(struct ctl_context *ctx) { bool must_exist = !shash_find(&ctx->options, "--if-exists"); @@ -27922,7 +31054,7 @@ index d19e1b6c6..ec373d094 100644 const char *id = ctx->argv[1]; const struct nbrec_logical_switch *ls = NULL; -@@ -1261,6 +1323,11 @@ nbctl_ls_del(struct ctl_context *ctx) +@@ -1261,6 +1324,11 @@ nbctl_ls_del(struct ctl_context *ctx) return; } @@ -27934,7 +31066,7 @@ index d19e1b6c6..ec373d094 100644 nbrec_logical_switch_delete(ls); } -@@ -1317,22 +1384,19 @@ lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, +@@ -1317,22 +1385,19 @@ lsp_by_name_or_uuid(struct ctl_context *ctx, const char *id, /* Returns the logical switch that contains 'lsp'. */ static char * OVS_WARN_UNUSED_RESULT @@ -27963,7 +31095,7 @@ index d19e1b6c6..ec373d094 100644 /* Can't happen because of the database schema */ return xasprintf("logical port %s is not part of any logical switch", lsp->name); -@@ -1353,6 +1417,7 @@ static void +@@ -1353,6 +1418,7 @@ static void nbctl_lsp_add(struct ctl_context *ctx) { bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; @@ -27971,7 +31103,7 @@ index d19e1b6c6..ec373d094 100644 const struct nbrec_logical_switch *ls = NULL; char *error = ls_by_name_or_uuid(ctx, ctx->argv[1], true, &ls); -@@ -1395,7 +1460,7 @@ nbctl_lsp_add(struct ctl_context *ctx) +@@ -1395,7 +1461,7 @@ nbctl_lsp_add(struct ctl_context *ctx) } const struct nbrec_logical_switch *lsw; @@ -27980,7 +31112,7 @@ index d19e1b6c6..ec373d094 100644 if (error) { ctx->error = error; return; -@@ -1448,31 +1513,27 @@ nbctl_lsp_add(struct ctl_context *ctx) +@@ -1448,31 +1514,27 @@ nbctl_lsp_add(struct ctl_context *ctx) } /* Insert the logical port into the logical switch. */ @@ -28025,7 +31157,7 @@ index d19e1b6c6..ec373d094 100644 /* Delete 'lsp' from the IDL. This won't have a real effect on the * database server (the IDL will suppress it in fact) but it means that it -@@ -1498,18 +1559,13 @@ nbctl_lsp_del(struct ctl_context *ctx) +@@ -1498,18 +1560,13 @@ nbctl_lsp_del(struct ctl_context *ctx) /* Find the switch that contains 'lsp', then delete it. */ const struct nbrec_logical_switch *ls; @@ -28050,7 +31182,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -1658,7 +1714,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx) +@@ -1658,7 +1715,7 @@ nbctl_lsp_set_addresses(struct ctl_context *ctx) } const struct nbrec_logical_switch *ls; @@ -28059,7 +31191,7 @@ index d19e1b6c6..ec373d094 100644 if (error) { ctx->error = error; return; -@@ -2299,17 +2355,11 @@ nbctl_acl_add(struct ctl_context *ctx) +@@ -2299,17 +2356,11 @@ nbctl_acl_add(struct ctl_context *ctx) } /* Insert the acl into the logical switch/port group. */ @@ -28079,7 +31211,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -2349,23 +2399,15 @@ nbctl_acl_del(struct ctl_context *ctx) +@@ -2349,23 +2400,15 @@ nbctl_acl_del(struct ctl_context *ctx) /* If priority and match are not specified, delete all ACLs with the * specified direction. */ if (ctx->argc == 3) { @@ -28109,7 +31241,7 @@ index d19e1b6c6..ec373d094 100644 return; } -@@ -2387,19 +2429,11 @@ nbctl_acl_del(struct ctl_context *ctx) +@@ -2387,19 +2430,11 @@ nbctl_acl_del(struct ctl_context *ctx) if (priority == acl->priority && !strcmp(ctx->argv[4], acl->match) && !strcmp(direction, acl->direction)) { @@ -28131,7 +31263,7 @@ index d19e1b6c6..ec373d094 100644 return; } } -@@ -2552,15 +2586,7 @@ nbctl_qos_add(struct ctl_context *ctx) +@@ -2552,15 +2587,7 @@ nbctl_qos_add(struct ctl_context *ctx) } /* Insert the qos rule the logical switch. */ @@ -28148,7 +31280,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -2597,34 +2623,31 @@ nbctl_qos_del(struct ctl_context *ctx) +@@ -2597,34 +2624,31 @@ nbctl_qos_del(struct ctl_context *ctx) /* If uuid was specified, delete qos_rule with the * specified uuid. */ if (ctx->argc == 3) { @@ -28195,7 +31327,7 @@ index d19e1b6c6..ec373d094 100644 return; } -@@ -2651,14 +2674,7 @@ nbctl_qos_del(struct ctl_context *ctx) +@@ -2651,14 +2675,7 @@ nbctl_qos_del(struct ctl_context *ctx) if (priority == qos->priority && !strcmp(ctx->argv[4], qos->match) && !strcmp(direction, qos->direction)) { @@ -28211,12 +31343,13 @@ index d19e1b6c6..ec373d094 100644 return; } } -@@ -2821,6 +2837,14 @@ nbctl_lb_add(struct ctl_context *ctx) +@@ -2821,6 +2838,15 @@ nbctl_lb_add(struct ctl_context *ctx) bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; bool add_duplicate = shash_find(&ctx->options, "--add-duplicate") != NULL; + bool empty_backend_rej = shash_find(&ctx->options, "--reject") != NULL; + bool empty_backend_event = shash_find(&ctx->options, "--event") != NULL; ++ bool add_route = shash_find(&ctx->options, "--add-route") != NULL; + + if (empty_backend_event && empty_backend_rej) { + ctl_error(ctx, @@ -28226,22 +31359,26 @@ index d19e1b6c6..ec373d094 100644 const char *lb_proto; bool is_update_proto = false; -@@ -2934,6 +2958,14 @@ nbctl_lb_add(struct ctl_context *ctx) +@@ -2934,6 +2960,18 @@ nbctl_lb_add(struct ctl_context *ctx) smap_add(CONST_CAST(struct smap *, &lb->vips), lb_vip_normalized, ds_cstr(&lb_ips_new)); nbrec_load_balancer_set_vips(lb, &lb->vips); ++ struct smap options = SMAP_INITIALIZER(&options); + if (empty_backend_rej) { -+ const struct smap options = SMAP_CONST1(&options, "reject", "true"); -+ nbrec_load_balancer_set_options(lb, &options); ++ smap_add(&options, "reject", "true"); + } + if (empty_backend_event) { -+ const struct smap options = SMAP_CONST1(&options, "event", "true"); -+ nbrec_load_balancer_set_options(lb, &options); ++ smap_add(&options, "event", "true"); ++ } ++ if (add_route) { ++ smap_add(&options, "add_route", "true"); + } ++ nbrec_load_balancer_set_options(lb, &options); ++ smap_destroy(&options); out: ds_destroy(&lb_ips_new); -@@ -3115,17 +3147,7 @@ nbctl_lr_lb_add(struct ctl_context *ctx) +@@ -3115,17 +3153,7 @@ nbctl_lr_lb_add(struct ctl_context *ctx) } /* Insert the load balancer into the logical router. */ @@ -28260,7 +31397,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -3158,15 +3180,7 @@ nbctl_lr_lb_del(struct ctl_context *ctx) +@@ -3158,15 +3186,7 @@ nbctl_lr_lb_del(struct ctl_context *ctx) if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { /* Remove the matching rule. */ @@ -28277,7 +31414,7 @@ index d19e1b6c6..ec373d094 100644 return; } } -@@ -3240,17 +3254,7 @@ nbctl_ls_lb_add(struct ctl_context *ctx) +@@ -3240,17 +3260,7 @@ nbctl_ls_lb_add(struct ctl_context *ctx) } /* Insert the load balancer into the logical switch. */ @@ -28296,7 +31433,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -3283,15 +3287,7 @@ nbctl_ls_lb_del(struct ctl_context *ctx) +@@ -3283,15 +3293,7 @@ nbctl_ls_lb_del(struct ctl_context *ctx) if (uuid_equals(&del_lb->header_.uuid, &lb->header_.uuid)) { /* Remove the matching rule. */ @@ -28313,7 +31450,7 @@ index d19e1b6c6..ec373d094 100644 return; } } -@@ -3378,6 +3374,7 @@ static void +@@ -3378,6 +3380,7 @@ static void nbctl_lr_del(struct ctl_context *ctx) { bool must_exist = !shash_find(&ctx->options, "--if-exists"); @@ -28321,7 +31458,7 @@ index d19e1b6c6..ec373d094 100644 const char *id = ctx->argv[1]; const struct nbrec_logical_router *lr = NULL; -@@ -3390,6 +3387,11 @@ nbctl_lr_del(struct ctl_context *ctx) +@@ -3390,6 +3393,11 @@ nbctl_lr_del(struct ctl_context *ctx) return; } @@ -28333,7 +31470,7 @@ index d19e1b6c6..ec373d094 100644 nbrec_logical_router_delete(lr); } -@@ -3645,7 +3647,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) +@@ -3645,7 +3653,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) return; } const char *action = ctx->argv[4]; @@ -28343,7 +31480,7 @@ index d19e1b6c6..ec373d094 100644 bool reroute = false; /* Validate action. */ -@@ -3665,7 +3668,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) +@@ -3665,7 +3674,8 @@ nbctl_lr_policy_add(struct ctl_context *ctx) /* Check if same routing policy already exists. * A policy is uniquely identified by priority and match */ bool may_exist = !!shash_find(&ctx->options, "--may-exist"); @@ -28353,7 +31490,7 @@ index d19e1b6c6..ec373d094 100644 const struct nbrec_logical_router_policy *policy = lr->policies[i]; if (policy->priority == priority && !strcmp(policy->match, ctx->argv[3])) { -@@ -3676,12 +3680,53 @@ nbctl_lr_policy_add(struct ctl_context *ctx) +@@ -3676,12 +3686,53 @@ nbctl_lr_policy_add(struct ctl_context *ctx) return; } } @@ -28411,7 +31548,7 @@ index d19e1b6c6..ec373d094 100644 } struct nbrec_logical_router_policy *policy; -@@ -3690,12 +3735,13 @@ nbctl_lr_policy_add(struct ctl_context *ctx) +@@ -3690,12 +3741,13 @@ nbctl_lr_policy_add(struct ctl_context *ctx) nbrec_logical_router_policy_set_match(policy, ctx->argv[3]); nbrec_logical_router_policy_set_action(policy, action); if (reroute) { @@ -28427,7 +31564,7 @@ index d19e1b6c6..ec373d094 100644 char *key, *value; value = xstrdup(ctx->argv[i]); key = strsep(&value, "="); -@@ -3705,7 +3751,10 @@ nbctl_lr_policy_add(struct ctl_context *ctx) +@@ -3705,7 +3757,10 @@ nbctl_lr_policy_add(struct ctl_context *ctx) ctl_error(ctx, "No value specified for the option : %s", key); smap_destroy(&options); free(key); @@ -28439,7 +31576,7 @@ index d19e1b6c6..ec373d094 100644 return; } free(key); -@@ -3713,18 +3762,12 @@ nbctl_lr_policy_add(struct ctl_context *ctx) +@@ -3713,18 +3768,12 @@ nbctl_lr_policy_add(struct ctl_context *ctx) nbrec_logical_router_policy_set_options(policy, &options); smap_destroy(&options); @@ -28463,7 +31600,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -3758,38 +3801,34 @@ nbctl_lr_policy_del(struct ctl_context *ctx) +@@ -3758,38 +3807,34 @@ nbctl_lr_policy_del(struct ctl_context *ctx) /* If uuid was specified, delete routing policy with the * specified uuid. */ if (ctx->argc == 3) { @@ -28516,7 +31653,7 @@ index d19e1b6c6..ec373d094 100644 return; } -@@ -3798,14 +3837,7 @@ nbctl_lr_policy_del(struct ctl_context *ctx) +@@ -3798,14 +3843,7 @@ nbctl_lr_policy_del(struct ctl_context *ctx) struct nbrec_logical_router_policy *routing_policy = lr->policies[i]; if (priority == routing_policy->priority && !strcmp(ctx->argv[3], routing_policy->match)) { @@ -28532,7 +31669,7 @@ index d19e1b6c6..ec373d094 100644 return; } } -@@ -3833,11 +3865,15 @@ static void +@@ -3833,11 +3871,15 @@ static void print_routing_policy(const struct nbrec_logical_router_policy *policy, struct ds *s) { @@ -28553,7 +31690,7 @@ index d19e1b6c6..ec373d094 100644 } else { ds_put_format(s, "%10"PRId64" %50s %15s", policy->priority, policy->match, policy->action); -@@ -3884,6 +3920,47 @@ nbctl_lr_policy_list(struct ctl_context *ctx) +@@ -3884,6 +3926,47 @@ nbctl_lr_policy_list(struct ctl_context *ctx) } free(policies); } @@ -28601,7 +31738,7 @@ index d19e1b6c6..ec373d094 100644 static void nbctl_lr_route_add(struct ctl_context *ctx) -@@ -3927,44 +4004,42 @@ nbctl_lr_route_add(struct ctl_context *ctx) +@@ -3927,44 +4010,42 @@ nbctl_lr_route_add(struct ctl_context *ctx) goto cleanup; } @@ -28672,7 +31809,7 @@ index d19e1b6c6..ec373d094 100644 goto cleanup; } -@@ -3981,12 +4056,27 @@ nbctl_lr_route_add(struct ctl_context *ctx) +@@ -3981,12 +4062,27 @@ nbctl_lr_route_add(struct ctl_context *ctx) if (policy) { nbrec_logical_router_static_route_set_policy(route, policy); } @@ -28702,7 +31839,7 @@ index d19e1b6c6..ec373d094 100644 route = nbrec_logical_router_static_route_insert(ctx->txn); nbrec_logical_router_static_route_set_ip_prefix(route, prefix); nbrec_logical_router_static_route_set_nexthop(route, next_hop); -@@ -4004,15 +4094,19 @@ nbctl_lr_route_add(struct ctl_context *ctx) +@@ -4004,15 +4100,19 @@ nbctl_lr_route_add(struct ctl_context *ctx) nbrec_logical_router_static_route_set_options(route, &options); } @@ -28731,7 +31868,7 @@ index d19e1b6c6..ec373d094 100644 cleanup: free(next_hop); -@@ -4069,11 +4163,8 @@ nbctl_lr_route_del(struct ctl_context *ctx) +@@ -4069,11 +4169,8 @@ nbctl_lr_route_del(struct ctl_context *ctx) output_port = ctx->argv[4]; } @@ -28745,7 +31882,7 @@ index d19e1b6c6..ec373d094 100644 /* Compare route policy, if specified. */ if (policy) { char *nb_policy = lr->static_routes[i]->policy; -@@ -4082,7 +4173,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) +@@ -4082,7 +4179,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) nb_is_src_route = true; } if (is_src_route != nb_is_src_route) { @@ -28753,7 +31890,7 @@ index d19e1b6c6..ec373d094 100644 continue; } } -@@ -4093,14 +4183,12 @@ nbctl_lr_route_del(struct ctl_context *ctx) +@@ -4093,14 +4189,12 @@ nbctl_lr_route_del(struct ctl_context *ctx) normalize_prefix_str(lr->static_routes[i]->ip_prefix); if (!rt_prefix) { /* Ignore existing prefix we couldn't parse. */ @@ -28768,7 +31905,7 @@ index d19e1b6c6..ec373d094 100644 continue; } } -@@ -4111,13 +4199,11 @@ nbctl_lr_route_del(struct ctl_context *ctx) +@@ -4111,13 +4205,11 @@ nbctl_lr_route_del(struct ctl_context *ctx) normalize_prefix_str(lr->static_routes[i]->nexthop); if (!rt_nexthop) { /* Ignore existing nexthop we couldn't parse. */ @@ -28782,7 +31919,7 @@ index d19e1b6c6..ec373d094 100644 continue; } } -@@ -4126,18 +4212,17 @@ nbctl_lr_route_del(struct ctl_context *ctx) +@@ -4126,18 +4218,17 @@ nbctl_lr_route_del(struct ctl_context *ctx) if (output_port) { char *rt_output_port = lr->static_routes[i]->output_port; if (!rt_output_port || strcmp(output_port, rt_output_port)) { @@ -28807,7 +31944,7 @@ index d19e1b6c6..ec373d094 100644 ctl_error(ctx, "no matching route: policy '%s', prefix '%s', nexthop " "'%s', output_port '%s'.", policy ? policy : "any", -@@ -4146,8 +4231,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) +@@ -4146,8 +4237,6 @@ nbctl_lr_route_del(struct ctl_context *ctx) output_port ? output_port : "any"); } @@ -28816,7 +31953,35 @@ index d19e1b6c6..ec373d094 100644 free(prefix); free(nexthop); } -@@ -4418,12 +4501,7 @@ nbctl_lr_nat_add(struct ctl_context *ctx) +@@ -4217,6 +4306,7 @@ nbctl_lr_nat_add(struct ctl_context *ctx) + char *new_logical_ip = NULL; + char *new_external_ip = NULL; + bool is_portrange = shash_find(&ctx->options, "--portrange") != NULL; ++ bool add_route = shash_find(&ctx->options, "--add-route") != NULL; + + char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr); + if (error) { +@@ -4333,6 +4423,11 @@ nbctl_lr_nat_add(struct ctl_context *ctx) + } + + int is_snat = !strcmp("snat", nat_type); ++ ++ if (is_snat && add_route) { ++ ctl_error(ctx, "routes cannot be added for snat types."); ++ goto cleanup; ++ } + for (size_t i = 0; i < lr->n_nat; i++) { + const struct nbrec_nat *nat = lr->nat[i]; + +@@ -4413,17 +4508,15 @@ nbctl_lr_nat_add(struct ctl_context *ctx) + } + + smap_add(&nat_options, "stateless", stateless ? "true":"false"); ++ if (add_route) { ++ smap_add(&nat_options, "add_route", "true"); ++ } + nbrec_nat_set_options(nat, &nat_options); + smap_destroy(&nat_options); /* Insert the NAT into the logical router. */ @@ -28830,7 +31995,7 @@ index d19e1b6c6..ec373d094 100644 cleanup: free(new_logical_ip); -@@ -4459,17 +4537,11 @@ nbctl_lr_nat_del(struct ctl_context *ctx) +@@ -4459,17 +4552,11 @@ nbctl_lr_nat_del(struct ctl_context *ctx) if (ctx->argc == 3) { /*Deletes all NATs with the specified type. */ @@ -28850,7 +32015,7 @@ index d19e1b6c6..ec373d094 100644 return; } -@@ -4491,13 +4563,7 @@ nbctl_lr_nat_del(struct ctl_context *ctx) +@@ -4491,13 +4578,7 @@ nbctl_lr_nat_del(struct ctl_context *ctx) continue; } if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) { @@ -28865,7 +32030,7 @@ index d19e1b6c6..ec373d094 100644 should_return = true; } free(old_ip); -@@ -4667,20 +4733,18 @@ lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, +@@ -4667,20 +4748,18 @@ lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, /* Returns the logical router that contains 'lrp'. */ static char * OVS_WARN_UNUSED_RESULT @@ -28892,7 +32057,7 @@ index d19e1b6c6..ec373d094 100644 } /* Can't happen because of the database schema */ -@@ -4777,15 +4841,7 @@ nbctl_lrp_set_gateway_chassis(struct ctl_context *ctx) +@@ -4777,15 +4856,7 @@ nbctl_lrp_set_gateway_chassis(struct ctl_context *ctx) nbrec_gateway_chassis_set_priority(gc, priority); /* Insert the logical gateway chassis into the logical router port. */ @@ -28909,7 +32074,7 @@ index d19e1b6c6..ec373d094 100644 free(gc_name); } -@@ -4802,14 +4858,7 @@ remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx) +@@ -4802,14 +4873,7 @@ remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx) * will actually cause the gateway chassis to be deleted when the * transaction is sent to the database server (due to garbage * collection). */ @@ -28925,7 +32090,7 @@ index d19e1b6c6..ec373d094 100644 } /* Delete 'gc' from the IDL. This won't have a real effect on -@@ -4893,6 +4942,7 @@ static void +@@ -4893,6 +4957,7 @@ static void nbctl_lrp_add(struct ctl_context *ctx) { bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; @@ -28933,7 +32098,7 @@ index d19e1b6c6..ec373d094 100644 const struct nbrec_logical_router *lr = NULL; char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr); -@@ -4942,7 +4992,7 @@ nbctl_lrp_add(struct ctl_context *ctx) +@@ -4942,7 +5007,7 @@ nbctl_lrp_add(struct ctl_context *ctx) } const struct nbrec_logical_router *bound_lr; @@ -28942,7 +32107,7 @@ index d19e1b6c6..ec373d094 100644 if (error) { ctx->error = error; return; -@@ -5040,31 +5090,27 @@ nbctl_lrp_add(struct ctl_context *ctx) +@@ -5040,31 +5105,27 @@ nbctl_lrp_add(struct ctl_context *ctx) } /* Insert the logical port into the logical router. */ @@ -28987,7 +32152,7 @@ index d19e1b6c6..ec373d094 100644 /* Delete 'lrp' from the IDL. This won't have a real effect on * the database server (the IDL will suppress it in fact) but it -@@ -5090,18 +5136,13 @@ nbctl_lrp_del(struct ctl_context *ctx) +@@ -5090,18 +5151,13 @@ nbctl_lrp_del(struct ctl_context *ctx) /* Find the router that contains 'lrp', then delete it. */ const struct nbrec_logical_router *lr; @@ -29012,7 +32177,7 @@ index d19e1b6c6..ec373d094 100644 } /* Print a list of logical router ports. */ -@@ -5275,7 +5316,7 @@ fwd_group_to_logical_switch(struct ctl_context *ctx, +@@ -5275,7 +5331,7 @@ fwd_group_to_logical_switch(struct ctl_context *ctx, } const struct nbrec_logical_switch *ls; @@ -29021,7 +32186,7 @@ index d19e1b6c6..ec373d094 100644 if (error) { ctx->error = error; return NULL; -@@ -5350,7 +5391,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) +@@ -5350,7 +5406,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) return; } if (lsp) { @@ -29030,7 +32195,7 @@ index d19e1b6c6..ec373d094 100644 if (error) { ctx->error = error; return; -@@ -5373,15 +5414,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) +@@ -5373,15 +5429,7 @@ nbctl_fwd_group_add(struct ctl_context *ctx) nbrec_forwarding_group_set_liveness(fwd_group, true); } @@ -29047,7 +32212,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -5403,14 +5436,8 @@ nbctl_fwd_group_del(struct ctl_context *ctx) +@@ -5403,14 +5451,8 @@ nbctl_fwd_group_del(struct ctl_context *ctx) for (int i = 0; i < ls->n_forwarding_groups; ++i) { if (!strcmp(ls->forwarding_groups[i]->name, fwd_group->name)) { @@ -29064,7 +32229,7 @@ index d19e1b6c6..ec373d094 100644 nbrec_forwarding_group_delete(fwd_group); return; } -@@ -5498,17 +5525,27 @@ struct ipv4_route { +@@ -5498,17 +5540,27 @@ struct ipv4_route { const struct nbrec_logical_router_static_route *route; }; @@ -29097,7 +32262,7 @@ index d19e1b6c6..ec373d094 100644 } return route_cmp_details(route1p->route, route2p->route); } -@@ -5519,16 +5556,22 @@ struct ipv6_route { +@@ -5519,16 +5571,22 @@ struct ipv6_route { const struct nbrec_logical_router_static_route *route; }; @@ -29124,7 +32289,7 @@ index d19e1b6c6..ec373d094 100644 if (ret) { return ret; } -@@ -5536,7 +5579,8 @@ ipv6_route_cmp(const void *route1_, const void *route2_) +@@ -5536,7 +5594,8 @@ ipv6_route_cmp(const void *route1_, const void *route2_) } static void @@ -29134,7 +32299,7 @@ index d19e1b6c6..ec373d094 100644 { char *prefix = normalize_prefix_str(route->ip_prefix); -@@ -5558,6 +5602,19 @@ print_route(const struct nbrec_logical_router_static_route *route, struct ds *s) +@@ -5558,6 +5617,19 @@ print_route(const struct nbrec_logical_router_static_route *route, struct ds *s) if (smap_get(&route->external_ids, "ic-learned-route")) { ds_put_format(s, " (learned)"); } @@ -29154,7 +32319,7 @@ index d19e1b6c6..ec373d094 100644 ds_put_char(s, '\n'); } -@@ -5623,7 +5680,16 @@ nbctl_lr_route_list(struct ctl_context *ctx) +@@ -5623,7 +5695,16 @@ nbctl_lr_route_list(struct ctl_context *ctx) ds_put_cstr(&ctx->output, "IPv4 Routes\n"); } for (int i = 0; i < n_ipv4_routes; i++) { @@ -29172,7 +32337,7 @@ index d19e1b6c6..ec373d094 100644 } if (n_ipv6_routes) { -@@ -5631,7 +5697,16 @@ nbctl_lr_route_list(struct ctl_context *ctx) +@@ -5631,7 +5712,16 @@ nbctl_lr_route_list(struct ctl_context *ctx) n_ipv4_routes ? "\n" : ""); } for (int i = 0; i < n_ipv6_routes; i++) { @@ -29190,7 +32355,7 @@ index d19e1b6c6..ec373d094 100644 } free(ipv4_routes); -@@ -6007,17 +6082,7 @@ cmd_ha_ch_grp_add_chassis(struct ctl_context *ctx) +@@ -6007,17 +6097,7 @@ cmd_ha_ch_grp_add_chassis(struct ctl_context *ctx) nbrec_ha_chassis_set_chassis_name(ha_chassis, chassis_name); nbrec_ha_chassis_set_priority(ha_chassis, priority); @@ -29209,7 +32374,7 @@ index d19e1b6c6..ec373d094 100644 } static void -@@ -6032,11 +6097,9 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) +@@ -6032,11 +6112,9 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) const char *chassis_name = ctx->argv[2]; struct nbrec_ha_chassis *ha_chassis = NULL; @@ -29221,7 +32386,7 @@ index d19e1b6c6..ec373d094 100644 break; } } -@@ -6047,14 +6110,7 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) +@@ -6047,14 +6125,7 @@ cmd_ha_ch_grp_remove_chassis(struct ctl_context *ctx) return; } @@ -29237,7 +32402,7 @@ index d19e1b6c6..ec373d094 100644 nbrec_ha_chassis_delete(ha_chassis); } -@@ -6231,7 +6287,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, +@@ -6231,7 +6302,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, struct ovsdb_idl_txn *txn; enum ovsdb_idl_txn_status status; struct ovsdb_symbol_table *symtab; @@ -29246,7 +32411,7 @@ index d19e1b6c6..ec373d094 100644 struct ctl_command *c; struct shash_node *node; int64_t next_cfg = 0; -@@ -6268,25 +6324,26 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, +@@ -6268,25 +6339,26 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, ds_init(&c->output); c->table = NULL; } @@ -29283,7 +32448,7 @@ index d19e1b6c6..ec373d094 100644 SHASH_FOR_EACH (node, &symtab->sh) { struct ovsdb_symbol *symbol = node->data; -@@ -6317,14 +6374,14 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, +@@ -6317,14 +6389,14 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, if (status == TXN_UNCHANGED || status == TXN_SUCCESS) { for (c = commands; c < &commands[n_commands]; c++) { if (c->syntax->postprocess) { @@ -29304,7 +32469,7 @@ index d19e1b6c6..ec373d094 100644 } } } -@@ -6412,6 +6469,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, +@@ -6412,6 +6484,7 @@ do_nbctl(const char *args, struct ctl_command *commands, size_t n_commands, done: ; } @@ -29312,7 +32477,7 @@ index d19e1b6c6..ec373d094 100644 ovsdb_symbol_table_destroy(symtab); ovsdb_idl_txn_destroy(txn); the_idl_txn = NULL; -@@ -6429,6 +6487,7 @@ out_error: +@@ -6429,6 +6502,7 @@ out_error: ovsdb_idl_txn_destroy(txn); the_idl_txn = NULL; @@ -29320,7 +32485,7 @@ index d19e1b6c6..ec373d094 100644 ovsdb_symbol_table_destroy(symtab); return error; } -@@ -6561,7 +6620,7 @@ static const struct ctl_command_syntax nbctl_commands[] = { +@@ -6561,7 +6635,7 @@ static const struct ctl_command_syntax nbctl_commands[] = { /* logical router route commands. */ { "lr-route-add", 3, 4, "ROUTER PREFIX NEXTHOP [PORT]", NULL, nbctl_lr_route_add, NULL, "--may-exist,--ecmp,--ecmp-symmetric-reply," @@ -29329,16 +32494,30 @@ index d19e1b6c6..ec373d094 100644 { "lr-route-del", 1, 4, "ROUTER [PREFIX [NEXTHOP [PORT]]]", NULL, nbctl_lr_route_del, NULL, "--if-exists,--policy=", RW }, { "lr-route-list", 1, 1, "ROUTER", NULL, nbctl_lr_route_list, NULL, -@@ -6588,7 +6647,7 @@ static const struct ctl_command_syntax nbctl_commands[] = { +@@ -6579,16 +6653,18 @@ static const struct ctl_command_syntax nbctl_commands[] = { + /* NAT commands. */ + { "lr-nat-add", 4, 7, + "ROUTER TYPE EXTERNAL_IP LOGICAL_IP" +- "[LOGICAL_PORT EXTERNAL_MAC] [EXTERNAL_PORT_RANGE]", NULL, +- nbctl_lr_nat_add, NULL, "--may-exist,--stateless,--portrange", RW }, ++ "[LOGICAL_PORT EXTERNAL_MAC] [EXTERNAL_PORT_RANGE]", ++ NULL, nbctl_lr_nat_add, ++ NULL, "--may-exist,--stateless,--portrange,--add-route", RW }, + { "lr-nat-del", 1, 3, "ROUTER [TYPE [IP]]", NULL, + nbctl_lr_nat_del, NULL, "--if-exists", RW }, + { "lr-nat-list", 1, 1, "ROUTER", NULL, nbctl_lr_nat_list, NULL, "", RO }, + { "lr-nat-update-ext-ip", 4, 4, "ROUTER TYPE IP ADDRESS_SET", NULL, nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW}, /* load balancer commands. */ - { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL, +- { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL, - nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW }, -+ nbctl_lb_add, NULL, "--may-exist,--add-duplicate,--reject,--event", RW }, ++ { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", ++ NULL, nbctl_lb_add, NULL, ++ "--may-exist,--add-duplicate,--reject,--event,--add-route", RW }, { "lb-del", 1, 2, "LB [VIP]", NULL, nbctl_lb_del, NULL, "--if-exists", RW }, { "lb-list", 0, 1, "[LB]", NULL, nbctl_lb_list, NULL, "", RO }, -@@ -6897,6 +6956,15 @@ server_loop(struct ovsdb_idl *idl, int argc, char *argv[]) +@@ -6897,6 +6973,15 @@ server_loop(struct ovsdb_idl *idl, int argc, char *argv[]) server_cmd_init(idl, &exiting); for (;;) { @@ -29354,7 +32533,7 @@ index d19e1b6c6..ec373d094 100644 ovsdb_idl_run(idl); if (!ovsdb_idl_is_alive(idl)) { int retval = ovsdb_idl_get_last_error(idl); -@@ -6912,6 +6980,7 @@ server_loop(struct ovsdb_idl *idl, int argc, char *argv[]) +@@ -6912,6 +6997,7 @@ server_loop(struct ovsdb_idl *idl, int argc, char *argv[]) break; } diff --git a/SPECS/ovn2.13.spec b/SPECS/ovn2.13.spec index 2eb1782..46a5cff 100644 --- a/SPECS/ovn2.13.spec +++ b/SPECS/ovn2.13.spec @@ -51,7 +51,7 @@ Summary: Open Virtual Network support Group: System Environment/Daemons URL: http://www.ovn.org/ Version: 20.12.0 -Release: 135%{?commit0:.%{date}git%{shortcommit0}}%{?dist} +Release: 161%{?commit0:.%{date}git%{shortcommit0}}%{?dist} Provides: openvswitch%{pkgver}-ovn-common = %{?epoch:%{epoch}:}%{version}-%{release} Obsoletes: openvswitch%{pkgver}-ovn-common < 2.11.0-1 @@ -62,8 +62,8 @@ License: ASL 2.0 and LGPLv2+ and SISSL # Always pull an upstream release, since this is what we rebase to. Source: https://github.com/ovn-org/ovn/archive/v%{version}.tar.gz#/ovn-%{version}.tar.gz -%define ovscommit ac85cdb38c1f33e7952bc4c0347d6c7873fb56a1 -%define ovsshortcommit ac85cdb +%define ovscommit e6ad4d8d9c9273f226ec9a993b64fccfb50bdf4c +%define ovsshortcommit e6ad4d8 Source10: https://github.com/openvswitch/ovs/archive/%{ovscommit}.tar.gz#/openvswitch-%{ovsshortcommit}.tar.gz %define ovsdir ovs-%{ovscommit} @@ -123,6 +123,7 @@ BuildRequires: python3-pyOpenSSL %if %{with libcapng} BuildRequires: libcap-ng libcap-ng-devel %endif +BuildRequires: tcpdump Requires: hostname openssl iproute module-init-tools @@ -272,7 +273,7 @@ rm -f $RPM_BUILD_ROOT/%{_bindir}/ovn-docker-overlay-driver \ %if %{with check} touch resolv.conf export OVS_RESOLV_CONF=$(pwd)/resolv.conf - if ! make check TESTSUITEFLAGS='%{_smp_mflags} -k ovn'; then + if ! make check TESTSUITEFLAGS='%{_smp_mflags}'; then cat tests/testsuite.log if ! make check TESTSUITEFLAGS='--recheck'; then cat tests/testsuite.log @@ -526,6 +527,110 @@ fi %{_unitdir}/ovn-controller-vtep.service %changelog +* Tue Jul 27 2021 Mark Michelson - 20.12.0-161 +- redhat: Include tcpdump as a dependency for `make check` + [261782c85c119da5c6fb0cf349b57c07c35b874f] + +* Tue Jul 27 2021 Gerrit Code Review - 20.12.0-160 +- Merge "redhat: Don't specify "ovn" keyword when running tests." into ovn2.13 + [774cd8e9381cd896d481e609ccc7ed4d95dda3ab] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-159 +- ovn.at: Fix "Symmetric IPv6 ECMP reply flows" test. + [415ecc46acec1b6e49ea67bc69062b98e54c5d18] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-158 +- ovs: Include all-zero IP SNAT capability detection. (#1939676) + [5cef68f5c08181ad136a0b6d3a5a054a9ac8104e] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-157 +- ovn-controller: Handle DNAT/no-NAT conntrack tuple collisions. (#1939676) + [80e66ddce15fcb3787861ba5c57d1858cf17bfda] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-156 +- ovn-controller: Detect OVS datapath capabilities. + [c3de566045fe0b1ac37f24ed9b753e6c263917d4] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-155 +- system-ovn.at: Use ADD_BR macro instead of bare ovs-vsctl. + [18b6c60886df27f9b6fdcee1ed75f7093ddc68d1] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-154 +- ovs: Include ovs-vswitchd segfault fixes. + [fe0a9da79a3446cc7e50186d46a4ffb81763790e] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-153 +- Disable logging to the console from ovstest. + [74116e31cea961275d1a95af8c4481eeaca46e22] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-152 +- Don't suppress localport traffic directed to external port (#1974062) + [b2a5784086b70f28055c6d0e9e5a20f5fdcb0a19] + +* Tue Jul 27 2021 Dumitru Ceara - 20.12.0-151 +- Revert "Don't suppress localport traffic directed to external port" + [ea137784242612759b9b1d7e70ae670ee3463b06] + +* Mon Jul 26 2021 Lorenzo Bianconi - 20.12.0-150 +- northd: do not centralize traffic for unclaimed virtual ports + [6653a6f7592073642afa015d93f9ebbe54197450] + +* Fri Jul 16 2021 Mark Michelson - 20.12.0-149 +- Don't suppress localport traffic directed to external port (#1974062) + [00db835727e7dca92056a5ade04eb5985b56659a] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-148 +- northd: Flood ARPs to routers for "unreachable" addresses. + [e81814f755a6fc30d84c6fa5f5fa5060b266b804] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-147 +- northd: Add options to automatically add routes for NATs and LBs. + [86348583c04e568725ac26224938828b202c8d12] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-146 +- northd: Add IP routing and ARP resolution flows for NAT/LB addresses. + [8bb2ff7b816f0e5171e65e3e5bc223aac45c8f3d] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-145 +- northd: Factor peer retrieval into its own function. + [1b3ccc18c502c919b79de0dca6f5e51b6bb218ba] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-144 +- northd: Precompute load balancer IP sets. (#1962338) + [679d4836a2649b9cc4720655d85ccd56d98b4820] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-143 +- northd: Consolidate load balancer healthcheck/svc code. + [6c57185cf0597493248d3cf0b50b457a6ec61d8f] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-142 +- lb: Remove hairpin_snat_ips from northd load balancers. + [db838be1be2e10ade3d19a4630ccfdbc21be8c94] + +* Fri Jul 09 2021 Mark Michelson - 20.12.0-141 +- northd: Swap src and dst eth addresses in router egress loop. + [cd8e18cf0bd5acac5a08637a063f56d6045e1d2d] + +* Wed Jun 09 2021 Fabrizio D'Angelo - 20.12.0-140 +- ovn-northd: Fix IPv6 ECMP symmetric reply flows (#1959008) + [d75df45c02ae675d2d3f4ea0375313e45bb3a71d] + +* Wed Jun 09 2021 Fabrizio D'Angelo - 20.12.0-139 +- Revert "ovn-northd: Fix IPv6 ECMP symmetric reply flows" + [08aa6c14a6bf11ce0877343430dca6171bbd7283] + +* Tue Jun 08 2021 Fabrizio D'Angelo - 20.12.0-138 +- ovn-northd: Fix IPv6 ECMP symmetric reply flows (#1959008) + [c5c3468186820a56d09b294b02f8c751e891921c] + +* Tue Jun 08 2021 Fabrizio D'Angelo - 20.12.0-137 +- ovn-nb.xml: Fix typo + [f60d4cda070c4bccbea53c2736a5474e3d95c6b7] + +* Thu Jun 03 2021 Mark Michelson - 20.12.0-136 +- tests: Fix inconsistent "ACL Conjunction" test. + [c6861168d5f8ca3fb96ddd5797bd60ee53900a55] + * Thu May 27 2021 Dumitru Ceara - 20.12.0-135 - if-status: Add OVS interface status management module. (#1952846) [ddfe75df4b14b512867c588572b10d35ea0b50ca]